diff --git a/.config/docker_example.yml b/.config/docker_example.yml index 89e6925408bc..940b095fe29c 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -114,6 +114,7 @@ redis: # Available methods: # aid ... Short, Millisecond accuracy +# aidx ... Millisecond accuracy # meid ... Similar to ObjectID, Millisecond accuracy # ulid ... Millisecond accuracy # objectid ... This is left for backward compatibility @@ -121,7 +122,7 @@ redis: # ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE # ID SETTINGS AFTER THAT! -id: 'aid' +id: 'aidx' # ┌─────────────────────┐ #───┘ Other configuration └───────────────────────────────────── diff --git a/.config/example.yml b/.config/example.yml index 49683c2f3092..086a6ca8fc99 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -125,6 +125,7 @@ redis: # Available methods: # aid ... Short, Millisecond accuracy +# aidx ... Millisecond accuracy # meid ... Similar to ObjectID, Millisecond accuracy # ulid ... Millisecond accuracy # objectid ... This is left for backward compatibility @@ -132,7 +133,7 @@ redis: # ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE # ID SETTINGS AFTER THAT! -id: 'aid' +id: 'aidx' # ┌─────────────────────┐ #───┘ Other configuration └───────────────────────────────────── @@ -159,6 +160,9 @@ id: 'aid' #deliverJobMaxAttempts: 12 #inboxJobMaxAttempts: 8 +# Local address used for outgoing requests +#outgoingAddress: 127.0.0.1 + # IP address family used for outgoing request (ipv4, ipv6 or dual) #outgoingAddressFamily: ipv4 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0eee9d503c60..861b0008a047 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "features": { "ghcr.io/devcontainers-contrib/features/pnpm:2": {}, "ghcr.io/devcontainers/features/node:1": { - "version": "20.5.0" + "version": "20.5.1" } }, "forwardPorts": [3000], diff --git a/.devcontainer/devcontainer.yml b/.devcontainer/devcontainer.yml index 5cfb6174caeb..5dcd41599acd 100644 --- a/.devcontainer/devcontainer.yml +++ b/.devcontainer/devcontainer.yml @@ -114,6 +114,7 @@ redis: # Available methods: # aid ... Short, Millisecond accuracy +# aidx ... Millisecond accuracy # meid ... Similar to ObjectID, Millisecond accuracy # ulid ... Millisecond accuracy # objectid ... This is left for backward compatibility @@ -121,7 +122,7 @@ redis: # ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE # ID SETTINGS AFTER THAT! -id: 'aid' +id: 'aidx' # ┌─────────────────────┐ #───┘ Other configuration └───────────────────────────────────── diff --git a/.github/misskey/test.yml b/.github/misskey/test.yml index f43f74be149a..7a4aa4ae6cce 100644 --- a/.github/misskey/test.yml +++ b/.github/misskey/test.yml @@ -12,4 +12,4 @@ db: redis: host: 127.0.0.1 port: 56312 -id: aid +id: aidx diff --git a/.github/reviewer-lottery.yml b/.github/reviewer-lottery.yml deleted file mode 100644 index c88e1342de17..000000000000 --- a/.github/reviewer-lottery.yml +++ /dev/null @@ -1,9 +0,0 @@ -groups: - - name: devs - reviewers: 2 - internal_reviewers: 1 - usernames: - - syuilo - - acid-chicken - - EbiseLutica - - tamaina diff --git a/.github/workflows/api-misskey-js.yml b/.github/workflows/api-misskey-js.yml index a845c36f0724..4cf523a6b913 100644 --- a/.github/workflows/api-misskey-js.yml +++ b/.github/workflows/api-misskey-js.yml @@ -9,12 +9,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3.5.3 + uses: actions/checkout@v4.0.0 - run: corepack enable - name: Setup Node.js - uses: actions/setup-node@v3.7.0 + uses: actions/setup-node@v3.8.1 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/check_copyright_year.yml b/.github/workflows/check_copyright_year.yml index 174b6ab25641..313265f6718e 100644 --- a/.github/workflows/check_copyright_year.yml +++ b/.github/workflows/check_copyright_year.yml @@ -10,7 +10,7 @@ jobs: check_copyright_year: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3.5.3 + - uses: actions/checkout@v4.0.0 - run: | if [ "$(grep Copyright COPYING | sed -e 's/.*2014-\([0-9]*\) .*/\1/g')" -ne "$(date +%Y)" ]; then echo "Please change copyright year!" diff --git a/.github/workflows/docker-develop.yml b/.github/workflows/docker-develop.yml index c839c64a72b9..05bb7f77f9c5 100644 --- a/.github/workflows/docker-develop.yml +++ b/.github/workflows/docker-develop.yml @@ -13,24 +13,24 @@ jobs: if: github.repository == 'misskey-dev/misskey' steps: - name: Check out the repo - uses: actions/checkout@v3.5.3 + uses: actions/checkout@v4.0.0 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v2.9.1 + uses: docker/setup-buildx-action@v3.0.0 with: platforms: linux/amd64,linux/arm64 - name: Docker meta id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: misskey/misskey - name: Log in to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and Push to Docker Hub - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: builder: ${{ steps.buildx.outputs.name }} context: . diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index da05f0cabe09..32a98a416d64 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -12,15 +12,15 @@ jobs: steps: - name: Check out the repo - uses: actions/checkout@v3.5.3 + uses: actions/checkout@v4.0.0 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v2.9.1 + uses: docker/setup-buildx-action@v3.0.0 with: platforms: linux/amd64,linux/arm64 - name: Docker meta id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: misskey/misskey tags: | @@ -31,12 +31,12 @@ jobs: type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} - name: Log in to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and Push to Docker Hub - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: builder: ${{ steps.buildx.outputs.name }} context: . diff --git a/.github/workflows/dockle.yml b/.github/workflows/dockle.yml index 02dbef35dfa9..d811944d61f6 100644 --- a/.github/workflows/dockle.yml +++ b/.github/workflows/dockle.yml @@ -14,7 +14,7 @@ jobs: env: DOCKER_CONTENT_TRUST: 1 steps: - - uses: actions/checkout@v3.5.3 + - uses: actions/checkout@v4.0.0 - run: | curl -L -o dockle.deb "https://github.com/goodwithtech/dockle/releases/download/v0.4.10/dockle_0.4.10_Linux-64bit.deb" sudo dpkg -i dockle.deb diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0afa01342da3..7c10c23e7789 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -11,7 +11,7 @@ jobs: pnpm_install: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3.5.3 + - uses: actions/checkout@v4.0.0 with: fetch-depth: 0 submodules: true @@ -19,7 +19,7 @@ jobs: with: version: 8 run_install: false - - uses: actions/setup-node@v3.7.0 + - uses: actions/setup-node@v3.8.1 with: node-version-file: '.node-version' cache: 'pnpm' @@ -38,7 +38,7 @@ jobs: - sw - misskey-js steps: - - uses: actions/checkout@v3.5.3 + - uses: actions/checkout@v4.0.0 with: fetch-depth: 0 submodules: true @@ -46,7 +46,7 @@ jobs: with: version: 7 run_install: false - - uses: actions/setup-node@v3.7.0 + - uses: actions/setup-node@v3.8.1 with: node-version-file: '.node-version' cache: 'pnpm' @@ -64,7 +64,7 @@ jobs: - backend - misskey-js steps: - - uses: actions/checkout@v3.5.3 + - uses: actions/checkout@v4.0.0 with: fetch-depth: 0 submodules: true @@ -72,7 +72,7 @@ jobs: with: version: 7 run_install: false - - uses: actions/setup-node@v3.7.0 + - uses: actions/setup-node@v3.8.1 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/ok-to-test.yml b/.github/workflows/ok-to-test.yml index 71d09f781444..c02b980e4da6 100644 --- a/.github/workflows/ok-to-test.yml +++ b/.github/workflows/ok-to-test.yml @@ -17,7 +17,7 @@ jobs: # See app.yml for an example app manifest - name: Generate token id: generate_token - uses: tibdex/github-app-token@v1 + uses: tibdex/github-app-token@v2 with: app_id: ${{ secrets.DEPLOYBOT_APP_ID }} private_key: ${{ secrets.DEPLOYBOT_PRIVATE_KEY }} diff --git a/.github/workflows/pr-preview-deploy.yml b/.github/workflows/pr-preview-deploy.yml index fd1417b65bc9..702d8917e35a 100644 --- a/.github/workflows/pr-preview-deploy.yml +++ b/.github/workflows/pr-preview-deploy.yml @@ -53,7 +53,7 @@ jobs: # Check out merge commit - name: Fork based /deploy checkout - uses: actions/checkout@v3.5.3 + uses: actions/checkout@v4.0.0 with: ref: 'refs/pull/${{ github.event.client_payload.pull_request.number }}/merge' diff --git a/.github/workflows/reviewer_lottery.yml b/.github/workflows/reviewer_lottery.yml deleted file mode 100644 index f8f7d225325d..000000000000 --- a/.github/workflows/reviewer_lottery.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: "Reviewer lottery" -on: - pull_request_target: - types: [opened, ready_for_review, reopened] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3.5.3 - - uses: uesteibar/reviewer-lottery@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml deleted file mode 100644 index b998a3e4019f..000000000000 --- a/.github/workflows/storybook.yml +++ /dev/null @@ -1,112 +0,0 @@ -name: Storybook - -on: - push: - branches: - - master - - develop - pull_request_target: - -jobs: - build: - runs-on: ubuntu-latest - - env: - NODE_OPTIONS: "--max_old_space_size=7168" - - steps: - - uses: actions/checkout@v3.5.3 - if: github.event_name != 'pull_request_target' - with: - fetch-depth: 0 - submodules: true - - uses: actions/checkout@v3.5.3 - if: github.event_name == 'pull_request_target' - with: - fetch-depth: 0 - submodules: true - ref: "refs/pull/${{ github.event.number }}/merge" - - name: Checkout actual HEAD - if: github.event_name == 'pull_request_target' - id: rev - run: | - echo "base=$(git rev-list --parents -n1 HEAD | cut -d" " -f2)" >> $GITHUB_OUTPUT - git checkout $(git rev-list --parents -n1 HEAD | cut -d" " -f3) - - name: Install pnpm - uses: pnpm/action-setup@v2 - with: - version: 8 - run_install: false - - name: Use Node.js 20.x - uses: actions/setup-node@v3.7.0 - with: - node-version-file: '.node-version' - cache: 'pnpm' - - run: corepack enable - - run: pnpm i --frozen-lockfile - - name: Check pnpm-lock.yaml - run: git diff --exit-code pnpm-lock.yaml - - name: Build misskey-js - run: pnpm --filter misskey-js build - - name: Build storybook - run: pnpm --filter frontend build-storybook - - name: Publish to Chromatic - if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/master' - run: pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static - env: - CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} - - name: Publish to Chromatic - if: github.event_name != 'pull_request_target' && github.ref != 'refs/heads/master' - id: chromatic_push - run: | - DIFF="${{ github.event.before }} HEAD" - if [ "$DIFF" = "0000000000000000000000000000000000000000 HEAD" ]; then - DIFF="HEAD" - fi - CHROMATIC_PARAMETER="$(node packages/frontend/.storybook/changes.js $(git diff-tree --no-commit-id --name-only -r $(echo "$DIFF") | xargs))" - if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then - echo "skip=true" >> $GITHUB_OUTPUT - fi - if pnpm --filter frontend chromatic -d storybook-static $(echo "$CHROMATIC_PARAMETER"); then - echo "success=true" >> $GITHUB_OUTPUT - else - echo "success=false" >> $GITHUB_OUTPUT - fi - env: - CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} - - name: Publish to Chromatic - if: github.event_name == 'pull_request_target' - id: chromatic_pull_request - run: | - DIFF="${{ steps.rev.outputs.base }} HEAD" - if [ "$DIFF" = "0000000000000000000000000000000000000000 HEAD" ]; then - DIFF="HEAD" - fi - CHROMATIC_PARAMETER="$(node packages/frontend/.storybook/changes.js $(git diff-tree --no-commit-id --name-only -r $(echo "$DIFF") | xargs))" - if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then - echo "skip=true" >> $GITHUB_OUTPUT - fi - BRANCH="${{ github.event.pull_request.head.user.login }}:${{ github.event.pull_request.head.ref }}" - if [ "$BRANCH" = "misskey-dev:${{ github.event.pull_request.head.ref }}" ]; then - BRANCH="${{ github.event.pull_request.head.ref }}" - fi - pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static --branch-name $BRANCH $(echo "$CHROMATIC_PARAMETER") - env: - CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} - - name: Notify that Chromatic detects changes - uses: actions/github-script@v6.4.0 - if: github.event_name != 'pull_request_target' && steps.chromatic_push.outputs.success == 'false' - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - github.rest.repos.createCommitComment({ - owner: context.repo.owner, - repo: context.repo.repo, - commit_sha: context.sha, - body: 'Chromatic detects changes. Please [review the changes on Chromatic](https://www.chromatic.com/builds?appId=6428f7d7b962f0b79f97d6e4).' - }) - - name: Upload Artifacts - uses: actions/upload-artifact@v3 - with: - name: storybook - path: packages/frontend/storybook-static diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index 57608e3c67e3..19496c8959f0 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: - node-version: [20.x] + node-version: [20.5.1] services: postgres: @@ -29,7 +29,7 @@ jobs: - 56312:6379 steps: - - uses: actions/checkout@v3.5.3 + - uses: actions/checkout@v4.0.0 with: submodules: true - name: Install pnpm @@ -38,7 +38,7 @@ jobs: version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3.7.0 + uses: actions/setup-node@v3.8.1 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml index 813941a7e856..0618a0ef0af8 100644 --- a/.github/workflows/test-frontend.yml +++ b/.github/workflows/test-frontend.yml @@ -13,10 +13,10 @@ jobs: strategy: matrix: - node-version: [20.x] + node-version: [20.5.1] steps: - - uses: actions/checkout@v3.5.3 + - uses: actions/checkout@v4.0.0 with: submodules: true - name: Install pnpm @@ -25,7 +25,7 @@ jobs: version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3.7.0 + uses: actions/setup-node@v3.8.1 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -51,7 +51,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20.x] + node-version: [20.5.1] browser: [chrome] services: @@ -68,7 +68,7 @@ jobs: - 56312:6379 steps: - - uses: actions/checkout@v3.5.3 + - uses: actions/checkout@v4.0.0 with: submodules: true # https://github.com/cypress-io/cypress-docker-images/issues/150 @@ -83,7 +83,7 @@ jobs: version: 7 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3.7.0 + uses: actions/setup-node@v3.8.1 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -101,7 +101,7 @@ jobs: - name: Cypress install run: pnpm exec cypress install - name: Cypress run - uses: cypress-io/github-action@v5 + uses: cypress-io/github-action@v6 with: install: false start: pnpm start:test diff --git a/.github/workflows/test-misskey-js.yml b/.github/workflows/test-misskey-js.yml index ced26f54f2c0..7999c183b160 100644 --- a/.github/workflows/test-misskey-js.yml +++ b/.github/workflows/test-misskey-js.yml @@ -16,17 +16,17 @@ jobs: strategy: matrix: - node-version: [20.x] + node-version: [20.5.1] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - name: Checkout - uses: actions/checkout@v3.5.3 + uses: actions/checkout@v4.0.0 - run: corepack enable - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3.7.0 + uses: actions/setup-node@v3.8.1 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-production.yml b/.github/workflows/test-production.yml index 9879139b549d..0504f42d169e 100644 --- a/.github/workflows/test-production.yml +++ b/.github/workflows/test-production.yml @@ -16,10 +16,10 @@ jobs: strategy: matrix: - node-version: [20.x] + node-version: [20.5.1] steps: - - uses: actions/checkout@v3.5.3 + - uses: actions/checkout@v4.0.0 with: submodules: true - name: Install pnpm @@ -28,7 +28,7 @@ jobs: version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3.7.0 + uses: actions/setup-node@v3.8.1 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.node-version b/.node-version index 6a148f2818d6..7cc2069986ba 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -20.5.0 +20.5.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 12796b00f95a..68db7f41c516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,31 +12,85 @@ --> -## 2023.8.0 (unreleased) +## 2023.9.0 (unreleased) ### General -- OAuth 2.0のサポート -- お知らせ機能の強化 +- Feat: OAuth 2.0のサポート +- Feat: お知らせ機能の強化 - ユーザー個別のお知らせを作成可能に - お知らせのバナー表示やダイアログ表示が可能に - お知らせのアイコンを設定可能に -- チャンネルをセンシティブ指定できるようになりました +- Feat: チャンネルをセンシティブ指定できるようになりました + - センシティブチャンネルのNoteのReNoteはデフォルトでHome TLに流れるようになりました + - センシティブチャンネルのノートはユーザープロフィールに表示されません +- Feat: 二要素認証のバックアップコードが生成されるようになりました + - ref. https://github.com/MisskeyIO/misskey/pull/121 +- Feat: 二要素認証でパスキーをサポートするようになりました +- Feat: 指定したユーザーが投稿したときに通知できるようになりました +- Feat: プロフィールでのリンク検証 +- Feat: 通知をテストできるようになりました +- Feat: PWAのアイコンが設定できるようになりました +- Enhance: 二要素認証設定時のセキュリティを強化 + - パスワード入力が必要な操作を行う際、二要素認証が有効であれば確認コードの入力も必要になりました +- Enhance: manifest.jsonをオーバーライド可能に +- Enhance: 依存関係の更新 +- Enhance: ローカリゼーションの更新 ### Client -- メニューのスイッチの動作を改善 -- 絵文字ピッカーの検索の表示件数を100件に増加 +- Feat: 任意のユーザーリストをタイムラインページにピン留めできるように + - 設定->クライアント設定->全般 から設定可能です +- Feat: Playで直接投稿フォームを埋め込めるように(`Ui:C:postForm`) +- Feat: クライアントを起動している間、デバイスの画面が自動でオフになるのを防ぐオプションを追加 +- Feat: 新しい実績を追加 +- Enhance: ノート詳細ページでリノート一覧、リアクション一覧タブを追加 + - ノートのメニューからは当該項目は消えました +- Enhance: センシティブなメディアを目立たせる設定を追加 +- Enhance: プロフィールにその人が作ったPlayの一覧出せるように +- Enhance: メニューのスイッチの動作を改善 +- Enhance: 絵文字ピッカーの検索の表示件数を100件に増加 +- Enhance: 投稿フォームのプレビューの表示状態を記憶するように - Enhance: ユーザーメニューでスイッチでユーザーリストに追加・削除できるように - Enhance: 自分が押したリアクションのデザインを改善 +- Enhance: ノート検索にローカルのみ検索可能なオプションの追加 +- Enhance: Renote自体を通報できるように +- Enhance: データセーバーモードの強化 +- Enhance: Renoteを管理者権限で削除可能に +- Enhance: `$[rainbow ]`記法が、動きのあるMFMが無効になっていても使用できるようになりました +- Enhance: Playの操作を行うAPI TokenをAPIコンソールから発行できるように +- Enhance: リアクションの表示サイズをより大きくできるように +- Enhance: AiScriptを0.16.0に更新 +- Enhance: AiScriptからMisskeyサーバーAPIを呼び出す際の制限を撤廃 +- Enhance: AiScriptで`LOCALE`として現在の設定言語を取得できるように +- Enhance: Mk:apiが失敗した時にエラー型の値(AiScript 0.16.0で追加)を返すように +- Enhance: ScratchpadでAsync:系関数やボタンのコールバックなどのエラーにもダイアログを出すように(試験的なためPlayなどには未実装) +- Enhance: ノート詳細ページ読み込み時のパフォーマンスが向上しました +- Enhance: タイムラインでリスト/アンテナ選択時のパフォーマンスを改善 +- Enhance: 「Moderation note」、「Add moderation note」をローカライズできるように +- Enhance: 細かなデザインの調整 - Fix: サーバー情報画面(`/instance-info/{domain}`)でブロックができないのを修正 - Fix: 未読のお知らせの「わかった」をクリック・タップしてもその場で「わかった」が消えない問題を修正 - Fix: iOSで画面を回転させるとテキストサイズが変わる問題を修正 +- Fix: word mute for sub note is not applied - Fix: タイムラインを下にスクロールしてノート画面に移動して再び戻ったら以前のスクロール位置を失う問題を修正 +- Fix: Misskeyプラグインをインストールする際のAiScriptバージョンのチェックが0.14.0以降に対応していない問題を修正 +- Fix: 他のサーバーのユーザーへ「メッセージを送信」した時の初期テキストのメンションが間違っている問題を修正 +- Fix: 環境によってはMisskey Webが開けない問題を修正 ### Server -- cacheRemoteFilesの初期値はfalseになりました -- ファイルアップロード時等にファイル名の拡張子を修正する関数(correctFilename)の挙動を改善 +- Change: cacheRemoteFilesの初期値はfalseになりました +- Enhance: ファイルアップロード時等にファイル名の拡張子を修正する関数(correctFilename)の挙動を改善 +- Enhance: Webhookのペイロードにサーバーのurlが含まれるようになりました +- Enhance: Webhook設定でsecretを空に出来るように +- Enhance: 使われていないアンテナの自動停止を設定可能に +- Enhance: nodeinfo 2.1対応 +- Enhance: 自分へのメンション一覧を取得する際のパフォーマンスを向上 +- Enhance: Docker環境でjemallocを使用することでメモリ使用量を削減 +- Fix: MK_ONLY_SERVERオプションを指定した際にクラッシュする問題を修正 +- Fix: ノート検索 `notes/search` にてhostを指定した際に検索結果に反映されるように - Fix: 一部のfeatured noteを照会できない問題を修正 - Fix: muteがapiからのuser list timeline取得で機能しない問題を修正 +- Fix: ジョブキュー管理画面の認証を回避できる問題を修正 +- Fix: 一部のサーバー内部エラーがスタックトレースを返さないように修正 ## 13.14.2 @@ -51,6 +105,7 @@ ### Server - Fix: APIのオフセットが壊れていたせいで「もっと見る」でもっと見れない問題を修正 - Fix: 外部サーバーの投稿がタイムラインに表示されないことがある問題を修正 +- Enhance: Add address bind config option (outgoingAddress) ## 13.14.1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 62bc11cd996b..484fd994130c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -301,6 +301,12 @@ export const handlers = [ Don't forget to re-run the `.storybook/generate.js` script after adding, editing, or removing the above files. ## Notes + +### Misskeyのドメイン固有の概念は`Mi`をprefixする +例えばGoogleが自社サービスをMap、Earth、DriveではなくGoogle Map、Google Earth、Google Driveのように命名するのと同じ +コード上でMisskeyのドメイン固有の概念には`Mi`をprefixすることで、他のドメインの同様の概念と区別できるほか、名前の衝突を防ぐ。 +ただし、文脈上Misskeyのものを指すことが明らかであり、名前の衝突の恐れがない場合は、一時的なローカル変数に限って`Mi`を省略してもよい。 + ### How to resolve conflictions occurred at pnpm-lock.yaml? Just execute `pnpm` to fix it. @@ -430,3 +436,6 @@ marginはそのコンポーネントを使う側が設定する ## その他 ### HTMLのクラス名で follow という単語は使わない 広告ブロッカーで誤ってブロックされる + +### indexというファイル名を使うな +ESMではディレクトリインポートは廃止されているのと、ディレクトリインポートせずともファイル名が index だと何故か一部のライブラリ?でディレクトリインポートだと見做されてエラーになる diff --git a/Dockerfile b/Dockerfile index ce9d740ab6b3..d397fe01cdba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax = docker/dockerfile:1.4 -ARG NODE_VERSION=20.5.0-bullseye +ARG NODE_VERSION=20.5.1-bullseye # build assets & compile TypeScript @@ -62,7 +62,8 @@ ARG GID="991" RUN apt-get update \ && apt-get install -y --no-install-recommends \ - ffmpeg tini curl \ + ffmpeg tini curl libjemalloc-dev libjemalloc2 \ + && ln -s /usr/lib/$(uname -m)-linux-gnu/libjemalloc.so.2 /usr/local/lib/libjemalloc.so \ && corepack enable \ && groupadd -g "${GID}" misskey \ && useradd -l -u "${UID}" -g "${GID}" -m -d /misskey misskey \ @@ -81,6 +82,7 @@ COPY --chown=misskey:misskey --from=native-builder /misskey/packages/backend/bui COPY --chown=misskey:misskey --from=native-builder /misskey/fluent-emojis /misskey/fluent-emojis COPY --chown=misskey:misskey . ./ +ENV LD_PRELOAD=/usr/local/lib/libjemalloc.so ENV NODE_ENV=production HEALTHCHECK --interval=5s --retries=20 CMD ["/bin/bash", "/misskey/healthcheck.sh"] ENTRYPOINT ["/usr/bin/tini", "--"] diff --git a/chart/files/default.yml b/chart/files/default.yml index e62032abfdb0..90b574b99f40 100644 --- a/chart/files/default.yml +++ b/chart/files/default.yml @@ -135,6 +135,7 @@ redis: # Available methods: # aid ... Short, Millisecond accuracy +# aidx ... Millisecond accuracy # meid ... Similar to ObjectID, Millisecond accuracy # ulid ... Millisecond accuracy # objectid ... This is left for backward compatibility @@ -142,7 +143,7 @@ redis: # ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE # ID SETTINGS AFTER THAT! -id: "aid" +id: "aidx" # ┌─────────────────────┐ #───┘ Other configuration └───────────────────────────────────── diff --git a/docker-compose.yml.example b/docker-compose.yml.example index a0061c5c20b1..60ba4dc8cafe 100644 --- a/docker-compose.yml.example +++ b/docker-compose.yml.example @@ -50,7 +50,7 @@ services: # meilisearch: # restart: always -# image: getmeili/meilisearch:v1.1.1 +# image: getmeili/meilisearch:v1.3.4 # environment: # - MEILI_NO_ANALYTICS=true # - MEILI_ENV=production diff --git a/gulpfile.mjs b/gulpfile.mjs deleted file mode 100644 index 9556eb795f35..000000000000 --- a/gulpfile.mjs +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Gulp tasks - */ - -import * as fs from 'node:fs'; -import gulp from 'gulp'; -import replace from 'gulp-replace'; -import terser from 'gulp-terser'; -import cssnano from 'gulp-cssnano'; - -import locales from './locales/index.js'; -import meta from './package.json' assert { type: "json" }; - -gulp.task('copy:backend:views', () => - gulp.src('./packages/backend/src/server/web/views/**/*').pipe(gulp.dest('./packages/backend/built/server/web/views')) -); - -gulp.task('copy:frontend:fonts', () => - gulp.src('./packages/frontend/node_modules/three/examples/fonts/**/*').pipe(gulp.dest('./built/_frontend_dist_/fonts/')) -); - -gulp.task('copy:frontend:tabler-icons', () => - gulp.src('./packages/frontend/node_modules/@tabler/icons-webfont/**/*').pipe(gulp.dest('./built/_frontend_dist_/tabler-icons/')) -); - -gulp.task('copy:frontend:locales', cb => { - fs.mkdirSync('./built/_frontend_dist_/locales', { recursive: true }); - - const v = { '_version_': meta.version }; - - for (const [lang, locale] of Object.entries(locales)) { - fs.writeFileSync(`./built/_frontend_dist_/locales/${lang}.${meta.version}.json`, JSON.stringify({ ...locale, ...v }), 'utf-8'); - } - - cb(); -}); - -gulp.task('build:backend:script', () => { - return gulp.src(['./packages/backend/src/server/web/boot.js', './packages/backend/src/server/web/bios.js', './packages/backend/src/server/web/cli.js']) - .pipe(replace('LANGS', JSON.stringify(Object.keys(locales)))) - .pipe(terser({ - toplevel: true - })) - .pipe(gulp.dest('./packages/backend/built/server/web/')); -}); - -gulp.task('build:backend:style', () => { - return gulp.src(['./packages/backend/src/server/web/style.css', './packages/backend/src/server/web/bios.css', './packages/backend/src/server/web/cli.css', './packages/backend/src/server/web/error.css']) - .pipe(cssnano({ - zindex: false - })) - .pipe(gulp.dest('./packages/backend/built/server/web/')); -}); - -gulp.task('build', gulp.parallel( - 'copy:frontend:locales', 'copy:backend:views', 'build:backend:script', 'build:backend:style', 'copy:frontend:fonts', 'copy:frontend:tabler-icons' -)); - -gulp.task('default', gulp.task('build')); - -gulp.task('watch', () => { - gulp.watch([ - './packages/*/src/**/*', - ], { ignoreInitial: false }, gulp.task('build')); -}); diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index 116973a4e664..4d5872c64d68 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -348,7 +348,6 @@ invite: "دعوة" driveCapacityPerLocalAccount: "حصة التخزين لكل مستخدم محلي" driveCapacityPerRemoteAccount: "حصة التخزين لكل مستخدم بعيد" inMb: "بالميغابايت" -iconUrl: "رابط الأيقونة" bannerUrl: "رابط صورة اللافتة" backgroundImageUrl: "رابط صورة الخلفية" basicInfo: "المعلومات الأساسية " @@ -645,6 +644,7 @@ optional: "اختياري" createNewClip: "أنشئ مِشبكَا جديدًا" confirmToUnclipAlreadyClippedNote: "هذه الملاحظة تنتمي للمشبك {name} سلفًا، أتريد حذفها منه⸮" public: "علني" +private: "خاص" i18nInfo: "يترجم متطوعون ميسكي إلى عدة لغات، يمكنك المساعدة عبر {link}" manageAccessTokens: "إدارة رموز الوصول" accountInfo: "معلومات الحساب" @@ -798,6 +798,7 @@ accountDeletionInProgress: "حذف الحساب جارٍ" usernameInfo: "الاسم الذي يميزك عن بافي مستخدمي هذا الخادم، يمكنك استخدام الحروف اللاتينية (a~z, A~Z) والأرقام (0~9) والشرطة السفلية (_). لا يمكنك تغييره بعد تسجيله." devMode: "وضع المُطوّر" keepCw: "أبقِ على تحذيرات المحتوى" +pubSub: "حسابات Pub/Sub" lastCommunication: "آخر تواصل" resolved: "عولج" unresolved: "لم يعالج" @@ -806,6 +807,7 @@ breakFollowConfirm: "أمتأكد من إزالة المتابِع ؟" itsOn: "مفعّل" itsOff: "معطّل" on: "مفعل" +off: "معطل" emailRequiredForSignup: "عنوان البريد الإلكتروني إلزامي للتسجيل" unread: "غير مقروءة" filter: "رشّح" @@ -852,6 +854,7 @@ recentNDays: "آخر {n} أيام" noEmailServerWarning: "خادم البريد غير مضبوط." thereIsUnresolvedAbuseReportWarning: "توجد بلاغات غير معالجة." recommended: "مقترح" +check: "التحقق" driveCapOverrideLabel: "غيّر حجم قرص التخزين لهذا المستخدم" driveCapOverrideCaption: "أعد الحجم إلى القيمة الافتراضية بإدخال 0 أو أقل." requireAdminForView: "لاستعراض هذه الصفحة وجب عليك الولوج كمدير." @@ -875,6 +878,7 @@ slow: "بطيء" fast: "سريع" sensitiveMediaDetection: "التعرف على المحتوى الحساس" localOnly: "المحلي فقط" +remoteOnly: "بُعدي فقط" failedToUpload: "فشل الرفع" cannotUploadBecauseInappropriate: "تعذر رفع الملف لوجود محتوى حساس فيه." cannotUploadBecauseNoFreeSpace: "تعذر رفع الملف لنقص مساحة التخزين." @@ -894,6 +898,7 @@ pushNotificationAlreadySubscribed: "إرسال الإشعارات مفعل سل pushNotificationNotSupported: "متصفحك لا يدعم إرسال الإشعارات أو المثيل لا يدعمها." sendPushNotificationReadMessage: "احذف الإشعارات فور قراءتها" sendPushNotificationReadMessageCaption: "هذا قد يزيد من معدل استهلاك الطاقة لجهازك." +windowMaximize: "املأ الشاشة" windowRestore: "استرجاع" caption: "التعليق التوضيحي" loggedInAsBot: "والج كآلي" @@ -951,6 +956,8 @@ accountMoved: "نقل هذا المستخدم حسابه:" accountMovedShort: "رُحل هذا الحساب." operationForbidden: "عملية ممنوعة" forceShowAds: "أظهر الإعلانات التجارية دائما" +reactionsList: "التفاعلات" +renotesList: "إعادات النشر" leftTop: "أعلى اليسار" rightTop: "أعلى اليمين" leftBottom: "أسفل اليسار" @@ -986,6 +993,12 @@ later: "لاحقاً" goToMisskey: "لميسكي" additionalEmojiDictionary: "قواميس إيموجي إضافية" installed: "مُثبت" +expirationDate: "تاريخ انتهاء الصلاحية" +unused: "غير مستعمَل" +expired: "منتهية صلاحيته" +icon: "الصورة الرمزية" +replies: "رد" +renotes: "أعد النشر" _initialAccountSetting: accountCreated: "نجح إنشاء حسابك!" letsStartAccountSetup: "إذا كنت جديدًا لنعدّ حسابك الشخصي." @@ -1071,6 +1084,7 @@ _role: description: "وصف الدور" permission: "أذونات الدور" assignTarget: "نوع الإسناد" + condition: "الشرط" options: "خيارات" policies: "السياسة العامة" priority: "الأولوية" @@ -1126,6 +1140,9 @@ _plugin: install: "ثبّت إضافات" installWarn: "رجاءً لا تثبت إضافات غير موثوقة." manage: "إدارة الإضافات" +_preferencesBackups: + createdAt: "تم إنشاؤه: {date} {time}" + updatedAt: "آخر تحديث: {date} {time}" _registry: scope: "الحيّز" key: "مفتاح" @@ -1250,6 +1267,9 @@ _time: minute: "د" hour: "سا" day: "ي" +_timelineTutorial: + title: "كيف تستخدم Misskey" + step3_1: "هل نشرت ملاحظتك الأولى؟" _2fa: alreadyRegistered: "سجلت سلفًا جهازًا للاستيثاق بعاملين." step1: "أولًا ثبّت تطبيق استيثاق على جهازك (مثل {a} و{b})." @@ -1487,22 +1507,22 @@ _notification: fileUploaded: "نجح رفع الملف" youGotMention: "{name} أشار إليك" youGotReply: "ردّ عليك {name}" - youGotQuote: "اقتبس منك {name}" - youRenoted: "إعادت نشر من {name}" + youGotQuote: "اقتبس {name} منشورك" + youRenoted: "أعاد {name} نشر منشورك" youWereFollowed: "يتابعك" youReceivedFollowRequest: "تلقيتَ طلب متابعة" yourFollowRequestAccepted: "قُبل طلب المتابعة" - pollEnded: "ظهرت نتائج الاستطلاع" + pollEnded: "انتهى الاستطلاع" unreadAntennaNote: "هوائي {name}" _types: all: "الكل" follow: "متابِعون جدد" mention: "الإشارات" reply: "الردود" - renote: "أعد النشر" + renote: "أعاد النشر" quote: "الاقتباسات" - reaction: "التفاعلات" - receiveFollowRequest: "طلبات المتابعة المتلقاة" + reaction: "التفاعل" + receiveFollowRequest: "طلبات المتابعة" followRequestAccepted: "طلبات المتابعة المقبولة" app: "إشعارات التطبيقات المرتبطة" _actions: @@ -1510,26 +1530,28 @@ _notification: reply: "رد" renote: "أعد النشر" _deck: - alwaysShowMainColumn: "أظهر العمود الرئيسي دائمًا" - columnAlign: "حاذِ الأعمدة" - addColumn: "أضف عمودًا" - swapLeft: "حرّك لليسار" - swapRight: "حرّك لليمين" - swapUp: "حرّك لأعلى" - swapDown: "حرّك لأسفل" - profile: "الملف الشخصي" + alwaysShowMainColumn: "أظهر العمود الأساسي دائمًا" + columnAlign: "محاذاة الأعمدة" + addColumn: "إضافة عمود" + swapLeft: "التحريك إلى اليسار" + swapRight: "التحريك إلى اليمين" + swapUp: "التحريك إلى الأعلى" + swapDown: "التحريك إلى الأسفل" + profile: "حسابي الشخصي" + newProfile: "ملف تعريفي جديد" + deleteProfile: "حذف الملف التعريفي" _columns: - main: "الرئيسي" - widgets: "الودجات" + main: "الرئيسية" + widgets: "التطبيقات المُصغّرة" notifications: "الإشعارات" - tl: "الخيط الزمني" + tl: "الخط الزمني" antenna: "الهوائيات" list: "القوائم" channel: "القنوات" mentions: "الإشارات" direct: "مباشرة" _webhookSettings: - name: "الإسم" - active: "مفعّل" + name: "الاسم" + active: "مُفعّل" _events: - reaction: "عند تلقي تفاعل" + reaction: "عند التفاعل" diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index 78dbd77eb2d8..f3475de225a5 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -328,7 +328,6 @@ invite: "আমন্ত্রণ" driveCapacityPerLocalAccount: "প্রত্যেক স্থানীয় ব্যাবহারকারীর জন্য ড্রাইভের জায়গা" driveCapacityPerRemoteAccount: "প্রত্যেক রিমোট ব্যাবহারকারীর জন্য ড্রাইভের জায়গা" inMb: "মেগাবাইটে লিখুন" -iconUrl: "আইকনের URL (ফ্যাভিকন, ইত্যাদি)" bannerUrl: "ব্যানার ছবির URL" backgroundImageUrl: "পটভূমির চিত্রের URL" basicInfo: "আপনার ব্যক্তিগত তথ্য" @@ -628,6 +627,7 @@ createNew: "নতুন" optional: "প্রয়োজনীয় নয়" createNewClip: "নতুন ক্লিপ তৈরি করুন" public: "সর্বজনীন" +private: "ব্যাক্তিগত" i18nInfo: "Misskey স্বেচ্ছাসেবকদের দ্বারা বিভিন্ন ভাষায় অনুবাদ করা হচ্ছে। আপনি {link} এ গিয়ে অনুবাদে সহযোগিতা করতে পারেন।" manageAccessTokens: "অ্যাক্সেস টোকেন পরিচালনা করুন" accountInfo: "অ্যাকাউন্টের তথ্য" @@ -837,6 +837,9 @@ show: "প্রদর্শন" color: "রং" horizontal: "পাশে" youFollowing: "অনুসরণ করা হচ্ছে" +icon: "প্রোফাইল ছবি" +replies: "জবাব" +renotes: "রিনোট" _role: priority: "অগ্রাধিকার" _priority: @@ -1040,7 +1043,6 @@ _2fa: alreadyRegistered: "আপনি ইতিমধ্যে একটি 2-ফ্যাক্টর অথেনটিকেশন ডিভাইস নিবন্ধন করেছেন৷" step1: "প্রথমে, আপনার ডিভাইসে {a} বা {b} এর মতো একটি অথেনটিকেশন অ্যাপ ইনস্টল করুন৷" step2: "এরপরে, অ্যাপের সাহায্যে প্রদর্শিত QR কোডটি স্ক্যান করুন।" - step2Url: "ডেস্কটপ অ্যাপে, নিম্নলিখিত URL লিখুন:" step3: "অ্যাপে প্রদর্শিত টোকেনটি লিখুন এবং আপনার কাজ শেষ।" step4: "আপনাকে এখন থেকে লগ ইন করার সময়, এইভাবে টোকেন লিখতে হবে।" securityKeyInfo: "আপনি একটি হার্ডওয়্যার সিকিউরিটি কী ব্যবহার করে লগ ইন করতে পারেন যা FIDO2 বা ডিভাইসের ফিঙ্গারপ্রিন্ট সেন্সর বা পিন সমর্থন করে৷" diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index e3a6ec11b2a1..ec52c57610ee 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -13,12 +13,14 @@ fetchingAsApObject: "Cercant en el Fediverse..." ok: "OK" gotIt: "Ho he entès!" cancel: "Cancel·lar" +noThankYou: "No, gràcies" enterUsername: "Introdueix el teu nom d'usuari" renotedBy: "Impulsat per {usuari}" noNotes: "Cap nota" noNotifications: "Cap notificació" instance: "Servidor" settings: "Preferències" +notificationSettings: "Paràmetres de notificacions" basicSettings: "Configuració bàsica" otherSettings: "Configuració avançada" openInWindow: "Obrir en una nova finestra" @@ -47,8 +49,15 @@ delete: "Elimina" deleteAndEdit: "Elimina i edita" deleteAndEditConfirm: "Segur que vols eliminar aquesta publicació i editar-la? Perdràs totes les reaccions, impulsos i respostes." addToList: "Afegir a una llista" +addToAntenna: "Afegir a l'antena" sendMessage: "Enviar un missatge" +copyRSS: "Copiar RSS" copyUsername: "Copiar nom d'usuari" +copyUserId: "Copiar ID d'usuari" +copyNoteId: "Copiar ID de nota" +copyFileId: "Copiar ID d'arxiu" +copyFolderId: "Copiar ID de carpeta" +copyProfileUrl: "Copiar URL del perfil" searchUser: "Cercar un usuari" reply: "Respondre" loadMore: "Carregar més" @@ -128,6 +137,7 @@ suspendConfirm: "Estàs segur que vols suspendre aquest compte?" unsuspendConfirm: "Estàs segur que vols treure la suspensió d'aquest compte?" selectList: "Tria una llista" selectAntenna: "Tria una antena" +editAntenna: "Modificar antena" selectWidget: "Triar un giny" editWidgets: "Editar ginys" editWidgetsExit: "Fet" @@ -298,8 +308,10 @@ manageAntennas: "Gestiona les antenes" antennaSource: "Font de l'antena" antennaKeywords: "Paraules clau a seguir" antennaExcludeKeywords: "Paraules clau a excloure" +antennaKeywordsDescription: "Separar amb espais per la condició AND o amb salts de línia per la condició OR." notifyAntenna: "Notifica'm les publicacions noves" withFileAntenna: "Només les publicacions amb fitxers" +antennaUsersDescription: "Llistar un nom d'usuari per línia" notesAndReplies: "Amb respostes" silence: "Silencia" silenceConfirm: "Segur que vols silenciar aquest usuari?" @@ -369,6 +381,11 @@ user: "Usuaris" global: "Global" searchByGoogle: "Cercar" file: "Fitxers" +replies: "Respondre" +renotes: "Impulsa" +_role: + _options: + antennaMax: "Nombre màxim d'antenes" _email: _follow: title: "t'ha seguit" @@ -384,7 +401,7 @@ _sfx: chat: "Xat" antenna: "Antenes" _2fa: - step2Url: "També pots inserir aquest enllaç i utilitzes una aplicació d'escriptori:" + renewTOTPCancel: "No, gràcies" _antennaSources: all: "Totes les publicacions" homeTimeline: "Publicacions dels usuaris seguits" @@ -430,6 +447,7 @@ _pages: _notification: youRenoted: "Impulsat per {name}" youWereFollowed: "t'ha seguit" + unreadAntennaNote: "Antena {name}" _types: all: "Tots" follow: "Seguint" diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index 21d0c464827c..18e7f02e69d2 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -226,6 +226,7 @@ preview: "Náhled" default: "Výchozí" defaultValueIs: "Základní hodnota: {value}" noCustomEmojis: "Bez Emoji" +noJobs: "Žádné úlohy" federating: "Sdružování" blocked: "Blokováno" suspended: "Suspendováno" @@ -353,7 +354,6 @@ invite: "Pozvat" driveCapacityPerLocalAccount: "Kapacita disku na lokálního uživatele" driveCapacityPerRemoteAccount: "Kapacita disku na vzdáleného uživatele" inMb: "V megabajtech" -iconUrl: "Favicon URL" bannerUrl: "Baner URL" backgroundImageUrl: "Adresa URL obrázku pozadí" basicInfo: "Základní informace" @@ -659,10 +659,13 @@ reporter: "Nahlásil" reporteeOrigin: "Původ nahlášení" reporterOrigin: "Původ nahlasovače" forwardReport: "Přeposlat nahlášení do vzdálené instance" +forwardReportIsAnonymous: "Místo vašeho účtu se ve vzdálené instanci zobrazí anonymní systémový účet jako nahlašovač." send: "Odeslat" +abuseMarkAsResolved: "Označit nahlášení jako vyřešené" openInNewTab: "Otevřít v nové kartě" openInSideView: "Otevřít v bočním panelu" defaultNavigationBehaviour: "Výchozí chování navigace" +editTheseSettingsMayBreakAccount: "Uprávou těchto nastavení si můžete poškodit účet." instanceTicker: "Informace instance o poznámkách" waitingFor: "Čeká se na {x}" random: "Náhodně" @@ -676,6 +679,7 @@ createNewClip: "Vytvořit nový klip" unclip: "Odepnout" confirmToUnclipAlreadyClippedNote: "Tahle poznámku je už součásti \"{name}\" klipu. Chcete ji místo toho odepnout z tohodle klipu?" public: "Veřejný" +private: "Soukromý" i18nInfo: "Misskey je překládán do jiných jazyků dobrovolníkama. Můžete pomoci na {link}." manageAccessTokens: "Spravovat přístupové tokeny" accountInfo: "Informace o účtu" @@ -695,6 +699,7 @@ no: "Ne" driveFilesCount: "Počet souborů na disku" driveUsage: "Využití disku" noCrawle: "Odmítat indexování crawleru" +noCrawleDescription: "Požádat vyhledávače aby neindexovali váš profil, poznámky, stránky, atd." lockedAccountInfo: "Pokud nenastavíte viditelnost poznámek na \"Pouze pro sledující\", budou poznámky viditelné všem i přesto že vyžadujete manuální potvrzení pro sledování." alwaysMarkSensitive: "Výchozně označovat jako citlivý" loadRawImages: "Načítat originální obrázky místo náhledů" @@ -702,25 +707,40 @@ disableShowingAnimatedImages: "Nepřehrávat animované obrázky" verificationEmailSent: "Ověřovací email byl zaslán. Ověření dokončíte kliknutím na odkaz v emailu." notSet: "Není nastaveno" emailVerified: "Váš e-mail byl ověřen" +noteFavoritesCount: "Počet oblíbených poznámek" +pageLikesCount: "Počet oblíbených stránek" +pageLikedCount: "Počet přijatých \"Libí se mi\"" contact: "Kontakt" useSystemFont: "Použít výchozí font systému" clips: "Oříznout" experimentalFeatures: "Experimentální funkce" +experimental: "Experimentální" +thisIsExperimentalFeature: "Tohle je experimentální funkce. Její funkce se může změnit a nemusí fungovat tak, jak bylo zamýšleno." developer: "Vývojář" +makeExplorable: "Udělat účet viditelný v \"Objevit\"" +makeExplorableDescription: "Pokud tohle vypnete, tak se účet přestane zobrazovat v sekci \"Objevit\"." +showGapBetweenNotesInTimeline: "Zobrazit mezeru mezi příspěvkama na časové ose" duplicate: "Duplikovat" left: "Vlevo" center: "Uprostřed" wide: "Široké" narrow: "Úzké" +reloadToApplySetting: "Tohle nastavení se použije až po obnovení stránky. Obnovit teď?" +needReloadToApply: "K projevení nastavení je zapotřebí obnovit stránku." +showTitlebar: "Zobrazit řádek s nadpisem" clearCache: "Vyprázdnit mezipaměť" +onlineUsersCount: "{n} uživatelů je online" nUsers: "{n} užívatelů" nNotes: "{n} poznámek" +sendErrorReports: "Odesílat chybové záznamy" +sendErrorReportsDescription: "Pokud je tato funkce zapnutá, budou se při výskytu problému sdílet podrobné informace o chybách se službou Misskey, což pomůže zlepšit kvalitu služby Misskey. Tyto informace budou zahrnovat například verzi operačního systému, jaký prohlížeč používáte, vaši aktivitu v Misskey atd." myTheme: "Moje vzhledy" backgroundColor: "Pozadí" accentColor: "Akcent" textColor: "Barva textu" saveAs: "Uložit jako…" advanced: "Pokročilé" +advancedSettings: "Pokročilá nastavení" value: "Hodnota" createdAt: "Vytvořeno" updatedAt: "Upraveno" @@ -728,7 +748,35 @@ saveConfirm: "Uložit změny?" deleteConfirm: "Opravdu smazat?" invalidValue: "Neplatná hodnota." registry: "Registr" +closeAccount: "Uzavřít účet" +currentVersion: "Aktuální verze" +latestVersion: "Nejnovější verze" +youAreRunningUpToDateClient: "Používáte nejnovější verzi klienta." +newVersionOfClientAvailable: "Nová verze klienta je k dispozici." +usageAmount: "Využití" +capacity: "Kapacita" +inUse: "Používáno" +editCode: "Upravit kód" +apply: "Potvrdit" +receiveAnnouncementFromInstance: "Dostávat oznámení z téhle instance" +emailNotification: "Emailové oznámení" +publish: "Zveřejnit" +inChannelSearch: "Vyhledat v kanálech" +useReactionPickerForContextMenu: "Otevřít výběr reakce na kliknutí pravého tlačítka myši" +typingUsers: "{users} píše..." +jumpToSpecifiedDate: "Skočit do konkrétního datumu" +showingPastTimeline: "Právě je zobrazována stará časová osa" +clear: "Vrátit" +markAllAsRead: "Označit všechno jako přečtené" +goBack: "Zpět" +unlikeConfirm: "Opravdu chcete odstranit like?" +fullView: "Plné zobrazení" +quitFullView: "Odejít z plného zobrazení" +addDescription: "Přidat popis" +userPagePinTip: "Zde můžete zobrazovat poznámky vybráním \"Připnout na profil\" z menu jednotlivých poznámek." +notSpecifiedMentionWarning: "Tahle poznámka zmiňuje uživatele, které nejsou mezi adresáty" info: "Informace" +userInfo: "Informace o uživateli" unknown: "Neznámý" onlineStatus: "Online status" hideOnlineStatus: "Skrýt Váš online status" @@ -748,10 +796,18 @@ user: "Uživatelé" administration: "Administrace" accounts: "Účty" switch: "Přepnout" +noMaintainerInformationWarning: "Informace o správci nejsou nastavené" +noBotProtectionWarning: "Ochrana proti botům není nastavena" configure: "Nastavit" +postToGallery: "Vytvořit nový příspěvek v galerii" +postToHashtag: "Přidat příspěvek k tomuhle hastagu" gallery: "Galerie" recentPosts: "Poslední příspěvky" +popularPosts: "Populární příspěvky" +shareWithNote: "Sdílet s poznámkou" ads: "Reklamy" +expiration: "Ukončit hlasování" +startingperiod: "Začátek" memo: "Memo" priority: "Priorita" high: "Vysoká" @@ -759,63 +815,701 @@ middle: "Střední" low: "Nízká" emailNotConfiguredWarning: "E-mailová adresa není nastavena." ratio: "Poměr" +previewNoteText: "Zobrazit náhled" +customCss: "Vlastní CSS" +customCssWarn: "Tohle nastavení by mělo být použito pouze v případě pokud víte co děláte. Vložením nesprávných hodnot může způsobit nefunkčnost klienta." global: "Globální" +squareAvatars: "Zobrazovat čtvercové avatary" sent: "Odeslat" +received: "Přijaté" +searchResult: "Výsledky hledání" hashtags: "Hashtagy" troubleshooting: "Poradce při potížích" +useBlurEffect: "Použít efekt rozostření v UI" +learnMore: "Zjistit více" +misskeyUpdated: "Misskey byl aktualizován!" whatIsNew: "Zobrazit změny" translate: "Přeložit" +translatedFrom: "Přeloženo z {x}" +accountDeletionInProgress: "Smazání účtu právě probíhá" +usernameInfo: "Jméno které identifikuje váš účet od jiných na tomhle serveru. Můžete použít abecedu (a~z, A~Z), čísla (0~9) nebo podtržítka (_). Uživatelské jména nemůžou být změněna později." +aiChanMode: "Režim Ai" +devMode: "Vývojářský režim" +keepCw: "Zachovat varování o obsahu" +pubSub: "Pub/Sub účty" +lastCommunication: "Poslední komunikace" +resolved: "Vyřešeno" +unresolved: "Nevyřešené" +breakFollow: "Odstranit sledujícího" +breakFollowConfirm: "Opravdu chcete odstranit tohodle sledujícího?" +itsOn: "Zapnuto" +itsOff: "Vypnuto" +on: "Zapnuto" +off: "Vypnuto" +emailRequiredForSignup: "Vyžadovat email pro registraci" +unread: "Nepřečtený" +filter: "Filtr" +controlPanel: "Ovládací panel" +manageAccounts: "Spravovat účty" +makeReactionsPublic: "Nastavit historii reakcí jako veřejnou" +makeReactionsPublicDescription: "Tohle zviditelný seznam vašich předchozích reakcí veřejně." +classic: "Klasický" +muteThread: "Ztlumit vlákno" +unmuteThread: "Zrušit ztlumení vlákna" +ffVisibility: "Viditelnost Sledovaných/Sledujících" +ffVisibilityDescription: "Umožní vám nastavit kdo uvidí koho sledujete a kdo vás sleduje." +continueThread: "Zobrazit pokračování vlákna" +deleteAccountConfirm: "Tohle nenávratně smaže váš účet, chcete pokračovat?" +incorrectPassword: "Nesprávné heslo." +voteConfirm: "Potvrdit hlas pro \"{choice}\"?" hide: "Skrýt" +useDrawerReactionPickerForMobile: "Zobrazit výběr reakcí jako šuplík na mobilním zařízení" +welcomeBackWithName: "Vítejte zpět, {name}" +clickToFinishEmailVerification: "Prosíme klikněte na [{ok}] pro dokončení ověření emailu." +overridedDeviceKind: "Typ zařízení" smartphone: "Telefon" tablet: "Tablet" auto: "Auto" +themeColor: "Barva motivu" size: "Velikost" numberOfColumn: "Počet sloupců" searchByGoogle: "Vyhledávání" +instanceDefaultLightTheme: "Výchozí světlý motiv instance" +instanceDefaultDarkTheme: "Výhozí tmavý motiv instance" +instanceDefaultThemeDescription: "Zadejte kód motivu v objektovém formátu" +mutePeriod: "Délka ztlumení" +period: "Časový limit" indefinitely: "Navždy" tenMinutes: "10 minut" oneHour: "1 hodina" oneDay: "1 den" oneWeek: "1 týden" +oneMonth: "1 měsíc" reflectMayTakeTime: "Může trvat nějakou dobu, než se projeví změny." +failedToFetchAccountInformation: "Nepodařily se načíst informace o účtě" +rateLimitExceeded: "Překročení rychlostního limitu" cropImage: "Oříznout obrázek" +cropImageAsk: "Chcete oříznout tenhle obrázek?" +cropYes: "Uříznout" +cropNo: "Použít tak jak je" file: "Soubor(ů)" recentNHours: "Posledních {n} hodin" recentNDays: "Posledních {n} dnů" +noEmailServerWarning: "Emailový server není nastavený" +thereIsUnresolvedAbuseReportWarning: "Jsou k dispozici nevyřešené nahlášení zneužití" recommended: "Doporučeno" +check: "Zkontrolovat" +driveCapOverrideLabel: "Změnit velikost disku pro tohoto uživatele" +driveCapOverrideCaption: "K vyresetování velikosti na výchozí hodnotu zadejte hodnotu 0 nebo nižší." +requireAdminForView: "Pro zobrazení se musíte přihlásit administrátorským účtem." +isSystemAccount: "Účet automaticky vytvořený a ovládaný serverem." +typeToConfirm: "Prosíme zadejte {x} pro potvrzení" deleteAccount: "Odstranit účet" document: "Dokumentace" +numberOfPageCache: "Počet stránek uložených v mezipaměti" +numberOfPageCacheDescription: "Zvýšením čísla zlepšíte pohodlí pro uživatele ale může to způsobit větší zátěž na server a na paměť." logoutConfirm: "Opravdu se chcete odhlásit?" +lastActiveDate: "Naposledy použito" +statusbar: "Stavový řádek" pleaseSelect: "Vybrat možnost" reverse: "Otočit" colored: "Barevné" +refreshInterval: "Interval obnovení" +label: "Popisek" type: "Typ" speed: "Rychlost" slow: "Pomalá" fast: "Rychlá" +sensitiveMediaDetection: "Detekce citlivého média" +localOnly: "Jenom lokální" +remoteOnly: "Jenom vzdáleně" +failedToUpload: "Nahrání se nezdařilo" +cannotUploadBecauseInappropriate: "Tenhle soubor se nenahrál, protože některé části byly detekovány jako nevhodné." +cannotUploadBecauseNoFreeSpace: "Nahrání se nezdařilo z důvodu nedostatku místa na disku." +cannotUploadBecauseExceedsFileSizeLimit: "Tenhle soubor nemůže být nahráný protože překračuje velikostní limit." +beta: "Beta verze" +enableAutoSensitive: "Automaticky označovat jako citlivé" +enableAutoSensitiveDescription: "Umožňuje automatickou detekci a označování citlivého média skrze strojového účení všude kde je možno. I pokud je tahle možnost vypnutá, může být povolena instancí." +activeEmailValidationDescription: "Umožňuje striktní validaci emailové adresy, která zahrnuje kontrolu pro jednorázové adresy a pokud je možno s ní komunikovat. Pokud je to vypnuté, bude se kontrolovat pouze formát emailu." +navbar: "Navigační panel" +shuffle: "Zamíchat" account: "Účty" +move: "Přesunout" +pushNotification: "Push oznámení" +subscribePushNotification: "Povolit push oznamení" +unsubscribePushNotification: "Vypnout push oznámení" +pushNotificationAlreadySubscribed: "Push oznámení jsou už zapnuté" +pushNotificationNotSupported: "Tenhle prohlížeč nepodporuje push oznámení" +sendPushNotificationReadMessage: "Odstraněnit oznámení push po jejich přečtení" +sendPushNotificationReadMessageCaption: "Tohle může zvýšit spotřebu energie vašeho zařízení." +windowMaximize: "Maximalizovat" +windowMinimize: "Minimalizovat" +windowRestore: "Obnovit" +caption: "Titulek" +loggedInAsBot: "Právě jste přihlášen jako bot" +tools: "Nástroje" +cannotLoad: "Načtení se nezdařilo" +numberOfProfileView: "Počet zobrazení profilu" +like: "To se mi líbí" +unlike: "Už se mi to nelíbí" +numberOfLikes: "Počet \"To se mi líbí\"" show: "Zobrazit" +neverShow: "Znovu nezobrazovat" +remindMeLater: "Možná později" +didYouLikeMisskey: "Oblíbili jste si Misskey?" +pleaseDonate: "{host} používá bezplatný software Misskey. Velmi bychom ocenili vaše dary, aby mohl vývoj Misskey pokračovat!" +roles: "Role" +role: "Role" +noRole: "Role nenalezena" +normalUser: "Normální uživatel" +undefined: "Neurčeno" +assign: "Přiřadit" +unassign: "Zrušit přirazení" color: "Barva" +manageCustomEmojis: "Spravovat vlastní emoji" +youCannotCreateAnymore: "Narazili jste na limit pro vytváření." +cannotPerformTemporary: "Dočasně nedostupné" +cannotPerformTemporaryDescription: "Tuto akci nelze dočasně provést z důvodu překročení limitu provedení. Chvíli počkejte a zkuste to znovu." +invalidParamError: "Neplatné parametry" +invalidParamErrorDescription: "Parametry požadavku jsou neplatné. Obvykle je to způsobeno chybou, ale může to být také způsobeno překročením limitů velikosti vstupů nebo podobně." +permissionDeniedError: "Operace zamítnuta" +permissionDeniedErrorDescription: "Tento účet nemá oprávnění k provedení této akce." +preset: "Předvolba" +selectFromPresets: "Vybrat z předvoleb" +achievements: "Úspěchy" +gotInvalidResponseError: "Neplatná odpověď serveru" +gotInvalidResponseErrorDescription: "Server může být nedostupný nebo na něm probíhá údržba. Zkuste to prosím později." +thisPostMayBeAnnoying: "Tato poznámka může ostatní obtěžovat." +thisPostMayBeAnnoyingHome: "Zveřejnit na domovskou časovou osu" +thisPostMayBeAnnoyingCancel: "Zrušit" +thisPostMayBeAnnoyingIgnore: "I přesto zveřejnit" +collapseRenotes: "Sbalit poznámky, které jste již viděli" +internalServerError: "Interní chyba serveru" +internalServerErrorDescription: "Server narazil na neočekávanou chybu." +copyErrorInfo: "Zkopírovat detaily erroru" +joinThisServer: "Zaregistrovat se v této instanci" +exploreOtherServers: "Podívat se na ostatní instance" +letsLookAtTimeline: "Podívejte se na časovou osu" +disableFederationConfirm: "Chcete opravdu vypnout federace?" +disableFederationConfirmWarn: "I v případě defederace budou příspěvky nadále veřejné, pokud nebude nastaveno jinak. Obvykle to není nutné." +disableFederationOk: "Vypnout" +invitationRequiredToRegister: "Tahle instance je pouze na pozvánku. Musíte zadat validní kód pozvánky." +emailNotSupported: "Tahle instance nepodporuje zasílání emailů" +postToTheChannel: "Vložit do kanálu" +cannotBeChangedLater: "Tohle nemůže být změněno později." +reactionAcceptance: "Přijímání reakcí" +likeOnly: "Jenom \"oblíbené\"" +likeOnlyForRemote: "Všechny (Pouze \"oblíbené\" pro vzdálenou instanci)" +nonSensitiveOnly: "Pouze bez citlivých medií" +nonSensitiveOnlyForLocalLikeOnlyForRemote: "Pouze bez citlivých medií (Pouze vzdálený \"oblíbený\")" +rolesAssignedToMe: "Přiřazené role ke mně" +resetPasswordConfirm: "Opravdu chcete resetovat heslo?" +sensitiveWords: "Citlivá slova" +sensitiveWordsDescription: "Viditelnost všech poznámek obsahujících některé z nakonfigurovaných slov bude automaticky nastavena na \"Domů\". Můžete jich uvést více tak, že je oddělíte pomocí řádků." +sensitiveWordsDescription2: "Použití mezer vytvoří výrazy AND a obklopení klíčových slov lomítky je změní na regulární výraz." +notesSearchNotAvailable: "Vyhledávání poznámek je nedostupné." +license: "Licence" +unfavoriteConfirm: "Opravdu chcete odstranit z oblíbených?" +myClips: "Moje klipy" +drivecleaner: "Čistič disku" +retryAllQueuesNow: "Obnovit všechny běžící fronty" +retryAllQueuesConfirmTitle: "Opravdu chcete obnovit všechno?" +retryAllQueuesConfirmText: "Tohle dočasně zvýší zatěž na server." +enableChartsForRemoteUser: "Vygenerovat grafy dat vzdálených uživatelů" +enableChartsForFederatedInstances: "Vygenerovat grafy dat vzdálených instancí" +showClipButtonInNoteFooter: "Přidat \"Připnout\" do akčního menu poznámky" +noteIdOrUrl: "ID nebo URL poznámky" +video: "Video" +videos: "Videa" +dataSaver: "Spořič dat" +accountMigration: "Migrace účtu" +accountMoved: "Tenhle uživatel se přesunul na nový účet:" +accountMovedShort: "Tenhle účet byl migrován." +operationForbidden: "Zakázaná operace" +forceShowAds: "Vždycky zobrazovat reklamy" +addMemo: "Přidat memo" +editMemo: "Upravit memo" +reactionsList: "Reakce" +renotesList: "Poznámky" +notificationDisplay: "Oznámení" +leftTop: "Vlevo nahoře" +rightTop: "Vpravo nahoře" +leftBottom: "Vlevo dole" +rightBottom: "Vpravo dole" +stackAxis: "Směr ukládání" +vertical: "Svisle" +horizontal: "Vodorovně" +position: "Pozice" +serverRules: "Pravidla serveru" +pleaseConfirmBelowBeforeSignup: "Abyste se mohli přihlásit na server, musíte souhlasit s následujícím." +pleaseAgreeAllToContinue: "Musíte souhlasit se vším abyste mohli pokračovat." +continue: "Pokračovat" +preservedUsernames: "Rezervované uživatelské jména" +preservedUsernamesDescription: "Seznam uživatelských jmén na rezervaci oddělené mezerama. Tyhle jména se potom nebudou moc použít při normálním procesu vytvoření účtu ale můžou být použiti manuálně administratorém. Existujících účtů se to nedotkne." +createNoteFromTheFile: "Vytvořit poznámku z tohodle souboru" +archive: "Archiv" +channelArchiveConfirmTitle: "Opravdu chcete archivovat {name}?" +channelArchiveConfirmDescription: "Archivovaný kanál se objeví v seznamu kanálů nebo ve výsledcích hledání. Nové poznámky se nedají vložit do seznamu." +thisChannelArchived: "Tenhle kanál je archivovaný" +displayOfNote: "Zobrazit poznámku" +initialAccountSetting: "Nastavení profilu" +youFollowing: "Sleduji" +preventAiLearning: "Odmítnout použití v strojovém učení (Generative AI)" +preventAiLearningDescription: "Požaduje, aby prohlížeče nepoužívaly zveřejněný textový nebo obrazový materiál atd. v datových sadách pro strojové učení (prediktivní / generativní umělá inteligence). Toho se dosáhne přidáním příznaku \"noai\" HTML-Response k příslušnému obsahu. Úplné prevence však tímto příznakem nelze dosáhnout, protože může být jednoduše ignorován." +options: "Možnosti" +specifyUser: "Upřesnit uživatele" +failedToPreviewUrl: "Náhled se nezdařil" +update: "Aktualizovat" +rolesThatCanBeUsedThisEmojiAsReaction: "Role, které můžou tuhle emoji použít jako reakci" +rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Pokud nejsou určena role, tak pak každý může použít tenhle emoji." +rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Role musí být veřejné." +cancelReactionConfirm: "Opravdu chcete odstranit vaší reakci?" +changeReactionConfirm: "Opravdu chcete změnit vaši reakci?" +later: "Později" +goToMisskey: "Jít na Misskey" +additionalEmojiDictionary: "Další slovníky emoji" +installed: "Nainstalováno" +branding: "Značka" +enableServerMachineStats: "Zveřejněnit statistiky hardwaru serveru" +enableIdenticonGeneration: "Povolit generování identicon uživatele" +turnOffToImprovePerformance: "Vypnutí této funkce může zvýšit výkon." +createInviteCode: "Vygenerovat pozvánku" +createWithOptions: "Vygenerovat s nastavením" +createCount: "Počet vytvořených pozvánek" +inviteCodeCreated: "Pozvánka vygenerována" +inviteLimitExceeded: "Překročili jste limit pozvánek, které můžete vygenerovat." +createLimitRemaining: "Limit pozvánek: {limit} zbývá" +inviteLimitResetCycle: "Tento limit se obnoví na hodnotu {limit} v {time}." +expirationDate: "Datum expirace" +noExpirationDate: "Bez expirace" +inviteCodeUsedAt: "Kód pozvánky použitý na" +registeredUserUsingInviteCode: "Pozvánku používá" +waitingForMailAuth: "Čeká se na ověření emailu" +inviteCodeCreator: "Pozvánku vytvořil" +usedAt: "Používá se v" +unused: "Nepoužívaná" +used: "Používaná" +expired: "Prošlá" +doYouAgree: "Souhlasíte?" +beSureToReadThisAsItIsImportant: "Přečtěte si prosím tyto důležité informace." +iHaveReadXCarefullyAndAgree: "Přečetl jsem si text \"{x}\" a souhlasím s ním." +icon: "Avatar" +replies: "Odpovědět" +renotes: "Přeposlat" +_initialAccountSetting: + accountCreated: "Váš účet byl úspěšně vytvořen!" + letsStartAccountSetup: "Pro začátek si nastavte svůj profil." + letsFillYourProfile: "Nejprve si nastavte svůj profil." + profileSetting: "Nastavení profilu" + privacySetting: "Nastavení soukromí" + theseSettingsCanEditLater: "Tato nastavení můžete vždy později změnit." + youCanEditMoreSettingsInSettingsPageLater: "Na stránce \"Nastavení\" můžete nakonfigurovat mnoho dalších nastavení. Nezapomeňte ji navštívit později." + followUsers: "Zkuste sledovat některé uživatele, kteří vás zajímají pro vystavění časový osy." + pushNotificationDescription: "Povolení push oznámení vám umožní přijímat oznámení od {name} přímo ve vašem zařízení." + initialAccountSettingCompleted: "Nastavení profilu dokončeno!" + haveFun: "Užívejte {name}!" + ifYouNeedLearnMore: "Pokud se chcete dozvědět více o tom, jak používat {name} (Misskey), navštivte {link}." + skipAreYouSure: "Opravdu chcete přeskočit nastavení profilu?" + laterAreYouSure: "Opravdu chcete provést nastavení profilu později?" +_serverRules: + description: "Soubor pravidel, která se zobrazí před registrací. Doporučuje se nastavit shrnutí podmínek služby." +_serverSettings: + iconUrl: "URL ikony" +_accountMigration: + moveFrom: "Migrace jiného účtu na tento účet" + moveFromSub: "Vytvořit alias na jiný účet" + moveFromLabel: "Původní účet #{n}" + moveFromDescription: "Pro účet, ze kterého se chcete přesunout, musíte vytvořit alias na tomto účtu.\nZadejte účet, ze kterého chcete přejít, v následujícím formátu: @username@server.example.com\nChcete-li alias odstranit, ponechte pole prázdné (nedoporučuje se)." + moveTo: "Přesunout tenhle účet do jiného" + moveToLabel: "Cílový účet pro přesunutí:" + moveCannotBeUndone: "Migrace účtu nemůže být vrácena." + moveAccountDescription: "Tím dojde k migraci vašeho účtu na jiný účet.\n ・Sledovatelé z tohoto účtu budou automaticky převedeni na nový účet.\n ・Tento účet zruší sledování všech uživatelů, které aktuálně sleduje.\n ・Na tomto účtu nebude možné vytvářet nové poznámky atd.\n\nZatímco migrace sledovaných uživatelů probíhá automaticky, pro migraci seznamu sledovaných uživatelů je nutné připravit některé kroky ručně. Za tímto účelem proveďte export sledovaných, který později naimportujete na nový účet v nabídce nastavení. Stejný postup platí pro seznamy i pro ztlumené a zablokované uživatele.\n\n(Tento výklad platí pro Misskey v13.12.0 a novější. Jiný software ActivityPub, například Mastodon, může fungovat jinak.)" + moveAccountHowTo: "Chcete-li migrovat, vytvořte nejprve alias tohoto účtu na účtu, na který chcete přejít.\nPo vytvoření aliasu zadejte účet, na který chcete přejít, v následujícím formátu: @username@server.example.com" + startMigration: "Migrovat" + migrationConfirm: "Opravdu chcete migrovat tento účet na {account}? Jednou zahájený proces nelze zastavit ani vrátit zpět a tento účet již nebudete moci používat v původním stavu." + movedAndCannotBeUndone: "\nTento účet byl převeden.\nMigraci nelze vrátit zpět." + postMigrationNote: "Tento účet zruší sledování všech účtů, které aktuálně sleduje, 24 hodin po dokončení migrace.\nPočet sledujících i následovníků se poté vynuluje. Aby se zabránilo tomu, že vaši sledující nebudou moci vidět příspěvky tohoto účtu určené pouze pro sledující, budou však tento účet sledovat i nadále." + movedTo: "Cílový účet pro přesunutí:" +_achievements: + earnedAt: "Odemčeno v" + _types: + _notes1: + title: "Dobrý den Misskey!" + description: "Zveřejněte vaší první poznámku" + flavor: "Užijte si to s Misskey!" + _notes10: + title: "Pár poznámek" + description: "Zveřejněte 10 poznámek" + _notes100: + title: "Hodně poznámek" + description: "Zveřejněte 100 poznámek" + _notes500: + title: "Zahlcen poznámkama" + description: "Zveřejněte 500 poznámek" + _notes1000: + title: "Hora poznámek" + description: "Zveřejněte 1000 poznámek" + _notes5000: + title: "Přetékající poznámky" + description: "Zveřejněte 5000 poznámek" + _notes10000: + title: "Super poznámka" + description: "Zveřejněte 10 000 poznámek" + _notes20000: + title: "Potřebuju... více... poznámek..." + description: "Zveřejněte 20 000 poznámek" + _notes30000: + title: "Poznámky, poznámky, POZNÁMKY!" + description: "Zveřejněte 30 000 poznámek" + _notes40000: + title: "Továrna na poznámky" + description: "Zveřejněte 40 000 poznámek" + _notes50000: + title: "Planeta poznámek" + description: "Zveřejněte 50 000 poznámek" + _notes60000: + title: "Poznámkový kvasar" + description: "Zveřejněte 60 000 poznámek" + _notes70000: + title: "Černá díra poznámek" + description: "Zveřejněte 70 000 poznámek" + _notes80000: + title: "Galaxie poznámek" + description: "Zveřejněte 80 000 poznámek" + _notes90000: + title: "Vesmír poznámek" + description: "Zveřejněte 90 000 poznámek" + _notes100000: + title: "ALL YOUR NOTE ARE BELONG TO US" + description: "Zveřejněte 100 000 poznámek" + flavor: "Máte toho hodně co říct." + _login3: + title: "Začátečník I" + description: "Přihlaste se celkově za 3 dny" + flavor: "Ode dneška mi říkejte Misskista." + _login7: + title: "Začátečník II" + description: "Přihlaste se celkově za 7 dní" + flavor: "Máte pocit, že už jste se v tom vyznali?" + _login15: + title: "Začátečník III" + description: "Přihlaste se celkově za 15 dní" + _login30: + title: "Misskista I" + description: "Přihlaste se celkově za 30 dní" + _login60: + title: "Misskista II" + description: "Přihlaste se celkově za 60 dní" + _login100: + title: "Misskista III" + description: "Přihlaste se celkově za 100 dní" + flavor: "Violent Misskista" + _login200: + title: "Stálý zákazník I" + description: "Přihlaste se celkově za 200 dní" + _login300: + title: "Stálý zákazník II" + description: "Přihlaste se celkově za 300 dní" + _login400: + title: "Stálý zákazník III" + description: "Přihlaste se celkově za 400 dní" + _login500: + title: "Expert I" + description: "Přihlaste se celkově za 500 dní" + flavor: "Moji přátelé, často se říká, že mám rád poznámky." + _login600: + title: "Expert II" + description: "Přihlaste se celkově za 600 dní" + _login700: + title: "Expert III" + description: "Přihlaste se celkově za 700 dní" + _login800: + title: "Mistr poznámek I" + description: "Přihlaste se celkově za 800 dní" + _login900: + title: "Mistr poznámek II" + description: "Přihlaste se celkově za 900 dní" + _login1000: + title: "Mistr poznámek III" + description: "Přihlaste se celkově za 1000 dní" + flavor: "Děkujeme, že používáte Misskey!" + _noteClipped1: + title: "Musím... připnout..." + description: "Připněte si první poznámku" + _noteFavorited1: + title: "Hvězdář" + description: "Oblíbena první poznámka" + _myNoteFavorited1: + title: "Hledání hvězd" + description: "Někdo si oblíbil jednu z vašich poznámek" + _profileFilled: + title: "Dobře připravený" + description: "Nastavte si profil" + _markedAsCat: + title: "Já jsem kočka" + description: "Označte váš účet \"jako kočka\"" + flavor: "Jméno ti dám později." + _following1: + title: "Sledujte prvního uživatele" + description: "Sledujte uživatele" + _following10: + title: "Drž se... drž se..." + description: "Sledujte 10 uživatelů" + _following50: + title: "Hodně přátel" + description: "Sledujte 50 uživatelů" + _following100: + title: "100 přátel" + description: "Sledujte 100 uživatelů" + _following300: + title: "Přetížení přátel" + description: "Sledujte 300 účtů" + _followers1: + title: "První sledující" + description: "Získejte 1 sledujícího" + _followers10: + title: "Sledujte mě!" + description: "Získejte 10 sledujících" + _followers50: + title: "Přicházejí davy" + description: "Získejte 50 sledujících" + _followers100: + title: "Populární" + description: "Získejte 100 sledujících" + _followers300: + title: "Prosíme srovnejte se do jedné řady!" + description: "Získejte 300 sledujících" + _followers500: + title: "Rádiová věž" + description: "Získejte 500 sledujících" + _followers1000: + title: "Influencer" + description: "Získejte 1000 sledujících" + _collectAchievements30: + title: "Sběratel úspěchů" + description: "Získejte 30 úspěchů" + _viewAchievements3min: + title: "Máš rád úspěchy" + description: "Koukejte na váš seznam úspěchů alespoň po dobu 3 minut" + _iLoveMisskey: + title: "Miluju Misskey" + description: "Zveřejněte \" I ❤ #Misskey\"" + flavor: "Vývojový tým Misskey si velmi váží vaší podpory!" + _foundTreasure: + title: "Hon za pokladem" + description: "Našli jste schovaný poklad!" + _client30min: + title: "Krátká pauza" + description: "Mějte otevřený Misskey alespoň po dobu 30 minut" + _client60min: + title: "Žádný \"Miss\" v Misskey" + description: "Mějte otevřený Misskey alespoň po dobu 60 minut" + _noteDeletedWithin1min: + title: "Ups, nevadí" + description: "Vymažte poznámku během minuty co ji zveřejníte" + _postedAtLateNight: + title: "Noční typ" + description: "Zveřejněte poznámku pozdě v noci" + flavor: "Je nejvyšší čas jít spát." + _postedAt0min0sec: + title: "Mluvící hodiny" + description: "Zveřejněte poznámku přesně v 00:00" + flavor: "Klik Klik Klik Bum" + _selfQuote: + title: "Sebereference" + description: "Citujte vlastní poznámku" + _htl20npm: + title: "Plynoucí časová osa" + description: "Mějte rychlost vaší domovské časové osy vyšší než 20 pzm (poznámek za minutu)." + _viewInstanceChart: + title: "Analytik" + description: "Zobrazte graf instance" + _outputHelloWorldOnScratchpad: + title: "Hello, world!" + description: "Dostaňte výpis \"hello world\" do Scratchpadu" + _open3windows: + title: "Splitscreen" + description: "Mějte otevřená alespoň 3 okna zároveň" + _driveFolderCircularReference: + title: "Okružní reference" + description: "Pokuste se o vytvoření rekurzivně vnořené složky v disku" + _reactWithoutRead: + title: "Opravdu jste to četl/a?" + description: "Reagujte na poznámku, která má více než 100 znaků, do 3 sekund od jejího zveřejnění." + _clickedClickHere: + title: "Klikněte sem" + description: "Kliknul si tam" + _justPlainLucky: + title: "Čisté štěstí" + description: "Mějte šanci na získání s pravděpodobností 0,005 % každých 10 sekund." + _setNameToSyuilo: + title: "Boží komplex" + description: "Nastavte si jméno na \"syuilo\"" + _passedSinceAccountCreated1: + title: "Roční výročí" + description: "Od vytvoření vašeho účtu uplynul jeden rok" + _passedSinceAccountCreated2: + title: "Dvouleté výročí" + description: "Od vytvoření vašeho účtu uplynuly dva roky" + _passedSinceAccountCreated3: + title: "Tříleté výročí" + description: "Od vytvoření vašeho účtu uplynuly tři roky" + _loggedInOnBirthday: + title: "Všechno nejlepší!" + description: "Přihlašte se v den vašich narozenin" + _loggedInOnNewYearsDay: + title: "Štastný nový rok!" + description: "Přihlašte se v den nového roku" + flavor: "Na další skvělý rok v této instanci" + _cookieClicked: + title: "Hra, ve které klikáte na sušenky" + description: "Klikněte na soubor cookie" + flavor: "Počkejte, jste na správné webové stránce?" + _brainDiver: + title: "Brain Diver" + description: "Zveřejněte odkaz na Brain Diver" + flavor: "Misskey-Misskey La-Tu-Ma" _role: + new: "Nová role" + edit: "Upravit roli" + name: "Název role" + description: "Popis role" + permission: "Oprávnění role" + descriptionOfPermission: "Moderators může provádět základní operace moderování.\nAdministrators může měnit všechna nastavení instance." + assignTarget: "Přiřadit" + descriptionOfAssignTarget: "Manual ručně změnit, kdo je součástí této role a kdo ne.\nConditional mít uživatelé automaticky přiřazováni a odebíráni z této role na základě podmínky." + manual: "Dokumentace" + conditional: "Podmíněné" + condition: "Podmínky" + isConditionalRole: "Tato role je podmíněná." + isPublic: "Veřejná role" + descriptionOfIsPublic: "Tato role se zobrazí v profilech přiřazených uživatelů." + options: "Nastavení" + policies: "Zásady" + baseRole: "Šablona role" + useBaseValue: "Použít hodnotu šablony role" + chooseRoleToAssign: "Vyberte roli, kterou chcete přiřadit" + iconUrl: "URL ikony" + asBadge: "Zobrazovat jako odznak" + descriptionOfAsBadge: "Ikona této role se zobrazí vedle uživatelského jména uživatelů s touto rolí, pokud je zapnuta." + isExplorable: "Udělat roli objevitelnou" + descriptionOfIsExplorable: "Časová osa této role a seznam uživatelů s touto rolí budou zveřejněny, pokud jsou povoleny." + displayOrder: "Pozice" + descriptionOfDisplayOrder: "Čím vyšší číslo, tím vyšší pozice v uživatelském rozhraní." + canEditMembersByModerator: "Umožnit moderátorům upravovat seznam členů pro tuto roli" + descriptionOfCanEditMembersByModerator: "Po zapnutí této role budou moci moderátoři i administrátoři přiřazovat a odebírat uživatele do této role. Pokud je tato funkce vypnutá, budou moci uživatele přiřazovat pouze správci." priority: "Priorita" _priority: low: "Nízká" middle: "Střední" high: "Vysoká" + _options: + gtlAvailable: "Může zobrazit globální časovou osu" + ltlAvailable: "Může zobrazit místní časovou osu" + canPublicNote: "Může posílat veřejné poznámky" + canInvite: "Může vytvářet kódy pozvánek instance" + inviteLimit: "Limit pozvánek" + inviteLimitCycle: "Limit mezi pozvánkama" + inviteExpirationTime: "Interval vypršení platnosti pozvánky" + canManageCustomEmojis: "Spravovat vlastní emoji" + driveCapacity: "Velikost disku" + alwaysMarkNsfw: "Vždy označovat soubory jako NSFW" + pinMax: "Maximální počet připnutých poznámek" + antennaMax: "Maximální počet antén" + wordMuteMax: "Maximální počet znaků povolených v ztlumených slovech" + webhookMax: "Maximální počet Webhooků" + clipMax: "Maximální počet připnutí" + noteEachClipsMax: "Maximální počet poznámek v připnutí" + userListMax: "Maximální počet seznamů uživatelů" + userEachUserListsMax: "Maximální počet uživatelů v seznamu uživatelů" + rateLimitFactor: "Limit rychlosti" + descriptionOfRateLimitFactor: "Nižší limity rychlosti jsou méně omezující, vyšší více omezující. " + canHideAds: "Může schovat reklamy" + canSearchNotes: "Použití vyhledávání poznámek" + _condition: + isLocal: "Místní uživatel" + isRemote: "Vzdálený uživatel" + createdLessThan: "Od vytvoření účtu uplynulo méně než X" + createdMoreThan: "Od vytvoření účtu uplynulo více než X" + followersLessThanOrEq: "Má X nebo méně sledujících" + followersMoreThanOrEq: "Má X nebo více sledujících" + followingLessThanOrEq: "Sleduje X nebo méně účtů" + followingMoreThanOrEq: "Sleduje X nebo více účtů" + notesLessThanOrEq: "Počet příspěvků je menší než/rovná se" + notesMoreThanOrEq: "Počet příspěvků je větší než/rovná se" + and: "AND kondice" + or: "OR kondice" + not: "NOT kondice" +_sensitiveMediaDetection: + description: "Snižuje náročnost moderování serveru díky automatickému rozpoznávání citlivých médií pomocí strojového učení. Tím se mírně zvýší zatížení serveru." + sensitivity: "Detekce citlivosti" + sensitivityDescription: "Snížení citlivosti povede k menšímu počtu chybných detekcí (falešně pozitivních), zatímco její zvýšení povede k menšímu počtu chybných detekcí (falešně negativních)." + setSensitiveFlagAutomatically: "Označit jako citlivé" + setSensitiveFlagAutomaticallyDescription: "Výsledky interní detekce se zachovají, i když je tato možnost vypnutá." + analyzeVideos: "Povolit analýzy videí" + analyzeVideosDescription: "Kromě obrázků analyzuje i videa. Tím se mírně zvýší zatížení serveru." +_emailUnavailable: + used: "Tato emailová adresa se již používá" + format: "Formát této emailové adresy je neplatný" + disposable: "Jednorázové emailové adresy se nesmí používat" + mx: "Tento e-mailový server je neplatný" + smtp: "Tento emailový server neodpovídá" +_ffVisibility: + public: "Zveřejnit" + followers: "Viditelné pouze pro sledující" + private: "Soukromý" +_signup: + almostThere: "Už to skoro je" + emailAddressInfo: "Zadejte prosím svou emailovou adresu. Nebude zveřejněna." + emailSent: "Na vaši e-mailovou adresu ({email}) byl odeslán potvrzovací e-mail. Kliknutím na přiložený odkaz dokončete vytvoření účtu." +_accountDelete: + accountDelete: "Smazat účet" + mayTakeTime: "Vzhledem k tomu, že odstranění účtu je proces náročný na zdroje, může jeho dokončení trvat určitou dobu v závislosti na tom, kolik obsahu jste vytvořili a kolik souborů jste nahráli." + sendEmail: "Po dokončení odstranění účtu bude na emailovou adresu registrovanou k tomuto účtu zaslán email." + requestAccountDelete: "Žádost o smazání účtu" + started: "Bylo zahájeno mazání." + inProgress: "V současné době probíhá mazání" _ad: back: "Zpět" + reduceFrequencyOfThisAd: "Zobrazovat tuto reklamu méně" + hide: "Schovat" + timezoneinfo: "Den v týdnu se určuje podle časového pásma serveru." +_forgotPassword: + enterEmail: "Zadejte emailovou adresu, kterou jste použili při registraci. Na ni vám pak bude zaslán odkaz, pomocí kterého si můžete obnovit heslo." + ifNoEmail: "Pokud jste při registraci nepoužili email, obraťte se na správce instance." + contactAdmin: "Tato instance nepodporuje používání emailových adres, pro obnovení hesla se obraťte na správce instance." _gallery: my: "Moje galerie" + liked: "Oblíbené příspěvky" + like: "To se mi líbí" + unlike: "Už se mi to nelíbí" _email: _follow: title: "Máte nového následovníka" + _receiveFollowRequest: + title: "Obdrželi jste žádost o sledování" _plugin: install: "Instalovat plugin" + installWarn: "Neinstalujte nedůvěryhodné pluginy." manage: "Správce pluginů" _preferencesBackups: list: "Vytvořit backup" + saveNew: "Uložit novou zálohu" loadFile: "Načíst ze souboru" + apply: "Použít pro toto zařízení" save: "Uložit změny" + inputName: "Zadejte prosím název pro tuto zálohu" + cannotSave: "Uložení selhalo" + nameAlreadyExists: "Záloha s názvem \"{name}\" již existuje. Zadejte prosím jiný název." + applyConfirm: "Opravdu chcete na toto zařízení použít zálohu \"{name}\"? Stávající nastavení tohoto zařízení bude přepsáno." + saveConfirm: "Uložit zálohu jako {name}?" + deleteConfirm: "Odstranit zálohu {name}?" + renameConfirm: "Přejmenovat tuto zálohu z \"{old}\" na \"{new}\"?" + noBackups: "Neexistují žádné zálohy. Nastavení klienta na tomto serveru můžete zálohovat pomocí \"Vytvořit novou zálohu\"." + createdAt: "Vytvořeno v: {date} {time}" + updatedAt: "Aktualizováno: {date} {time}" + cannotLoad: "Načítání selhalo" + invalidFile: "Neplatný typ souboru" _registry: scope: "Rozsah" key: "Klíč" @@ -823,46 +1517,236 @@ _registry: domain: "Doména" createKey: "Vytvořit klíč" _aboutMisskey: + about: "Misskey je open-source software vyvíjený syuilo od roku 2014." + contributors: "Hlavní přispěvatelé" allContributors: "Všichni přispěvatelé" source: "Zdrojový kód" + translation: "Přeložit Misskey" + donate: "Přispějte na Misskey" + morePatrons: "Vážíme si také podpory mnoha dalších pomocníků, kteří zde nejsou uvedeni. Děkujeme! 🥰" + patrons: "Patroni" +_displayOfSensitiveMedia: + respect: "Skrýt média označená jako citlivá" + ignore: "Zobrazit média označená jako citlivá" + force: "Skrýt všechna média" +_instanceTicker: + none: "Nikdy nezobrazovat" + remote: "Zobrazit pro vzdálené uživatelé" + always: "Vždy zobrazovat" +_serverDisconnectedBehavior: + reload: "Automatické znovunačtení" + dialog: "Zobrazení dialogového okna s varováním" + quiet: "Zobrazit nerušivé upozornění" _channel: + create: "Vytvořit kanál" + edit: "Upravit kanál" + setBanner: "Nastavit banner" + removeBanner: "Odstranit banner" featured: "Trendy" + owned: "Vlastněný" + following: "Sledovaný" + usersCount: "{n} Účastníků" + notesCount: "{n} Poznámek" + nameAndDescription: "Název a popis" + nameOnly: "Pouze název" _menuDisplay: + sideFull: "Postranně" + sideIcon: "Postranně (Ikony)" top: "Nahoru" hide: "Skrýt" +_wordMute: + muteWords: "Ztlumená slova" + muteWordsDescription: "Podmínku AND oddělujte mezerami, podmínku OR oddělujte řádkovými zlomy." + muteWordsDescription2: "Chcete-li použít regulární výrazy, obklopte klíčová slova lomítky." + softDescription: "Skrýt poznámky, které splňují nastavené podmínky, z časové osy." + hardDescription: "Zabrání přidání poznámek splňujících nastavené podmínky na časovou osu. Kromě toho nebudou tyto poznámky přidány na časovou osu, ani když se podmínky změní." + soft: "Měkký" + hard: "Tvrdý" + mutedNotes: "Ztlumené poznámky" +_instanceMute: + instanceMuteDescription: "Tímhle se ztlumí všechny poznámky/poznámky z uvedených instancí, včetně poznámek uživatelů, kteří odpovídají uživateli ze ztlumené instance." + instanceMuteDescription2: "Oddělte novými řádky" + title: "Skryje poznámky z uvedených případů." + heading: "Seznam instancí, které mají být ztlumeny" _theme: + explore: "Objevit témata" install: "Nainstalovat vzhled" manage: "Správa vzhledů" code: "Kód vzhledu" description: "Popis" + installed: "{name} byl nainstalován" installedThemes: "Nainstalované vzhledy" + builtinThemes: "Vestavěné temáta" + alreadyInstalled: "Tento vzhled je již nainstalován." + invalid: "Formát tohoto tématu je neplatný" + make: "Vytvořit téma" + base: "Základ" + addConstant: "Přidat konstantu" constant: "Konstanta" defaultValue: "Výchozí hodnota" color: "Barva" + refProp: "Odkázat na vlastnost" + refConst: "Odkázat na konstantu" key: "Klíč" func: "Funkce " + funcKind: "Typ funkce" + argument: "Argument" + basedProp: "Odkazovaná vlastnost" + alpha: "Průhlednost" + darken: "Ztmavit" + lighten: "Zesvětlit" + inputConstantName: "Zadejte název pro tuto konstantu" + importInfo: "Pokud zde zadáte kód motivu, můžete jej importovat do editoru motivu." + deleteConstantConfirm: "Opravdu chcete odstranit konstantu {const}?" keys: + accent: "Akcent" + bg: "Pozadí" + fg: "Text" + focus: "Fokus" + indicator: "Indikátor" + panel: "Panely" shadow: "Stín" header: "Nadpis" + navBg: "Pozadí postranního panelu" + navFg: "Text na postranním panelu" + navHoverFg: "Text na postranním panelu (Hover)" + navActive: "Text na postranním panelu (Aktivní)" + navIndicator: "Indikátor na postranním panelu" link: "Odkaz" hashtag: "Hashtag" mention: "Zmínění" + mentionMe: "Zmínky (mě)" renote: "Přeposlat" + modalBg: "Pozadí Modalu" divider: "Dělící čára" + scrollbarHandle: "Rukojeť posuvníku" + scrollbarHandleHover: "Rukojeť posuvníku (Hover)" + dateLabelFg: "Text štítku s datem" + infoBg: "Pozadí informací" + infoFg: "Text informací" + infoWarnBg: "Pozadí varování" + infoWarnFg: "Text varování" + cwBg: "Pozadí CW tlačítka" + cwFg: "Text CW tlačítka" + cwHoverBg: "Pozadí CW tlačítka (Hover)" + toastBg: "Pozadí oznámení" + toastFg: "Text oznámení" + buttonBg: "Pozadí tlačítka" + buttonHoverBg: "Pozadí tlačítka (Hover)" + inputBorder: "Ohraničení vstupního pole" + listItemHoverBg: "Pozadí položky seznamu (Hover)" + driveFolderBg: "Pozadí složky disku" + wallpaperOverlay: "Překrytí tapety" + badge: "Odznak" + messageBg: "Pozadí chatu" + accentDarken: "Akcent (Ztmavený)" + accentLighten: "Akcent (Zesvětlený)" + fgHighlighted: "Zvýrazněný text" _sfx: note: "Poznámky" + noteMy: "Moje poznámka" notification: "Oznámení" chat: "Zprávy" + chatBg: "Chat (Pozadí)" + antenna: "Antény" + channel: "Oznámení kanálu" _ago: future: "Budoucí" justNow: "Teď" + secondsAgo: "Před {n}s" + minutesAgo: "Před {n}min" + hoursAgo: "Před {n}h" + daysAgo: "Před {n}d" + weeksAgo: "Před {n}t" + monthsAgo: "Před {n}m" + yearsAgo: "Před {n}r" invalid: "Nic nebylo nalezeno" _time: second: "Sekund" minute: "Minut" hour: "Hodin" + day: "Dnů" +_timelineTutorial: + title: "Jak používat Misskey" + step1_1: "Toto je \"časová osa\". Zde se chronologicky zobrazují všechny \"poznámky\" odeslané na {name}." + step1_2: "Existuje několik různých časových plánů. Například \"Domácí časová osa\" bude obsahovat poznámky uživatelů, které sledujete, a \"Místní časová osa\" bude obsahovat poznámky všech uživatelů {name}." + step2_1: "Zkusme zveřejnit poznámku. Můžete tak učinit stisknutím tlačítka s ikonou tužky." + step2_2: "Co takhle napsat sebepředstavení, nebo jen \"Ahoj {name}!\", pokud se vám nechce?" + step3_1: "Dokončil jsi svou první poznámku?" + step3_2: "Na časové ose by se nyní měla zobrazit vaše první poznámka." + step4_1: "K poznámkám můžete také připojit \"Reakce\"." + step4_2: "Chcete-li připojit reakci, stiskněte na poznámce znaménko \"+\" a vyberte emoji, kterým chcete reagovat." _2fa: + alreadyRegistered: "Již jste zaregistrovali dvoufaktorové ověřovací zařízení." + registerTOTP: "Registrovat aplikaci autentizátoru" + passwordToTOTP: "Zadejte své heslo" + step1: "Nejprve si do zařízení nainstalujte aplikaci pro ověřování (například {a} nebo {b})." + step2: "Poté naskenujte QR kód zobrazený na této obrazovce." + step2Click: "Kliknutím na tento QR kód můžete zaregistrovat 2FA do bezpečnostního klíče nebo aplikace autentizace telefonu." + step3Title: "Zadejte ověřovací kód" + step3: "Pro dokončení nastavení zadejte token poskytnutý vaší aplikací." + step4: "Od této chvíle budou všechny budoucí pokusy o přihlášení vyžadovat tento přihlašovací token." + securityKeyNotSupported: "Váš prohlížeč nepodporuje bezpečnostní klíče." + registerTOTPBeforeKey: "Nastavte aplikaci autentizátoru pro registraci bezpečnostního nebo přístupového klíče." + securityKeyInfo: "Kromě ověřování otiskem prstu nebo PIN můžete nastavit také ověřování pomocí hardwarových bezpečnostních klíčů, které podporují FIDO2, a svůj účet tak dále zabezpečit." + registerSecurityKey: "Registrace bezpečnostního nebo přístupového klíče" + securityKeyName: "Zadejte název klíče" + tapSecurityKey: "Při registraci bezpečnostního nebo přístupového klíče postupujte podle svého prohlížeče." + removeKey: "Odstranit bezpečnostní klíč" + removeKeyConfirm: "Opravdu chcete odstranit klíč {name}?" + whyTOTPOnlyRenew: "Aplikaci autentizátoru nelze odstranit, dokud je zaregistrován bezpečnostní klíč." + renewTOTP: "Překonfigurování aplikace autentizátor" + renewTOTPConfirm: "Tohle způsobí, že ověřovací kódy z předchozí aplikace přestanou fungovat." + renewTOTPOk: "Přenastavit" renewTOTPCancel: "Ne děkuji" +_permissions: + "read:account": "Zobrazit informace o účtu" + "write:account": "Upravit informace o účtu" + "read:blocks": "Zobrazit seznam blokovaných uživatelů" + "write:blocks": "Upravit seznam blokovaných uživatelů" + "read:drive": "Přístup k souborům a složkám na disku" + "write:drive": "Úprava nebo odstranění souborů a složek na disku" + "read:favorites": "Zobrazit seznam oblíbených" + "write:favorites": "Upravit seznam oblíbených" + "read:following": "Zobrazit informace o tom, koho sledujete" + "write:following": "Sledování nebo zrušení sledování jiných účtů" + "read:messaging": "Zobrazit chat" + "write:messaging": "Sestavit nebo mazat zprávy chatu" + "read:mutes": "Zobrazit seznam ztlumených uživatelů" + "write:mutes": "Upravit seznam ztlumených uživatelů" + "write:notes": "Sestavit nebo odstranit poznámky" + "read:notifications": "Zobrazit oznámení" + "write:notifications": "Spravit oznámení" + "read:reactions": "Zobrazit vaše reakce" + "write:reactions": "Upravit své reakce" + "write:votes": "Hlasovat v anketě" + "read:pages": "Zobrazit své stránky" + "write:pages": "Upravit nebo odstranit stránky" + "read:page-likes": "Zobrazit to se mi líbí na stránkách" + "write:page-likes": "Upravit to se mi líbí na stránkách" + "read:user-groups": "Zobrazit skupiny uživatelů" + "write:user-groups": "Upravit nebo odstranit skupiny uživatelů" + "read:channels": "Zobrazit své kanály" + "write:channels": "Upravit kanály" + "read:gallery": "Zobrazit galerii" + "write:gallery": "Upravit galerii" + "read:gallery-likes": "Zobrazit seznam to se mi líbí příspěvků v galerii" + "write:gallery-likes": "Upravit seznam to se mi líbí příspěvků v galerii" +_auth: + shareAccessTitle: "Udělovat oprávnění k aplikacím" + shareAccess: "Chcete autorizovat \"{name}\" pro přístup k tomuto účtu?" + shareAccessAsk: "Opravdu chcete této aplikaci povolit přístup k vašemu účtu?" + permission: "{jméno} požaduje tato oprávnění" + permissionAsk: "Tato aplikace požaduje následující oprávnění" + pleaseGoBack: "Vraťte se prosím zpět do aplikace" + callback: "Návrat k aplikaci" + denied: "Přístup odepřen" + pleaseLogin: "Pro autorizaci aplikací se prosím přihlaste." +_antennaSources: + all: "Všechny poznámky" + homeTimeline: "Poznámky sledovaných uživatelů" + users: "Poznámky konkrétních uživatelů" + userList: "Poznámky z určitého seznamu uživatelů" _weekday: sunday: "Neděle" monday: "Pondělí" @@ -874,38 +1758,81 @@ _weekday: _widgets: profile: "Váš profil" instanceInfo: "Informace o instanci" + memo: "Přilepené poznámky" notifications: "Oznámení" timeline: "Časová osa" calendar: "Kalendář" trends: "Trendy" clock: "Hodiny" rss: "RSS čtečka" + rssTicker: "RSS Ticker" activity: "Aktivita" photos: "Fotky" digitalClock: "Digitální hodiny" + unixClock: "Hodiny UNIX" federation: "Federace" + instanceCloud: "Cloud instance" + postForm: "Formulář pro odeslání" slideshow: "Prezentace" button: "Tlačítko" onlineUsers: "Online uživatelé" jobQueue: "Fronta úloh" + serverMetric: "Metriky serveru" aiscript: "AiScript conzole" + aiscriptApp: "Aplikace AiScript" aichan: "Ai" + userList: "Seznam uživatelů" _userList: chooseList: "Vybrat seznam" + clicker: "Clicker" _cw: hide: "Skrýt" show: "Zobrazit více" + chars: "{count} charakterů" + files: "{count} souborů" _poll: + noOnlyOneChoice: "Jsou zapotřebí alespoň dvě možnosti" + choiceN: "Volba {n}" noMore: "Více už přidat nemůžete" + canMultipleVote: "Umožnit výběr více možností" + expiration: "Ukončení ankety" infinite: "Nikdy" + at: "Ukončit v" + after: "Ukončit po" deadlineDate: "Datum ukončení" deadlineTime: "Hodin" duration: "Trvání" + votesCount: "{n} hlasů" + totalVotes: "{n} hlasů celkově" + vote: "Hlasovat v anketě" + showResult: "Zobrazit výsledky" + voted: "Odhlasováno" + closed: "Uzavřeno" + remainingDays: "Zbývá {d} den/dní a {h} hodin/a" + remainingHours: "Zbývá {h} hodin/a a {m} minut/a" + remainingMinutes: "Zbývá {m} minut/a a {s} sekund/a" + remainingSeconds: "Zbývá {s} sekund/a" _visibility: + public: "Veřejný" + publicDescription: "Vaše poznámka bude viditelná pro všechny uživatele" home: "Domů" + homeDescription: "Zveřejnit příspěvek pouze na domovskou časovou osu" followers: "Sledující" + followersDescription: "Zviditelnit pouze pro své sledující" + specified: "Přímý" + specifiedDescription: "Zviditelnit pouze pro určité uživatele" + disableFederation: "Defederace" + disableFederationDescription: "Nepřenášet do jiných instancí" _postForm: + replyPlaceholder: "Odpovědět na tuto poznámku..." + quotePlaceholder: "Citovat tuto poznámku..." + channelPlaceholder: "Zveřejnit příspěvek do kanálu..." _placeholders: + a: "Co máte v plánu?" + b: "Co se děje kolem vás?" + c: "Co máte na mysli?" + d: "Co chcete říct?" + e: "Začít psát..." f: "Čekám, až něco napíšete..." _profile: name: "Jméno" @@ -913,36 +1840,100 @@ _profile: description: "O mně" youCanIncludeHashtags: "V popisku o Vás můžete použít i hastagy." metadata: "Doplňující informace" + metadataEdit: "Upravit doplňující informace" + metadataDescription: "Pomocí nich můžete ve svém profilu zobrazit doplňující informační pole." + metadataLabel: "Popisek" metadataContent: "Obsah" + changeAvatar: "Změnit avatara" + changeBanner: "Změnit banner" _exportOrImport: allNotes: "Všechny poznámky" + favoritedNotes: "Oblíbené poznámky" followingList: "Sledovaní" muteList: "Ztlumit" blockingList: "Zablokovat" userLists: "Seznamy" + excludeMutingUsers: "Vyloučit ztlumené uživatele" + excludeInactiveUsers: "Vyloučit neaktivní uživatele" _charts: federation: "Federace" apRequest: "Požadavek" + usersIncDec: "Rozdíl v počtech uživatelů" usersTotal: "Celkem uživatelů" activeUsers: "Aktivní uživatelé" + notesIncDec: "Rozdíl v počtu poznámek" + localNotesIncDec: "Rozdíl v počtu místních poznámek" + remoteNotesIncDec: "Rozdíl v počtu vzdálených poznámek" notesTotal: "Celkový počet poznámek" + filesIncDec: "Rozdíl v počtu souborů" + filesTotal: "Celkový počet souborů" + storageUsageIncDec: "Rozdíl ve využití úložiště" + storageUsageTotal: "Celkové využití úložiště" +_instanceCharts: + requests: "Požadavky" + users: "Rozdíl v počtech uživatelů" + usersTotal: "Kumulativní počet uživatelů" + notes: "Rozdíl v počtu poznámek" + notesTotal: "Kumulativní počet poznámek" + ff: "Rozdíl v počtu sledovaných uživatelů / sledujících" + ffTotal: "Kumulativní počet sledovaných uživatelů / sledujících" + cacheSize: "Rozdíl ve velikosti mezipaměti" + cacheSizeTotal: "Kumulativní celková velikost mezipaměti" + files: "Rozdíl v počtu souborů" + filesTotal: "Kumulativní počet souborů" _timelines: home: "Domů" + local: "Místní" + social: "Sociální síť" global: "Globální" _play: + new: "Vytvořit Play" + edit: "Upravit Play" + created: "Play vytvořen" + updated: "Play upraven" + deleted: "Play smazán" + pageSetting: "Nastavení Play" + editThisPage: "Upravit tenhle Play" + viewSource: "Zobrazit zdroj" + my: "Moje Plays" + liked: "To se mi líbí Plays" + featured: "Populární" + title: "Titulek" script: "Skript" summary: "Popis" _pages: newPage: "Vytvořit novou stránku" editPage: "Upravit stránku" + readPage: "Prohlížení zdroje této stránky" created: "Stránka byla úspěšně vytvořena" updated: "Stránka byla úspěšně aktualizována" deleted: "Stránka byla úspěšně smazána" pageSetting: "Nastavení stránky" + nameAlreadyExists: "Zadaná adresa URL stránky již existuje" + invalidNameTitle: "Zadaná adresa URL stránky je neplatná" invalidNameText: "Ujistěte se že jméno stránky je vyplněno" + editThisPage: "Upravit tuto stránku" + viewSource: "Zobrazit zdroj" + viewPage: "Zobrazit své stránky" + like: "To se mi líbí" + unlike: "Už se mi to nelíbí" + my: "Moje stránky" + liked: "To se mi líbí stránky" + featured: "Populární" + inspector: "Inspektor" contents: "Obsah" + content: "Blok stránky" + variables: "Proměnné" + title: "Titulek" + url: "URL stránky" + summary: "Přehled stránky" + alignCenter: "Vycentrovat prvky" + hideTitleWhenPinned: "Skrytí názvu stránky při připnutí k profilu" + font: "Písmo" fontSerif: "Serif" fontSansSerif: "Sans Serif" + eyeCatchingImageSet: "Nastavení miniatury" + eyeCatchingImageRemove: "Smazání miniatury" chooseBlock: "Přidat blok" selectType: "Vyberte typ" contentBlocks: "Obsah" @@ -954,8 +1945,28 @@ _pages: section: "Sekce" image: "Obrázky" button: "Tlačítko" + note: "Vestavěná poznámka" + _note: + id: "ID poznámky" + idDescription: "Adresu URL poznámky můžete vložit také sem." + detailed: "Podrobné zobrazení" +_relayStatus: + requesting: "Čeká se" + accepted: "Schváleno" + rejected: "Odmítnuto" _notification: + fileUploaded: "Soubor úspěšně nahrán" + youGotMention: "{name} vás zmínil" + youGotReply: "{name} vám odpověděl" + youGotQuote: "{name} vás citoval" + youRenoted: "Poznámka od {jméno}" youWereFollowed: "Máte nového následovníka" + youReceivedFollowRequest: "Obdrželi jste žádost o sledování" + yourFollowRequestAccepted: "Vaše žádost o sledování byla přijata" + pollEnded: "Výsledky ankety jsou k dispozici" + unreadAntennaNote: "Anténa {name}" + emptyPushNotificationMessage: "Push oznámení byla aktualizována" + achievementEarned: "Úspěch odemčen" _types: all: "Vše" follow: "Sledovaní" @@ -964,17 +1975,64 @@ _notification: renote: "Přeposlat" quote: "Citovat" reaction: "Reakce" + pollEnded: "Anketa končí" + receiveFollowRequest: "Obdržené žádosti o sledování" + followRequestAccepted: "Přijaté žádosti o sledování" + achievementEarned: "Úspěch odemčen" + app: "Oznámení z propojených aplikací" _actions: + followBack: "vás začal sledovat zpět" reply: "Odpovědět" renote: "Přeposlat" _deck: + alwaysShowMainColumn: "Vždy zobrazovat hlavní sloupec" + columnAlign: "Zarovnat sloupce" + addColumn: "Přidat sloupec" + configureColumn: "Nastavení sloupců" + swapLeft: "Prohodit s levým sloupcem" + swapRight: "Prohodit s pravým sloupcem" + swapUp: "Prohodit s výše uvedeným sloupcem" + swapDown: "Prohodit s níže uvedeným sloupcem" + stackLeft: "Nahromadit v levém sloupci" + popRight: "Popnout sloupec na pravou stranu" + profile: "Profil" + newProfile: "Nový profil" + deleteProfile: "Smazat profil" + introduction: "Vytvořte si dokonalé rozhraní volným uspořádáním sloupců!" + introduction2: "Kliknutím na tlačítko + v pravé části obrazovky můžete kdykoli přidat nové sloupce." + widgetsIntroduction: "V nabídce sloupce vyberte možnost \"Upravit widgety\" a přidejte widget." + useSimpleUiForNonRootPages: "Použít zjednodušené uživatelské rozhraní pro navigaci na stránkách" _columns: + main: "Hlavní" + widgets: "Widgety" notifications: "Oznámení" tl: "Časová osa" antenna: "Antény" list: "Seznamy" channel: "Kanály" mentions: "Zmínění" + direct: "Přímý" + roleTimeline: "Časová osa role" +_dialog: + charactersExceeded: "Překročili jste maximální počet znaků! V současné době je na hodnotě {current} z {max}." + charactersBelow: "Nedosahujete minimálního limitu znaků! V současné době je na {current} z {min}." +_disabledTimeline: + title: "Časová osa vypnuta" + description: "Tuto časovou osu nemůžete používat v rámci svých současných rolí." +_drivecleaner: + orderBySizeDesc: "Sestupná velikost souborů" + orderByCreatedAtAsc: "Vzestupné datumy" _webhookSettings: + createWebhook: "Vytvořit Webhook" name: "Jméno" + secret: "Tajné" + events: "Události Webhook" active: "Zapnuto" + _events: + follow: "Při sledování uživatele" + followed: "Při sledování" + note: "Při zveřejňování poznámky" + reply: "Při obdržení odpovědi" + renote: "Při renotaci poznámky" + reaction: "Při obdržení reakce" + mention: "Při zmínce" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 040e8836ef2c..16f210202b25 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -45,6 +45,7 @@ pin: "An dein Profil anheften" unpin: "Von deinem Profil lösen" copyContent: "Inhalt kopieren" copyLink: "Link kopieren" +copyLinkRenote: "Renote-Link kopieren" delete: "Löschen" deleteAndEdit: "Löschen und Bearbeiten" deleteAndEditConfirm: "Möchtest du diese Notiz wirklich löschen und bearbeiten? Alle Reaktionen, Renotes und Antworten dieser Notiz werden verloren gehen." @@ -156,6 +157,7 @@ addEmoji: "Emoji hinzufügen" settingGuide: "Empfohlene Einstellung" cacheRemoteFiles: "Dateien von fremden Instanzen im Cache speichern" cacheRemoteFilesDescription: "Ist diese Einstellung deaktiviert, so werden Dateien fremder Instanzen direkt von dort geladen. Hierdurch wird Speicherplatz auf diesem Server gespart, aber durch fehlende Generierung von Vorschaubildern mehr Bandbreite verwendet." +youCanCleanRemoteFilesCache: "Klicke auf den 🗑️-Knopf der Dateiverwaltungsansicht, um den Cache zu leeren." cacheRemoteSensitiveFiles: "Sensitive Dateien von fremden Instanzen im Cache speichern" cacheRemoteSensitiveFilesDescription: "Ist diese Einstellung deaktiviert, so werden sensitive Dateien fremder Instanzen direkt von dort ohne Zwischenspeicherung geladen." flagAsBot: "Als Bot markieren" @@ -230,7 +232,7 @@ noJobs: "Keine Jobs vorhanden" federating: "Wird föderiert" blocked: "Blockiert" suspended: "Gesperrt" -all: "Alles" +all: "Alle" subscribing: "Wird abonniert" publishing: "Wird veröffentlicht" notResponding: "Antwortet nicht" @@ -319,7 +321,7 @@ copyUrl: "URL kopieren" rename: "Umbenennen" avatar: "Profilbild" banner: "Banner" -displayOfSensitiveMedia: "Anzeige von sensiblen Medien" +displayOfSensitiveMedia: "Darstellung sensibler Medien" whenServerDisconnected: "Bei Verbindungsverlust zum Server" disconnectedFromServer: "Die Verbindung zum Server wurde getrennt" reload: "Aktualisieren" @@ -354,7 +356,6 @@ invite: "Einladen" driveCapacityPerLocalAccount: "Drive-Kapazität pro lokalem Benutzerkonto" driveCapacityPerRemoteAccount: "Drive-Kapazität pro Benutzer fremder Instanzen" inMb: "In Megabytes" -iconUrl: "Icon-URL (favicon etc)" bannerUrl: "Banner-URL" backgroundImageUrl: "Hintergrundbild-URL" basicInfo: "Grundlegende Informationen" @@ -410,13 +411,16 @@ aboutMisskey: "Über Misskey" administrator: "Administrator" token: "Token" 2fa: "Zwei-Faktor-Authentifizierung" +setupOf2fa: "Zweifaktorauthentifizierung einrichten" totp: "Authentifizierungs-App" totpDescription: "Logge dich via Authentifizierungs-App mit Einmalpasswort ein" moderator: "Moderator" moderation: "Moderation" +moderationNote: "Moderationsnotiz" +addModerationNote: "Moderationsnotiz hinzufügen" nUsersMentioned: "Von {n} Benutzern erwähnt" -securityKeyAndPasskey: "Security-Tokens und Passkeys" -securityKey: "Sicherheitsschlüssel" +securityKeyAndPasskey: "Hardware-Sicherheitsschlüssel und Passkeys" +securityKey: "Hardware-Sicherheitsschlüssel" lastUsed: "Zuletzt benutzt" lastUsedAt: "Zuletzt verwendet: {t}" unregister: "Deaktivieren" @@ -540,7 +544,7 @@ chooseEmoji: "Emoji auswählen" unableToProcess: "Der Vorgang konnte nicht abgeschlossen werden" recentUsed: "Vor kurzem verwendet" install: "Installieren" -uninstall: "Uninstallieren" +uninstall: "Deinstallieren" installedApps: "Authorisierte Anwendungen" nothing: "Hier gibt es nichts zu sehen" installedDate: "Authorisiert am" @@ -631,7 +635,7 @@ regexpErrorDescription: "Im regulären Ausdruck deiner {tab}en Wortstummschaltun instanceMute: "Instanzstummschaltungen" userSaysSomething: "{name} hat etwas gesagt" makeActive: "Aktivieren" -display: "Anzeigen" +display: "Anzeigeart" copy: "Kopieren" metrics: "Metriken" overview: "Übersicht" @@ -653,6 +657,7 @@ behavior: "Verhalten" sample: "Beispiel" abuseReports: "Meldungen" reportAbuse: "Melden" +reportAbuseRenote: "Renote melden" reportAbuseOf: "{name} melden" fillAbuseReportDescription: "Bitte gib zusätzliche Informationen zu dieser Meldung an. Falls es sich um eine spezielle Notiz handelt, bitte gib dessen URL an." abuseReported: "Deine Meldung wurde versendet. Vielen Dank." @@ -680,6 +685,7 @@ createNewClip: "Neuen Clip erstellen" unclip: "Aus Clip entfernen" confirmToUnclipAlreadyClippedNote: "Diese Notiz ist bereits im \"{name}\" Clip enthalten. Möchtest du sie aus diesem Clip entfernen?" public: "Öffentlich" +private: "Privat" i18nInfo: "Misskey wird durch freiwillige Helfer in viele verschiedene Sprachen übersetzt. Auf {link} kannst du mithelfen." manageAccessTokens: "Zugriffstokens verwalten" accountInfo: "Benutzerkonto-Informationen" @@ -1018,7 +1024,7 @@ retryAllQueuesConfirmText: "Dies wird zu einer temporären Erhöhung der Serverl enableChartsForRemoteUser: "Diagramme für Nutzer fremder Instanzen erstellen" enableChartsForFederatedInstances: "Diagramme für fremde Instanzen erstellen" showClipButtonInNoteFooter: "\"Clip\" zum Notizmenu hinzufügen" -largeNoteReactions: "Reaktionen vergrößert anzeigen" +reactionsDisplaySize: "Reaktionsanzeigegröße" noteIdOrUrl: "Notiz-ID oder URL" video: "Video" videos: "Videos" @@ -1052,7 +1058,7 @@ archive: "Archivieren" channelArchiveConfirmTitle: "{name} wirklich archivieren?" channelArchiveConfirmDescription: "Ein archivierter Kanal taucht nicht mehr in der Kanalliste oder in Suchergebnissen auf. Zudem können ihm keine Beiträge mehr hinzugefügt werden." thisChannelArchived: "Dieser Kanal wurde archiviert." -displayOfNote: "Anzeige von Notizen" +displayOfNote: "Darstellung von Notizen" initialAccountSetting: "Kontoeinrichtung" youFollowing: "Gefolgt" preventAiLearning: "Verwendung in machinellem Lernen (Generative bzw. Prediktive AI/KI) ablehnen" @@ -1094,6 +1100,27 @@ expired: "Abgelaufen" doYouAgree: "Zustimmen?" beSureToReadThisAsItIsImportant: "Lies bitte diese wichtige Informationen." iHaveReadXCarefullyAndAgree: "Ich habe den Text \"{x}\" gelesen und stimme zu." +dialog: "Dialogfeld" +icon: "Symbol" +forYou: "Für dich" +currentAnnouncements: "Aktuelle Ankündigungen" +pastAnnouncements: "Alte Ankündigungen" +youHaveUnreadAnnouncements: "Es gibt neue Ankündigungen." +useSecurityKey: "Folge bitten den Anweisungen deines Browsers bzw. Gerätes und verwende deinen Hardware-Sicherheitsschlüssel oder Passkey." +replies: "Antworten" +renotes: "Renotes" +loadReplies: "Antworten anzeigen" +loadConversation: "Unterhaltung anzeigen" +pinnedList: "Angeheftete Liste" +_announcement: + forExistingUsers: "Nur für existierende Nutzer" + forExistingUsersDescription: "Ist diese Option aktiviert, wird diese Ankündigung nur Nutzern angezeigt, die zum Zeitpunkt der Ankündigung bereits registriert sind. Ist sie deaktiviert, wird sie auch Nutzern, die sich nach dessen Veröffentlichung registrieren, angezeigt." + needConfirmationToRead: "Separate Lesebestätigung erfordern" + needConfirmationToReadDescription: "Ist dies aktiviert, so wird beim Markieren dieser Ankündigung als gelesen ein separates Bestätigungsfenster angezeigt. Auch wird sie von der \"Alle als gelesen markieren\"-Funktion ausgenommen." + end: "Ankündigung archivieren" + tooManyActiveAnnouncementDescription: "Zu viele aktive Ankündigungen können die Benutzerfreundlichkeit verschlechtern. Es wird empfohlen, veraltete Ankündigungen zu archivieren." + readConfirmTitle: "Als gelesen markieren?" + readConfirmText: "Dies markiert den Inhalt von \"{title}\" als gelesen." _initialAccountSetting: accountCreated: "Dein Konto wurde erfolgreich erstellt!" letsStartAccountSetup: "Lass uns nun dein Konto einrichten." @@ -1111,6 +1138,13 @@ _initialAccountSetting: laterAreYouSure: "Die Kontoeinrichtung wirklich später erledigen?" _serverRules: description: "Eine Reihe von Regeln, die vor der Registrierung angezeigt werden. Eine Zusammenfassung der Nutzungsbedingungen anzuzeigen ist empfohlen." +_serverSettings: + iconUrl: "Icon-URL" + appIconDescription: "Gibt das zu verwendende Icon bei der Anzeige von {host} als App an." + appIconUsageExample: "Beispielsweise als PWA, oder bei Lesezeichen auf dem Startbildschirm von Smartphones" + appIconStyleRecommendation: "Da das Icon zu einem Kreis oder Quadrat zugeschnitten wird, wird ein Icon mit gefülltem Margin um den Inhalt herum empfohlen." + appIconResolutionMustBe: "Die Mindestauflösung ist {resolution}." + manifestJsonOverride: "Überschreiben von manifest.json" _accountMigration: moveFrom: "Von einem anderen Konto zu diesem migrieren" moveFromSub: "Alias für ein anderes Konto erstellen" @@ -1365,6 +1399,9 @@ _achievements: title: "Brain Diver" description: "Sende den Link zu Brain Diver" flavor: "Misskey-Misskey La-Tu-Ma" + _smashTestNotificationButton: + title: "Testüberfluss" + description: "Betätige den Benachrichtigungstest mehrfach innerhalb einer extrem kurzen Zeitspanne" _role: new: "Rolle erstellen" edit: "Rolle bearbeiten" @@ -1679,15 +1716,15 @@ _2fa: step1: "Installiere zuerst eine Authentifizierungsapp (z.B. {a} oder {b}) auf deinem Gerät." step2: "Dann, scanne den angezeigten QR-Code mit deinem Gerät." step2Click: "Durch Klicken dieses QR-Codes kannst du Verifikation mit deinem Security-Token oder einer App registrieren." - step2Url: "Nutzt du ein Desktopprogramm kannst du alternativ diese URL eingeben:" + step2Uri: "Nutzt du ein Desktopprogramm, gib folgende URI eingeben" step3Title: "Authentifizierungsscode eingeben" step3: "Gib zum Abschluss den Token ein, der von deiner App angezeigt wird." + setupCompleted: "Einrichtung abgeschlossen" step4: "Alle folgenden Anmeldeversuche werden ab sofort die Eingabe eines solchen Tokens benötigen." - securityKeyNotSupported: "Dein Browser unterstützt keine Security-Tokens." + securityKeyNotSupported: "Dein Browser unterstützt keine Hardware-Sicherheitsschlüssel." registerTOTPBeforeKey: "Um einen Security-Token oder einen Passkey zu registrieren, musst du zuerst eine Authentifizierungs-App registrieren." securityKeyInfo: "Du kannst neben Fingerabdruck- oder PIN-Authentifizierung auf deinem Gerät auch Anmeldung mit Hilfe eines FIDO2-kompatiblen Hardware-Sicherheitsschlüssels einrichten." - chromePasskeyNotSupported: "Chrome-Passkeys werden zur Zeit nicht unterstützt." - registerSecurityKey: "Security-Token oder Passkey registrieren" + registerSecurityKey: "Hardware-Sicherheitsschlüssel oder Passkey registrieren" securityKeyName: "Schlüsselname eingeben" tapSecurityKey: "Bitten folge den Anweisungen deines Browsers zur Registrierung" removeKey: "Sicherheitsschlüssel entfernen" @@ -1697,6 +1734,11 @@ _2fa: renewTOTPConfirm: "Codes der bisherigen App werden hierdurch nutzlos" renewTOTPOk: "Neu einrichten" renewTOTPCancel: "Abbrechen" + checkBackupCodesBeforeCloseThisWizard: "Notiere bitte deine Backup-Codes, bevor du dieses Fenster schließt." + backupCodes: "Backup-Codes" + backupCodesDescription: "Verwende diese Codes, falls du nicht mehr auf deine App zur Zweifaktorauthentifizierung zugreifen kannst. Jeder Code kann nur einmal verwendet werden. Bewahre sie an einem sicheren Ort auf." + backupCodeUsedWarning: "Ein Backup-Code wurde verwendet. Falls du den Zugriff zu deiner Zweifaktorauthentifizierungsapp verloren hast, konfiguriere diese bitte möglichst bald erneut." + backupCodesExhaustedWarning: "Alle Backup-Codes wurden verwendet. Falls du den Zugang zu deiner Zweifaktorauthentifizierungsapp verlierst, wirst du dich nicht mehr in dieses Konto einloggen können. Bitte konfiguriere diese App erneut." _permissions: "read:account": "Deine Benutzerkontoinformationen lesen" "write:account": "Deine Benutzerkontoinformationen bearbeiten" @@ -1730,6 +1772,10 @@ _permissions: "write:gallery": "Deine Galerie bearbeiten" "read:gallery-likes": "Liste deiner mit \"Gefällt mir\" markierten Galerie-Beiträge lesen" "write:gallery-likes": "Liste deiner mit \"Gefällt mir\" markierten Galerie-Beiträge bearbeiten" + "read:flash": "Deine Plays lesen" + "write:flash": "Deine Plays bearbeiten oder löschen" + "read:flash-likes": "Liste der Plays, die mir gefallen, lesen" + "write:flash-likes": "Liste der Plays, die mir gefallen, bearbeiten" _auth: shareAccessTitle: "Verteilung von App-Berechtigungen" shareAccess: "Möchtest du „{name}“ authorisieren, auf dieses Benutzerkonto zugreifen zu können?" @@ -1965,6 +2011,10 @@ _notification: unreadAntennaNote: "Antenne {name}" emptyPushNotificationMessage: "Push-Benachrichtigungen wurden aktualisiert" achievementEarned: "Errungenschaft freigeschaltet" + testNotification: "Testbenachrichtigung" + checkNotificationBehavior: "Aussehen von Benachrichtigungen überprüfen" + sendTestNotification: "Testbenachrichtigung senden" + notificationWillBeDisplayedLikeThis: "Benachrichtigungen sehen so aus" _types: all: "Alle" follow: "Neue Follower" @@ -2000,6 +2050,8 @@ _deck: introduction2: "Klicke auf das + rechts um wann immer du möchtest neue Spalten hinzuzufügen." widgetsIntroduction: "Drücke bitte \"Widgets bearbeiten\" im Spaltenmenü und füge ein Widget hinzu." useSimpleUiForNonRootPages: "Simple Benutzeroberfläche für navigierte Seiten verwenden" + usedAsMinWidthWhenFlexible: "Ist \"Automatische Breitenanpassung\" aktiviert, wird hierfür die minimale Breite verwendet" + flexible: "Automatische Breitenanpassung" _columns: main: "Hauptspalte" widgets: "Widgets" diff --git a/locales/el-GR.yml b/locales/el-GR.yml index 41b1ea7c6566..e8ed6f118ef3 100644 --- a/locales/el-GR.yml +++ b/locales/el-GR.yml @@ -287,6 +287,9 @@ searchByGoogle: "Αναζήτηση" file: "Αρχεία" recommended: "Προτεινόμενα" cannotUploadBecauseNoFreeSpace: "Το ανέβασμα απέτυχε λόγω ανεπαρκούς Αποθηκευτικού Χώρου" +icon: "Εικονίδιο" +replies: "Απάντηση" +renotes: "Κοινοποίηση σημειώματος" _email: _follow: title: "Έχετε ένα νέο ακόλουθο" diff --git a/locales/en-US.yml b/locales/en-US.yml index 097aba76ba6a..2d13e777d013 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -45,9 +45,10 @@ pin: "Pin to profile" unpin: "Unpin from profile" copyContent: "Copy contents" copyLink: "Copy link" +copyLinkRenote: "Copy renote link" delete: "Delete" deleteAndEdit: "Delete and edit" -deleteAndEditConfirm: "Are you sure you want to delete this note and edit it? You will lose all reactions, renotes and replies to it." +deleteAndEditConfirm: "Are you sure you want to redraft this note? This means you will lose all reactions, renotes, and replies to it." addToList: "Add to list" addToAntenna: "Add to antenna" sendMessage: "Send a message" @@ -74,7 +75,7 @@ import: "Import" export: "Export" files: "Files" download: "Download" -driveFileDeleteConfirm: "Are you sure you want to delete \"{name}\"? It will also vanish from all contents that use it." +driveFileDeleteConfirm: "Do you want to remove the file \"{name}\"? Some content using this file will also be removed." unfollowConfirm: "Are you sure you want to unfollow {name}?" exportRequested: "You've requested an export. This may take a while. It will be added to your Drive once completed." importRequested: "You've requested an import. This may take a while." @@ -105,7 +106,7 @@ unfollow: "Unfollow" followRequestPending: "Follow request pending" enterEmoji: "Enter an emoji" renote: "Renote" -unrenote: "Take back renote" +unrenote: "Remove renote" renoted: "Renoted." cantRenote: "This post can't be renoted." cantReRenote: "A renote can't be renoted." @@ -156,6 +157,7 @@ addEmoji: "Add an emoji" settingGuide: "Recommended settings" cacheRemoteFiles: "Cache remote files" cacheRemoteFilesDescription: "When this setting is disabled, remote files are loaded directly from the remote instance. Disabling this will decrease storage usage, but increase traffic, as thumbnails will not be generated." +youCanCleanRemoteFilesCache: "You can clear the cache by clicking the 🗑️ button in the file management view." cacheRemoteSensitiveFiles: "Cache sensitive remote files" cacheRemoteSensitiveFilesDescription: "When this setting is disabled, sensitive remote files are loaded directly from the remote instance without caching." flagAsBot: "Mark this account as a bot" @@ -354,7 +356,6 @@ invite: "Invite" driveCapacityPerLocalAccount: "Drive capacity per local user" driveCapacityPerRemoteAccount: "Drive capacity per remote user" inMb: "In megabytes" -iconUrl: "Icon URL" bannerUrl: "Banner image URL" backgroundImageUrl: "Background image URL" basicInfo: "Basic info" @@ -410,10 +411,13 @@ aboutMisskey: "About Misskey" administrator: "Administrator" token: "Token" 2fa: "Two-factor authentication" +setupOf2fa: "Setup two-factor authentification" totp: "Authenticator App" totpDescription: "Use an authenticator app to enter one-time passwords" moderator: "Moderator" moderation: "Moderation" +moderationNote: "Moderation note" +addModerationNote: "Add moderation note" nUsersMentioned: "Mentioned by {n} users" securityKeyAndPasskey: "Security- and passkeys" securityKey: "Security key" @@ -579,7 +583,7 @@ serviceworkerInfo: "Must be enabled for push notifications." deletedNote: "Deleted note" invisibleNote: "Invisible note" enableInfiniteScroll: "Automatically load more" -visibility: "Visiblility" +visibility: "Visibility" poll: "Poll" useCw: "Hide content" enablePlayer: "Open video player" @@ -653,6 +657,7 @@ behavior: "Behavior" sample: "Sample" abuseReports: "Reports" reportAbuse: "Report" +reportAbuseRenote: "Report renote" reportAbuseOf: "Report {name}" fillAbuseReportDescription: "Please fill in details regarding this report. If it is about a specific note, please include its URL." abuseReported: "Your report has been sent. Thank you very much." @@ -680,6 +685,7 @@ createNewClip: "Create new clip" unclip: "Unclip" confirmToUnclipAlreadyClippedNote: "This note is already part of the \"{name}\" clip. Do you want to remove it from this clip instead?" public: "Public" +private: "Private" i18nInfo: "Misskey is being translated into various languages by volunteers. You can help at {link}." manageAccessTokens: "Manage access tokens" accountInfo: "Account Info" @@ -1018,7 +1024,7 @@ retryAllQueuesConfirmText: "This will temporarily increase the server load." enableChartsForRemoteUser: "Generate remote user data charts" enableChartsForFederatedInstances: "Generate remote instance data charts" showClipButtonInNoteFooter: "Add \"Clip\" to note action menu" -largeNoteReactions: "Enlargen displayed reactions" +reactionsDisplaySize: "Reaction display size" noteIdOrUrl: "Note ID or URL" video: "Video" videos: "Videos" @@ -1094,6 +1100,27 @@ expired: "Expired" doYouAgree: "Agree?" beSureToReadThisAsItIsImportant: "Please read this important information." iHaveReadXCarefullyAndAgree: "I have read the text \"{x}\" and agree." +dialog: "Dialog" +icon: "Icon" +forYou: "For you" +currentAnnouncements: "Current announcements" +pastAnnouncements: "Past announcements" +youHaveUnreadAnnouncements: "There are unread announcements." +useSecurityKey: "Please follow your browser's or device's instructions to use your security- or passkey." +replies: "Reply" +renotes: "Renotes" +loadReplies: "Show replies" +loadConversation: "Show conversation" +pinnedList: "Pinned list" +_announcement: + forExistingUsers: "Existing users only" + forExistingUsersDescription: "This announcement will only be shown to users existing at the point of publishment if enabled. If disabled, those newly signing up after it has been posted will also see it." + needConfirmationToRead: "Require separate read confirmation" + needConfirmationToReadDescription: "A separate prompt to confirm marking this announcement as read will be displayed if enabled. This announcement will also be excluded from any \"Mark all as read\" functionality." + end: "Archive announcement" + tooManyActiveAnnouncementDescription: "Having too many active announcements may worsen the user experience. Please consider archiving announcements that have become obsolete." + readConfirmTitle: "Mark as read?" + readConfirmText: "This will mark the contents of \"{title}\" as read." _initialAccountSetting: accountCreated: "Your account was successfully created!" letsStartAccountSetup: "For starters, let's set up your profile." @@ -1111,6 +1138,13 @@ _initialAccountSetting: laterAreYouSure: "Really do profile setup later?" _serverRules: description: "A set of rules to be displayed before registration. Setting a summary of the Terms of Service is recommended." +_serverSettings: + iconUrl: "Icon URL" + appIconDescription: "Specifies the icon to use when {host} is displayed as an app." + appIconUsageExample: "E.g. As PWA, or when displayed as a home screen bookmark on a phone" + appIconStyleRecommendation: "As the icon may be cropped to a square or circle, an icon with colored margin around the content is recommended." + appIconResolutionMustBe: "The minimum resolution is {resolution}." + manifestJsonOverride: "manifest.json Override" _accountMigration: moveFrom: "Migrate another account to this one" moveFromSub: "Create alias to another account" @@ -1365,6 +1399,9 @@ _achievements: title: "Brain Diver" description: "Post the link to Brain Diver" flavor: "Misskey-Misskey La-Tu-Ma" + _smashTestNotificationButton: + title: "Test overflow" + description: "Trigger the notification test repeatedly within an extremely short time" _role: new: "New role" edit: "Edit role" @@ -1679,14 +1716,14 @@ _2fa: step1: "First, install an authentication app (such as {a} or {b}) on your device." step2: "Then, scan the QR code displayed on this screen." step2Click: "Clicking on this QR code will allow you to register 2FA to your security key or phone authenticator app." - step2Url: "You can also enter this URL if you're using a desktop program:" + step2Uri: "Enter the following URI if you are using a desktop program" step3Title: "Enter an authentication code" step3: "Enter the token provided by your app to finish setup." + setupCompleted: "Setup complete" step4: "From now on, any future login attempts will ask for such a login token." securityKeyNotSupported: "Your browser does not support security keys." registerTOTPBeforeKey: "Please set up an authenticator app to register a security or pass key." securityKeyInfo: "Besides fingerprint or PIN authentication, you can also setup authentication via hardware security keys that support FIDO2 to further secure your account." - chromePasskeyNotSupported: "Chrome passkeys are currently not supported." registerSecurityKey: "Register a security or pass key" securityKeyName: "Enter a key name" tapSecurityKey: "Please follow your browser to register the security or pass key" @@ -1697,6 +1734,11 @@ _2fa: renewTOTPConfirm: "This will cause verification codes from your previous app to stop working" renewTOTPOk: "Reconfigure" renewTOTPCancel: "Cancel" + checkBackupCodesBeforeCloseThisWizard: "Before you close this window, please note the following backup codes." + backupCodes: "Backup codes" + backupCodesDescription: "You can use these codes to gain access to your account in case of becoming unable to use your two-factor authentificator app. Each can only be used once. Please keep them in a safe place." + backupCodeUsedWarning: "A backup code has been used. Please reconfigure two-factor authentification as soon as possible if you are no longer able to use it." + backupCodesExhaustedWarning: "All backup codes have been used. Should you lose access to your two-factor authentification app, you will be unable to access this account. Please reconfigure two-factor authentification." _permissions: "read:account": "View your account information" "write:account": "Edit your account information" @@ -1718,10 +1760,10 @@ _permissions: "read:reactions": "View your reactions" "write:reactions": "Edit your reactions" "write:votes": "Vote on a poll" - "read:pages": "View your pages" - "write:pages": "Edit or delete your pages" - "read:page-likes": "View your likes on pages" - "write:page-likes": "Edit your likes on pages" + "read:pages": "View your Pages" + "write:pages": "Edit or delete your Pages" + "read:page-likes": "View list of liked Pages" + "write:page-likes": "Edit list of liked Pages" "read:user-groups": "View your user groups" "write:user-groups": "Edit or delete your user groups" "read:channels": "View your channels" @@ -1730,6 +1772,10 @@ _permissions: "write:gallery": "Edit your gallery" "read:gallery-likes": "View your list of liked gallery posts" "write:gallery-likes": "Edit your list of liked gallery posts" + "read:flash": "View Play" + "write:flash": "Edit Plays" + "read:flash-likes": "View list of liked Plays" + "write:flash-likes": "Edit list of liked Plays" _auth: shareAccessTitle: "Granting application permissions" shareAccess: "Would you like to authorize \"{name}\" to access this account?" @@ -1965,6 +2011,10 @@ _notification: unreadAntennaNote: "Antenna {name}" emptyPushNotificationMessage: "Push notifications have been updated" achievementEarned: "Achievement unlocked" + testNotification: "Test notification" + checkNotificationBehavior: "Check notification appearance" + sendTestNotification: "Send test notification" + notificationWillBeDisplayedLikeThis: "Notifications look like this" _types: all: "All" follow: "New followers" @@ -2000,6 +2050,8 @@ _deck: introduction2: "Click on the + on the right of the screen to add new colums whenever you want." widgetsIntroduction: "Please select \"Edit widgets\" in the column menu and add a widget." useSimpleUiForNonRootPages: "Use simplified UI to navigated pages" + usedAsMinWidthWhenFlexible: "Minimum width will be used for this when the \"Auto-adjust width\" option is enabled" + flexible: "Auto-adjust width" _columns: main: "Main" widgets: "Widgets" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 99a0eea4e508..bd638af9c252 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -8,10 +8,10 @@ search: "Buscar" notifications: "Notificaciones" username: "Nombre de usuario" password: "Contraseña" -forgotPassword: "Olvidé mi Contraseña" +forgotPassword: "Olvidé mi contraseña" fetchingAsApObject: "Buscando en el fediverso" ok: "OK" -gotIt: "¡Lo tengo!" +gotIt: "Entendido" cancel: "Cancelar" noThankYou: "No gracias" enterUsername: "Introduce el nombre de usuario" @@ -20,8 +20,8 @@ noNotes: "No hay notas" noNotifications: "No hay notificaciones" instance: "Instancia" settings: "Configuración" -notificationSettings: "Configurar las notificaciones" -basicSettings: "Configuración Básica" +notificationSettings: "Ajustes de notificaciones" +basicSettings: "Configuración básica" otherSettings: "Configuración avanzada" openInWindow: "Abrir en una ventana" profile: "Perfil" @@ -45,6 +45,7 @@ pin: "Fijar al perfil" unpin: "Desfijar" copyContent: "Copiar contenido" copyLink: "Copiar enlace" +copyLinkRenote: "Copiar enlace de renota" delete: "Borrar" deleteAndEdit: "Borrar y editar" deleteAndEditConfirm: "¿Estás seguro de que quieres borrar esta nota y editarla? Perderás todas las reacciones, renotas y respuestas." @@ -55,8 +56,8 @@ copyRSS: "Copiar RSS" copyUsername: "Copiar nombre de usuario" copyUserId: "Copiar ID del usuario" copyNoteId: "Copiar ID de la nota" -copyFileId: "Copiar un archivo ID" -copyFolderId: "Copiar carpeta ID" +copyFileId: "Copiar ID del archivo" +copyFolderId: "Copiar ID de carpeta" copyProfileUrl: "Copiar la URL del perfil" searchUser: "Buscar un usuario" reply: "Responder" @@ -156,6 +157,7 @@ addEmoji: "Agregar emoji" settingGuide: "Configuración sugerida" cacheRemoteFiles: "Mantener en cache los archivos remotos" cacheRemoteFilesDescription: "Si desactiva esta configuración, Los archivos remotos se cargarán desde el link directo sin usar la caché. Con eso se puede ahorrar almacenamiento del servidor, pero eso aumentará el tráfico al no crear miniaturas." +youCanCleanRemoteFilesCache: "Puedes vaciar la caché pulsando en el botón 🗑️ en el administrador de archivos." cacheRemoteSensitiveFiles: "Cachear archivos remotos sensibles" cacheRemoteSensitiveFilesDescription: "Cuando esta opción está desactivada, los archivos remotos sensibles son cargador directamente de la instancia origen sin ser cacheados." flagAsBot: "Esta cuenta es un bot" @@ -354,7 +356,6 @@ invite: "Invitar" driveCapacityPerLocalAccount: "Capacidad del drive por usuario local" driveCapacityPerRemoteAccount: "Capacidad del drive por usuario remoto" inMb: "En megabytes" -iconUrl: "URL de la imagen del avatar" bannerUrl: "URL de la imagen del banner" backgroundImageUrl: "URL de la imagen de fondo" basicInfo: "Información básica" @@ -410,10 +411,13 @@ aboutMisskey: "Sobre Misskey" administrator: "Administrador" token: "Token" 2fa: "Autenticación de doble factor" +setupOf2fa: "Configurar la autenticación de dos factores" totp: "Aplicación autentícadora" totpDescription: "Ingresa una contaseña de un sólo uso usando la aplicación autenticadora" moderator: "Moderador" moderation: "Moderación" +moderationNote: "Nota de moderación" +addModerationNote: "Añadir nota de moderación" nUsersMentioned: "{n} usuarios mencionados" securityKeyAndPasskey: "Clave de seguridad / clave de paso" securityKey: "Clave de seguridad" @@ -653,6 +657,7 @@ behavior: "Comportamiento" sample: "Muestra" abuseReports: "Reportes" reportAbuse: "Reportar" +reportAbuseRenote: "Reportar renota" reportAbuseOf: "Reportar a {name}" fillAbuseReportDescription: "Ingrese los detalles del reporte. Si hay una nota en particular, ingrese la URL de esta." abuseReported: "Se ha enviado el reporte. Muchas gracias." @@ -680,6 +685,7 @@ createNewClip: "Crear clip nuevo" unclip: "Quitar clip" confirmToUnclipAlreadyClippedNote: "Esta nota ya está incluida en el clip \"{name}\". ¿Quiere quitar la nota del clip?" public: "Público" +private: "Privado" i18nInfo: "Misskey está siendo traducido a varios idiomas gracias a voluntarios. Se puede colaborar traduciendo en {link}" manageAccessTokens: "Administrar tokens de acceso" accountInfo: "Información de la Cuenta" @@ -1018,7 +1024,7 @@ retryAllQueuesConfirmText: "La carga del servidor está incrementándose tempora enableChartsForRemoteUser: "Generar gráficas de usuarios remotos." enableChartsForFederatedInstances: "Generar gráficos de servidores remotos" showClipButtonInNoteFooter: "Añadir \"Clip\" al menú de notas" -largeNoteReactions: "Agrandar las reacciones de las notas" +reactionsDisplaySize: "Tamaño de las reacciones" noteIdOrUrl: "ID o URL de la nota" video: "Video" videos: "Video" @@ -1094,6 +1100,24 @@ expired: "Caducada" doYouAgree: "¿Está de acuerdo?" beSureToReadThisAsItIsImportant: "Por favor lea esto que es importante" iHaveReadXCarefullyAndAgree: "He leído el texto {x} y estoy de acuerdo" +dialog: "Diálogo" +icon: "Avatar" +forYou: "Para ti" +currentAnnouncements: "Anuncios actuales" +pastAnnouncements: "Anuncios anteriores" +youHaveUnreadAnnouncements: "Hay anuncios sin leer" +useSecurityKey: "Por favor, sigue las instrucciones de tu dispositivo o navegador para usar tu clave de seguridad o tu clave de paso." +replies: "Responder" +renotes: "Renotar" +_announcement: + forExistingUsers: "Solo para usuarios registrados" + forExistingUsersDescription: "Este anuncio solo se mostrará a aquellos usuarios registrados en el momento de su publicación. Si se deshabilita esta opción, aquellos usuarios que se registren tras su publicación también lo verán." + needConfirmationToRead: "Requerir confirmación de lectura aparte" + needConfirmationToReadDescription: "Si se habilita esta opción, se pedirá una confirmación de lectura aparte. Además, este anuncio será excluido de cualquier funcionalidad de \"Marcar todos como leídos\"." + end: "Anuncios archivados" + tooManyActiveAnnouncementDescription: "Tener demasiados anuncios activos empeora la experiencia de usuario. Por favor, considera archivar aquellos anuncios que hayan quedado obsoletos." + readConfirmTitle: "¿Marcar como leído?" + readConfirmText: "Esto marcará el contenido de \"{title}\" como leído." _initialAccountSetting: accountCreated: "¡La cuenta ha sido creada!" letsStartAccountSetup: "Para empezar, creemos tu perfil." @@ -1111,6 +1135,9 @@ _initialAccountSetting: laterAreYouSure: "¿Realmente quieres configurar tu perfil después?" _serverRules: description: "Un conjunto de reglas que serán mostradas antes del registro. Configurar un sumario de términos de servicio es recomendado." +_serverSettings: + iconUrl: "URL del ícono" + manifestJsonOverride: "Sobreescribir manifest.json" _accountMigration: moveFrom: "Trasladar de otra cuenta a ésta" moveFromSub: "Crear un alias para otra cuenta." @@ -1365,6 +1392,9 @@ _achievements: title: "Brain Diver" description: "Publicaste un vínculo a \"Brain Diver\"" flavor: "Misskey-Misskey La-Tu-Ma" + _smashTestNotificationButton: + title: "Sobrecarga de pruebas" + description: "Envía muchas notificaciones de prueba en un corto espacio de tiempo" _role: new: "Crear rol" edit: "Editar rol" @@ -1679,14 +1709,14 @@ _2fa: step1: "Primero, instale en su dispositivo la aplicación de autenticación {a} o {b} u otra." step2: "Luego, escanee con la aplicación el código QR mostrado en pantalla." step2Click: "Clicking on this QR code will allow you to register 2FA to your security key or phone authenticator app.\nTocar este código QR te permitirá registrar la autenticación 2FA a tu llave de seguridad o aplicación autenticadora." - step2Url: "En una aplicación de escritorio se puede ingresar la siguiente URL:" + step2Uri: "Si usas una aplicación de escritorio, introduce en ella la siguiente URL." step3Title: "Ingresa un código de autenticación" step3: "Para terminar, ingrese el token mostrado en la aplicación." + setupCompleted: "Configuración completada" step4: "Ahora cuando inicie sesión, ingrese el mismo token" securityKeyNotSupported: "Tu navegador no soporta claves de autenticación." registerTOTPBeforeKey: "Please set up an authenticator app to register a security or pass key.\npor favor. configura una aplicación de autenticación para registrar una llave de seguridad." securityKeyInfo: "Se puede configurar el inicio de sesión usando una clave de seguridad de hardware que soporte FIDO2 o con un certificado de huella digital o con un PIN" - chromePasskeyNotSupported: "Las llaves de seguridad de Chrome no son soportadas por el momento." registerSecurityKey: "Registrar una llave de seguridad" securityKeyName: "Ingresa un nombre para la clave" tapSecurityKey: "Por favor, sigue tu navegador para registrar una llave de seguridad" @@ -1697,6 +1727,11 @@ _2fa: renewTOTPConfirm: "This will cause verification codes from your previous app to stop working\nEsto hará que los códigos de verificación de la aplicación anterior dejen de funcionar" renewTOTPOk: "Reconfigurar" renewTOTPCancel: "No gracias" + checkBackupCodesBeforeCloseThisWizard: "Por favor, copia los siguientes códigos de respaldo antes de finalizar el asistente." + backupCodes: "Códigos de Respaldo" + backupCodesDescription: "En caso de que no puedas usar tu aplicación de autenticación, podrás usar los códigos de respaldo que figuran abajo para acceder a tu cuenta. Asegúrate de guardar en lugar seguro los códigos de respaldo. Cada uno de los códigos de respaldo es de un solo uso." + backupCodeUsedWarning: "Has usado todos los códigos de respaldo. Si dejas de tener acceso a tu aplicación de autenticación, no podrás volver a iniciar sesión en tu cuenta. Por favor, reconfigura tu aplicación de autenticación lo antes posible." + backupCodesExhaustedWarning: "Has usado todos los códigos de respaldo. Si dejas de tener acceso a tu aplicación de autenticación, no podrás volver a iniciar sesión en la cuenta que figura arriba. Por favor, reconfigura tu aplicación de autenticación lo antes posible." _permissions: "read:account": "Ver información de la cuenta" "write:account": "Editar información de la cuenta" @@ -1730,6 +1765,10 @@ _permissions: "write:gallery": "Editar galería" "read:gallery-likes": "Ver favoritos de la galería" "write:gallery-likes": "Editar favoritos de la galería" + "read:flash": "Ver Play" + "write:flash": "Editar Plays" + "read:flash-likes": "Ver los Play que me gustan" + "write:flash-likes": "Editar lista de Play que me gustan" _auth: shareAccessTitle: "Permisos de la aplicación" shareAccess: "¿Desea permitir el acceso a la cuenta \"{name}\"?" @@ -1965,6 +2004,10 @@ _notification: unreadAntennaNote: "Antena {name}" emptyPushNotificationMessage: "Se han actualizado las notificaciones push" achievementEarned: "Logro desbloqueado" + testNotification: "Notificación de prueba" + checkNotificationBehavior: "Comprobar comportamiento de la notificación" + sendTestNotification: "Enviar notificación de prueba" + notificationWillBeDisplayedLikeThis: "Las notificaciones tendrán este aspecto" _types: all: "Todo" follow: "Siguiendo" @@ -2000,6 +2043,8 @@ _deck: introduction2: "Presiona en la + de la derecha de la pantalla para añadir nuevas columnas donde quieras." widgetsIntroduction: "Por favor selecciona \"Editar Widgets\" en el menú columna y agrega un widget." useSimpleUiForNonRootPages: "Mostrar páginas no pertenecientes a la raíz con la interfaz simple" + usedAsMinWidthWhenFlexible: "Se usará el ancho mínimo cuando la opción \"Autoajustar ancho\" esté habilitada" + flexible: "Autoajustar ancho" _columns: main: "Principal" widgets: "Widgets" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 526e80b2d36d..3d9de3ef3242 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -47,7 +47,7 @@ copyContent: "Copier le contenu" copyLink: "Copier le lien" delete: "Supprimer" deleteAndEdit: "Supprimer et réécrire" -deleteAndEditConfirm: "Êtes-vous sûr·e de vouloir supprimer cette note et la reformuler ? Vous perdrez toutes les réactions, renotes et réponses y afférentes." +deleteAndEditConfirm: "Êtes-vous sûr de vouloir effacer cette note et la modifier ? Vous perdrez toutes les réactions, renotes et réponses." addToList: "Ajouter à une liste" addToAntenna: "Ajouter à l’antenne" sendMessage: "Envoyer un message" @@ -58,7 +58,7 @@ copyNoteId: "Copier l'identifiant de la note" copyFileId: "Copier l'identifiant du fichier" copyFolderId: "Copier l'identifiant du dossier" copyProfileUrl: "Copier l'URL du profil" -searchUser: "Chercher un utilisateur" +searchUser: "Chercher un·e utilisateur·rice" reply: "Répondre" loadMore: "Afficher plus …" showMore: "Voir plus" @@ -76,14 +76,14 @@ files: "Fichiers" download: "Télécharger" driveFileDeleteConfirm: "Êtes-vous sûr de vouloir supprimer le fichier \"{name}\" ? Les notes liées à ce fichier seront aussi supprimées." unfollowConfirm: "Désirez-vous vous désabonner de {name} ?" -exportRequested: "Vous avez demandé une exportation. L’opération pourrait prendre un peu de temps. Une terminée, le fichier résultant sera ajouté au Drive." +exportRequested: "Vous avez demandé une exportation. L’opération pourrait prendre un peu de temps. Une fois terminée, le fichier sera ajouté au Drive." importRequested: "Vous avez initié un import. Cela pourrait prendre un peu de temps." lists: "Listes" noLists: "Vous n’avez aucune liste" note: "Note" notes: "Notes" following: "Abonnements" -followers: "Abonnés" +followers: "Abonné·e·s" followsYou: "Vous suit" createList: "Créer une liste" manageLists: "Gérer les listes" @@ -138,6 +138,7 @@ unblockConfirm: "Êtes-vous sûr·e de vouloir débloquer ce compte ?" suspendConfirm: "Êtes-vous sûr·e de vouloir suspendre ce compte ?" unsuspendConfirm: "Êtes-vous sûr·e de vouloir annuler la suspension de ce compte ?" selectList: "Sélectionner une liste" +editList: "Modifier la liste" selectChannel: "Sélectionner un canal" selectAntenna: "Sélectionner une antenne" editAntenna: "Modifier l'antenne" @@ -250,7 +251,7 @@ announcements: "Annonces" imageUrl: "URL de l’image" remove: "Supprimer" removed: "Supprimé" -removeAreYouSure: "Êtes-vous sûr·e de vouloir supprimer「{x}」?" +removeAreYouSure: "Êtes-vous sûr·e de vouloir supprimer « {x} » ?" deleteAreYouSure: "Êtes-vous sûr·e de vouloir supprimer「{x}」?" resetAreYouSure: "Voulez-vous réinitialiser ?" saved: "Enregistré" @@ -271,6 +272,7 @@ startMessaging: "Commencer à discuter" nUsersRead: "Lu par {n} personnes" agreeTo: "J’accepte {0}" agree: "Accepter" +basicNotesBeforeCreateAccount: "Notes importantes" termsOfService: "Conditions d'utilisation" start: "Commencer" home: "Principal" @@ -349,7 +351,6 @@ invite: "Inviter" driveCapacityPerLocalAccount: "Volume du Drive par utilisateur local" driveCapacityPerRemoteAccount: "Volume du Drive par utilisateur distant" inMb: "en mégaoctets" -iconUrl: "URL de l'icône" bannerUrl: "URL de l’image de la bannière" backgroundImageUrl: "URL de l'image d'arrière-plan" basicInfo: "Informations basiques" @@ -409,6 +410,8 @@ totp: "Application d'authentification" totpDescription: "Entrez un mot de passe à usage unique à l'aide d'une application d'authentification" moderator: "Modérateur·rice·s" moderation: "Modérations" +moderationNote: "Note de modération" +addModerationNote: "Ajouter une note de modération" nUsersMentioned: "{n} utilisateur·rice·s mentionné·e·s" securityKey: "Clé de sécurité" lastUsed: "Dernier utilisé" @@ -598,7 +601,7 @@ tokenRequested: "Autoriser l'accès au compte" pluginTokenRequestedDescription: "Ce plugin pourra utiliser les autorisations définies ici." notificationType: "Type de notifications" edit: "Editer" -emailServer: "Serveur mail" +emailServer: "Serveur de messagerie" enableEmail: "Activer la distribution de courriel" emailConfigInfo: "Utilisé pour confirmer votre adresse de courriel et la réinitialisation de votre mot de passe en cas d’oubli." email: "E-mail " @@ -665,6 +668,7 @@ createNew: "Créer nouveau" optional: "Facultatif" createNewClip: "Créer un nouveau clip" public: "Public" +private: "Privé" i18nInfo: "Misskey est traduit dans différentes langues par des bénévoles. Vous pouvez contribuer à {link}." manageAccessTokens: "Gérer les jetons d'accès" accountInfo: " Informations du compte " @@ -818,6 +822,7 @@ translatedFrom: "Traduit depuis {x}" accountDeletionInProgress: "La suppression de votre compte est en cours" usernameInfo: "C'est un nom qui identifie votre compte sur l'instance de manière unique. Vous pouvez utiliser des lettres de l'alphabet (minuscules et majuscules), des chiffres (de 0 à 9), ou bien le tiret « _ ». Vous ne pourrez pas modifier votre nom d'utilisateur·rice par la suite." aiChanMode: "Mode Ai" +devMode: "Mode développement" keepCw: "Garder le CW" pubSub: "Comptes Pub/Sub" lastCommunication: "Dernière communication" @@ -827,10 +832,12 @@ breakFollow: "Ne plus suivre" breakFollowConfirm: "Êtes-vous sûr de vouloir vous désabonner ?" itsOn: "Activé" itsOff: "Désactivé" +on: "Activé" +off: "Désactivé" emailRequiredForSignup: "Une adresse e-mail est nécessaire pour créer un compte" unread: "Non lu" filter: "Filtre" -controlPanel: "Panneau de contrôle" +controlPanel: "Panneau de configuration" manageAccounts: "Gérer les comptes" makeReactionsPublic: "Rendre les réactions publiques" makeReactionsPublicDescription: "Ceci rendra la liste de toutes vos réactions données publique." @@ -935,20 +942,31 @@ roles: "Rôles" role: "Rôles" noRole: "Aucun rôle" normalUser: "Simple utilisateur·rice" +undefined: "Non défini" assign: "Attribuer" color: "Couleur" manageCustomEmojis: "Gestion des émojis personnalisés" preset: "Préréglage" selectFromPresets: "Sélectionner à partir des préréglages" +achievements: "Accomplissements" thisPostMayBeAnnoying: "Cette note peut gêner d'autres personnes." +thisPostMayBeAnnoyingHome: "Publier vers le fil principal" thisPostMayBeAnnoyingCancel: "Annuler" +thisPostMayBeAnnoyingIgnore: "Publier quand-même" +internalServerError: "Erreur interne du serveur" +copyErrorInfo: "Copier les détails de l’erreur" +exploreOtherServers: "Trouver une autre instance" +disableFederationOk: "Désactiver" license: "Licence" video: "Vidéo" videos: "Vidéos" dataSaver: "Économiseur de données" accountMigration: "Migration de compte" accountMoved: "Cet·te utilisateur·rice a migré son compte vers :" +accountMovedShort: "Ce compte a migré" +operationForbidden: "Opération non autorisée" addMemo: "Ajouter un mémo" +reactionsList: "Réactions" notificationDisplay: "Style des notifications" leftTop: "En haut à gauche" rightTop: "En haut à droite" @@ -957,12 +975,37 @@ rightBottom: "En bas à droite" vertical: "Vertical" horizontal: "Latéral" serverRules: "Règles du serveur" +archive: "Archive" youFollowing: "Abonné·e" +later: "Plus tard" +goToMisskey: "Retour vers Misskey" +expirationDate: "Date d’expiration" +usedAt: "Utilisé le" +unused: "Non-utilisé" +used: "Utilisé" +expired: "Expiré" +doYouAgree: "Êtes-vous d’accord ?" +icon: "Avatar" +forYou: "Pour vous" +replies: "Répondre" +renotes: "Renoter" +_announcement: + readConfirmTitle: "Marquer comme lu ?" +_initialAccountSetting: + profileSetting: "Paramètres du profil" + privacySetting: "Paramètres de confidentialité" +_accountMigration: + moveToLabel: "Compte vers lequel vous migrez :" + startMigration: "Migrer" + movedTo: "Compte vers lequel vous migrez :" _achievements: _types: _notes1: + title: "Je viens tout juste de configurer mon msky" description: "Publiez votre première note" flavor: "Passez un bon moment avec Misskey !" + _notes10: + title: "Quelques notes" _notes100: title: "Beaucoup de notes" _notes100000: @@ -977,16 +1020,23 @@ _achievements: title: "Débutant Ⅲ" description: "Se connecter pour un total de 15 jours" _login30: + title: "Misskeynaute I" description: "Se connecter pour un total de 30 jours" _login60: + title: "Misskeynaute II" description: "Se connecter pour un total de 60 jours" _login100: + title: "Misskeynaute III" description: "Se connecter pour un total de 100 jours" + flavor: "Misskeynaute acharné·e" _login200: + title: "Régulier I" description: "Se connecter pour un total de 200 jours" _login300: + title: "Régulier II" description: "Se connecter pour un total de 300 jours" _login400: + title: "Régulier III" description: "Se connecter pour un total de 400 jours" _login500: description: "Se connecter pour un total de 500 jours" @@ -1000,6 +1050,8 @@ _achievements: description: "Se connecter pour un total de 900 jours" _login1000: flavor: "Merci d'utiliser Misskey !" + _profileFilled: + description: "Configuration de votre profil" _markedAsCat: title: "Je suis un chat" flavor: "Je n'ai pas encore de nom" @@ -1009,6 +1061,16 @@ _achievements: title: "Abonnez-moi !" _iLoveMisskey: title: "J’adore Misskey" + description: "Publication « J’❤ #Misskey »" + _foundTreasure: + title: "Chasse au trésor" + description: "Vous avez trouvé le trésor caché" + _postedAtLateNight: + flavor: "C’est l’heure d’aller au lit." + _postedAt0min0sec: + title: "Horloge parlante" + description: "Publication d’une note à 00:00" + flavor: "Tic tac, tic tac, tic tac, ding !" _viewInstanceChart: title: "Analyste" _loggedInOnBirthday: @@ -1018,7 +1080,11 @@ _achievements: _cookieClicked: flavor: "Attendez une minute, vous êtes sur le mauvais site web ?" _role: + name: "Nom du rôle" + description: "Description du rôle" + permission: "Rôle et autorisations" assignTarget: "Attribuer" + condition: "Condition" priority: "Priorité" _priority: low: "Basse" @@ -1110,6 +1176,8 @@ _aboutMisskey: donate: "Soutenir Misskey" morePatrons: "Nous apprécions vraiment le soutien de nombreuses autres personnes non mentionnées ici. Merci à toutes et à tous ! 🥰" patrons: "Contributeurs" +_displayOfSensitiveMedia: + force: "Masquer tous les médias" _instanceTicker: none: "Cacher " remote: "Montrer pour les utilisateur·ice·s distant·e·s" @@ -1128,6 +1196,8 @@ _channel: following: "Abonné·e" usersCount: "{n} Participant·e·s" notesCount: "{n} Notes" + nameAndDescription: "Nom et description" + nameOnly: "Nom seulement" _menuDisplay: sideFull: "Latéral" sideIcon: "Latéral (icônes)" @@ -1245,16 +1315,24 @@ _time: minute: "min" hour: "h" day: "j" +_timelineTutorial: + title: "Comment utiliser Misskey" + step3_1: "Avez-vous publié votre première note ?" _2fa: alreadyRegistered: "Configuration déjà achevée." step1: "Tout d'abord, installez une application d'authentification, telle que {a} ou {b}, sur votre appareil." step2: "Ensuite, scannez le code QR affiché sur l’écran." - step2Url: "Vous pouvez également saisir cette URL si vous utilisez un programme de bureau :" + step3Title: "Veuillez saisir le code d’authentification" step3: "Entrez le jeton affiché sur votre application pour compléter la configuration." + setupCompleted: "Configuration terminée avec succès !" step4: "À partir de maintenant, ce même jeton vous sera demandé à chacune de vos connexions." + securityKeyNotSupported: "Votre navigateur ne prend pas en charge les clés de sécurité." securityKeyInfo: "Vous pouvez configurer l'authentification WebAuthN pour sécuriser davantage le processus de connexion grâce à une clé de sécurité matérielle qui prend en charge FIDO2, ou bien en configurant l'authentification par empreinte digitale ou par code PIN sur votre appareil." + securityKeyName: "Nom de la clé" removeKeyConfirm: "Voulez-vous supprimer {name} ?" + renewTOTPOk: "Reconfigurer" renewTOTPCancel: "Pas maintenant" + backupCodes: "Codes de Secours" _permissions: "read:account": "Afficher les informations du compte" "write:account": "Mettre à jour les informations de votre compte" @@ -1471,7 +1549,7 @@ _pages: fontSerif: "Serif" fontSansSerif: "Sans Serif" eyeCatchingImageSet: "Définir une image attractive" - eyeCatchingImageRemove: "Supprimer l'image attractive" + eyeCatchingImageRemove: "Supprimer la miniature" chooseBlock: "Ajouter un bloc" selectType: "Choisir un type" contentBlocks: "Contenu" @@ -1504,6 +1582,7 @@ _notification: pollEnded: "Les résultats du sondage sont disponibles" unreadAntennaNote: "Antenne {name}" emptyPushNotificationMessage: "Les notifications push ont été mises à jour" + achievementEarned: "Accomplissement" _types: all: "Toutes" follow: "Nouvel·le abonné·e" @@ -1515,6 +1594,7 @@ _notification: pollEnded: "Sondages se cloturant" receiveFollowRequest: "Demande d'abonnement reçue" followRequestAccepted: "Demande d'abonnement acceptée" + achievementEarned: "Accomplissement" app: "Notifications provenant des apps" _actions: followBack: "Suivre" @@ -1536,6 +1616,7 @@ _deck: deleteProfile: "Supprimer le profil" introduction: "Créez l’interface parfaite qui vous sied en arrangeant librement les colonnes !" introduction2: "Cliquez sur le + à droite de l'écran pour ajouter de nouvelles colonnes quand vous le souhaitez." + flexible: "Ajuster automatiquement la largeur" _columns: main: "Principale" widgets: "Widgets" diff --git a/locales/generateDTS.js b/locales/generateDTS.js index 7369dfbb4702..7af773f3b168 100644 --- a/locales/generateDTS.js +++ b/locales/generateDTS.js @@ -1,6 +1,11 @@ import * as fs from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname } from 'node:path'; import * as yaml from 'js-yaml'; -import * as ts from 'typescript'; +import ts from 'typescript'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); function createMembers(record) { return Object.entries(record) diff --git a/locales/hu-HU.yml b/locales/hu-HU.yml index fdab9645c160..023a91494d02 100644 --- a/locales/hu-HU.yml +++ b/locales/hu-HU.yml @@ -77,6 +77,7 @@ smtpUser: "Felhasználónév" smtpPass: "Jelszó" user: "Felhasználók" searchByGoogle: "Keresés" +renotes: "Renote" _theme: keys: renote: "Renote" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index c6ed07ebc9d0..c817850fe2a5 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -133,12 +133,12 @@ renoteMute: "Matikan renote" renoteUnmute: "Batal mematikan renote" block: "Blokir" unblock: "Buka blokir" -suspend: "Bekukan" -unsuspend: "Buka pembekuan" +suspend: "Tangguhkan" +unsuspend: "Batalkan penangguhan" blockConfirm: "Apakah kamu yakin ingin memblokir akun ini?" unblockConfirm: "Apakah kamu yakin ingin membuka blokir akun ini?" -suspendConfirm: "Apakah kamu yakin ingin membekukan akun ini?" -unsuspendConfirm: "Apakah kamu yakin ingin membuka pembekuan akun ini?" +suspendConfirm: "Apakah kamu yakin ingin menangguhkan akun ini?" +unsuspendConfirm: "Apakah kamu yakin ingin membatalkan penangguhan akun ini?" selectList: "Pilih daftar" editList: "Sunting daftar" selectChannel: "Pilih kanal" @@ -156,6 +156,8 @@ addEmoji: "Tambahkan emoji" settingGuide: "Pengaturan rekomendasi" cacheRemoteFiles: "Tembolokkan berkas dari instansi luar" cacheRemoteFilesDescription: "Ketika pengaturan ini dinonaktifkan, berkas dari instansi luar akan dimuat langsung. Menonaktifkan ini akan mengurangi penggunaan penyimpanan peladen, namun dapat menyebabkan peningkatan lalu lintas bandwidth, karena keluku tidak dihasilkan." +cacheRemoteSensitiveFiles: "Tembolokkan berkas dari instansi luar" +cacheRemoteSensitiveFilesDescription: "Menonaktifkan pengaturan ini menyebabkan berkas sensitif dari instansi luar ditautkan secara langsung, bukan ditembolok." flagAsBot: "Atur akun ini sebagai Bot" flagAsBotDescription: "Jika akun ini dikendalikan oleh program, tetapkanlah opsi ini. Jika diaktifkan, ini akan berfungsi sebagai tanda bagi pengembang lain untuk mencegah interaksi berantai dengan bot lain dan menyesuaikan sistem internal Misskey untuk memperlakukan akun ini sebagai bot." flagAsCat: "Atur akun ini sebagai kucing" @@ -227,7 +229,7 @@ noCustomEmojis: "Tidak ada emoji kustom" noJobs: "Tidak ada kerja" federating: "memfederasi" blocked: "Diblokir" -suspended: "Diberhentikan" +suspended: "Ditangguhkan" all: "Semua" subscribing: "Berlangganan" publishing: "Sedang menyiarkan langsung" @@ -352,7 +354,6 @@ invite: "Undang" driveCapacityPerLocalAccount: "Kapasitas drive per pengguna lokal" driveCapacityPerRemoteAccount: "Kapasitas drive per pengguna remote" inMb: "dalam Megabytes" -iconUrl: "URL Gambar ikon" bannerUrl: "URL Banner" backgroundImageUrl: "URL Gambar latar" basicInfo: "Informasi Umum" @@ -391,10 +392,10 @@ withReplies: "Termasuk balasan" connectedTo: "Akun berikut terhubung" notesAndReplies: "Catatan dan balasan" withFiles: "Media" -silence: "Bungkam" -silenceConfirm: "Apakah kamu yakin ingin membungkam pengguna ini?" -unsilence: "Hapus bungkam" -unsilenceConfirm: "Apakah kamu ingin untuk batal membungkam pengguna ini?" +silence: "Senyapkan" +silenceConfirm: "Apakah kamu yakin ingin menyenyapkan pengguna ini?" +unsilence: "Batalkan senyap" +unsilenceConfirm: "Apakah kamu ingin untuk batal menyenyapkan pengguna ini?" popularUsers: "Pengguna populer" recentlyUpdatedUsers: "Pengguna dengan aktivitas terkini" recentlyRegisteredUsers: "Pengguna baru saja bergabung" @@ -557,10 +558,10 @@ deleteAllFiles: "Hapus semua berkas" deleteAllFilesConfirm: "Apakah kamu yakin ingin menghapus semua berkas?" removeAllFollowing: "Batalkan mengikuti semua pengguna" removeAllFollowingDescription: "Batal mengikuti semua akun dari {host}. Mohon jalankan ini ketika instansi sudah tidak ada lagi." -userSuspended: "Pengguna ini telah dibekukan." -userSilenced: "Pengguna ini telah dibungkam." -yourAccountSuspendedTitle: "Akun ini dibekukan" -yourAccountSuspendedDescription: "Akun ini dibekukan karena melanggar ketentuan penggunaan layanan peladen atau semacamnya. Hubungi admin apabila ingin tahu alasan lebih lanjut. Mohon untuk tidak membuat akun baru." +userSuspended: "Pengguna ini telah ditangguhkan" +userSilenced: "Pengguna ini telah disenyapkan." +yourAccountSuspendedTitle: "Akun ini ditangguhkan" +yourAccountSuspendedDescription: "Akun ini ditangguhkan karena melanggar ketentuan penggunaan layanan peladen atau semacamnya. Hubungi admin apabila ingin mengetahui alasan lebih lanjut. Mohon untuk tidak membuat akun baru." tokenRevoked: "Token tidak valid" tokenRevokedDescription: "Token ini telah kedaluwarsa. Mohon masuk lagi." accountDeleted: "Akun telah dihapus" @@ -678,6 +679,7 @@ createNewClip: "Buat klip baru" unclip: "Batalkan klip" confirmToUnclipAlreadyClippedNote: "Catatan ini sudah disertakan di klip \"{name}\". Yakin ingin membatalkan catatan dari klip ini?" public: "Publik" +private: "Tersembunyi" i18nInfo: "Misskey diterjemahkan ke dalam banyak bahasa oleh sukarelawan. Kamu juga dapat ikut membantu menerjemahkannya di {link}." manageAccessTokens: "Kelola token akses" accountInfo: "Informasi akun" @@ -987,7 +989,7 @@ internalServerErrorDescription: "Peladen sedang mengalami galat tak terduga" copyErrorInfo: "Salin detil galat" joinThisServer: "Gabung peladen ini" exploreOtherServers: "Cari peladen lain" -letsLookAtTimeline: "LIhat timeline" +letsLookAtTimeline: "LIhat lini masa" disableFederationConfirm: "Matikan federasi?" disableFederationConfirmWarn: "Mematikan federasi tidak membuat kiriman menjadi privat. Umumnya, mematikan federasi tidak diperlukan." disableFederationOk: "Matikan federasi" @@ -1016,7 +1018,6 @@ retryAllQueuesConfirmText: "Hal ini akan meningkatkan beban sementara ke peladen enableChartsForRemoteUser: "Buat bagan data pengguna instansi luar" enableChartsForFederatedInstances: "Buat bagan data peladen instansi luar" showClipButtonInNoteFooter: "Tambahkan \"Klip\" ke menu aksi catatan" -largeNoteReactions: "Besarkan reaksi yang ditampilkan" noteIdOrUrl: "ID catatan atau URL" video: "Video" videos: "Video" @@ -1073,9 +1074,32 @@ enableServerMachineStats: "Tampilkan informasi mesin peladen menjadi publik" enableIdenticonGeneration: "Nyalakan pembuatan Identicon per pengguna" turnOffToImprovePerformance: "Matikan untuk tingkatkan performa." createInviteCode: "Buat kode undangan" +createWithOptions: "Buat dengan opsi" +createCount: "Jumlah undangan" inviteCodeCreated: "Kode undangan dibuat" inviteLimitExceeded: "Kamu telah mencapai jumlah maksimum kode undangan yang dapat dibuat." +createLimitRemaining: "Kode undangan yang dapat dibuat: tersisa {limit}" +inviteLimitResetCycle: "Kamu dapat membuat hingga {limit} kode undangan dalam {time}." expirationDate: "Tanggal kedaluwarsa" +noExpirationDate: "tidak ada tanggal kedaluwarsa" +inviteCodeUsedAt: "Kode undangan digunakan pada" +registeredUserUsingInviteCode: "Undangan digunakan oleh" +waitingForMailAuth: "Menunggu verifikasi surel" +inviteCodeCreator: "Undangan dibuat oleh" +usedAt: "Digunakan pada" +unused: "Tidak digunakan" +used: "Digunakan" +expired: "Kedaluwarsa" +doYouAgree: "Apa kamu setuju?" +beSureToReadThisAsItIsImportant: "Mohon baca informasi penting berikut." +iHaveReadXCarefullyAndAgree: "Saya telah membaca \"{x}\" dan menyetujui." +dialog: "Dialog" +icon: "Avatar" +forYou: "Untuk Anda" +currentAnnouncements: "Pengumuman Saat Ini" +pastAnnouncements: "Pengumuman Terdahulu" +replies: "Balas" +renotes: "Renote" _initialAccountSetting: accountCreated: "Akun kamu telah sukses dibuat!" letsStartAccountSetup: "Untuk pemula, ayo atur profilmu dulu." @@ -1093,6 +1117,8 @@ _initialAccountSetting: laterAreYouSure: "Yakin banget untuk atur profil nanti?" _serverRules: description: "Daftar peraturan akan ditampilkan sebelum pendaftaran. Mengatur ringkasan dari Syarat dan Ketentuan sangat direkomendasikan." +_serverSettings: + iconUrl: "URL ikon" _accountMigration: moveFrom: "Pindahkan akun lain ke akun ini" moveFromSub: "Buat alias ke akun lain" @@ -1386,6 +1412,9 @@ _role: ltlAvailable: "Dapat melihat lini masa lokal" canPublicNote: "Dapat mengirim catatan publik" canInvite: "Dapat membuat kode undangan instansi" + inviteLimit: "Batas jumlah undangan" + inviteLimitCycle: "Interval Penerbitan Kode Undangan" + inviteExpirationTime: "Interval kedaluwarsa undangan" canManageCustomEmojis: "Dapat mengelola Emoji kustom" driveCapacity: "Kapasitas Drive" alwaysMarkNsfw: "Selalu tandai berkas sebagai NSFW" @@ -1448,6 +1477,7 @@ _ad: back: "Kembali" reduceFrequencyOfThisAd: "Tampilkan iklan ini lebih sedikit" hide: "Jangan tampilkan" + timezoneinfo: "Hari dalam satu minggu ditentukan dari zona waktu peladen." _forgotPassword: enterEmail: "Masukkan alamat surel yang kamu gunakan pada saat mendaftar. Sebuah tautan untuk mengatur ulang kata sandi kamu akan dikirimkan ke alamat surel tersebut." ifNoEmail: "Apabila kamu tidak menggunakan surel pada saat pendaftaran, mohon hubungi admin segera." @@ -1499,6 +1529,10 @@ _aboutMisskey: donate: "Donasi ke Misskey" morePatrons: "Kami sangat mengapresiasi dukungan dari banyak penolong lain yang tidak tercantum disini. Terima kasih! 🥰" patrons: "Pendukung" +_displayOfSensitiveMedia: + respect: "Sembunyikan media yang ditandai sensitif" + ignore: "Tampilkan media yang ditandai sensitif" + force: "Sembunyikan semua media" _instanceTicker: none: "Jangan tampilkan" remote: "Tampilkan untuk pengguna instansi luar" @@ -1653,14 +1687,12 @@ _2fa: step1: "Pertama, pasang aplikasi otentikasi (seperti {a} atau {b}) di perangkat kamu." step2: "Lalu, pindai kode QR yang ada di layar." step2Click: "Mengeklik kode QR ini akan membolehkanmu untuk mendaftarkan 2FA ke security-key atau aplikasi autentikator ponsel." - step2Url: "Di aplikasi desktop, masukkan URL berikut:" step3Title: "Masukkan kode autentikasi" step3: "Masukkan token yang telah disediakan oleh aplikasimu untuk menyelesaikan pemasangan." step4: "Mulai sekarang, upaya login apapun akan meminta token login dari aplikasi otentikasi kamu." securityKeyNotSupported: "Peramban kamu tidak mendukung security key." registerTOTPBeforeKey: "Mohon atur aplikasi autentikator untuk mendaftarkan security key atau passkey." securityKeyInfo: "Kamu dapat memasang otentikasi WebAuthN untuk mengamankan proses login lebih lanjut dengan tidak hanya perangkat keras kunci keamanan yang mendukung FIDO2, namun juga sidik jari atau otentikasi PIN pada perangkatmu." - chromePasskeyNotSupported: "Passkey Chrome saat ini tidak didukung." registerSecurityKey: "Daftarkan security key atau passkey." securityKeyName: "Masukkan nama key." tapSecurityKey: "Mohon ikuti peramban kamu untuk mendaftarkan security key atau passkey" @@ -1671,6 +1703,7 @@ _2fa: renewTOTPConfirm: "Hal ini akan menyebabkan kode verifikasi dari aplikasi autentikator sebelumnya berhenti bekerja" renewTOTPOk: "Atur ulang" renewTOTPCancel: "Tidak sekarang." + backupCodes: "Kode Pencadangan" _permissions: "read:account": "Lihat informasi akun" "write:account": "Sunting informasi akun" @@ -1973,6 +2006,7 @@ _deck: introduction: "Buat antarmuka sempurna untukmu dengan menata kolom secara bebas!" introduction2: "Klik \"+\" pada kanan layar untuk menambahkan kolom baru kapanpun yang kamu mau." widgetsIntroduction: "Mohon pilih \"Sunting gawit\" pada menu kolom dan tambahkan gawit." + useSimpleUiForNonRootPages: "Gunakan antarmuka sederhana ke halaman yang dituju" _columns: main: "Utama" widgets: "Widget" diff --git a/locales/index.d.ts b/locales/index.d.ts index 4984dd18ec5d..3009c9918519 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -48,6 +48,7 @@ export interface Locale { "unpin": string; "copyContent": string; "copyLink": string; + "copyLinkRenote": string; "delete": string; "deleteAndEdit": string; "deleteAndEditConfirm": string; @@ -358,7 +359,6 @@ export interface Locale { "driveCapacityPerLocalAccount": string; "driveCapacityPerRemoteAccount": string; "inMb": string; - "iconUrl": string; "bannerUrl": string; "backgroundImageUrl": string; "basicInfo": string; @@ -414,10 +414,13 @@ export interface Locale { "administrator": string; "token": string; "2fa": string; + "setupOf2fa": string; "totp": string; "totpDescription": string; "moderator": string; "moderation": string; + "moderationNote": string; + "addModerationNote": string; "nUsersMentioned": string; "securityKeyAndPasskey": string; "securityKey": string; @@ -657,6 +660,7 @@ export interface Locale { "sample": string; "abuseReports": string; "reportAbuse": string; + "reportAbuseRenote": string; "reportAbuseOf": string; "fillAbuseReportDescription": string; "abuseReported": string; @@ -684,6 +688,7 @@ export interface Locale { "unclip": string; "confirmToUnclipAlreadyClippedNote": string; "public": string; + "private": string; "i18nInfo": string; "manageAccessTokens": string; "accountInfo": string; @@ -708,6 +713,7 @@ export interface Locale { "alwaysMarkSensitive": string; "loadRawImages": string; "disableShowingAnimatedImages": string; + "highlightSensitiveMedia": string; "verificationEmailSent": string; "notSet": string; "emailVerified": string; @@ -1022,7 +1028,7 @@ export interface Locale { "enableChartsForRemoteUser": string; "enableChartsForFederatedInstances": string; "showClipButtonInNoteFooter": string; - "largeNoteReactions": string; + "reactionsDisplaySize": string; "noteIdOrUrl": string; "video": string; "videos": string; @@ -1104,6 +1110,18 @@ export interface Locale { "currentAnnouncements": string; "pastAnnouncements": string; "youHaveUnreadAnnouncements": string; + "useSecurityKey": string; + "replies": string; + "renotes": string; + "loadReplies": string; + "loadConversation": string; + "pinnedList": string; + "keepScreenOn": string; + "verifiedLink": string; + "notifyNotes": string; + "unnotifyNotes": string; + "authentication": string; + "authenticationRequiredToContinue": string; "_announcement": { "forExistingUsers": string; "forExistingUsersDescription": string; @@ -1133,6 +1151,14 @@ export interface Locale { "_serverRules": { "description": string; }; + "_serverSettings": { + "iconUrl": string; + "appIconDescription": string; + "appIconUsageExample": string; + "appIconStyleRecommendation": string; + "appIconResolutionMustBe": string; + "manifestJsonOverride": string; + }; "_accountMigration": { "moveFrom": string; "moveFromSub": string; @@ -1462,6 +1488,10 @@ export interface Locale { "description": string; "flavor": string; }; + "_smashTestNotificationButton": { + "title": string; + "description": string; + }; }; }; "_role": { @@ -1806,18 +1836,17 @@ export interface Locale { "_2fa": { "alreadyRegistered": string; "registerTOTP": string; - "passwordToTOTP": string; "step1": string; "step2": string; "step2Click": string; - "step2Url": string; + "step2Uri": string; "step3Title": string; "step3": string; + "setupCompleted": string; "step4": string; "securityKeyNotSupported": string; "registerTOTPBeforeKey": string; "securityKeyInfo": string; - "chromePasskeyNotSupported": string; "registerSecurityKey": string; "securityKeyName": string; "tapSecurityKey": string; @@ -1828,6 +1857,11 @@ export interface Locale { "renewTOTPConfirm": string; "renewTOTPOk": string; "renewTOTPCancel": string; + "checkBackupCodesBeforeCloseThisWizard": string; + "backupCodes": string; + "backupCodesDescription": string; + "backupCodeUsedWarning": string; + "backupCodesExhaustedWarning": string; }; "_permissions": { "read:account": string; @@ -1862,6 +1896,10 @@ export interface Locale { "write:gallery": string; "read:gallery-likes": string; "write:gallery-likes": string; + "read:flash": string; + "write:flash": string; + "read:flash-likes": string; + "write:flash-likes": string; }; "_auth": { "shareAccessTitle": string; @@ -1987,6 +2025,7 @@ export interface Locale { "metadataContent": string; "changeAvatar": string; "changeBanner": string; + "verifiedLinkDescription": string; }; "_exportOrImport": { "allNotes": string; @@ -2115,9 +2154,14 @@ export interface Locale { "youReceivedFollowRequest": string; "yourFollowRequestAccepted": string; "pollEnded": string; + "newNote": string; "unreadAntennaNote": string; "emptyPushNotificationMessage": string; "achievementEarned": string; + "testNotification": string; + "checkNotificationBehavior": string; + "sendTestNotification": string; + "notificationWillBeDisplayedLikeThis": string; "_types": { "all": string; "follow": string; @@ -2156,6 +2200,8 @@ export interface Locale { "introduction2": string; "widgetsIntroduction": string; "useSimpleUiForNonRootPages": string; + "usedAsMinWidthWhenFlexible": string; + "flexible": string; "_columns": { "main": string; "widgets": string; diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 864918117e70..d8455c3d8144 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -21,7 +21,7 @@ noNotifications: "Nessuna notifica" instance: "Istanza" settings: "Impostazioni" notificationSettings: "Preferenze di notifica" -basicSettings: "Impostazioni generali" +basicSettings: "Impostazioni base" otherSettings: "Altre impostazioni" openInWindow: "Apri in una finestra" profile: "Profilo" @@ -45,6 +45,7 @@ pin: "Fissa sul profilo" unpin: "Non fissare sul profilo" copyContent: "Copia il contenuto" copyLink: "Copia il link" +copyLinkRenote: "Copia collegamento alla Rinota" delete: "Elimina" deleteAndEdit: "Elimina e modifica" deleteAndEditConfirm: "Vuoi davvero cancellare questa nota e scriverla di nuovo? Verranno eliminate anche tutte le reazioni, rinote e risposte collegate." @@ -63,9 +64,9 @@ reply: "Rispondi" loadMore: "Mostra di più" showMore: "Espandi" showLess: "Comprimi" -youGotNewFollower: "Ha iniziato a seguirti" +youGotNewFollower: "Ti sta seguendo" receiveFollowRequest: "Hai ricevuto una richiesta di follow" -followRequestAccepted: "Richiesta di follow accettata" +followRequestAccepted: "Ha accettato la tua richiesta di follow" mention: "Menzioni" mentions: "Menzioni" directNotes: "Note dirette" @@ -74,8 +75,8 @@ import: "Importa" export: "Esporta" files: "Allegati" download: "Scarica" -driveFileDeleteConfirm: "Vuoi davvero eliminare il file \"{name}\"? Anche gli allegati verranno eliminati." -unfollowConfirm: "Vuoi smettere di seguire {name}?" +driveFileDeleteConfirm: "Vuoi davvero eliminare il file \"{name}\", e le Note a cui è stato allegato?" +unfollowConfirm: "Vuoi davvero smettere di seguire {name}?" exportRequested: "Hai richiesto un'esportazione, e potrebbe volerci tempo. Quando sarà compiuta, il file verrà aggiunto direttamente al Drive." importRequested: "Hai richiesto un'importazione. Può volerci tempo. " lists: "Liste" @@ -84,7 +85,7 @@ note: "Nota" notes: "Note" following: "Follow" followers: "Follower" -followsYou: "Ti segue" +followsYou: "Segue" createList: "Aggiungi una nuova lista" manageLists: "Gestisci liste" error: "Errore" @@ -105,7 +106,7 @@ unfollow: "Non seguire" followRequestPending: "Richiesta in approvazione" enterEmoji: "Inserisci emoji" renote: "Rinota" -unrenote: "Annulla rinota" +unrenote: "Elimina la Rinota" renoted: "Rinotato!" cantRenote: "È impossibile rinotare questa nota." cantReRenote: "È impossibile rinotare una Rinota." @@ -137,7 +138,7 @@ suspend: "Sospendi" unsuspend: "Revoca la sospensione" blockConfirm: "Vuoi davvero bloccare il profilo?" unblockConfirm: "Vuoi davvero sbloccare il profilo?" -suspendConfirm: "Vuoi sospendere questo profilo?" +suspendConfirm: "Vuoi davvero sospendere questo profilo?" unsuspendConfirm: "Vuoi revocare la sospensione si questo profilo?" selectList: "Seleziona una lista" editList: "Modifica Lista" @@ -156,6 +157,7 @@ addEmoji: "Aggiungi un emoji" settingGuide: "Configurazione suggerita" cacheRemoteFiles: "Memorizza i file remoti nella cache" cacheRemoteFilesDescription: "Disabilitando questa opzione, i file remoti verranno linkati direttamente senza essere memorizzati nella cache. Sarà possibile risparmiare spazio di archiviazione sul server, ma il traffico aumenterà in quanto non verranno generate anteprime." +youCanCleanRemoteFilesCache: "Puoi svuotare tutta la cache cliccando il bottone 🗑️ nella gestione file" cacheRemoteSensitiveFiles: "Memorizza nella cache i file sensibili remoti" cacheRemoteSensitiveFilesDescription: "Disattivando questa opzione, i file sensibili verranno caricati direttamente dall'istanza remota senza essere salvati dal server." flagAsBot: "Io sono un robot" @@ -164,7 +166,7 @@ flagAsCat: "Sono un gatto" flagAsCatDescription: "La modalità \"sono un gatto\" aggiunge le orecchie al tuo profilo" flagShowTimelineReplies: "Mostra le risposte alle note sulla timeline." flagShowTimelineRepliesDescription: "Attivando, la timeline mostra le Note del profilo ed anche le risposte ad altre Note" -autoAcceptFollowed: "Accetta automaticamente le richieste di follow da utenti che già segui" +autoAcceptFollowed: "Accetta automaticamente le richieste di follow da profili che già segui" addAccount: "Aggiungi profilo" reloadAccountsList: "Ricarica l'elenco dei profili" loginFailed: "Accesso non riuscito" @@ -254,7 +256,7 @@ imageUrl: "URL dell'immagine" remove: "Elimina" removed: "Eliminato con successo" removeAreYouSure: "Vuoi davvero eliminare \"{x}\"?" -deleteAreYouSure: "Eliminare \"{x}\"?" +deleteAreYouSure: "Vuoi davvero eliminare \"{x}\"?" resetAreYouSure: "Ripristinare?" saved: "Salvato" messaging: "Messaggi" @@ -273,10 +275,10 @@ noMoreHistory: "Non c'è più cronologia da visualizzare" startMessaging: "Nuovo messaggio" nUsersRead: "Letto da {n} persone" agreeTo: "Sono d'accordo con {0}" -agree: "D'accordo" +agree: "Accetto" agreeBelow: "Accetto quanto riportato sotto" basicNotesBeforeCreateAccount: "Note importanti" -termsOfService: "Informativa Privacy" +termsOfService: "Informativa ai sensi degli artt. 13 e 14 del Regolamento UE 2016/679 per la protezione dei dati personali (GDPR)" start: "Inizia!" home: "Home" remoteUserCaution: "Le informazioni potrebbero essere incomplete poiché questo profilo remoto potrebbe non essere completamente federato." @@ -354,7 +356,6 @@ invite: "Invita" driveCapacityPerLocalAccount: "Capienza del Drive per profilo locale" driveCapacityPerRemoteAccount: "Capienza del Drive per profilo remoto" inMb: "in Megabytes" -iconUrl: "URL di icona (favicon, ecc.)" bannerUrl: "URL dell'immagine d'intestazione" backgroundImageUrl: "URL dello sfondo" basicInfo: "Informazioni fondamentali" @@ -394,9 +395,9 @@ connectedTo: "Connessione ai seguenti profili:" notesAndReplies: "Note e risposte" withFiles: "Con file in allegato" silence: "Silenzia" -silenceConfirm: "Vuoi davvero silenziare l'utente?" +silenceConfirm: "Vuoi davvero silenziare questo profilo?" unsilence: "Riattiva" -unsilenceConfirm: "Vuoi davvero riattivare l'utente?" +unsilenceConfirm: "Vuoi davvero riattivare questo profilo?" popularUsers: "Utenti popolari" recentlyUpdatedUsers: "Utenti attivi di recente" recentlyRegisteredUsers: "Utenti registrati di recente" @@ -410,10 +411,13 @@ aboutMisskey: "Informazioni di Misskey" administrator: "Amministratore" token: "Token" 2fa: "Autenticazione a due fattori" +setupOf2fa: "Impostare l'autenticazione a due fattori" totp: "App di autenticazione" totpDescription: "Inserisci un codice OTP tramite un'app di autenticazione" moderator: "Moderatore" moderation: "moderazione" +moderationNote: "Promemoria di moderazione" +addModerationNote: "Aggiungi promemoria di moderazione" nUsersMentioned: "{n} profili menzionati" securityKeyAndPasskey: "Chiave di sicurezza e accesso" securityKey: "Chiave di sicurezza" @@ -526,9 +530,9 @@ serverLogs: "Log del server" deleteAll: "Cancella cronologia" showFixedPostForm: "Visualizzare la finestra di pubblicazione in cima alla timeline" showFixedPostFormInChannel: "Per i canali, mostra il modulo di pubblicazione in cima alla timeline" -newNoteRecived: "Vedi le nuove note" +newNoteRecived: "Nuove note da leggere" sounds: "Impostazioni suoni" -sound: "Impostazioni suoni" +sound: "Suono" listen: "Ascolta" none: "Nessuno" showInPage: "Visualizza in pagina" @@ -557,7 +561,7 @@ disablePagesScript: "Disabilita AiScript nelle pagine" updateRemoteUser: "Aggiornare le informazioni di utente remot@" deleteAllFiles: "Elimina tutti i file" deleteAllFilesConfirm: "Vuoi davvero eliminare tutti i file?" -removeAllFollowing: "Cancella tutti i follows" +removeAllFollowing: "Annulla tutti i follow" removeAllFollowingDescription: "Cancella tutti i follows del server {host}. Per favore, esegui se, ad esempio, l'istanza non esiste più." userSuspended: "L'utente è in sospensione" userSilenced: "L'utente è silenziat@." @@ -652,9 +656,10 @@ fileIdOrUrl: "ID o URL del file" behavior: "Comportamento" sample: "Esempio" abuseReports: "Segnalazioni" -reportAbuse: "Segnalazioni" +reportAbuse: "Segnala" +reportAbuseRenote: "Segnala la Rinota" reportAbuseOf: "Segnala {name}" -fillAbuseReportDescription: "Si prega di spiegare il motivo della segnalazione. Se riguarda una nota precisa, si prega di collegare anche l'URL della nota." +fillAbuseReportDescription: "Per favore, spiegaci il motivo della segnalazione. Se riguarda una Nota precisa, indica anche l'indirizzo URL." abuseReported: "La segnalazione è stata inviata. Grazie." reporter: "il corrispondente" reporteeOrigin: "Origine del segnalato" @@ -680,6 +685,7 @@ createNewClip: "Crea una Clip" unclip: "Togli Nota dalla Clip" confirmToUnclipAlreadyClippedNote: "Questa nota è già inclusa in \"{name}\". Si desidera escludere la nota?" public: "Pubblica" +private: "Privato" i18nInfo: "Misskey è tradotto in diverse lingue da volontari. Anche tu puoi contribuire su {link}." manageAccessTokens: "Gestisci token di accesso" accountInfo: "Informazioni profilo" @@ -760,7 +766,7 @@ editCode: "Modifica codice" apply: "Applica" receiveAnnouncementFromInstance: "Ricevi i messaggi informativi dall'istanza" emailNotification: "Eventi per notifiche via mail" -publish: "Pubblico" +publish: "Pubblicare" inChannelSearch: "Cerca in canale" useReactionPickerForContextMenu: "Cliccare sul tasto destro per aprire il pannello di reazioni" typingUsers: "{users} sta(nno) scrivendo" @@ -819,8 +825,8 @@ previewNoteText: "Anteprima del testo" customCss: "CSS personalizzato" customCssWarn: "Questa impostazione deve essere eseguita da una persona esperta. Una configurazione errata può impedire al client di utilizzare correttamente il sistema." global: "Federata" -squareAvatars: "Mostra l'immagine del profilo come quadrato" -sent: "Inviare" +squareAvatars: "Foto profilo squadrate" +sent: "Inviato" received: "Ricevuto" searchResult: "Risultati della Ricerca" hashtags: "Hashtag" @@ -829,8 +835,8 @@ useBlurEffect: "Utilizza effetto sfocatura" learnMore: "Più dettagli" misskeyUpdated: "Misskey è stato aggiornato!" whatIsNew: "Visualizza le informazioni sull'aggiornamento" -translate: "Traduzione" -translatedFrom: "Tradotto da {x}" +translate: "Traduci" +translatedFrom: "Traduzione da {x}" accountDeletionInProgress: "È in corso l'eliminazione del profilo" usernameInfo: "Un nome per identificare univocamente il tuo profilo sull'istanza. Puoi utilizzare caratteri alfanumerici maiuscoli, minuscoli e il trattino basso (_). Non potrai cambiare nome utente in seguito." aiChanMode: "Modalità Ai" @@ -840,8 +846,8 @@ pubSub: "Publish/Subscribe del profilo" lastCommunication: "La comunicazione più recente" resolved: "Risolto" unresolved: "Non risolto" -breakFollow: "Non seguire" -breakFollowConfirm: "Vuoi davvero togliere follower?" +breakFollow: "Non farti più seguire" +breakFollowConfirm: "Vuoi davvero smettere di seguire questo profilo?" itsOn: "Abilitato" itsOff: "Disabilitato" on: "Acceso" @@ -854,11 +860,11 @@ manageAccounts: "Gestisci i profili" makeReactionsPublic: "Pubblicare la lista delle reazioni." makeReactionsPublicDescription: "La lista delle reazioni che avete fatto è a disposizione di tutti." classic: "Classico" -muteThread: "Silenzia la conversazione" +muteThread: "Silenzia conversazione" unmuteThread: "Riattiva la conversazione" ffVisibility: "Visibilità delle connessioni" ffVisibilityDescription: "Puoi scegliere a chi mostrare le tue relazioni con altri profili nel fediverso." -continueThread: "Altri thread." +continueThread: "Altre conversazioni" deleteAccountConfirm: "Così verrà eliminato il profilo. Vuoi procedere?" incorrectPassword: "La password è errata." voteConfirm: "Votare per「{choice}」?" @@ -1018,7 +1024,7 @@ retryAllQueuesConfirmText: "Potrebbe sovraccaricare il server temporaneamente." enableChartsForRemoteUser: "Abilita i grafici per i profili remoti" enableChartsForFederatedInstances: "Abilita i grafici per le istanze federate" showClipButtonInNoteFooter: "Aggiungi il bottone Clip tra le azioni delle Note" -largeNoteReactions: "Ingrandisci le reazioni" +reactionsDisplaySize: "Grandezza delle reazioni" noteIdOrUrl: "ID della Nota o URL" video: "Video" videos: "Video" @@ -1030,8 +1036,8 @@ operationForbidden: "Operazione non consentita" forceShowAds: "Mostra sempre i banner" addMemo: "Aggiungi Memo" editMemo: "Modifica Memo" -reactionsList: "Elenco delle reazioni" -renotesList: "Elenco di Rinota" +reactionsList: "Chi ha reagito?" +renotesList: "Chi ha Rinotato?" notificationDisplay: "Stile delle notifiche" leftTop: "In alto a sinistra" rightTop: "In alto a destra" @@ -1042,8 +1048,8 @@ vertical: "Verticale" horizontal: "Laterale" position: "Posizione" serverRules: "Regolamento" -pleaseConfirmBelowBeforeSignup: "Ai sensi del regolamento EU 679/2016 GDPR, autorizzo il trattamento dati personali come descritto nella informativa Privacy." -pleaseAgreeAllToContinue: "Per continuare, occorre selezionare ed essere d'accordo su tutto." +pleaseConfirmBelowBeforeSignup: "Per iscriversi, occorre essere d'accordo con le seguenti condizioni." +pleaseAgreeAllToContinue: "Occorre accettare tutte le condizioni prima di continuare." continue: "Continua" preservedUsernames: "Nomi utente riservati" preservedUsernamesDescription: "Elenca, uno per linea, i nomi utente che non possono essere registrati durante la creazione del profilo. La restrizione non si applica agli amministratori. Inoltre, i profili già registrati sono esenti." @@ -1082,7 +1088,7 @@ inviteLimitExceeded: "Hai raggiunto il numero massimo di codici invito generabil createLimitRemaining: "Inviti generabili: {limit} rimanenti" inviteLimitResetCycle: "Alle {time}, il limite verrà ripristinato a {limit}" expirationDate: "Scadenza" -noExpirationDate: "Perpetuo" +noExpirationDate: "Senza scadenza" inviteCodeUsedAt: "Codice di invito usato alle" registeredUserUsingInviteCode: "Codice di invito usato da" waitingForMailAuth: "In attesa della verifica email" @@ -1091,9 +1097,30 @@ usedAt: "Usato alle" unused: "Inutilizzato" used: "Utilizzato" expired: "Scaduto" -doYouAgree: "Sei d'accordo?" +doYouAgree: "Accetti le condizioni?" beSureToReadThisAsItIsImportant: "Si prega di leggere attentamente perché è importante." -iHaveReadXCarefullyAndAgree: "Ho letto accuratamente \"{x}\" e sono d'accordo." +iHaveReadXCarefullyAndAgree: "Dichiaro di aver letto attentamente \"{x}\" e accettarne le condizioni." +dialog: "Dialogo" +icon: "Ritratto" +forYou: "Per te" +currentAnnouncements: "Annunci attuali" +pastAnnouncements: "Annunci precedenti" +youHaveUnreadAnnouncements: "Ci sono Annunci non letti" +useSecurityKey: "Per utilizzare la chiave di sicurezza o la passkey, segui le indicazioni del dispositivo" +replies: "Rispondi" +renotes: "Rinota" +loadReplies: "Leggi le risposte" +loadConversation: "Leggi la conversazione" +pinnedList: "Elenco in primo piano" +_announcement: + forExistingUsers: "Solo ai profili attuali" + forExistingUsersDescription: "L'annuncio sarà visibile solo ai profili esistenti in questo momento. Se disabilitato, sarà visibile anche ai profili che verranno creati dopo la pubblicazione di questo annuncio." + needConfirmationToRead: "Richiede la conferma di lettura" + needConfirmationToReadDescription: "Sarà visualizzata una finestra di dialogo che richiede la conferma di lettura. Inoltre, non è soggetto a conferme di lettura massicce." + end: "Archivia l'annuncio" + tooManyActiveAnnouncementDescription: "L'esperienza delle persone può peggiorare se ci sono troppi annunci attivi. Considera anche l'archiviazione degli annunci conclusi." + readConfirmTitle: "Segnare come già letto?" + readConfirmText: "Hai già letto \"{title}˝?" _initialAccountSetting: accountCreated: "Il tuo profilo è stato creato!" letsStartAccountSetup: "Per iniziare, impostiamo il tuo profilo." @@ -1111,6 +1138,13 @@ _initialAccountSetting: laterAreYouSure: "Vuoi davvero rimandare la configurazione iniziale?" _serverRules: description: "In Europa è necessario mostrare l'informativa sul trattamento dei dati personali, prima della registrazione al servizio." +_serverSettings: + iconUrl: "URL dell'icona" + appIconDescription: "Indicare l'icona da usare quando {host} viene salvata come App." + appIconUsageExample: "Ad esempio quando si aggiunge il segnalibro alla PWA (Progressive Web App), oppure alla schermata iniziale del dispositivo mobile " + appIconStyleRecommendation: "Poiché l'icona potrebbe essere ritagliata in un quadrato o in un cerchio, si raccomanda che abbia un margine colorato." + appIconResolutionMustBe: "La risoluzione minima è {resolution}" + manifestJsonOverride: "Sostituire il file manifest.json" _accountMigration: moveFrom: "Migra un altro profilo dentro a questo" moveFromSub: "Crea un alias verso un altro profilo remoto" @@ -1365,6 +1399,9 @@ _achievements: title: "Brain Diver" description: "Pubblica un link a Brain Diver" flavor: "Sulle note di Brain Diver" + _smashTestNotificationButton: + title: "Prove eccessive" + description: "Hai provato le notifiche consecutivamente in un periodo di tempo molto breve" _role: new: "Nuovo ruolo" edit: "Modifica ruolo" @@ -1451,7 +1488,7 @@ _emailUnavailable: mx: "Server email non corretto" smtp: "Il server email non risponde" _ffVisibility: - public: "Pubblico" + public: "Pubblica" followers: "Mostra solo ai follower" private: "Invisibile" _signup: @@ -1489,11 +1526,11 @@ _plugin: installWarn: "Si prega di installare soltanto estensioni che provengono da fonti affidabili." manage: "Gestisci estensioni" _preferencesBackups: - list: "I backup creati." + list: "Elenco di impostazioni salvate in precedenza" saveNew: "Nuovo salvataggio" - loadFile: "Importa file" + loadFile: "Carica da file" apply: "Applicabile a questo dispositivo" - save: "Sovrascrivi il file di salvataggio" + save: "Sovrascrivi il backup" inputName: "Inserire il nome del backup." cannotSave: "Impossibile salvare." nameAlreadyExists: "Il nome del backup \"{name}\" esiste già. Si prega di specificare un nome diverso." @@ -1679,14 +1716,14 @@ _2fa: step1: "Innanzitutto, installare sul dispositivo un'applicazione di autenticazione come {a} o {b}." step2: "Quindi, scansionare il codice QR visualizzato con l'app." step2Click: "Cliccando sul codice QR, puoi registrarlo con l'app di autenticazione o il portachiavi installato sul tuo dispositivo." - step2Url: "Nell'applicazione desktop inserire il seguente URL: " + step2Uri: "Inserisci il seguente URL se desideri utilizzare una App per PC" step3Title: "Inserisci il codice di verifica" step3: "Inserite il token visualizzato nell'app e il gioco è fatto." + setupCompleted: "Impostazione completata" step4: "D'ora in poi, quando si accede, si inserisce il token nello stesso modo." securityKeyNotSupported: "Il tuo browser non supporta le chiavi di sicurezza." registerTOTPBeforeKey: "Ti occorre un'app di autenticazione con OTP, prima di registrare la chiave di sicurezza." securityKeyInfo: "È possibile impostare il dispositivo per accedere utilizzando una chiave di sicurezza hardware che supporta FIDO2 o un'impronta digitale o un PIN sul dispositivo." - chromePasskeyNotSupported: "Le passkey di Chrome non sono attualmente supportate." registerSecurityKey: "Registra la chiave di sicurezza" securityKeyName: "Inserisci il nome della chiave" tapSecurityKey: "Segui le istruzioni del browser e registra la chiave di sicurezza." @@ -1697,6 +1734,11 @@ _2fa: renewTOTPConfirm: "I codici di verifica nelle app di autenticazione esistenti smetteranno di funzionare" renewTOTPOk: "Ripristina" renewTOTPCancel: "No grazie" + checkBackupCodesBeforeCloseThisWizard: "Prima di chiudere questa procedura guidata, salva i tuoi codici usa-e-getta in un posto sicuro." + backupCodes: "Codici usa-e-getta" + backupCodesDescription: "Puoi usare questi codici usa-e-getta per ottenere l'accesso al tuo profilo in caso sia impossibile usare l'App col codice OTP. Salvali in un posto sicuro." + backupCodeUsedWarning: "È stato usato un codice usa-e-getta. Per favore, riconfigura l'autenticazione a due fattori il prima possibile, nel caso la configurazione precedente abbia smesso di funzionare." + backupCodesExhaustedWarning: "Hai esaurito i codici usa-e-getta. Se l'App che genera il codice OTP non è più disponibile, non potrai più accedere al tuo profilo. Ripeti la configurazione per l'autenticazione a due fattori." _permissions: "read:account": "Visualizza le informazioni sul profilo" "write:account": "Modifica le informazioni sul profilo" @@ -1730,6 +1772,10 @@ _permissions: "write:gallery": "Gestione della galleria" "read:gallery-likes": "Visualizza i contenuti della galleria." "write:gallery-likes": "Manipolazione dei \"Mi piace\" della galleria." + "read:flash": "Visualizza Play" + "write:flash": "Modifica Play" + "read:flash-likes": "Visualizza lista di Play piaciuti" + "write:flash-likes": "Modifica lista di Play piaciuti" _auth: shareAccessTitle: "Permessi dell'applicazione" shareAccess: "Vuoi autorizzare {name} ad accedere al tuo profilo?" @@ -1965,6 +2011,10 @@ _notification: unreadAntennaNote: "Antenna {name}" emptyPushNotificationMessage: "Le notifiche push sono state aggiornate." achievementEarned: "Obiettivo raggiunto" + testNotification: "Prova la notifica" + checkNotificationBehavior: "Prova il comportamento della notifica" + sendTestNotification: "Spedisci una notifica di prova" + notificationWillBeDisplayedLikeThis: "La notifica apparirà così" _types: all: "Tutto" follow: "Novità follower" @@ -1986,7 +2036,7 @@ _deck: alwaysShowMainColumn: "Mostra sempre la colonna principale" columnAlign: "Allineare colonne" addColumn: "Aggiungi colonna" - configureColumn: "Impostazioni della colonna." + configureColumn: "Impostazioni colonna" swapLeft: "Sposta a sinistra" swapRight: "Sposta a destra" swapUp: "Sposta in alto" @@ -2000,6 +2050,8 @@ _deck: introduction2: "È possibile aggiungere colonne in qualsiasi momento premendo + sulla destra dello schermo." widgetsIntroduction: "Dal menu della colonna, selezionare \"Modifica i riquadri\" per aggiungere un un riquadro con funzionalità" useSimpleUiForNonRootPages: "Visualizza sotto pagine con interfaccia web semplice" + usedAsMinWidthWhenFlexible: "Se \"larghezza flessibile\" è abilitato, questa diventa la larghezza minima" + flexible: "Larghezza flessibile" _columns: main: "Principale" widgets: "Riquadri" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 9089cb1d3b53..b30fd5333df9 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -15,7 +15,7 @@ gotIt: "わかった" cancel: "キャンセル" noThankYou: "やめておく" enterUsername: "ユーザー名を入力" -renotedBy: "{user}がRenote" +renotedBy: "{user}がリノート" noNotes: "ノートはありません" noNotifications: "通知はありません" instance: "サーバー" @@ -45,9 +45,10 @@ pin: "ピン留め" unpin: "ピン留め解除" copyContent: "内容をコピー" copyLink: "リンクをコピー" +copyLinkRenote: "リノートのリンクをコピー" delete: "削除" deleteAndEdit: "削除して編集" -deleteAndEditConfirm: "このノートを削除してもう一度編集しますか?このノートへのリアクション、Renote、返信も全て削除されます。" +deleteAndEditConfirm: "このノートを削除してもう一度編集しますか?このノートへのリアクション、リノート、返信も全て削除されます。" addToList: "リストに追加" addToAntenna: "アンテナに追加" sendMessage: "メッセージを送信" @@ -104,13 +105,13 @@ followRequests: "フォロー申請" unfollow: "フォロー解除" followRequestPending: "フォロー許可待ち" enterEmoji: "絵文字を入力" -renote: "Renote" -unrenote: "Renote解除" -renoted: "Renoteしました。" -cantRenote: "この投稿はRenoteできません。" -cantReRenote: "RenoteをRenoteすることはできません。" +renote: "リノート" +unrenote: "リノート解除" +renoted: "リノートしました。" +cantRenote: "この投稿はリノートできません。" +cantReRenote: "リノートをリノートすることはできません。" quote: "引用" -inChannelRenote: "チャンネル内Renote" +inChannelRenote: "チャンネル内リノート" inChannelQuote: "チャンネル内引用" pinnedNote: "ピン留めされたノート" pinned: "ピン留め" @@ -355,7 +356,6 @@ invite: "招待" driveCapacityPerLocalAccount: "ローカルユーザーひとりあたりのドライブ容量" driveCapacityPerRemoteAccount: "リモートユーザーひとりあたりのドライブ容量" inMb: "メガバイト単位" -iconUrl: "アイコン画像のURL (faviconなど)" bannerUrl: "バナー画像のURL" backgroundImageUrl: "背景画像のURL" basicInfo: "基本情報" @@ -411,10 +411,13 @@ aboutMisskey: "Misskeyについて" administrator: "管理者" token: "確認コード" 2fa: "二要素認証" +setupOf2fa: "二要素認証のセットアップ" totp: "認証アプリ" totpDescription: "認証アプリを使ってワンタイムパスワードを入力" moderator: "モデレーター" moderation: "モデレーション" +moderationNote: "モデレーションノート" +addModerationNote: "モデレーションノートを追加する" nUsersMentioned: "{n}人が投稿" securityKeyAndPasskey: "セキュリティキー・パスキー" securityKey: "セキュリティキー" @@ -654,6 +657,7 @@ behavior: "動作" sample: "サンプル" abuseReports: "通報" reportAbuse: "通報" +reportAbuseRenote: "リノートを通報" reportAbuseOf: "{name}を通報する" fillAbuseReportDescription: "通報理由の詳細を記入してください。対象のノートがある場合はそのURLも記入してください。" abuseReported: "内容が送信されました。ご報告ありがとうございました。" @@ -681,14 +685,15 @@ createNewClip: "新しいクリップを作成" unclip: "クリップ解除" confirmToUnclipAlreadyClippedNote: "このノートはすでにクリップ「{name}」に含まれています。ノートをこのクリップから除外しますか?" public: "パブリック" +private: "非公開" i18nInfo: "Misskeyは有志によって様々な言語に翻訳されています。{link}で翻訳に協力できます。" manageAccessTokens: "アクセストークンの管理" accountInfo: "アカウント情報" notesCount: "ノートの数" repliesCount: "返信した数" -renotesCount: "Renoteした数" +renotesCount: "リノートした数" repliedCount: "返信された数" -renotedCount: "Renoteされた数" +renotedCount: "リノートされた数" followingCount: "フォロー数" followersCount: "フォロワー数" sentReactionsCount: "リアクションした数" @@ -705,6 +710,7 @@ lockedAccountInfo: "フォローを承認制にしても、ノートの公開範 alwaysMarkSensitive: "デフォルトでメディアをセンシティブ設定にする" loadRawImages: "添付画像のサムネイルをオリジナル画質にする" disableShowingAnimatedImages: "アニメーション画像を再生しない" +highlightSensitiveMedia: "メディアがセンシティブであることを分かりやすく表示" verificationEmailSent: "確認のメールを送信しました。メールに記載されたリンクにアクセスして、設定を完了してください。" notSet: "未設定" emailVerified: "メールアドレスが確認されました" @@ -984,7 +990,7 @@ thisPostMayBeAnnoying: "この投稿は迷惑になる可能性があります thisPostMayBeAnnoyingHome: "ホームに投稿" thisPostMayBeAnnoyingCancel: "やめる" thisPostMayBeAnnoyingIgnore: "このまま投稿" -collapseRenotes: "見たことのあるRenoteを省略して表示" +collapseRenotes: "見たことのあるリノートを省略して表示" internalServerError: "サーバー内部エラー" internalServerErrorDescription: "サーバー内部で予期しないエラーが発生しました。" copyErrorInfo: "エラー情報をコピー" @@ -1019,7 +1025,7 @@ retryAllQueuesConfirmText: "一時的にサーバーの負荷が増大するこ enableChartsForRemoteUser: "リモートユーザーのチャートを生成" enableChartsForFederatedInstances: "リモートサーバーのチャートを生成" showClipButtonInNoteFooter: "ノートのアクションにクリップを追加" -largeNoteReactions: "ノートのリアクションを大きく表示" +reactionsDisplaySize: "リアクションの表示サイズ" noteIdOrUrl: "ノートIDまたはURL" video: "動画" videos: "動画" @@ -1032,7 +1038,7 @@ forceShowAds: "常に広告を表示する" addMemo: "メモを追加" editMemo: "メモを編集" reactionsList: "リアクション一覧" -renotesList: "Renote一覧" +renotesList: "リノート一覧" notificationDisplay: "通知の表示" leftTop: "左上" rightTop: "右上" @@ -1101,6 +1107,18 @@ forYou: "あなたへ" currentAnnouncements: "現在のお知らせ" pastAnnouncements: "過去のお知らせ" youHaveUnreadAnnouncements: "未読のお知らせがあります。" +useSecurityKey: "ブラウザまたはデバイスの指示に従って、セキュリティキーまたはパスキーを使用してください。" +replies: "返信" +renotes: "リノート" +loadReplies: "返信を見る" +loadConversation: "会話を見る" +pinnedList: "ピン留めされたリスト" +keepScreenOn: "デバイスの画面を常にオンにする" +verifiedLink: "このリンク先の所有者であることが確認されました" +notifyNotes: "投稿を通知" +unnotifyNotes: "投稿の通知を解除" +authentication: "認証" +authenticationRequiredToContinue: "続けるには認証を行ってください" _announcement: forExistingUsers: "既存ユーザーのみ" @@ -1131,6 +1149,14 @@ _initialAccountSetting: _serverRules: description: "新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。" +_serverSettings: + iconUrl: "アイコン画像のURL" + appIconDescription: "{host}がアプリとして表示される際のアイコンを指定します。" + appIconUsageExample: "例: PWAや、スマートフォンのホーム画面にブックマークとして追加された時など" + appIconStyleRecommendation: "円形もしくは角丸にクロップされる場合があるため、塗り潰された余白のある背景を持つことが推奨されます。" + appIconResolutionMustBe: "解像度は必ず{resolution}である必要があります。" + manifestJsonOverride: "manifest.jsonのオーバーライド" + _accountMigration: moveFrom: "別のアカウントからこのアカウントに移行" moveFromSub: "別のアカウントへエイリアスを作成" @@ -1386,6 +1412,9 @@ _achievements: title: "Brain Diver" description: "Brain Diverへのリンクを投稿した" flavor: "Misskey-Misskey La-Tu-Ma" + _smashTestNotificationButton: + title: "テスト過剰" + description: "通知のテストをごく短時間のうちに連続して行った" _role: new: "ロールの作成" @@ -1724,18 +1753,17 @@ _timelineTutorial: _2fa: alreadyRegistered: "既に設定は完了しています。" registerTOTP: "認証アプリの設定を開始" - passwordToTOTP: "パスワードを入力してください" step1: "まず、{a}や{b}などの認証アプリをお使いのデバイスにインストールします。" step2: "次に、表示されているQRコードをアプリでスキャンします。" step2Click: "QRコードをクリックすると、お使いの端末にインストールされている認証アプリやキーリングに登録できます。" - step2Url: "デスクトップアプリでは次のURIを入力します:" + step2Uri: "デスクトップアプリを使用する場合は次のURIを入力します" step3Title: "確認コードを入力" - step3: "アプリに表示されている確認コード(トークン)を入力して完了です。" - step4: "これからログインするときも、同じように確認コードを入力します。" + step3: "アプリに表示されている確認コード(トークン)を入力します。" + setupCompleted: "設定が完了しました" + step4: "これからログインするときも、同じようにコードを入力します。" securityKeyNotSupported: "お使いのブラウザはセキュリティキーに対応していません。" registerTOTPBeforeKey: "セキュリティキー・パスキーを登録するには、まず認証アプリの設定を行なってください。" securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキー、端末の生体認証やPINロック、パスキーといった、WebAuthn由来の鍵を登録します。" - chromePasskeyNotSupported: "Chromeのパスキーは現在サポートしていません。" registerSecurityKey: "セキュリティキー・パスキーを登録する" securityKeyName: "キーの名前を入力" tapSecurityKey: "ブラウザの指示に従い、セキュリティキーやパスキーを登録してください" @@ -1743,9 +1771,14 @@ _2fa: removeKeyConfirm: "{name}を削除しますか?" whyTOTPOnlyRenew: "セキュリティキーが登録されている場合、認証アプリの設定は解除できません。" renewTOTP: "認証アプリを再設定" - renewTOTPConfirm: "今までの認証アプリの確認コードは使用できなくなります" + renewTOTPConfirm: "今までの認証アプリの確認コードおよびバックアップコードは使用できなくなります" renewTOTPOk: "再設定する" renewTOTPCancel: "やめておく" + checkBackupCodesBeforeCloseThisWizard: "このウィザードを閉じる前に、以下のバックアップコードを確認してください。" + backupCodes: "バックアップコード" + backupCodesDescription: "認証アプリが使用できなくなった場合、以下のバックアップコードを使ってアカウントにアクセスできます。これらのコードは必ず安全な場所に保管してください。各コードは一回だけ使用できます。" + backupCodeUsedWarning: "バックアップコードが使用されました。認証アプリが使えなくなっている場合、なるべく早く認証アプリを再設定してください。" + backupCodesExhaustedWarning: "バックアップコードが全て使用されました。認証アプリを利用できない場合、これ以上アカウントにアクセスできなくなります。認証アプリを再登録してください。" _permissions: "read:account": "アカウントの情報を見る" @@ -1780,6 +1813,10 @@ _permissions: "write:gallery": "ギャラリーを操作する" "read:gallery-likes": "ギャラリーのいいねを見る" "write:gallery-likes": "ギャラリーのいいねを操作する" + "read:flash": "Playを見る" + "write:flash": "Playを操作する" + "read:flash-likes": "Playのいいねを見る" + "write:flash-likes": "Playのいいねを操作する" _auth: shareAccessTitle: "アプリへのアクセス許可" @@ -1903,6 +1940,7 @@ _profile: metadataContent: "内容" changeAvatar: "アイコン画像を変更" changeBanner: "バナー画像を変更" + verifiedLinkDescription: "内容にURLを設定すると、リンク先のWebサイトに自分のプロフィールへのリンクが含まれている場合に所有者確認済みアイコンを表示させることができます。" _exportOrImport: allNotes: "全てのノート" @@ -2030,9 +2068,14 @@ _notification: youReceivedFollowRequest: "フォローリクエストが来ました" yourFollowRequestAccepted: "フォローリクエストが承認されました" pollEnded: "アンケートの結果が出ました" + newNote: "新しい投稿" unreadAntennaNote: "アンテナ {name}" emptyPushNotificationMessage: "プッシュ通知の更新をしました" achievementEarned: "実績を獲得" + testNotification: "通知テスト" + checkNotificationBehavior: "通知の表示を確かめる" + sendTestNotification: "テスト通知を送信する" + notificationWillBeDisplayedLikeThis: "通知はこのように表示されます" _types: all: "すべて" @@ -2071,6 +2114,8 @@ _deck: introduction2: "画面の右にある + を押して、いつでもカラムを追加できます。" widgetsIntroduction: "カラムのメニューから、「ウィジェットの編集」を選択してウィジェットを追加してください" useSimpleUiForNonRootPages: "非ルートページは簡易UIで表示" + usedAsMinWidthWhenFlexible: "「幅を自動調整」が有効の場合、これが幅の最小値となります" + flexible: "幅を自動調整" _columns: main: "メイン" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index 80c810d26b3f..936f6ebb7b26 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -76,7 +76,7 @@ files: "ファイル" download: "ダウンロード" driveFileDeleteConfirm: "ファイル「{name}」をほかしてええか?このファイルを添付したノートも消えてまうで。" unfollowConfirm: "{name}のフォローを解除してもええんか?" -exportRequested: "エクスポートしてな、ってリクエストしたけど、これ多分めっちゃ時間かかるで。エクスポート終わったら「ドライブ」に突っ込んどくで。" +exportRequested: "エクスポートしてな、って言うたけど、これ多分めっちゃ時間かかるで。エクスポート終わったら「ドライブ」に突っ込んどくで。" importRequested: "インポートしてな、ってリクエストしたけど、これ多分めっちゃ時間かかるで。" lists: "リスト" noLists: "リストなんてあらへんで" @@ -156,6 +156,7 @@ addEmoji: "絵文字を追加" settingGuide: "ええ感じの設定" cacheRemoteFiles: "リモートのファイルをキャッシュする" cacheRemoteFilesDescription: "この設定を切っとったら、リモートファイルをキャッシュせんと直リンクするようになるで。サーバーの容量は節約できるけど、サムネイルを作らんなるから通信量が増えるで。" +youCanCleanRemoteFilesCache: "ファイル管理にある🗑️ボタンでキャッシュ全部ほかすで。" cacheRemoteSensitiveFiles: "リモートのセンシティブなファイルをキャッシュする" cacheRemoteSensitiveFilesDescription: "この設定を無効にすると、リモートのセンシティブなファイルはキャッシュせず直リンクするようになるで。" flagAsBot: "Botにするで" @@ -354,7 +355,6 @@ invite: "来てや" driveCapacityPerLocalAccount: "ローカルユーザーはんひとりあたりのドライブ容量" driveCapacityPerRemoteAccount: "リモートユーザーはんひとりあたりのドライブ容量" inMb: "メガバイト単位" -iconUrl: "アイコン画像のURL" bannerUrl: "バナー画像のURL" backgroundImageUrl: "背景画像のURL" basicInfo: "基本情報" @@ -414,6 +414,8 @@ totp: "認証アプリ" totpDescription: "認証アプリ使うてワンタイムパスワードを入れる" moderator: "モデレーター" moderation: "モデレーション" +moderationNote: "モデレーションノート" +addModerationNote: "モデレーションノートを追加するで" nUsersMentioned: "{n}人が投稿" securityKeyAndPasskey: "セキュリティキー・パスキー" securityKey: "セキュリティキー" @@ -680,6 +682,7 @@ createNewClip: "新しいクリップを作るで" unclip: "クリップ解除するで" confirmToUnclipAlreadyClippedNote: "このノートはすでにクリップ「{name}」に含まれとるで。ノートをこのクリップから除外しよか?" public: "パブリック" +private: "非公開" i18nInfo: "Misskeyは有志によっていろんな言語に翻訳されとるで。{link}で翻訳に協力したってやー。" manageAccessTokens: "アクセストークンの管理" accountInfo: "アカウント情報" @@ -699,7 +702,7 @@ no: "あかん" driveFilesCount: "ドライブのファイル数" driveUsage: "ドライブ使用量やで" noCrawle: "クローラーによるインデックスを拒否するで" -noCrawleDescription: "検索エンジンにあんたのユーザーページ、ノート、Pagesとかのコンテンツを登録(インデックス)せぇへんように頼むで。" +noCrawleDescription: "検索エンジンにあんたのユーザーページ、ノート、Pagesとかのコンテンツを登録(インデックス)せんように頼むで。邪魔すんねんやったら帰って〜。" lockedAccountInfo: "フォローを承認制にしとっても、ノートの公開範囲を「フォロワー」にせぇへん限り、誰でもあんたのノートを見れるで。" alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にするで" loadRawImages: "添付画像のサムネイルをオリジナル画質にするで" @@ -745,7 +748,7 @@ value: "値" createdAt: "作成した日" updatedAt: "更新日時" saveConfirm: "保存するで?" -deleteConfirm: "ホンマに削除するで?" +deleteConfirm: "ホンマにほかすで?" invalidValue: "有効な値じゃないみたいやで。" registry: "レジストリ" closeAccount: "アカウントを閉鎖する" @@ -1018,7 +1021,6 @@ retryAllQueuesConfirmText: "一時的にサーバー重なるかもしれへん enableChartsForRemoteUser: "リモートユーザーのチャートを作る" enableChartsForFederatedInstances: "リモートサーバーのチャートを作る" showClipButtonInNoteFooter: "ノートのアクションにクリップを追加" -largeNoteReactions: "ノートのツッコミを大きする" noteIdOrUrl: "ノートIDかURL" video: "動画" videos: "動画" @@ -1070,7 +1072,7 @@ later: "あとで" goToMisskey: "Misskeyへ" additionalEmojiDictionary: "絵文字の追加辞書" installed: "インストール済み" -branding: "あ" +branding: "ブランディング" enableServerMachineStats: "サーバーのマシン情報見せびらかすで" enableIdenticonGeneration: "ユーザーごとのIdenticon生成を有効にする" turnOffToImprovePerformance: "オフにしたらえらい軽うなるで。" @@ -1094,6 +1096,24 @@ expired: "期限切れ" doYouAgree: "同意するんか?" beSureToReadThisAsItIsImportant: "重要やから絶対読んでや。" iHaveReadXCarefullyAndAgree: "「{x}」の内容をよう読んで、同意するで。" +dialog: "ダイアログ" +icon: "アイコン" +forYou: "あんたへ" +currentAnnouncements: "現在のお知らせやで" +pastAnnouncements: "過去のお知らせやで" +youHaveUnreadAnnouncements: "あんたまだこのお知らせ読んどらんやろ。" +useSecurityKey: "ブラウザまたはデバイスの言う通りに、セキュリティキーまたはパスキーを使ってや。" +replies: "返事" +renotes: "Renote" +_announcement: + forExistingUsers: "もうおるユーザーのみ" + forExistingUsersDescription: "有効にすると、このお知らせ作成時点でおるユーザーにのみお知らせが表示されます。無効にすると、このお知らせ作成後にアカウントを作成したユーザーにもお知らせが表示されます。" + needConfirmationToRead: "既読にするのに確認が必要やで" + needConfirmationToReadDescription: "有効にすると、このお知らせを既読にする際に確認ダイアログが表示されます。また、一括既読操作の対象にもなりません。" + end: "お知らせを終了" + tooManyActiveAnnouncementDescription: "アクティブなお知らせが多いため、UXが低下する可能性があります。終了したお知らせはアーカイブすることを検討した方がええよ。" + readConfirmTitle: "既読にしてええんやな?" + readConfirmText: "「{title}」の内容を読み、既読にします。" _initialAccountSetting: accountCreated: "アカウント作り終わったで。" letsStartAccountSetup: "アカウントの初期設定をしよか。" @@ -1111,6 +1131,8 @@ _initialAccountSetting: laterAreYouSure: "初期設定あとでやり直すん?" _serverRules: description: "新規登録前に見せる、サーバーの簡潔なルールを設定すんで。内容は使うための決め事の要約とすることを推奨するわ。" +_serverSettings: + iconUrl: "アイコン画像のURL" _accountMigration: moveFrom: "別のアカウントからこのアカウントに引っ越す" moveFromSub: "別のアカウントへエイリアスを作る" @@ -1679,14 +1701,12 @@ _2fa: step1: "ほんなら、{a}や{b}とかの認証アプリを使っとるデバイスにインストールしてな。" step2: "次に、ここにあるQRコードをアプリでスキャンしてな~。" step2Click: "QRコードをクリックすると、今使とる端末に入っとる認証アプリとかキーリングに登録できるで。" - step2Url: "デスクトップアプリやったら次のURLを入力してや:" step3Title: "確認コードを入れてーや" step3: "アプリに表示されているトークンを入力して終わりや。" step4: "これからログインするときも、同じようにトークンを入力するんやで" securityKeyNotSupported: "今使とるブラウザはセキュリティキーに対応してへんのやってさ。" registerTOTPBeforeKey: "セキュリティキー・パスキーを登録するんやったら、まず認証アプリを設定してーな。" securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキーか端末の指紋認証やPINを使ってログインするように設定できるで。" - chromePasskeyNotSupported: "Chromeのパスキーは今んとこ対応してないねん。" registerSecurityKey: "セキュリティキー・パスキーを登録するわ" securityKeyName: "キーの名前を入れてーや" tapSecurityKey: "ブラウザが言うこと聞いて、セキュリティキーとかパスキー登録しといでや" @@ -1694,7 +1714,7 @@ _2fa: removeKeyConfirm: "{name}を消すん?" whyTOTPOnlyRenew: "セキュリティキーが登録されとったら、認証アプリの設定は解除できへんで。" renewTOTP: "認証アプリをもっかい設定" - renewTOTPConfirm: "今までの人称アプリの確認コードは使えんくなるけどええか?" + renewTOTPConfirm: "今までの認証アプリの確認コードは使えんくなるけどええか?" renewTOTPOk: "もっかい設定する" renewTOTPCancel: "やめとく" _permissions: @@ -1730,6 +1750,10 @@ _permissions: "write:gallery": "ギャラリーを操作するで" "read:gallery-likes": "ギャラリーのいいねを見るで" "write:gallery-likes": "ギャラリーのいいねを操作するで" + "read:flash": "Playを見る" + "write:flash": "Playを操作する" + "read:flash-likes": "Playのええやん!を見る" + "write:flash-likes": "Playのええやん!を見る" _auth: shareAccessTitle: "アプリへのアクセス許してやったらどうや" shareAccess: "「{name}」がアカウントにアクセスすることを許可してええか?" diff --git a/locales/jbo-EN.yml b/locales/jbo-EN.yml index ed97d539c095..d4fea291d790 100644 --- a/locales/jbo-EN.yml +++ b/locales/jbo-EN.yml @@ -1 +1,3 @@ --- +_lang_: "la .lojban." +headlineMisskey: "lo se tcana noi jorne fi loi notci" diff --git a/locales/kab-KAB.yml b/locales/kab-KAB.yml index 18fd8f5a58d1..22e24d3baac0 100644 --- a/locales/kab-KAB.yml +++ b/locales/kab-KAB.yml @@ -56,6 +56,7 @@ accounts: "Imiḍan" searchByGoogle: "Nadi" file: "Ifuyla" account: "Imiḍan" +replies: "Err" _email: _follow: title: "Yeṭṭafaṛ-ik·em-id" diff --git a/locales/kn-IN.yml b/locales/kn-IN.yml index ef66f3fbd234..b3ad46f2b16a 100644 --- a/locales/kn-IN.yml +++ b/locales/kn-IN.yml @@ -61,6 +61,7 @@ smtpPass: "ಗುಪ್ತಪದ" user: "ಬಳಕೆದಾರ" searchByGoogle: "ಹುಡುಕು" file: "ಕಡತಗಳು" +replies: "ಉತ್ತರಿಸು" _email: _follow: title: "ಹಿಂಬಾಲಿಸಿದರು" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 42a829c125b6..23996cbe9554 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -2,20 +2,20 @@ _lang_: "한국어" headlineMisskey: "노트로 연결되는 네트워크" introMisskey: "환영합니다! Misskey는 오픈 소스 분산형 마이크로 블로그 서비스입니다.\n'노트'를 작성해서 지금 일어나고 있는 일을 공유하거나, 당신만의 이야기를 모두에게 발신하세요📡\n'리액션' 기능으로 친구의 노트에 총알같이 반응을 추가할 수도 있습니다👍\n새로운 세계를 탐험해 보세요🚀" -poweredByMisskeyDescription: "{name}은(는) 오픈소스 플랫폼 Misskey를 사용한 서버 가운데 하나입니다." +poweredByMisskeyDescription: "{name}은(는) 오픈소스 플랫폼Misskey를 사용한 서비스(Misskey 인스턴스라고 불립니다) 중 하나입니다." monthAndDay: "{month}월 {day}일" search: "검색" notifications: "알림" username: "유저명" password: "비밀번호" forgotPassword: "비밀번호 재설정" -fetchingAsApObject: "연합에서 조회 중" +fetchingAsApObject: "연합에 조회 중" ok: "확인" gotIt: "알겠어요" cancel: "취소" noThankYou: "나중에" enterUsername: "유저명 입력" -renotedBy: "{user}님의 리노트" +renotedBy: "{user}님이 리노트" noNotes: "노트가 없습니다" noNotifications: "표시할 알림이 없습니다" instance: "서버" @@ -45,6 +45,7 @@ pin: "프로필에 고정" unpin: "프로필에서 고정 해제" copyContent: "내용 복사" copyLink: "링크 복사" +copyLinkRenote: "Renote 링크 복사" delete: "삭제" deleteAndEdit: "삭제 후 편집" deleteAndEditConfirm: "이 노트를 삭제한 뒤 다시 편집하시겠습니까? 이 노트에 대한 리액션, 리노트, 답글 또한 모두 삭제됩니다." @@ -155,7 +156,8 @@ emojiUrl: "이모지 URL" addEmoji: "이모지 추가" settingGuide: "추천 설정" cacheRemoteFiles: "리모트 파일을 캐시" -cacheRemoteFilesDescription: "이 설정을 해지하면 리모트 파일을 캐시하지 않고 해당 파일을 직접 링크하게 됩니다. 그에 따라 서버의 저장 공간을 절약할 수 있지만, 썸네일이 생성되지 않기 때문에 통신량이 증가합니다." +cacheRemoteFilesDescription: "이 설정을 활성화하면 리모트 파일을 이 서버의 스토리지에 캐시합니다. 미디어의 표시가 빨라지지만, 서버의 저장 용량을 크게 소모합니다. 리모트 유저의 미디어를 얼마나 보관할 지는 역할의 드라이브 용량 제한에 따라 결정되며, 정해진 용량을 넘길 경우 오래된 파일부터 차례대로 삭제한 뒤 링크로 전환합니다. \n비활성화하면 리모트 파일을 직접 링크하며, 이 경우 이미지 썸네일 생성 및 유저 프라이버시 보호를 위해 default.yml에서 proxyRemoteFiles를 true로 설정하는 것을 권장합니다." +youCanCleanRemoteFilesCache: "파일 관리 화면의 🗑️ 버튼을 눌러 모든 캐시를 삭제할 수 있습니다." cacheRemoteSensitiveFiles: "리모트의 민감한 파일을 캐시" cacheRemoteSensitiveFilesDescription: "이 설정을 비활성화하면 리모트의 민감한 파일은 캐시하지 않고 리모트에서 직접 가져오도록 합니다." flagAsBot: "나는 봇입니다" @@ -329,7 +331,7 @@ watch: "지켜보기" unwatch: "지켜보기 해제" accept: "허가" reject: "거부" -normal: "정상" +normal: "일반" instanceName: "서버 이름" instanceDescription: "서버 소개" maintainerName: "관리자 이름" @@ -354,7 +356,6 @@ invite: "초대" driveCapacityPerLocalAccount: "로컬 유저 한 명당 드라이브 용량" driveCapacityPerRemoteAccount: "리모트 유저 한 명당 드라이브 용량" inMb: "메가바이트 단위" -iconUrl: "아이콘 URL" bannerUrl: "배너 이미지 URL" backgroundImageUrl: "배경 이미지 URL" basicInfo: "기본 정보" @@ -410,6 +411,7 @@ aboutMisskey: "Misskey에 대하여" administrator: "관리자" token: "토큰" 2fa: "2단계 인증" +setupOf2fa: "2단계 인증 설정" totp: "인증 앱" totpDescription: "인증 앱을 사용하여 일회성 비밀번호 입력" moderator: "모더레이터" @@ -653,6 +655,7 @@ behavior: "동작" sample: "예시" abuseReports: "신고" reportAbuse: "신고" +reportAbuseRenote: "Renote를 신고" reportAbuseOf: "{name}을 신고하기" fillAbuseReportDescription: "신고하려는 이유를 자세히 알려주세요. 특정 게시물을 신고할 때에는 게시물의 URL도 포함해 주세요." abuseReported: "신고를 보냈습니다. 신고해 주셔서 감사합니다." @@ -680,6 +683,7 @@ createNewClip: "새 클립 만들기" unclip: "클립 해제" confirmToUnclipAlreadyClippedNote: "이 노트는 이미 \"{name}\" 클립에 포함되어 있습니다. 클립을 해제하시겠습니까?" public: "공개" +private: "비공개" i18nInfo: "Misskey는 자원봉사자들에 의해 다양한 언어로 번역되고 있습니다. {link}에서 번역에 참가할 수 있습니다." manageAccessTokens: "액세스 토큰 관리" accountInfo: "계정 정보" @@ -876,7 +880,7 @@ numberOfColumn: "한 줄에 보일 리액션의 수" searchByGoogle: "검색" instanceDefaultLightTheme: "서버 기본 라이트 테마" instanceDefaultDarkTheme: "서버 기본 다크 테마" -instanceDefaultThemeDescription: "객체 형식의 테마 코드를 입력해 주세요." +instanceDefaultThemeDescription: "객체 형식({}로 감싼 형태)의 테마 코드를 입력해 주세요." mutePeriod: "뮤트할 기간" period: "기간" indefinitely: "무기한" @@ -1018,7 +1022,7 @@ retryAllQueuesConfirmText: "일시적으로 서버의 부하가 증가할 수 enableChartsForRemoteUser: "리모트 유저의 차트를 생성" enableChartsForFederatedInstances: "리모트 서버의 차트를 생성" showClipButtonInNoteFooter: "노트 동작에 클립을 추가" -largeNoteReactions: "노트의 리액션을 크게 표시" +reactionsDisplaySize: "리액션 표시 크기" noteIdOrUrl: "노트 ID 및 URL" video: "동영상" videos: "동영상" @@ -1091,6 +1095,27 @@ usedAt: "사용 시각" unused: "사용되지 않음" used: "사용됨" expired: "만료됨" +doYouAgree: "동의하십니까?" +beSureToReadThisAsItIsImportant: "중요하므로 반드시 읽어주십시오." +iHaveReadXCarefullyAndAgree: "\"{x}\"의 내용을 읽고 동의합니다." +dialog: "다이얼로그" +icon: "아바타" +forYou: "당신에게" +currentAnnouncements: "현재 공지사항" +pastAnnouncements: "과거 공지사항" +youHaveUnreadAnnouncements: "읽지 않은 공지사항이 있습니다." +useSecurityKey: "브라우저 또는 기기의 안내에 따라 보안 키 또는 패스키를 사용해 주십시오." +replies: "답글" +renotes: "리노트" +_announcement: + forExistingUsers: "기존 유저에게만 알림" + forExistingUsersDescription: "활성화하면 이 공지사항을 게시한 시점에서 이미 가입한 유저에게만 표시합니다. 비활성화하면 게시 후에 가입한 유저에게도 표시합니다." + needConfirmationToRead: "읽음으로 표시하기 전에 확인하기" + needConfirmationToReadDescription: "활성화하면 이 공지사항을 읽음으로 표시하기 전에 확인 알림창을 띄웁니다. '모두 읽음'의 대상에서도 제외됩니다." + end: "공지에서 내리기" + tooManyActiveAnnouncementDescription: "공지사항이 너무 많을 경우, 사용자 경험에 영향을 끼칠 가능성이 있습니다. 오래된 공지사항은 아카이브하시는 것을 권장드립니다." + readConfirmTitle: "읽음으로 표시합니까?" + readConfirmText: "\"{title}\"을(를) 읽음으로 표시합니다." _initialAccountSetting: accountCreated: "계정 생성이 완료되었습니다!" letsStartAccountSetup: "계정의 초기 설정을 진행합니다." @@ -1108,6 +1133,8 @@ _initialAccountSetting: laterAreYouSure: "초기 설정을 나중에 진행하시겠습니까?" _serverRules: description: "회원 가입 이전에 간단하게 표시할 서버 규칙입니다. 이용 약관의 요약으로 구성하는 것을 추천합니다." +_serverSettings: + iconUrl: "아이콘 URL" _accountMigration: moveFrom: "다른 계정에서 이 계정으로 이사" moveFromSub: "다른 계정에 대한 별칭을 생성" @@ -1676,14 +1703,14 @@ _2fa: step1: "먼저, {a}나 {b}등의 인증 앱을 사용 중인 디바이스에 설치합니다." step2: "그 후, 표시되어 있는 QR코드를 앱으로 스캔합니다." step2Click: "QR 코드를 클릭하면 기기에 설치된 인증 앱에 등록할 수 있습니다." - step2Url: "데스크톱 앱에서는 다음 URL을 입력하세요:" + step2Uri: "데스크톱 앱을 사용하려면 다음 URI를 입력하십시오" step3Title: "인증 코드 입력" step3: "앱에 표시된 토큰을 입력하시면 완료됩니다." + setupCompleted: "설정 완료했습니다" step4: "다음 로그인부터는 토큰을 입력해야 합니다." securityKeyNotSupported: "이 브라우저는 보안 키를 지원하지 않습니다." registerTOTPBeforeKey: "보안 키 또는 패스키를 등록하려면 인증 앱을 등록하십시오." securityKeyInfo: "FIDO2를 지원하는 하드웨어 보안 키 혹은 디바이스의 지문인식이나 화면잠금 PIN을 이용해서 로그인하도록 설정할 수 있습니다." - chromePasskeyNotSupported: "현재 Chrome의 패스키는 지원되지 않습니다." registerSecurityKey: "보안 키 또는 패스키 등록" securityKeyName: "키 이름 입력" tapSecurityKey: "브라우저의 지시에 따라 보안 키 또는 패스키를 등록하여 주십시오" @@ -1694,6 +1721,11 @@ _2fa: renewTOTPConfirm: "기존에 등록되어 있던 인증 키는 사용하지 못하게 됩니다." renewTOTPOk: "재설정" renewTOTPCancel: "취소" + checkBackupCodesBeforeCloseThisWizard: "이 위자드를 닫기 전에 아래 백업 코드를 확인하십시오" + backupCodes: "백업 코드" + backupCodesDescription: "인증 앱을 사용할 수 없게 된 경우 아래 백업 코드를 사용하여 계정에 액세스 할 수 있습니다.이 코드들은 반드시 안전한 장소에 보관하십시오.각 코드는 한 번만 사용할 수 있습니다." + backupCodeUsedWarning: "백업 코드가 사용되었습니다.인증 앱을 사용할 수 없게 된 경우, 조속히 인증 앱을 다시 설정해 주십시오." + backupCodesExhaustedWarning: "백업 코드가 모두 사용되었습니다.인증 앱을 사용할 수 없는 경우 더 이상 계정에 액세스하는 것이 불가능합니다.인증 앱을 다시 등록해 주세요." _permissions: "read:account": "계정의 정보를 봅니다" "write:account": "계정의 정보를 변경합니다" @@ -1727,6 +1759,10 @@ _permissions: "write:gallery": "갤러리를 추가하거나 삭제합니다" "read:gallery-likes": "갤러리의 좋아요를 확인합니다" "write:gallery-likes": "갤러리에 좋아요를 추가하거나 취소합니다" + "read:flash": "Play를 봅니다" + "write:flash": "Play를 조작합니다" + "read:flash-likes": "Play의 좋아요를 봅니다" + "write:flash-likes": "Play의 좋아요를 조작합니다" _auth: shareAccessTitle: "어플리케이션의 접근 허가" shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?" @@ -1962,6 +1998,10 @@ _notification: unreadAntennaNote: "안테나 {name}" emptyPushNotificationMessage: "푸시 알림이 갱신되었습니다" achievementEarned: "도전 과제를 달성했습니다" + testNotification: "알림 테스트" + checkNotificationBehavior: "알림 표시를 체크하기" + sendTestNotification: "테스트 알림 보내기" + notificationWillBeDisplayedLikeThis: "알림이 이렇게 표시됩니다" _types: all: "전부" follow: "팔로잉" @@ -1997,6 +2037,8 @@ _deck: introduction2: "나중에라도 화면 우측의 + 버튼을 눌러 새 칼럼을 추가할 수 있습니다." widgetsIntroduction: "칼럼 메뉴의 \"위젯 편집\"에서 위젯을 추가해 주세요" useSimpleUiForNonRootPages: "루트 이외의 페이지로 접속한 경우 UI 간략화하기" + usedAsMinWidthWhenFlexible: "'폭 자동 조정'이 활성화된 경우 최소 폭으로 사용됩니다" + flexible: "폭 자동 조정" _columns: main: "메인" widgets: "위젯" diff --git a/locales/lo-LA.yml b/locales/lo-LA.yml index 4f73b70550e0..37251a95c8b6 100644 --- a/locales/lo-LA.yml +++ b/locales/lo-LA.yml @@ -107,7 +107,11 @@ enterEmoji: "ປ້ອນອີໂມຈິ" renote: "Renote" unrenote: "ເລີກ Renote" renoted: "ເກັບບັນທຶກໄວ້" +cantRenote: "ໂພສນີ້ບໍ່ສາມາດຖືກບັນທຶກໄວ້ຄືນໃໝ່ໄດ້" +cantReRenote: "ບໍ່ສາມາດບັນທຶກຄືນໃໝ່ໄດ້" quote: "ລວມຂໍ້ຄວາມອ້າງອີງ" +inChannelRenote: "ຊ່ອງພຽງແຕ່ Renote" +inChannelQuote: "ຊ່ອງເທົ່ານັ້ນ Quote" pinnedNote: "ບັນທຶກທີ່ປັກໝຸດໄວ້" pinned: "ປັກໝຸດໄປຫາໂປຣໄຟລ໌" you: "ເຈົ້າ" @@ -226,9 +230,12 @@ fromUrl: "ຈາກ URL" uploadFromUrl: "ອັບໂຫຼດຈາກ URL" uploadFromUrlDescription: "URL ຂອງໄຟລ໌ທີ່ທ່ານຕ້ອງການອັບໂຫລດ" uploadFromUrlRequested: "ຮ້ອງຂໍການອັບໂຫລດ" +explore: "ສຳຫຼວດ" messageRead: "ອ່ານແລ້ວ" startMessaging: "ເລີ່ມການສົນທະນາໃໝ່" nUsersRead: "ອ່ານໂດຍ {n}" +agree: "ຍອມຮັບ" +termsOfService: "ເງື່ອນໄຂການບໍລິການ" start: "ເລີ່ມຕົ້ນນຳໃຊ້ເລີຍ" home: "ໜ້າຫຼັກ" activity: "ກິດຈະກຳ" @@ -266,6 +273,7 @@ inputNewDescription: "ໃສ່ຄຳບັນຍາຍໃໝ່" inputNewFolderName: "ໃສ່ຊື່ໂຟນເດີໃໝ່" circularReferenceFolder: "ໂຟນເດີປາຍທາງແມ່ນໂຟນເດີຍ່ອຍຂອງໂຟນເດີທີ່ທ່ານຕ້ອງການຍ້າຍ" rename: "ປ່ຽນຊື່" +doNothing: "ບໍ່ສົນໃຈ" watch: "ເບິ່ງ" unwatch: "ຢຸດເບິ່ງ" accept: "ອະນຸຍາດ" @@ -294,7 +302,14 @@ enableRegistration: "ເປີດໃຊ້ການລົງທະບຽນຜ invite: "ເຊີນ" driveCapacityPerLocalAccount: "ຄວາມອາດສາມາດຂັບຕໍ່ຜູ້ໃຊ້ທ້ອງຖິ່ນ" driveCapacityPerRemoteAccount: "ໄດຣຟ໌ຄວາມອາດສາມາດຕໍ່ຜູ້ໃຊ້ທາງໄກ" +basicInfo: "ຂໍ້ມຸນເບື້ອງຕົ້ນ" pinnedNotes: "ບັນທຶກທີ່ປັກໝຸດໄວ້" +hcaptchaSiteKey: "ກະແຈໄຊທ໌" +hcaptchaSecretKey: "ກະແຈລັບ" +recaptcha: "reCAPTCHA" +enableRecaptcha: "ເປີດໃຊ້ງານລີແຄ໋ບຈາ" +recaptchaSiteKey: "ກະແຈໄຊທ໌" +recaptchaSecretKey: "ກະແຈລັບ" turnstileSiteKey: "ກະແຈໄຊທ໌" turnstileSecretKey: "ກະແຈລັບ" name: "ຊື່" @@ -302,24 +317,41 @@ userList: "ລາຍການ" about: "ກ່ຽວກັບ" aboutMisskey: "ກ່ຽວກັບ Misskey" administrator: "ຜູ້ບໍລິຫານ" +token: "ໂທເຄັນ" share: "ແບ່ງປັນ" notFound: "ບໍ່ພົບ" cacheClear: "ລຶບລ້າງແຄສ" +help: "ຊ່ວຍເຫຼືອ" +close: "ປິດ" invites: "ເຊີນ" +members: "ສະມາຊິກ" +transfer: "ໂອນຍ້າຍ" title: "ຫົວຂໍ້" text: "ຂໍ້ຄວາມ" enable: "ເປີດໃຊ້" next: "ຕໍ່ໄປ" +retype: "ເຂົ້າໄປອີກຄັ້ງ" +quoteAttached: "ວົງຢືມ" invitations: "ເຊີນ" +unavailable: "ບໍ່​ສາ​ມາດ​ໃຊ້​ໄດ້" language: "ພາສາ" +aboutX: "ກ່ຽວກັບ {x}" +emojiStyle: "ຮູບແບບອີໂມຈິ" native: "ພາ​ສາ​ແມ່" +noHistory: "​ບໍ່​ມີ​ລາຍ​ການ​ຢູ່​ບ່ອນ​ນີ້" +doing: "ກຳລັງປະມວນຜົນ..." category: "ຫມວດຫມູ່" tags: "ແທ໋ກ" createAccount: "ສ້າງບັນຊີ" existingAccount: "ທີ່ມີຢູ່" dashboard: "ໜ້າປັດ" local: "ທ້ອງຖິ່ນ" +numberOfDays: "ຈຳນວນມື້" +objectStorageBucket: "Bucket" +objectStoragePrefix: "Prefix" +objectStorageEndpoint: "Endpoint" objectStorageRegion: "ພາກ​ພື້ນ" +deleteAll: "ລຶບທັງໝົດ" sounds: "ສຽງ" sound: "ສຽງ" none: "ບໍ່ມີ" @@ -333,18 +365,42 @@ ascendingOrder: "ນ້ອຍໄປຫາໃຫຍ່" descendingOrder: "ໃຫຍ່ຫານ້ອຍ" output: "ຜົນຜະລິດ" script: "ບົດ​ຄວາມ" +menu: "ເມນູ" +rearrange: "ຈັດລຽງຄືນ" +poll: "ການພູນ" +description: "ລາຍລະອຽດ" +author: "ຜູ້ຂຽນ" +manage: "ການຈັດການ" +plugins: "ປລັ໋ກອີນ" +width: "ກວ້າງ" +height: "ຄວາມສູງ" +large: "ໃຫຍ່." +medium: "ປານກາງ" +small: "ເລັກ" +permission: "ການອະນຸຍາດ" +notificationType: "​ປະເພດການ​ແຈ້ງ​ເຕືອນ" +edit: "ແກ້ໄຂ" +email: "ອີເມວ" smtpHost: "ໂຮດສ" smtpUser: "ຊື່ຜູ້ໃຊ້" smtpPass: "ລະຫັດຜ່ານ" clearCache: "ລຶບລ້າງແຄສ" info: "ກ່ຽວກັບ" user: "ຜູ້ໃຊ້ຕ່າງໆ" +administration: "ການຈັດການ" +middle: "ປານກາງ" searchByGoogle: "ຄົ້ນຫາ" file: "ໄຟລ໌" +replies: "ຕອບ​ໄປ​ທີ" +renotes: "Renote" +_role: + _priority: + middle: "ປານກາງ" _email: _follow: title: "ໄດ້ຕິດຕາມທ່ານ" _theme: + description: "ລາຍລະອຽດ" keys: mention: "ໄດ້ກ່າວມາ" renote: "Renote" @@ -383,6 +439,7 @@ _timelines: home: "ໜ້າຫຼັກ" _play: script: "ບົດ​ຄວາມ" + summary: "ລາຍລະອຽດ" _pages: blocks: image: "ຮູບພາບ" diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml index ae2881a7d4e1..d75f80731692 100644 --- a/locales/nl-NL.yml +++ b/locales/nl-NL.yml @@ -338,7 +338,6 @@ invite: "Uitnodigen" driveCapacityPerLocalAccount: "Opslagruimte per lokale gebruiker" driveCapacityPerRemoteAccount: "Opslagruimte per externe gebruiker" inMb: "in megabytes" -iconUrl: "Pictogram URL" bannerUrl: "Banner URL" backgroundImageUrl: "URL afbeelding" basicInfo: "Basisinformatie" @@ -426,6 +425,9 @@ pushNotificationAlreadySubscribed: "Pushberichtrn al ingeschakeld" windowMaximize: "Maximaliseren" windowRestore: "Herstellen" loggedInAsBot: "Momenteel als bot ingelogd" +icon: "Avatar" +replies: "Antwoord" +renotes: "Herdelen" _email: _follow: title: "volgde jou" diff --git a/locales/no-NO.yml b/locales/no-NO.yml index ec2900527b65..35a3866b9555 100644 --- a/locales/no-NO.yml +++ b/locales/no-NO.yml @@ -461,6 +461,9 @@ videos: "Videoer" continue: "Fortsett" youFollowing: "Følger" options: "Alternativ" +icon: "Avatar" +replies: "Svar" +renotes: "Renote" _initialAccountSetting: theseSettingsCanEditLater: "Du kan endre disse innstillingene senere." _achievements: diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index 8b6b4be7d05f..065e228c0cb5 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -333,7 +333,6 @@ invite: "Zaproś" driveCapacityPerLocalAccount: "Powierzchnia dyskowa na lokalnego użytkownika" driveCapacityPerRemoteAccount: "Powierzchnia dyskowa na zdalnego użytkownika" inMb: "W megabajtach" -iconUrl: "Adres URL ikony" bannerUrl: "Adres URL banera" backgroundImageUrl: "Adres URL tła" basicInfo: "Podstawowe informacje" @@ -644,6 +643,7 @@ createNewClip: "Utwórz nowy klip" unclip: "Odczep" confirmToUnclipAlreadyClippedNote: "Ten wpis jest już częścią klipu \"{name}\". Czy chcesz ją usunąć z tego klipu?" public: "Publiczny" +private: "Prywatne" i18nInfo: "Misskey jest tłumaczone na wiele języków przez wolontariuszy. Możesz pomóc na {link}." manageAccessTokens: "Zarządzaj tokenami dostępu" accountInfo: "Informacje o koncie" @@ -870,6 +870,9 @@ like: "Polub" show: "Wyświetlanie" color: "Kolor" youFollowing: "Śledzeni" +icon: "Awatar" +replies: "Odpowiedz" +renotes: "Udostępnij" _role: priority: "Priorytet" _priority: @@ -1089,6 +1092,8 @@ _2fa: step3: "Wprowadź token podany w aplikacji, aby ukończyć konfigurację." step4: "Od teraz, przy każdej próbie logowania otrzymasz prośbę o token logowania." removeKeyConfirm: "Usunąć kopię zapasową {name}?" + renewTOTPConfirm: "Spowoduje to, że kody weryfikacyjne z poprzedniej aplikacji przestaną działać" + renewTOTPOk: "Rekonfiguruj" renewTOTPCancel: "Nie teraz" _permissions: "read:account": "Wyświetl informacje o swoim koncie" @@ -1102,8 +1107,10 @@ _permissions: "read:following": "Wyświetlanie informacji o obserwowanych" "write:following": "Obserwowanie lub cofanie obserwacji innych kont" "read:messaging": "Zobacz swoje czaty" + "write:messaging": "Tworzenie lub usuwanie wiadomości czatu" "read:mutes": "Wyświetlanie listy osób, które wyciszyłeś(-aś)" "write:mutes": "Edycja listy osób, które wyciszyłeś(-aś)" + "write:notes": "Tworzenie lub usuwanie wpisów" "read:notifications": "Wyświetlanie powiadomień" "write:notifications": "Działanie na powiadomieniach" "read:reactions": "Wyświetlanie reakcji" @@ -1119,9 +1126,23 @@ _permissions: "write:channels": "Edytuj swoje kanały" "read:gallery": "Zobacz swoją galerię" "write:gallery": "Edytuj swoją galerię" + "read:gallery-likes": "Wyświetlanie listy polubionych postów w galerii" + "write:gallery-likes": "Edytowanie listy polubionych postów w galerii" _auth: + shareAccessTitle: "Przyznawanie uprawnień aplikacji" shareAccess: "Czy chcesz autoryzować „{name}” do dostępu do tego konta?" + shareAccessAsk: "Czy na pewno chcesz zezwolić tej aplikacji na dostęp do Twojego konta?" + permission: "{name} żąda następujących uprawnień" permissionAsk: "Ta aplikacja wymaga następujących uprawnień:" + pleaseGoBack: "Proszę, wróć do aplikacji" + callback: "Powracanie do aplikacji" + denied: "Odmowa dostępu" + pleaseLogin: "Zaloguj się, aby autoryzować aplikacje." +_antennaSources: + all: "Wszystkie wpisy" + homeTimeline: "Wpisy obserwowanych użytkowników" + users: "Wpisy określonych użytkowników" + userList: "Wpisy z określonej listy użytkowników" _weekday: sunday: "Niedziela" monday: "Poniedziałek" @@ -1154,8 +1175,10 @@ _widgets: serverMetric: "Metryka serwera" aiscript: "Konsola AiScript" aichan: "Ai" + userList: "Lista użytkowników" _userList: chooseList: "Wybierz listę" + clicker: "Clicker" _cw: hide: "Ukryj" show: "Załaduj więcej" @@ -1187,10 +1210,16 @@ _visibility: public: "Publiczny" publicDescription: "Twój wpis pojawi się w publicznych osiach czasu" home: "Strona główna" + homeDescription: "Publikuj tylko na głównej osi czasu" followers: "Obserwujący" + followersDescription: "Widoczne tylko dla obserwujących" specified: "Bezpośredni" specifiedDescription: "Napisz tylko określonym użytkownikom" + disableFederationDescription: "Nie przesyłaj do innych instancji" _postForm: + replyPlaceholder: "Odpowiedz na ten wpis..." + quotePlaceholder: "Zacytuj ten wpis…" + channelPlaceholder: "Publikuj na kanale..." _placeholders: a: "Co się dzieje?" b: "Co się wydarzyło?" @@ -1212,17 +1241,29 @@ _profile: changeBanner: "Zmień baner" _exportOrImport: allNotes: "Wszystkie wpisy" + favoritedNotes: "Ulubione wpisy" followingList: "Obserwowani" muteList: "Wycisz" blockingList: "Zablokuj" userLists: "Listy" + excludeMutingUsers: "Wyklucz wyciszonych użytkowników" + excludeInactiveUsers: "Wyklucz nieaktywnych użytkowników" _charts: federation: "Federacja" apRequest: "Żądania" + usersIncDec: "Różnica w liczbie użytkowników" usersTotal: "Łącznie # użytkowników" activeUsers: "Aktywni użytkownicy" + notesIncDec: "Różnica w liczbie wpisów" + notesTotal: "Całkowita liczba wpisów" + filesIncDec: "Różnica w liczbie plików" + filesTotal: "Całkowita liczba plików" + storageUsageIncDec: "Różnica w wykorzystaniu pamięci" + storageUsageTotal: "Całkowite wykorzystanie pamięci" _instanceCharts: requests: "Żądania" + users: "Różnica w liczbie użytkowników" + notes: "Różnica w liczbie wpisów" notesTotal: "Łącznie # wpisów" ff: "Różnica w # obserwujących" ffTotal: "Łączna liczba # obserwujących" @@ -1347,5 +1388,16 @@ _deck: mentions: "Wspomnienia" direct: "Bezpośredni" _webhookSettings: + createWebhook: "Stwórz Webhook" name: "Nazwa" + secret: "Sekret" + events: "Uruchomienie Webhooka" active: "Właczono" + _events: + follow: "Po zaobserwowaniu użytkownika" + followed: "Po zostaniu zaobserwowanym" + note: "Po opublikowaniu wpisu" + reply: "Po otrzymaniu odpowiedzi" + renote: "Po udostępnieniu wpisu" + reaction: "Po otrzymaniu reakcji" + mention: "Po zostaniu wspomnianym" diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml index 18fe955eae99..826f0b45a1b7 100644 --- a/locales/pt-PT.yml +++ b/locales/pt-PT.yml @@ -4,7 +4,7 @@ headlineMisskey: "Uma rede ligada por notas" introMisskey: "Bem-vindo! O Misskey é um serviço de microblog descentralizado de código aberto.\nCrie \"notas\" para compartilhar o que está acontecendo agora ou para se expressar com todos à sua volta 📡\nVocê também pode adicionar rapidamente reações às notas de outras pessoas usando a função \"Reações\" 👍\nVamos explorar um novo mundo 🚀" poweredByMisskeyDescription: "{name} é um dos servidores da plataforma de código aberto Misskey." monthAndDay: "{day}/{month}" -search: "Buscar" +search: "Pesquisar" notifications: "Notificações" username: "Nome de usuário" password: "Senha" @@ -25,36 +25,37 @@ basicSettings: "Configurações básicas" otherSettings: "Outras configurações" openInWindow: "Abrir em um janela" profile: "Perfil" -timeline: "Timeline" +timeline: "Linha do tempo" noAccountDescription: "Este usuário não tem uma descrição." login: "Iniciar sessão" loggingIn: "Iniciando sessão…" logout: "Sair" signup: "Registrar-se" uploading: "Enviando…" -save: "Guardar" +save: "Salvar" users: "Usuários" addUser: "Adicionar usuário" favorite: "Adicionar aos favoritos" -favorites: "Adicionar aos favoritos" +favorites: "Favoritos" unfavorite: "Remover dos favoritos" favorited: "Adicionado aos favoritos." alreadyFavorited: "Já adicionado aos favoritos." cantFavorite: "Não foi possível adicionar aos favoritos." -pin: "Afixar no perfil" +pin: "Fixar no perfil" unpin: "Desafixar do perfil" copyContent: "Copiar conteúdos" copyLink: "Copiar link" +copyLinkRenote: "Copiar o link da repostagem" delete: "Excluir" deleteAndEdit: "Excluir e editar" deleteAndEditConfirm: "Deseja excluir esta nota e editá-la novamente? Todas as reações, compartilhamentos e respostas a esta nota também serão excluídas." addToList: "Adicionar a lista" addToAntenna: "Adicionar à antena" -sendMessage: "Enviar uma mensagem" +sendMessage: "Enviar mensagem" copyRSS: "Copiar RSS" copyUsername: "Copiar nome de utilizador" -copyUserId: "Copiar o ID do utilizador" -copyNoteId: "Copiar o ID da publicação" +copyUserId: "Copiar ID do utilizador" +copyNoteId: "Copiar ID da publicação" copyFileId: "Copiar o ID do arquivo" copyFolderId: "Copiar o ID da pasta" copyProfileUrl: "Copiar a URL do perfil" @@ -89,7 +90,7 @@ createList: "Criar lista" manageLists: "Gerenciar listas" error: "Erro" somethingHappened: "Ocorreu um erro" -retry: "Tentar novamente" +retry: "Tente novamente" pageLoadError: "Ocorreu um erro ao carregar a página." pageLoadErrorDescription: "Isso geralmente acontece devido ao cache do navegador ou da rede. Tente limpar o cache ou aguarde um pouco antes de tentar novamente." serverIsDead: "Não há resposta do servidor. Aguarde um momento e tente novamente." @@ -156,6 +157,7 @@ addEmoji: "Adicionar um Emoji" settingGuide: "Guia de configuração" cacheRemoteFiles: "Cache de arquivos remotos" cacheRemoteFilesDescription: "Ao desativar esta configuração, os arquivos remotos não serão mais armazenados em cache e serão vinculados diretamente. Isso economizará espaço de armazenamento no servidor, mas os thumbnails não serão gerados, o que pode aumentar o tráfego de dados." +youCanCleanRemoteFilesCache: "Pode excluir todos os caches com o botão 🗑️ de gestão de arquivos." cacheRemoteSensitiveFiles: "Fazer cache de arquivos remotos sensíveis" cacheRemoteSensitiveFilesDescription: "Desativar essa configuração faz com que arquivos remotos sensíveis sejam vinculados diretamente em vez de armazenados em cache." flagAsBot: "Marcar conta como robô" @@ -231,7 +233,7 @@ federating: "Federando" blocked: "Bloqueado" suspended: "Suspenso" all: "Todos" -subscribing: "Subscrito" +subscribing: "Inscrito" publishing: "Publicando" notResponding: "Sem resposta" instanceFollowing: "Seguir a instância" @@ -278,15 +280,15 @@ agreeBelow: "Eu concordo com o seguinte" basicNotesBeforeCreateAccount: "Observações importantes" termsOfService: "Termos de Uso" start: "começar" -home: "casa" +home: "Início" remoteUserCaution: "As informações estão incompletas porque é um utilizador remoto." activity: "atividade" images: "imagem" image: "imagem" -birthday: "aniversário" +birthday: "Aniversário" yearsOld: "{age} anos" registeredDate: "Data de registro" -location: "Lugar, colocar" +location: "Localização" theme: "tema" themeForLightMode: "Temas usados ​​no modo de luz" themeForDarkMode: "Temas usados ​​no modo escuro" @@ -295,7 +297,7 @@ dark: "Escuro" lightThemes: "Tema claro" darkThemes: "Tema escuro" syncDeviceDarkMode: "Sincronize com o modo escuro do dispositivo" -drive: "Unidades" +drive: "Drive" fileName: "Nome do Ficheiro" selectFile: "Selecione os arquivos" selectFiles: "Selecione os arquivos" @@ -354,7 +356,6 @@ invite: "Convidar" driveCapacityPerLocalAccount: "Capacidade do drive por usuário local" driveCapacityPerRemoteAccount: "Capacidade do drive por usuário remoto" inMb: "Em ‘megabytes’" -iconUrl: "URL da imagem do ícone (favicon, etc.)" bannerUrl: "URL da imagem do ‘banner’" backgroundImageUrl: "URL da imagem de fundo" basicInfo: "Informações básicas" @@ -428,7 +429,7 @@ reduceUiAnimation: "Reduzir a animação da ‘interface’ do utilizador" share: "Compartilhar" notFound: "Não encontrado" notFoundDescription: "Não havia página correspondente ao URL especificado." -uploadFolder: "Destino de ‘upload’ padrão" +uploadFolder: "Destino de upload padrão" cacheClear: "Excluir memória transitória" markAsReadAllNotifications: "Marcar todas as notificações como lidas" markAsReadAllUnreadNotes: "Marcar todas as postagens como lidas" @@ -489,7 +490,7 @@ fontSize: "Tamanho do texto" mediaListWithOneImageAppearance: "Altura da lista de mídias com apenas uma imagem" limitTo: "Até {x}" noFollowRequests: "Não há pedidos de seguidor pendentes" -openImageInNewTab: "Abrir a imagem numa nova aba" +openImageInNewTab: "Abrir a imagem em uma nova aba" dashboard: "Painel de controle" local: "Local" remote: "Remoto" @@ -601,7 +602,7 @@ useFullReactionPicker: "Usar o seletor de reações completo" width: "Largura" height: "Altura" large: "Grande" -medium: "Média" +medium: "Médio" small: "Pequeno" generateAccessToken: "Gerar token de acesso" permission: "Permissões" @@ -653,6 +654,7 @@ behavior: "Comportamento" sample: "Exemplo" abuseReports: "Denúncias" reportAbuse: "Denúncias" +reportAbuseRenote: "Reportar repostagem" reportAbuseOf: "Denunciar {name}" fillAbuseReportDescription: "Por favor, forneça detalhes sobre o motivo da denúncia. Se houver uma nota específica envolvida, inclua também a URL dela." abuseReported: "Denúncia enviada. Obrigado por sua ajuda." @@ -680,6 +682,7 @@ createNewClip: "Criar novo clipe" unclip: "Remover do clipe" confirmToUnclipAlreadyClippedNote: "Esta nota já está incluída no clipe \"{name}\". Você deseja remover a nota deste clipe?" public: "Público" +private: "Privado" i18nInfo: "Misskey é traduzido para várias línguas por voluntários. Você pode ajudar com as traduções em {link}." manageAccessTokens: "Gerenciar tokens de acesso" accountInfo: "Informações da conta" @@ -715,63 +718,263 @@ useSystemFont: "Utilizar a fonte padrão do sistema" clips: "Clipe" experimentalFeatures: "Funcionalidades Experimentais" experimental: "Experimental" +thisIsExperimentalFeature: "Este é um recurso experimental. As funções podem mudar ou pode não funcionar corretamente." +developer: "Programador" +makeExplorable: "Deixe a sua conta mais fácil de encontrar." +makeExplorableDescription: "Se você desativá-lo, outros usuários não poderão encontrar a sua conta na aba Descoberta." +showGapBetweenNotesInTimeline: "Mostrar um espaço entre as notas na linha de tempo" duplicate: "Duplicar" left: "Esquerda" +center: "Centralizar" wide: "Largo" narrow: "Estreito" +reloadToApplySetting: "As configurações serão refletidas após recarregar a página. Deseja recarregar agora?" +needReloadToApply: "É necessário recarregar a página para refletir as alterações." showTitlebar: "Exibir barra de título" clearCache: "Limpar o cache" +onlineUsersCount: "Pessoas Online" +nUsers: "Usuários" +nNotes: "Notas" +sendErrorReports: "Enviar relatórios de erro" sendErrorReportsDescription: "Ao ativar essa opção, informações detalhadas de erro serão compartilhadas com o Misskey em caso de problemas, o que pode ajudar a melhorar a qualidade do software. As informações de erro podem incluir a versão do sistema operacional, o tipo de navegador e o sua atividade no Misskey." +myTheme: "Meu tema" backgroundColor: "Cor de fundo" +accentColor: "Cor de destaque" +textColor: "Cor do texto" +saveAs: "Salvar como" advanced: "Avançado" +advancedSettings: "Configurações avançadas" value: "Valor" +createdAt: "Data de criação" +updatedAt: "Última atualização" +saveConfirm: "Deseja salvá-lo?" deleteConfirm: "Confirma a exclusão?" +invalidValue: "Valor inválido" +registry: "Registo" +closeAccount: "Encerrar conta" +currentVersion: "Versão Atual" +latestVersion: "Última versão" +youAreRunningUpToDateClient: "Você está usando a última versão do cliente" +newVersionOfClientAvailable: "Nova versão do cliente disponível" +usageAmount: "Quantidade utilizada" capacity: "Capacidade" +inUse: "Em uso" +editCode: "Editar código" apply: "Aplicar" +receiveAnnouncementFromInstance: "Receba as notificações da instância" emailNotification: "Notificações por e-mail" publish: "Publicar" +inChannelSearch: "Pesquisar no canal" +useReactionPickerForContextMenu: "Clique com o botão direito do mouse para abrir o seletor de reações." +typingUsers: "digitando" +jumpToSpecifiedDate: "Pular para uma data específica" +showingPastTimeline: "Visualizar linha de tempo anterior" +clear: "Limpar" +markAllAsRead: "Marcar todas como lidas" goBack: "Voltar" unlikeConfirm: "Deseja realmente deixar de curtir?" +fullView: "Visão completa" +quitFullView: "Sair da visualização completa" +addDescription: "Adicionar descrição" +userPagePinTip: "Notas podem ser mostradas aqui ao clicar em \"Fixar no Perfil\" no menu de notas individuais." +notSpecifiedMentionWarning: "Esta nota menciona usuários que não foram incluídos como recipientes." info: "Informações" +userInfo: "Informações do Usuário" unknown: "Desconhecido" +onlineStatus: "On-line" +hideOnlineStatus: "Ocultar o status on-line." +hideOnlineStatusDescription: "Esconder que está Ativo reduzirá a utilidade de certas funções (como, por exemplo, a Pesquisa)." +online: "Online" +active: "Ativo" +offline: "Inativo" +notRecommended: "Não recomendado" +botProtection: "Proteção contra Bot" +instanceBlocking: "Instâncias bloqueadas" +selectAccount: "Selecionar conta" +switchAccount: "Trocar conta" enabled: "Ativado" disabled: "Desativado" +quickAction: "Ações rápidas" user: "Usuários" administration: "Administrar" +accounts: "Contas" +switch: "Trocar" +noMaintainerInformationWarning: "A informação de administrador não foi configurada." +noBotProtectionWarning: "A proteção contra bots não foi configurada." +configure: "Configurar" +postToGallery: "Criar publicação em galeria" +postToHashtag: "Publicar nesta Hashtag" +gallery: "Galeria" +recentPosts: "Notas recentes" +popularPosts: "Notas populares" +shareWithNote: "Compartilhar em Notas" ads: "Anúncios" +expiration: "Data limite" +startingperiod: "Data de início" +memo: "Nota" +priority: "Prioridade" +high: "Alto" middle: "Meio" +low: "Baixo" emailNotConfiguredWarning: "Endereço de e-mail não configurado. " +ratio: "Ratio" +previewNoteText: "Visualizar Nota" +customCss: "CSS Personalizado" +customCssWarn: "Esta configuração só deve ser usada se souber o que está fazendo. Valores impróprios podem causar erros no funcionamento do cliente." +global: "Global" +squareAvatars: "Exibir ícones quadrados" sent: "Enviar" +received: "Recebido" +searchResult: "Pesquisar" +hashtags: "Hashtags" +troubleshooting: "Resolução de problemas" +useBlurEffect: "Usar efeito de desfoque na UI" +learnMore: "Saiba mais" +misskeyUpdated: "Misskey foi atualizado!" +whatIsNew: "Ver atualizações" translate: "Traduzir" +translatedFrom: "Traduzido de" +accountDeletionInProgress: "Encerramento de conta em andamento" usernameInfo: "O nome para identificar exclusivamente a sua conta no servidor. Pode conter letras (az, AZ), números (0~9) e sublinhados (_). O nome de usuário não pode ser alterado posteriormente." +aiChanMode: "Modo AI-chan" +devMode: "Modo de Desenvolvedor" +keepCw: "Manter aviso de conteúdo" +pubSub: "Publicar/Inscrever no perfil" +lastCommunication: "Ultima atualização" +resolved: "Resolvido" +unresolved: "Não resolvido" breakFollow: "Remover seguidor" breakFollowConfirm: "Deseja realmente deixar de seguir?" +itsOn: "Ativado" +itsOff: "Desativado" on: "Ligado" off: "Desligado" emailRequiredForSignup: "Tornar o endereço de e-mail obrigatório durante o cadastro" unread: "Não lido" +filter: "Filtrar" +controlPanel: "Painel de controle" +manageAccounts: "Gerenciar contas" +makeReactionsPublic: "Deixar o histórico de reações em Público" +makeReactionsPublicDescription: "Isto vai deixar o histórico de todas as suas reações visíveis para qualquer um ver." +classic: "Clássico" +muteThread: "Silenciar esta conversa" +unmuteThread: "Desativar silêncio desta conversa" +ffVisibility: "Visibilidade de Seguidos/Seguidores" +ffVisibilityDescription: "Permite configurar quem pode ver quem lhe segue e quem você está seguindo." +continueThread: "Ver mais desta conversa" deleteAccountConfirm: "Deseja realmente excluir a conta?" +incorrectPassword: "Senha inválida." +voteConfirm: "Deseja confirmar o seu voto em \"{choice}\"?" hide: "Ocultar" useDrawerReactionPickerForMobile: "Mostrar em formato de gaveta" +welcomeBackWithName: "Bem-vindo de volta, {name}" clickToFinishEmailVerification: "Clique em [{ok}] para completar a validação do endereço de e-mail." +overridedDeviceKind: "Sobrepor dispositivo" +smartphone: "Celular" +tablet: "Tablet" auto: "Automático" -searchByGoogle: "Buscar" +themeColor: "Cor do tema" +size: "Tamanho" +numberOfColumn: "Número da coluna" +searchByGoogle: "Pesquisar" +instanceDefaultLightTheme: "Tema diurno padrão para toda a instância" +instanceDefaultDarkTheme: "Tema noturno para toda a instância" +instanceDefaultThemeDescription: "Insira o código do tema em formato de objeto." +mutePeriod: "Duração de silenciamento" +period: "Data limite" +indefinitely: "Indefinitivamente" +tenMinutes: "10 minutos" +oneHour: "1 hora" +oneDay: "1 dia" +oneWeek: "1 semana" +oneMonth: "1 mês" +reflectMayTakeTime: "As mudanças podem demorar a aparecer." +failedToFetchAccountInformation: "Não foi possível obter informações da conta" +rateLimitExceeded: "Taxa limite excedido" +cropImage: "Recortar imagem" +cropImageAsk: "Deseja recortar esta imagem?" +cropYes: "Recortar" +cropNo: "Manter deste jeito" file: "Ficheiros" +recentNHours: "Últimas {n} horas" +recentNDays: "Últimos {n} dias" noEmailServerWarning: "Servidor de e-mail não configurado." +thereIsUnresolvedAbuseReportWarning: "Existem denúncias não resolvidas." +recommended: "Recomendado" +check: "Verificar" driveCapOverrideLabel: "Altere a capacidade do drive para este usuário" driveCapOverrideCaption: "Altere a capacidade para o valor padrão informando o valor 0 ou inferior." +requireAdminForView: "Para visualizar, é necessário acessar com uma conta de administrador." +isSystemAccount: "É uma conta criada e gerenciada automaticamente pelo sistema." +typeToConfirm: "Para realizar essa operação, digite {x}." deleteAccount: "Excluir conta" +document: "Documentação" +numberOfPageCache: "Número de cache de página" +numberOfPageCacheDescription: "Aumentar isso melhora a conveniência, mas também resulta em maior carga e uso de memória." +logoutConfirm: "Gostaria de encerrar a sessão?" +lastActiveDate: "Última data de uso" +statusbar: "Barra de status" +pleaseSelect: "Por favor, selecione." +reverse: "Inversão" +colored: "Colorido" +refreshInterval: "Intervalo de atualização" +type: "Tipo" +speed: "Velocidade" +slow: "Lento" +fast: "Rápido" +sensitiveMediaDetection: "Detecção de conteúdo sensível" +localOnly: "Apenas local" +remoteOnly: "Apenas remoto" +cannotUploadBecauseExceedsFileSizeLimit: "Não é possível realizar o upload deste arquivo porque ele excede o tamanho máximo permitido." +beta: "Beta" +enableAutoSensitive: "Marcar automaticamente como conteúdo sensível" enableAutoSensitiveDescription: "Quando disponível, a marcação de mídia sensível será automaticamente atribuído ao conteúdo de mídia usando aprendizado de máquina. Mesmo que você desative essa função, em alguns servidores, isso pode ser configurado automaticamente." activeEmailValidationDescription: "A validação do endereço de e-mail do usuário será realizada de forma mais rigorosa, considerando se é um endereço descartável ou se é possível realizar comunicação efetiva. Se desativado, apenas a validade do formato do endereço será verificada como uma sequência de caracteres." +shuffle: "Aleatório" +account: "Contas" +move: "Mover" +pushNotification: "Notificações Push" +subscribePushNotification: "Ativar notificações push" +unsubscribePushNotification: "Desativar notificações push" +windowMinimize: "Minimizar" +windowRestore: "Restaurar" +caption: "legenda" +tools: "Ferramentas" like: "Curtir" unlike: "Remover curtida" numberOfLikes: "Número de curtidas" show: "Visualizar" +neverShow: "Não exibir novamente" +remindMeLater: "Lembrar mais tarde" didYouLikeMisskey: "Você gostou do Misskey?" pleaseDonate: "O Misskey é um software gratuito utilizado por {host}. Para que possamos continuar o desenvolvimento, pedimos que considerem fazer doações. A sua contribuição é muito importante!" roles: "Cargos" role: "Cargo" noRole: "Nenhum cargo" +normalUser: "Usuários padrão" +undefined: "Indefinido" +assign: "Atribuir" +unassign: "Remover" +color: "Cor" +manageCustomEmojis: "Gerenciar Emojis customizados" +youCannotCreateAnymore: "Você atingiu o limite de criação." +cannotPerformTemporary: "Ação temporariamente indisponível" +cannotPerformTemporaryDescription: "Esta ação não pôde ser concluída devido ao excesso de pedidos em sucessão. Tente novamente em alguns momentos." +invalidParamError: "Parâmetros inválidos" +invalidParamErrorDescription: "Parâmetros requisitados inválidos. Isto normalmente acontece devido a um erro, mas também pode ocorrer devido à entrada de valores além do limite ou algo semelhante." +permissionDeniedError: "Operação recusada" +permissionDeniedErrorDescription: "Esta conta não tem permissão para executar esta ação." +preset: "Predefinições" +selectFromPresets: "Escolher de predefinições" +achievements: "Conquistas" +gotInvalidResponseError: "Resposta do servidor inválida" +gotInvalidResponseErrorDescription: "Servidor fora do ar ou em manutenção. Favor tentar mais tarde." +thisPostMayBeAnnoying: "Esta nota pode incomodar outras pessoas." +thisPostMayBeAnnoyingHome: "Postar na linha do tempo inicial" +thisPostMayBeAnnoyingCancel: "Cancelar" +thisPostMayBeAnnoyingIgnore: "Postar mesmo assim" +collapseRenotes: "Ocultar repostagens já visualizadas" +internalServerError: "Erro interno de servidor" emailNotSupported: "O envio de e-mails não é suportado nesta instância" likeOnly: "Apenas curtidas" likeOnlyForRemote: "Tudo (somente curtidas remotas)" @@ -780,7 +983,20 @@ rolesAssignedToMe: "Cargos atribuídos a mim" unfavoriteConfirm: "Deseja realmente remover dos favoritos?" drivecleaner: "Limpeza do drive" retryAllQueuesConfirmTitle: "Gostaria de tentar novamente agora?" +reactionsDisplaySize: "Tamanho de exibição das reações" +reactionsList: "Reações" +renotesList: "Repostagens" +leftTop: "Superior esquerdo" +rightTop: "Superior direito" +leftBottom: "Inferior esquerdo" +rightBottom: "Inferior direito" +vertical: "Vertical" +horizontal: "Exibir painel lateral inteiro" +position: "Posição" +serverRules: "Regras do servidor" +continue: "Continuar" preservedUsernamesDescription: "Liste os nomes de usuário que deseja reservar, separando-os por quebras de linha. Os nomes de usuário especificados aqui não poderão ser utilizados durante a criação de contas. No entanto, esta restrição não se aplica quando a conta é criada por um administrador. Além disso, as contas que já existem não serão afetadas." +archive: "Arquivo" channelArchiveConfirmTitle: "Deseja realmente arquivar {name}?" youFollowing: "Seguindo" preventAiLearningDescription: "Solicita-se que o conteúdo de notas e imagens enviadas não seja usado como objeto de aprendizado por sistemas externos de geração de texto ou imagens. Isso é alcançado incluindo a flag 'noai' na resposta HTML. No entanto, o cumprimento dessa solicitação depende do próprio sistema de IA, portanto, não é garantia total de prevenção de aprendizado." @@ -789,8 +1005,13 @@ rolesThatCanBeUsedThisEmojiAsReaction: "Cargos que podem utilizar este emoji com rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Se nenhum cargo for especificado, qualquer pessoa pode usar este emoji como reação." rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Estes cargos devem ser públicos." waitingForMailAuth: "Verificação de e-mail pendente " +icon: "Avatar" +replies: "Responder" +renotes: "Repostar" _initialAccountSetting: followUsers: "Siga usuários que lhe interessam para criar a sua linha do tempo." +_serverSettings: + iconUrl: "URL do ícone" _accountMigration: moveFromDescription: "Se você deseja migrar de outra conta para esta, é necessário criar um alias aqui. Por favor, insira a conta de origem da migração no seguinte formato: @username@server.example.com. Para excluir o alias, deixe o campo em branco e clique em salvar (não recomendado)." moveAccountDescription: "Você está migrando para uma nova conta.\n ・Seus seguidores irão automaticamente seguir a nova conta.\n ・Todas as suas conexões de seguidores nesta conta serão removidas.\n ・Você não poderá mais criar novas notas nesta conta.\n\nA migração dos seguidores é automática, mas a migração das pessoas que você segue deve ser feita manualmente. Antes de migrar, exporte quem você está seguindo nesta conta e, assim que migrar, importe essa lista na nova conta.\nO mesmo se aplica para listas, silenciamentos e bloqueios, que também devem ser migrados manualmente.\n\n(Esta descrição se refere ao comportamento do servidor Misskey v13.12.0 ou posterior. Outros softwares ActivityPub, como Mastodon, podem ter comportamentos diferentes.)" @@ -989,7 +1210,7 @@ _role: priority: "Prioridade" _priority: low: "Baixa" - middle: "Média" + middle: "Médio" high: "Alta" _options: gtlAvailable: "Visualizar Linha do Tempo Global" @@ -1038,9 +1259,11 @@ _emailUnavailable: mx: "O servidor de informado é inválido" smtp: "O servidor de e-mail não está respondendo" _ffVisibility: - public: "Publicar" + public: "Público" followers: "Visível apenas para seguidores" + private: "Privado" _signup: + almostThere: "Quase pronto" emailAddressInfo: "Por favor, insira o seu endereço de e-mail. Ele não será divulgado." emailSent: "Um e-mail de confirmação foi enviado para o endereço de e-mail fornecido ({email}). Acesse o link fornecido no e-mail para concluir a criação de sua conta." _accountDelete: @@ -1052,6 +1275,8 @@ _accountDelete: inProgress: "A exclusão está em andamento" _ad: back: "Voltar" + reduceFrequencyOfThisAd: "Diminuir frequência deste anúncio" + hide: "Não exibir anúncios" _forgotPassword: enterEmail: "Por favor, insira o endereço de e-mail usado no cadastro de sua conta. Um link para redefinição de senha será enviado para esse endereço." ifNoEmail: "Caso você não tenha registrado um endereço de e-mail, por favor, entre em contato com o administrador." @@ -1072,8 +1297,18 @@ _preferencesBackups: _channel: featured: "Destaques" following: "Seguindo" + usersCount: "usuários ativos" + notesCount: "notas" + nameAndDescription: "Nome e descrição" +_menuDisplay: + sideFull: "Exibir painel lateral inteiro" + top: "Exibir barra superior" + hide: "Ocultar" +_instanceMute: + instanceMuteDescription: "Todas as notas e repostagens do servidor configurado serão silenciados, incluindo respostas aos usuários do servidor mutado." _theme: description: "Descrição" + alpha: "Opacidade" deleteConstantConfirm: "Confirma a exclusão da constante {const}?" keys: mention: "Menção" @@ -1160,7 +1395,7 @@ _poll: canMultipleVote: "Permitir múltipla seleção" vote: "Votar em enquetes" _visibility: - home: "casa" + home: "Início" followers: "Seguidores" followersDescription: "Tornar visível apenas para os meus seguidores" _profile: @@ -1175,7 +1410,7 @@ _exportOrImport: _charts: federation: "União" _timelines: - home: "casa" + home: "Início" _play: new: "Criar Play" edit: "Editar Play" @@ -1205,13 +1440,14 @@ _notification: youGotMention: "{name} te mencionou" youGotReply: "{name} te respondeu" youGotQuote: "{name} te citou" + youRenoted: "Repostagens de {name}" youWereFollowed: "Você tem um novo seguidor" youReceivedFollowRequest: "Você recebeu um pedido de seguidor" yourFollowRequestAccepted: "Seu pedido de seguidor foi aceito" pollEnded: "Os resultados da enquete agora estão disponíveis" emptyPushNotificationMessage: "As notificações de alerta foram atualizadas" _types: - all: "Todos" + all: "Todas" follow: "Seguindo" mention: "Menção" reply: "Respostas" @@ -1257,3 +1493,4 @@ _webhookSettings: _events: follow: "Quando seguindo um usuário" followed: "Quando sendo seguido" + renote: "Quando repostado" diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml index 2d3099858db7..605fcc82f749 100644 --- a/locales/ro-RO.yml +++ b/locales/ro-RO.yml @@ -328,7 +328,6 @@ invite: "Invită" driveCapacityPerLocalAccount: "Capacitatea Drive-ului per utilizator local" driveCapacityPerRemoteAccount: "Capacitatea Drive-ului per utilizator extern" inMb: "În megabytes" -iconUrl: "URL-ul iconiței" bannerUrl: "URL-ul imaginii de banner" backgroundImageUrl: "URL-ul imaginii de fundal" basicInfo: "Informații de bază" @@ -630,6 +629,9 @@ sent: "Trimite" searchByGoogle: "Caută" file: "Fișiere" show: "Arată" +icon: "Avatar" +replies: "Răspunde" +renotes: "Re-notează" _role: _priority: middle: "Mediu" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index f83df32886e4..597b5f579189 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -354,7 +354,6 @@ invite: "Пригласить" driveCapacityPerLocalAccount: "Объём диска на одного локального пользователя" driveCapacityPerRemoteAccount: "Объём диска на одного пользователя с другого сайта" inMb: "В мегабайтах" -iconUrl: "Ссылка на аватар" bannerUrl: "Ссылка на изображение в шапке" backgroundImageUrl: "Ссылка на фоновое изображение" basicInfo: "Общая информация" @@ -680,6 +679,7 @@ createNewClip: "Новая подборка" unclip: "Убрать из подборки" confirmToUnclipAlreadyClippedNote: "Эта заметка уже есть в подборке «{name}». Удалить из этой подборки?" public: "Общедоступно" +private: "Показываются только вам" i18nInfo: "Misskey переводят на разные языки добровольцы со всего света. Ваша помощь тоже пригодится здесь: {link}." manageAccessTokens: "Управление токенами доступа" accountInfo: "Сведения об учётной записи" @@ -1016,23 +1016,66 @@ retryAllQueuesConfirmTitle: "Хотите попробовать ещё раз?" retryAllQueuesConfirmText: "Нагрузка на сервер может увеличиться" enableChartsForRemoteUser: "Создание диаграмм для удалённых пользователей" enableChartsForFederatedInstances: "Создание диаграмм для удалённых серверов" -largeNoteReactions: "Показывать большие реакции на заметки" noteIdOrUrl: "ID или ссылка на заметку" video: "Видео" videos: "Видео" dataSaver: "Экономия трафика" accountMigration: "Перенести учётную запись" accountMoved: "Учетная запись перенесена" +accountMovedShort: "Эта учётная запись перемещена" operationForbidden: "Эта операция невозможна." +forceShowAds: "Всегда отображать рекламу" addMemo: "Добавить заметку" editMemo: "Редактировать заметку" reactionsList: "Реакции" renotesList: "Репосты" notificationDisplay: "Отображение уведомления" leftTop: "Верхний левый угол" +rightTop: "Сверху справа" +leftBottom: "Снизу слева" +rightBottom: "Снизу справа" +vertical: "Вертикальная" horizontal: "Сбоку" +position: "Позиция" +serverRules: "Правила сервера" +pleaseConfirmBelowBeforeSignup: "Для регистрации на данном сервере, необходимо согласится с нижеследующими положениями." +pleaseAgreeAllToContinue: "Чтобы продолжить, необходимо поставить отметки во всех полях \"согласен\"." +continue: "Продолжить" +preservedUsernames: "Зарезервированные имена пользователей" +preservedUsernamesDescription: "Перечислите зарезервированные имена пользователей, отделяя их строками. Они станут недоступны при создании учётной записи. Это ограничение не применяется при создании учётной записи администраторами. Также, уже существующие учётные записи останутся без изменений." +createNoteFromTheFile: "Создать заметку из этого файла" +archive: "Архив" +channelArchiveConfirmTitle: "Переместить {name} в архив?" +channelArchiveConfirmDescription: "Архивированные каналы перестанут отображаться в списке каналов или результатах поиска. В них также нельзя будет добавлять новые записи." +displayOfNote: "Отображение заметок" +initialAccountSetting: "Настройка профиля" youFollowing: "Подписки" +preventAiLearning: "Отказаться от использования в машинном обучении (Генеративный ИИ)" options: "Настройки ролей" +specifyUser: "Указанный пользователь" +failedToPreviewUrl: "Предварительный просмотр недоступен" +update: "Обновить" +later: "Позже" +goToMisskey: "К Misskey" +additionalEmojiDictionary: "Дополнительные словари эмодзи" +installed: "Установлено" +branding: "Бренд" +expirationDate: "Дата истечения" +unused: "Неиспользуемый" +expired: "Срок действия приглашения истёк" +doYouAgree: "Согласны?" +icon: "Аватар" +replies: "Ответить" +renotes: "Репост" +_initialAccountSetting: + accountCreated: "Аккаунт успешно создан!" + letsStartAccountSetup: "Давайте настроим вашу учётную запись." + profileSetting: "Настройки профиля" + privacySetting: "Настройки конфиденциальности" + initialAccountSettingCompleted: "Первоначальная настройка успешно завершена!" + skipAreYouSure: "Пропустить настройку?" +_serverSettings: + iconUrl: "Адрес на иконку роли" _achievements: earnedAt: "Разблокировано в" _types: @@ -1569,14 +1612,12 @@ _2fa: step1: "Прежде всего, установите на устройство приложение для аутентификации, например, {a} или {b}." step2: "Далее отсканируйте отображаемый QR-код при помощи приложения." step2Click: "Нажав на QR-код, вы можете зарегистрироваться с помощью приложения для аутентификации или брелка для ключей, установленного на вашем устройстве." - step2Url: "Если пользуетесь приложением на компьютере, можете ввести в него эту строку (URL):" step3Title: "Введите проверочный код" step3: "И наконец, введите код, который покажет приложение." step4: "Теперь при каждом входе на сайт вам нужно будет вводить код из приложения аналогичным образом." securityKeyNotSupported: "Ваш браузер не поддерживает ключи безопасности." registerTOTPBeforeKey: "Чтобы зарегистрировать ключ безопасности и пароль, сначала настройте приложение аутентификации." securityKeyInfo: "Вы можете настроить вход с помощью аппаратного ключа безопасности, поддерживающего FIDO2, или отпечатка пальца или PIN-кода на устройстве." - chromePasskeyNotSupported: "В настоящее время Chrome не поддерживает пароль-ключи." registerSecurityKey: "Зарегистрируйте ключ безопасности ・Passkey" securityKeyName: "Введите имя для ключа" tapSecurityKey: "Пожалуйста, следуйте инструкциям в вашем браузере, чтобы зарегистрировать свой ключ безопасности или пароль" @@ -1899,6 +1940,7 @@ _deck: channel: "Каналы" mentions: "Упоминания" direct: "Личное" + roleTimeline: "История Ролей" _dialog: charactersExceeded: "Превышено максимальное количество символов! У вас {current} / из {max}" charactersBelow: "Это ниже минимального количества символов! У вас {current} / из {min}" @@ -1906,5 +1948,6 @@ _disabledTimeline: title: "Лента отключена" description: "Ваша текущая роль не позволяет пользоваться этой лентой." _webhookSettings: + createWebhook: "Создать вебхук" name: "Название" active: "Вкл." diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index f4c598ac832c..a5639ce59b84 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -337,7 +337,6 @@ invite: "Pozvať" driveCapacityPerLocalAccount: "Kapacita disku pre používateľa" driveCapacityPerRemoteAccount: "Kapacita disku pre vzdialeného používateľa" inMb: "V megabajtoch" -iconUrl: "Favicon URL" bannerUrl: "URL obrázku bannera" backgroundImageUrl: "URL obrázku pozadia" basicInfo: "Základné informácie" @@ -654,6 +653,7 @@ createNewClip: "Vytvoriť nový klip" unclip: "Odopnúť" confirmToUnclipAlreadyClippedNote: "Táto poznámka je už pripnutá ako \"{name}\". Naozaj ju chcete odopnúť?" public: "Verejné" +private: "Súkromné" i18nInfo: "Misskey je prekladaný do rôznych jazykov dobrovoľníkmi. Pomôcť môžete na {link}." manageAccessTokens: "Spravovať prístupové tokeny" accountInfo: "Informácie o účte" @@ -918,6 +918,9 @@ pleaseDonate: "Misskey je bezplatný softvér, ktorý používa {host}. Prosím, color: "Farba" horizontal: "Strana" youFollowing: "Sledované" +icon: "Avatar" +replies: "Odpovedať" +renotes: "Preposlať" _role: priority: "Priorita" _priority: @@ -1147,7 +1150,6 @@ _2fa: alreadyRegistered: "Už ste zaregistrovali 2-faktorové autentifikačné zariadenie." step1: "Najprv si nainštalujte autentifikačnú aplikáciu (napríklad {a} alebo {b}) na svoje zariadenie." step2: "Potom, naskenujte QR kód zobrazený na obrazovke." - step2Url: "Do aplikácie zadajte nasledujúcu URL adresu:" step3: "Nastavenie dokončíte zadaním tokenu z vašej aplikácie." step4: "Od teraz, všetky ďalšie prihlásenia budú vyžadovať prihlasovací token." securityKeyInfo: "Okrem odtlačku prsta alebo PIN autentifikácie si môžete nastaviť autentifikáciu cez hardvérový bezpečnostný kľúč podporujúci FIDO2 a tak ešte viac zabezpečiť svoj účet." diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml index 07f43afe2e7f..0c47ef68903b 100644 --- a/locales/sv-SE.yml +++ b/locales/sv-SE.yml @@ -337,7 +337,6 @@ registration: "Registrera" enableRegistration: "Aktivera registrering av nya användare" invite: "Inbjudan" inMb: "I megabyte" -iconUrl: "URL till profilbilden" bannerUrl: "URL till banner-bilden" basicInfo: "Grundläggande info" pinnedUsers: "Fästa användare" @@ -485,6 +484,9 @@ windowRestore: "Återställ" pleaseDonate: "Misskey är en gratis programvara som används på {host}. Donera gärna för att göra utvecklingen ständigt, tack!" resetPasswordConfirm: "Återställ verkligen ditt lösenord?" dataSaver: "Databesparing" +icon: "Profilbild" +replies: "Svara" +renotes: "Omnotera" _achievements: _types: _open3windows: diff --git a/locales/th-TH.yml b/locales/th-TH.yml index ddbafbe79ad7..852e341648d8 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -45,6 +45,7 @@ pin: "ปักหมุดไปยังโปรไฟล์" unpin: "เลิกปักหมุดจากโปรไฟล์" copyContent: "คัดลอกเนื้อหา" copyLink: "คัดลอกลิงก์" +copyLinkRenote: "คัดลอกลิงก์รีโน้ต" delete: "ลบ" deleteAndEdit: "ลบและแก้ไข" deleteAndEditConfirm: "นายแน่ใจแล้วเหรอ? ว่าต้องการลบโน้ตนี้และแก้ไข คุณอาจจะสูญเสียการโต้ตอบ, โน้ต, และการตอบกลับทั้งหมดได้นะ" @@ -74,7 +75,7 @@ import: "นำเข้า" export: "นำออก" files: "ไฟล์" download: "ดาวน์โหลด" -driveFileDeleteConfirm: "นายแน่ใจแล้วหรอ? ว่าต้องการลบไฟล์ \"{name}\" โน้ตย่อที่แนบมากับไฟล์นี้ก็จะถูกลบด้วยนะ" +driveFileDeleteConfirm: "คุณต้องการลบไฟล์ \"{name}\" ใช่หรือไม่? โน้ตย่อที่แนบมากับไฟล์นี้ก็จะถูกลบไปด้วย" unfollowConfirm: "นายแน่ใจแล้วหรอว่าต้องการเลิกติดตาม {name}?" exportRequested: "เมื่อคุณได้ร้องขอการส่งออก อาจจะต้องใช้เวลาสักครู่ และจะถูกเพิ่มในไดรฟ์ของคุณเมื่อเสร็จสิ้นแล้ว" importRequested: "เมื่อคุณได้ร้องขอการนำเข้า อาจจะต้องใช้เวลาสักครู่นะ" @@ -92,8 +93,8 @@ somethingHappened: "อุ๊ย ! มีอะไรบางอย่างผ retry: "ลองใหม่อีกครั้ง" pageLoadError: "เกิดข้อผิดพลาดในการโหลดหน้านี้" pageLoadErrorDescription: "โดยปกติแล้วมักจะเกิดจากข้อผิดพลาดของเครือข่ายหรือแคชของเบราว์เซอร์ ลองล้างแคชแล้วลองใหม่อีกครั้งหลังจากรอสักครู่ " -serverIsDead: "เซิร์ฟเวอร์นี้ไม่มีการตอบสนอง ได้โปรดกรุณารอสักครู่แล้วลองใหม่อีกครั้งนะ" -youShouldUpgradeClient: "หากต้องการดูหน้านี้ได้โปรดกรุณา รีเซ็ตเพื่ออัปเดตไคลเอ็นต์ของคุณนะ" +serverIsDead: "เซิร์ฟเวอร์นี้ไม่มีการตอบสนอง โปรดกรุณารอสักครู่แล้วลองใหม่อีกครั้ง" +youShouldUpgradeClient: "หากต้องการดูหน้านี้ กรุณาโหลดหน้าใหม่เพื่ออัปเดตไคลเอ็นต์ของคุณ" enterListName: "ใส่ชื่อสำหรับรายการลิสต์" privacy: "ความเป็นส่วนตัว" makeFollowManuallyApprove: "ติดตามคำขอที่ต้องได้รับการอนุมัติ" @@ -109,7 +110,7 @@ unrenote: "เลิกรีโน้ต" renoted: "รีโน้ตแล้ว" cantRenote: "โพสต์นี้ไม่สามารถรีโน้ตไว้ใหม่ได้นะ" cantReRenote: "ไม่สามารถรีโน้ตเอาไว้ใหม่ได้นะ" -quote: "อ้างคำพูด" +quote: "อ้างอิง" inChannelRenote: "รีโน้ตช่องแชลแนลเท่านั้น" inChannelQuote: "อ้างช่องเท่านั้น" pinnedNote: "โน้ตที่ปักหมุดเอาไว้" @@ -137,7 +138,7 @@ suspend: "ถูกระงับ" unsuspend: "ยกเลิกระงับ" blockConfirm: "คุณแน่ใจแล้วเหรอ? ว่าต้องการบล็อกบัญชีนี้" unblockConfirm: "คุณแน่ใจแล้วเหรอ? ว่าต้องการปลดบล็อคบัญชีนี้" -suspendConfirm: "นายแน่ใจแล้วเหรอว่าต้องการระงับบัญชีนี้อ่ะ?" +suspendConfirm: "แน่ใจว่าคุณต้องการระงับบัญชีนี้?" unsuspendConfirm: "นายแน่ใจแล้วหรอ? ว่าต้องการยกเลิกการระงับบัญชีนี้" selectList: "เลือกรายการ" editList: "แก้ไขรายการ" @@ -156,6 +157,7 @@ addEmoji: "แทรกอีโมจิ" settingGuide: "การตั้งค่าที่แนะนำ" cacheRemoteFiles: "แคชไฟล์ระยะไกล" cacheRemoteFilesDescription: "เมื่อปิดใช้งานการตั้งค่านี้ ไฟล์ระยะไกลนั้นจะถูกโหลดโดยตรงจากอินสแตนซ์ระยะไกล แต่กรณีการปิดใช้งานนี้จะช่วยลดปริมาณการใช้พื้นที่จัดเก็บข้อมูล แต่เพิ่มปริมาณการใช้งาน เพราะเนื่องจากจะไม่มีการสร้างภาพขนาดย่อ" +youCanCleanRemoteFilesCache: "คุณสามารถล้างแคชได้โดยคลิกที่ปุ่ม 🗑️ ในมุมมองการจัดการไฟล์" cacheRemoteSensitiveFiles: "ไฟล์ระยะไกลที่มีความละเอียดอ่อนแคช" cacheRemoteSensitiveFilesDescription: "เมื่อปิดการใช้งานแล้วการตั้งค่านี้ ไฟล์รีโมตที่มีความละเอียดอ่อนนั้นจะถูกโหลดโดยตรงจากอินสแตนซ์ระยะไกลโดยที่ไม่มีการแคช" flagAsBot: "ทำเครื่องหมายบอกว่าบัญชีนี้เป็นบอท" @@ -254,7 +256,7 @@ imageUrl: "url รูปภาพ" remove: "ลบ" removed: "ถูกลบไปแล้ว" removeAreYouSure: "นายแน่ใจจริงหรอว่าต้องการที่จะลบออก \"{x}\"" -deleteAreYouSure: "นายแน่ใจจริงหรอว่าต้องการที่จะลบออก \"{x}\"" +deleteAreYouSure: "ต้องการลบ {x} หรือไม่คะ?" resetAreYouSure: "รีเซ็ตเลยไหม" saved: "บันทึกแล้ว" messaging: "แชท" @@ -308,8 +310,8 @@ renameFolder: "เปลี่ยนชื่อโฟลเดอร์" deleteFolder: "ลบโฟลเดอร์" addFile: "เพิ่มไฟล์" emptyDrive: "ไดรฟ์ของคุณว่างเปล่านะ" -emptyFolder: "โฟลเดอร์นี้น่าจะว่างเปล่านะ" -unableToDelete: "ไม่สามารถลบออกได้นะ" +emptyFolder: "โฟลเดอร์นี้ว่างเปล่า" +unableToDelete: "ไม่สามารถลบออกได้" inputNewFileName: "ป้อนชื่อไฟล์ใหม่นะ" inputNewDescription: "กรุณาใส่แคปชั่นใหม่" inputNewFolderName: "กรุณาใส่ชื่อโฟลเดอร์ใหม่นะ\n" @@ -354,7 +356,6 @@ invite: "เชิญชวน" driveCapacityPerLocalAccount: "ความจุของไดรฟ์ต่อผู้ใช้ภายในเครื่อง" driveCapacityPerRemoteAccount: "ความจุของไดรฟ์ต่อผู้ใช้ระยะไกล" inMb: "เป็นเมกะไบต์" -iconUrl: "ไอคอน URL" bannerUrl: "URL รูปภาพแบนเนอร์" backgroundImageUrl: "URL ภาพพื้นหลัง" basicInfo: "ข้อมูลเบื้องต้น" @@ -363,7 +364,7 @@ pinnedUsersDescription: "ลิสต์ชื่อผู้ใช้โดย pinnedPages: "หน้าที่ปักหมุด" pinnedPagesDescription: "ป้อนเส้นทางของหน้าที่คุณต้องการตรึงไว้ที่หน้าแรกของอินสแตนซ์นี้ โดยคั่นด้วยตัวแบ่งบรรทัด" pinnedClipId: "ID ของคลิปที่จะปักหมุด" -pinnedNotes: "โน้ตที่ปักหมุดเอาไว้" +pinnedNotes: "โน้ตที่ปักหมุดไว้" hcaptcha: "hCaptcha" enableHcaptcha: "เปิดใช้ hCaptcha" hcaptchaSiteKey: "คีย์ไซต์" @@ -404,12 +405,13 @@ recentlyDiscoveredUsers: "ผู้ใช้ที่เพิ่งค้นพ exploreUsersCount: "มีผู้ใช้ {จำนวน} ราย" exploreFediverse: "สำรวจเฟดดิเวิร์ส" popularTags: "แท็กยอดนิยม" -userList: "รายการ" +userList: "ลิสต์" about: "เกี่ยวกับ" aboutMisskey: "เกี่ยวกับ Misskey" administrator: "ผู้ดูแลระบบ" token: "โทเค็น" 2fa: "การยืนยันตัวตนแบบสองชั้น" +setupOf2fa: "ตั้งค่าการยืนยันตัวตนแบบสองชั้น" totp: "แอป Authenticator" totpDescription: "ใช้แอปยืนยันตัวตนเพื่อป้อนรหัสผ่านแบบใช้ครั้งเดียว" moderator: "ผู้ควบคุม" @@ -444,7 +446,7 @@ text: "ข้อความ" enable: "เปิดใช้งาน" next: "ถัด​ไป" retype: "พิมพ์รหัสอีกครั้ง" -noteOf: "โน้ต โดย {ผู้ใช้งาน}" +noteOf: "โน้ต โดย {user}" quoteAttached: "อ้างอิง" quoteQuestion: "นายต้องการที่จะอ้างอิงหรอ?" noMessagesYet: "ยังไม่มีข้อความนะ" @@ -653,6 +655,7 @@ behavior: "พฤติกรรม" sample: "ตัวอย่าง" abuseReports: "รายงาน" reportAbuse: "รายงาน" +reportAbuseRenote: "รายงานรีโน้ต" reportAbuseOf: "รายงาน {ชื่อ}" fillAbuseReportDescription: "กรุณากรอกรายละเอียดเกี่ยวกับรายงานนี้ หากเป็นเรื่องเกี่ยวกับโน้ตโดยเฉพาะ ได้โปรดระบุ URL" abuseReported: "เราได้ส่งรายงานของคุณไปแล้ว ขอบคุณมากๆนะ" @@ -680,6 +683,7 @@ createNewClip: "สร้างคลิปใหม่" unclip: "ลบคลิป" confirmToUnclipAlreadyClippedNote: "โน้ตนี้เป็นส่วนหนึ่งของคลิป \"{name}\" แล้ว คุณต้องการลบออกจากคลิปนี้แทนอย่างงั้นหรอ?" public: "สาธารณะ" +private: "ส่วนตัว" i18nInfo: "Misskey กำลังได้รับการแปลเป็นภาษาต่างๆ โดยอาสาสมัคร คุณสามารถช่วยเหลือได้ที่ {link}" manageAccessTokens: "การจัดการโทเค็นการเข้าถึง" accountInfo: "ข้อมูลบัญชี" @@ -957,7 +961,7 @@ show: "แสดงผล" neverShow: "ไม่ต้องแสดงข้อความนี้อีก" remindMeLater: "ไว้ครั้งหน้าแล้วกัน" didYouLikeMisskey: "คุณเคยชอบ Misskey ไหม?" -pleaseDonate: "{host} ใช้ซอฟต์แวร์ฟรี Misskey เราขอขอบคุณการบริจาคของคุณอย่างสูงเพื่อให้การพัฒนา Misskey สามารถดำเนินต่อไปได้นะ!" +pleaseDonate: "Misskey เป็นซอฟต์แวร์ฟรีที่ใช้งานโดย {host} เราขอขอบคุณการสนับสนุนของคุณอย่างสูงเพื่อให้การพัฒนา Misskey สามารถดำเนินต่อไปได้!" roles: "บทบาท" role: "บทบาท" noRole: "ไม่พบบทบาท" @@ -1018,7 +1022,6 @@ retryAllQueuesConfirmText: "สิ่งนี้จะเพิ่มการ enableChartsForRemoteUser: "สร้างแผนภูมิข้อมูลผู้ใช้ระยะไกล" enableChartsForFederatedInstances: "สร้างแผนภูมิข้อมูลอินสแตนซ์ระยะไกล" showClipButtonInNoteFooter: "เพิ่ม \"คลิป\" เพื่อบันทึกเมนูการทำงาน" -largeNoteReactions: "ขยายรีแอคชั่นการแสดงผล" noteIdOrUrl: "โน้ต ID หรือ URL" video: "วีดีโอ" videos: "วีดีโอ" @@ -1094,6 +1097,20 @@ expired: "หมดอายุแล้ว" doYouAgree: "ยอมรับมั้ย?" beSureToReadThisAsItIsImportant: "กรุณาอ่านข้อมูลที่สำคัญอันนี้" iHaveReadXCarefullyAndAgree: "ฉันได้อ่านข้อความ \"{x}\" และยินยอม" +dialog: "ไดอะล็อก" +icon: "ไอคอน" +forYou: "สำหรับคุณ" +replies: "ตอบกลับ" +renotes: "รีโน้ต" +loadReplies: "แสดงการตอบกลับ" +loadConversation: "แสดงบทสนทนา" +_announcement: + forExistingUsersDescription: "การประกาศนี้จะแสดงต่อผู้ใช้ที่มีอยู่ ณ จุดที่เผยแพร่นั้นๆถ้าหากเปิดใช้งาน ถ้าหากปิดใช้งานผู้ที่กำลังสมัครใหม่หลังจากโพสต์แล้วนั้นก็จะเห็นเช่นกัน" + needConfirmationToReadDescription: "ข้อความแจ้งแยก ถ้าหากต้องการเพื่อยืนยันว่ากำลังทำเครื่องหมายประกาศนี้ว่าอ่านแล้วจะแสดงขึ้นถ้าหากเปิดใช้งาน การประกาศนั้นจะไม่รวมอยู่ในฟังก์ชั่นว่า \"ทำเครื่องหมายทั้งหมดว่าอ่านแล้ว\"" + end: "ประกาศเก็บถาวร" + tooManyActiveAnnouncementDescription: "การมีประกาศที่ใช้งานมากเกินไปนั้นอาจจะทำให้ประสบการณ์ของผู้ใช้งานนั้นดูแย่ลง โปรดกรุณาพิจารณาการเก็บประกาศที่ล้าสมัยด้วยนะค่ะ" + readConfirmTitle: "ทำเครื่องหมายบอกว่าอ่านแล้วเลยมั้ย?" + readConfirmText: "การดำเนินการนี้จะทำเครื่องหมายเนื้อหาของ \"{title}\" บอกว่าอ่านแล้วนะ" _initialAccountSetting: accountCreated: "คุณได้สร้างบัญชีของคุณสำเร็จเรียบร้อยแล้ว!" letsStartAccountSetup: "สำหรับผู้เริ่มต้นมาตั้งค่าโปรไฟล์ของคุณกันเถอะ" @@ -1105,12 +1122,14 @@ _initialAccountSetting: followUsers: "ลองติดตามผู้ใช้บางคนที่คุณอาจจะสนใจเพื่อสร้างไทม์ไลน์ของคุณสิ !" pushNotificationDescription: "กำลังเปิดใช้งานการแจ้งเตือนแบบพุชจะช่วยให้คุณได้รับการแจ้งเตือนจาก {name} โดยตรงบนอุปกรณ์ของคุณนะ" initialAccountSettingCompleted: "ตั้งค่าโปรไฟล์เสร็จสมบูรณ์แล้ว!" - haveFun: "สนุกกับ {name}!" + haveFun: "ขอให้สนุก {name}!" ifYouNeedLearnMore: "ถ้าหากคุณต้องการเรียนรู้เพิ่มเติมเกี่ยวกับวิธีใช้ {ชื่อ} (Misskey) กรุณาไปที่ {link}" skipAreYouSure: "ต้องการข้ามการตั้งค่าโปรไฟล์จริงๆแบบนั้นหรอ?" laterAreYouSure: "ต้องการตั้งค่าโปรไฟล์ในภายหลังจริงๆอย่างงั้นหรอ?" _serverRules: description: "ชุดของกฎที่จะแสดงก่อนการลงทะเบียนเราขอแนะนำให้ตั้งค่าสรุปข้อกำหนดในการให้บริการ" +_serverSettings: + iconUrl: "ไอคอน URL" _accountMigration: moveFrom: "ย้ายข้อมูลบัญชีอื่นไปยังอีกบัญชีนี้หนึ่ง" moveFromSub: "สร้างนามแฝงไปยังบัญชีอื่น" @@ -1130,8 +1149,8 @@ _achievements: earnedAt: "ได้รับเมื่อ" _types: _notes1: - title: "เพียงแค่ตั้งค่า msky ของฉัน" - description: "โพสต์โน้ตครั้งแรกของคุณ" + title: "just setting up my msky" + description: "โพสต์โน้ตแรกของคุณ" flavor: "ขอให้มีช่วงเวลาที่ดีกับ Misskey นะคะ!" _notes10: title: "โน้ตบางอย่าง" @@ -1290,7 +1309,7 @@ _achievements: _iLoveMisskey: title: "ฉันรัก Misskey" description: "โพสต์ \"I ❤ #Misskey\"" - flavor: "ทีมผู้พัฒนา Misskey ได้ขอบคุณสำหรับการสนับสนุนของคุณ!" + flavor: "ขอบคุณที่ใช้ Misskey! by ทีมผู้พัฒนา" _foundTreasure: title: "ล่าสมบัติ" description: "คุณพบสมบัติที่ซ่อนอยู่" @@ -1298,7 +1317,7 @@ _achievements: title: "พักผ่อนสักหน่อย" description: "ใช้เวลา 30 นาทีบน Misskey" _client60min: - title: "ไม่พบ \"Miss\" ใน Misskey " + title: "ไม่มี \"Miss\" ใน Misskey " description: "เปิด Misskey ค้างไว้แล้วอย่างน้อย 60 นาที" _noteDeletedWithin1min: title: "ไม่เป็นไร" @@ -1365,6 +1384,8 @@ _achievements: title: "Brain Diver" description: "โพสต์ลิงก์ไปยัง Brain Diver" flavor: "Misskey-Misskey La-Tu-Ma" + _smashTestNotificationButton: + title: "ทดสอบโอเวอร์โฟลว์" _role: new: "บทบาทใหม่" edit: "แก้ไขบทบาท" @@ -1447,7 +1468,7 @@ _sensitiveMediaDetection: _emailUnavailable: used: "ที่อยู่อีเมลนี้ได้ถูกใช้ไปแล้ว" format: "รูปแบบของที่อยู่อีเมลนี้ไม่ถูกต้อง" - disposable: "ที่อยู่อีเมลที่ใช้แล้วทิ้งนั้นไม่สามารถใช้ได้" + disposable: "ไม่สามารถใช้อีเมลชั่วคราวได้" mx: "เซิร์ฟเวอร์อีเมลนี้ไม่ถูกต้อง" smtp: "เซิร์ฟเวอร์อีเมลนี้ไม่มีการตอบสนอง" _ffVisibility: @@ -1517,9 +1538,9 @@ _aboutMisskey: contributors: "ผู้สนับสนุนหลัก" allContributors: "ผู้มีส่วนร่วมทั้งหมด" source: "ซอร์สโค้ด" - translation: "รับแปลภาษา Misskey" + translation: "แปลภาษา Misskey" donate: "บริจาคให้กับ Misskey" - morePatrons: "เราขอขอบคุณสำหรับความช่วยเหลือจากผู้ช่วยอื่นๆ ที่ไม่ได้ระบุไว้ที่นี่นะ ขอขอบคุณ! 🥰" + morePatrons: " ขอบคุณทุกท่านที่ร่วมกันช่วยเหลือตลอดมานะคะ 🥰" patrons: "สมาชิกพันธมิตร" _displayOfSensitiveMedia: respect: "ซ่อนสื่อทำเครื่องหมายบอกว่าละเอียดอ่อน" @@ -1679,14 +1700,13 @@ _2fa: step1: "ขั้นตอนแรก ติดตั้งแอปยืนยันตัวตน (เช่น {a} หรือ {b}) บนอุปกรณ์ของคุณ" step2: "จากนั้นสแกนรหัส QR ที่แสดงบนหน้าจอนี้" step2Click: "การคลิกที่รหัส QR นี้จะช่วยให้คุณนั้นสามารถลงทะเบียน 2FA กับคีย์ความปลอดภัยหรือแอปตรวจสอบความถูกต้องของโทรศัพท์ได้" - step2Url: "คุณยังสามารถป้อนบน URL นี้หากคุณใช้โปรแกรมเดสก์ท็อป:" step3Title: "ป้อนรหัสยืนยัน" step3: "ป้อนโทเค็นที่แอปของคุณให้มาเพื่อเสร็จสิ้นการตั้งค่า" + setupCompleted: "ตั้งค่าสำเร็จแล้ว" step4: "นับจากนี้เป็นต้นไปการพยายามเข้าสู่ระบบในอนาคตนั้น อาจจะต้องขอโทเค็นในการเข้าสู่ระบบดังกล่าว" securityKeyNotSupported: "เบราว์เซอร์ของคุณไม่รองรับคีย์ความปลอดภัยนะ" registerTOTPBeforeKey: "กรุณาตั้งค่าแอปยืนยันตัวตนเพื่อลงทะเบียนรหัสความปลอดภัยหรือรหัสผ่าน" securityKeyInfo: "นอกจากนี้การตรวจสอบความถูกต้องด้วยลายนิ้วมือหรือ PIN แล้ว คุณยังสามารถตั้งค่าการตรวจสอบสิทธิ์ผ่านคีย์ความปลอดภัยของฮาร์ดแวร์ที่รองรับ FIDO2 เพื่อเพิ่มความปลอดภัยให้กับบัญชีของคุณ" - chromePasskeyNotSupported: "ขณะนี้ยังไม่รองรับรหัสผ่านของ Chrome" registerSecurityKey: "ลงทะเบียนรหัสความปลอดภัยหรือรหัสผ่าน" securityKeyName: "ป้อนชื่อคีย์" tapSecurityKey: "กรุณาทำตามเบราว์เซอร์ของคุณเพื่อลงทะเบียนรหัสความปลอดภัยหรือรหัสผ่าน" @@ -1697,6 +1717,7 @@ _2fa: renewTOTPConfirm: "วิธีการแบบนี้จะทําให้รหัสยืนยันจากแอพก่อนหน้าของคุณหยุดทํางานเลยนะ" renewTOTPOk: "ตั้งค่าคอนฟิกใหม่" renewTOTPCancel: "ไม่เป็นไร" + backupCodes: "รหัสสำรองข้อมูล" _permissions: "read:account": "ดูข้อมูลบัญชีของคุณ" "write:account": "แก้ไขข้อมูลบัญชีของคุณ" @@ -1730,6 +1751,10 @@ _permissions: "write:gallery": "แก้ไขแกลเลอรี่ของคุณ" "read:gallery-likes": "ดูรายการโพสต์ในแกลเลอรีที่ชอบของคุณ" "write:gallery-likes": "แก้ไขรายการโพสต์ในแกลเลอรีที่ชอบของคุณ" + "read:flash": "วิว เพลย์" + "write:flash": "แก้ไขเพลย์" + "read:flash-likes": "ดูรายชื่อของไลค์ เพลย์" + "write:flash-likes": "แก้ไขรายชื่อของไลค์ เพลย์" _auth: shareAccessTitle: "การให้สิทธิ์แอปพลิเคชัน" shareAccess: "คุณต้องการอนุญาตให้ \"{name}\" เข้าถึงบัญชีนี้เลยมั้ย?" @@ -1768,7 +1793,7 @@ _widgets: photos: "รูปภาพ" digitalClock: "นาฬิกาดิจิตอล" unixClock: "นาฬิกา UNIX" - federation: "สหพันธ์" + federation: "Fediration" instanceCloud: "อินสแตนซ์คลาวด์" postForm: "แบบฟอร์มการโพสต์" slideshow: "แสดงภาพนิ่ง" @@ -1778,7 +1803,7 @@ _widgets: serverMetric: "ตัวชี้วัดเซิร์ฟเวอร์" aiscript: "AiScript คอนโซล" aiscriptApp: "AiScript แอพ" - aichan: "เอไอ" + aichan: "ไอ" userList: "รายชื่อผู้ใช้" _userList: chooseList: "เลือกรายการ" @@ -1965,6 +1990,10 @@ _notification: unreadAntennaNote: "เสาอากาศ {name}" emptyPushNotificationMessage: "การแจ้งเตือนแบบพุชได้รับการอัพเดทแล้ว" achievementEarned: "รับความสำเร็จ" + testNotification: "ทดสอบการแจ้งเตือน" + checkNotificationBehavior: "ตรวจสอบลักษณะที่ปรากฏการแจ้งเตือน" + sendTestNotification: "ส่งทดสอบการแจ้งเตือน" + notificationWillBeDisplayedLikeThis: "การแจ้งเตือนมีลักษณะแบบนี้" _types: all: "ทั้งหมด" follow: "กำลังติดตาม" @@ -2000,6 +2029,8 @@ _deck: introduction2: "คลิกที่เครื่องหมาย + ทางขวาของหน้าจอเพื่อเพิ่มคอลัมน์ใหม่ทุกครั้งที่คุณต้องการ" widgetsIntroduction: "กรุณาเลือก \"แก้ไขวิดเจ็ต\" ในเมนูคอลัมน์และเพิ่มวิดเจ็ต" useSimpleUiForNonRootPages: "แสดง UI ของ Root Page อย่างง่าย " + usedAsMinWidthWhenFlexible: "ความกว้างขั้นต่ำนั้นจะถูกใช้งานสำหรับสิ่งนี้เมื่อเปิดใช้งานตัวเลือก \"ปรับความกว้างอัตโนมัติ\" หากเลือกเปิดใช้งานแล้ว" + flexible: "ปรับความกว้างอัตโนมัติ" _columns: main: "หลัก" widgets: "วิดเจ็ต" diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml index 42cc7da3f898..f8fb275eb9be 100644 --- a/locales/tr-TR.yml +++ b/locales/tr-TR.yml @@ -259,6 +259,7 @@ messaging: "Mesajlar" upload: "Yükle" keepOriginalUploading: "Orijinal görseli koru" keepOriginalUploadingDescription: "Orijinal olarak yüklenen görüntüyü olduğu gibi kaydeder. Kapatılırsa, yükleme sırasında web'de görüntülenecek bir sürüm oluşturulur." +fromDrive: "Drive Dosyasından" fromUrl: "Bağlantıdan" uploadFromUrl: "Bağlantıdan yükle" uploadFromUrlDescription: "Yüklemek istediğiniz dosyanın bağlantısı" @@ -305,12 +306,42 @@ renameFolder: "Klasörü Yeniden Adlandır" deleteFolder: "Klasörü sil" addFile: "Dosya ekle" emptyDrive: "Sürücü boş" +emptyFolder: "Bu klasör boş" +unableToDelete: "Silme mümkün değil" +inputNewFileName: "Yeni dosya ismini girin" +inputNewDescription: "Yeni bir başlık gir" +inputNewFolderName: "Yeni klasör ismini girin" +circularReferenceFolder: "Hedef klasör taşınan klasörün bir alt klasörü." hasChildFilesOrFolders: "Klasör boş olmadığından silinemiyor" +copyUrl: "URL'yi kopyala" +rename: "Yeniden adlandır" +avatar: "Avatar" +banner: "Banner" +displayOfSensitiveMedia: "Hassas içerik gösterimi" +whenServerDisconnected: "Sunucu bağlantısı kesildiğinde" +disconnectedFromServer: "Sunucu bağlantısı koptu" +reload: "Yenile" doNothing: "Bir şey yapma" reloadConfirm: "Zaman akışı yenilensin mi?" +watch: "İzle" +unwatch: "İzlemeyi bırak" +accept: "Kabul et" +reject: "Reddet" +normal: "Normal" +instanceName: "Sunucu ismi" +instanceDescription: "Sunucu açıklaması" maintainerName: "Yönetici ismi" +maintainerEmail: "Yöneticinin e-postası" +tosUrl: "Hizmet Koşulları Bağlantısı" +thisYear: "Bu yıl" +thisMonth: "Bu ay" +today: "Bugün" monthX: "{month} ay" +pages: "Sayfalar" +integration: "Entegrasyon" enableRegistration: "Kayıtlara izin ver" +basicInfo: "Temel bilgiler" +pinnedUsers: "Sabitlenmiş kullanıcılar" pinnedNotes: "Sabitlenen" manageAntennas: "Anten ayarları" userList: "Listeler" @@ -322,6 +353,7 @@ smtpHost: "Sağlayıcı" smtpUser: "Kullanıcı Adı" smtpPass: "Şifre" notificationSetting: "Bildirim ayarları" +instanceTicker: "Notların sunucu bilgileri" noCrawleDescription: "Arama motorlarından profilinde, notlarında, sayfalarında vb. dolaşılmamasını ve dizine eklememesini talep et." clearCache: "Ön belleği temizle" onlineUsersCount: "{n} kullanıcı çevrim içi" @@ -338,6 +370,9 @@ pushNotificationNotSupported: "Push bildirimleri sunucu veya tarayıcı tarafın noRole: "Rol bulunamadı" color: "Renk" addMemo: "Kısa not ekle" +icon: "Avatar" +replies: "yanıt" +renotes: "vazgeçme" _accountDelete: started: "Silme işlemi başlatıldı" _email: diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 1ac07ff9b202..777933bf53e8 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -20,6 +20,7 @@ noNotes: "Немає нотаток" noNotifications: "Немає сповіщень" instance: "Інстанс" settings: "Налаштування" +notificationSettings: "Параметри сповіщень" basicSettings: "Основні налаштування" otherSettings: "Інші налаштування" openInWindow: "Відкрити у вікні" @@ -48,9 +49,12 @@ delete: "Видалити" deleteAndEdit: "Видалити й редагувати" deleteAndEditConfirm: "Ви впевнені, що хочете видалити цю нотатку та відредагувати її? Ви втратите всі реакції, поширення та відповіді на неї." addToList: "Додати до списку" +addToAntenna: "Додати в антени" sendMessage: "Надіслати повідомлення" copyRSS: "Скопіювати RSS" copyUsername: "Скопіювати ім’я користувача" +copyUserId: "Копіювати ID користувача" +copyNoteId: "блокнот ID користувача" searchUser: "Пошук користувачів" reply: "Відповісти" loadMore: "Показати більше" @@ -334,7 +338,6 @@ invite: "Запросити" driveCapacityPerLocalAccount: "Об'єм диска на одного локального користувача" driveCapacityPerRemoteAccount: "Об'єм диска на одного віддаленого користувача" inMb: "В мегабайтах" -iconUrl: "URL аватара" bannerUrl: "URL банера" backgroundImageUrl: "URL-адреса фонового зображення" basicInfo: "Основна інформація" @@ -644,6 +647,7 @@ createNewClip: "Створити нотатку" unclip: "Незакріплений" confirmToUnclipAlreadyClippedNote: "Ця нотатка вже включена до кліпу \"{name}\". Ви хочете виключити нотатку з цього кліпу?" public: "Публічний" +private: "Приватне" i18nInfo: "Misskey перекладається на різні мови волонтерами. Ви можете допомогти: {link}" manageAccessTokens: "Керування токенами доступу" accountInfo: "Інформація про акаунт" @@ -900,6 +904,9 @@ exploreOtherServers: "Знайти інший сервер" letsLookAtTimeline: "Перегляд історії" horizontal: "Збоку" youFollowing: "Підписки" +icon: "Аватар" +replies: "Відповісти" +renotes: "Поширити" _achievements: earnedAt: "Відкрито" _types: @@ -1331,7 +1338,6 @@ _2fa: alreadyRegistered: "Двофакторна автентифікація вже налаштована." step1: "Спершу встановіть на свій пристрій програму автентифікації (наприклад {a} або {b})." step2: "Потім відскануйте QR-код, який відображається на цьому екрані." - step2Url: "Ви також можете ввести цю URL-адресу, якщо використовуєте програму для ПК:" step3: "Щоб завершити налаштування, введіть токен, наданий вашою програмою." step4: "Відтепер будь-які майбутні спроби входу вимагатимуть такого токена." renewTOTPCancel: "Не зараз" diff --git a/locales/uz-UZ.yml b/locales/uz-UZ.yml index 6eb5af78cfc2..8dbcbd9d0967 100644 --- a/locales/uz-UZ.yml +++ b/locales/uz-UZ.yml @@ -43,8 +43,8 @@ alreadyFavorited: "allaqachon sevimlilar orasida" cantFavorite: "sevimlilarga qo'shib bo'lmadi" pin: "Profilga qadab qo'yish" unpin: "Profildan olib tashlash" -copyContent: "kontentni nusxalash" -copyLink: "Havolani nuxalash" +copyContent: "Tarkibini nusxalash" +copyLink: "Havolani nusxalash" delete: "O'chirib tashlash" deleteAndEdit: "O'chirish va tahrirlash" deleteAndEditConfirm: "O'chirib, tahrirlamoqchiligingizga ishonchingiz komilmi? Siz bu qaydga tegishli barcha reaktsiyalar va javoblarni yo'qotasiz." @@ -156,6 +156,7 @@ addEmoji: "Emoji qo'shish" settingGuide: "Tavsiya qilingan sozlamalar" cacheRemoteFiles: "Tashqi fayllarni keshlash" cacheRemoteFilesDescription: "Ushbu sozlama o'chirilgan bo'lsa tashqi fayllar bevosita tashqi serverdan yuklanadi. Buni o'chirish ombor ishlatilishini kamaytiradi, lekin traffikni ko'paytiradi, chunki eskizlar generatsiya qilinmaydi." +youCanCleanRemoteFilesCache: "Fayl menejeridagi 🗑️ tugmasi yordamida barcha keshlarni oʻchirib tashlashingiz mumkin." cacheRemoteSensitiveFiles: "Tashqi fayllarni keshlash" cacheRemoteSensitiveFilesDescription: "Bu sozlama oʻchiq boʻlsa, \"barcha ko'rishi mumkin bo'lmagan\" fayllar keshlashsiz toʻgʻridan-toʻgʻri masofaviy serverdan yuklanadi." flagAsBot: "Ushbu akkauntni bot sifatida belgilash" @@ -246,6 +247,7 @@ newPasswordRetype: "Yangi parolni boshqatdan tering" attachFile: "Fayl biriktirish" more: "Ko'proq!" featured: "ta'kidlash" +usernameOrUserId: "Foydalanuvchi nomi yoki identifikatori" noSuchUser: "Foydalanuvchi topilmadi" lookup: "So'rov" announcements: "Bildirishnomalar" @@ -259,7 +261,10 @@ saved: "Saqlandi" messaging: "Suhbat" upload: "Yuklash" keepOriginalUploading: "Asl rasmni saqlang" +keepOriginalUploadingDescription: "Rasmlarni yuklashda asl nusxasini saqlaydi. Agar o'chirilgan bo'lsa, brauzer yuklangandan keyin nashr qilish uchun rasm yaratadi." +fromDrive: "Drive orqali" fromUrl: "URL dan" +uploadFromUrl: "URL orqali yuklash" uploadFromUrlDescription: "Yuklamoqchi bo'lgan faylingizga havola" uploadFromUrlRequested: "yuklab olish so'ralgan" uploadFromUrlMayTakeTime: "Yuklash tugallanishi uchun biroz vaqt ketishi mumkin." @@ -275,6 +280,7 @@ basicNotesBeforeCreateAccount: "Muhim qaydlar" termsOfService: "Foydalanish shartlari" start: "Boshlash" home: "Bosh sahifa" +remoteUserCaution: "Bu foydalanuvchi uzoqda bo'lganligi sababli, ko'rsatilgan ma'lumotlar to'liq bo'lmasligi mumkin." activity: "Faollik" images: "Rasmlar" image: "Rasm" @@ -308,11 +314,13 @@ unableToDelete: "O'chirilmadi" inputNewFileName: "Yangi fayl nomini kiriting" inputNewDescription: "Iltimos, yangi sarlavha kiriting." inputNewFolderName: "Yangi papka nomini kiriting" +circularReferenceFolder: "Belgilangan papka siz ko'chirmoqchi bo'lgan jildning pastki jildidir." hasChildFilesOrFolders: "Bu papka boʻsh emas va uni oʻchirib boʻlmaydi." copyUrl: "Bog'lamadan nusxa olish" rename: "Qayta nomlash" avatar: "Avatar" banner: "Banner" +displayOfSensitiveMedia: "Nozik kontentni ko'rish" whenServerDisconnected: "server bilan aloqa uzilganda" disconnectedFromServer: "Server bilan ulanish uzulib qoldi" reload: "Yangilash" @@ -340,15 +348,20 @@ connectService: "Ulash" disconnectService: "Uzish" enableLocalTimeline: "Mahalliy vaqt mintaqasini yoqing" enableGlobalTimeline: "Global vaqt mintaqasini yoqing" +disablingTimelinesInfo: "Administratorlar va Moderatorlar har doim barcha vaqt jadvallariga kirish huquqiga ega bo'ladilar, hatto ular yoqilmagan bo'lsa ham." registration: "Ro'yxatdan o'tish" enableRegistration: "Ro'yxatdan o'tishni yoqing" invite: "Taklif qilish" +driveCapacityPerLocalAccount: "Har bir mahalliy foydalanuvchi uchun disk maydoni" +driveCapacityPerRemoteAccount: "Har bir masofaviy foydalanuvchi uchun disk maydoni" inMb: "Megabaytlarda" -iconUrl: "Ikonkaning URL manzili (masalan: favicon)" +bannerUrl: "Banner URLi" backgroundImageUrl: "Fon rasmi URL manzili" basicInfo: "Asosiy ma'lumot" pinnedUsers: "Qadalgan foydalanuvchilar" +pinnedUsersDescription: "Har bir qatorga bitta foydalanuvchi nomini kiriting. Bu yerda sanab oʻtilgan foydalanuvchilar “Oʻrganish” yorligʻiga bogʻlanadi." pinnedPages: "Qadalgan Sahifalar" +pinnedClipId: "Qadalgan xabar IDsi" pinnedNotes: "Qadalgan qayd" hcaptcha: "hCaptcha" enableHcaptcha: "hCaptchani yoqish" @@ -358,19 +371,37 @@ recaptcha: "reCAPTCHA" enableRecaptcha: "reCAPTCHA ni yoqish" recaptchaSiteKey: "Sayt kaliti" recaptchaSecretKey: "Maxfiy kalit" +turnstile: "Turniket" +enableTurnstile: "Turniketni yoqish" turnstileSiteKey: "Sayt kaliti" turnstileSecretKey: "Maxfiy kalit" +avoidMultiCaptchaConfirm: "\nBir nechta Captcha tizimlaridan foydalanish ular o'rtasida noqulaylik olib kelishi mumkin. Hozirda faol bo'lgan boshqa Captcha tizimlarini o'chirib qo'ymoqchimisiz? Agar siz ularning faol bo'lishini istasangiz, bekor qilish tugmasini bosing." antennas: "Antennalar" manageAntennas: "Antennalarni boshqarish" name: "Ism" antennaSource: "Antenna manbai" antennaKeywords: "Kalit so'zni qabul qilish" +antennaExcludeKeywords: "Istisno qilingan kalit so'zlar" +antennaKeywordsDescription: "VA sharti uchun bo'shliqlar bilan yoki YOKI sharti uchun qator uzilishlari bilan ajrating." notifyAntenna: "Yangi qaydlar haqida menga xabar bering" +withFileAntenna: "Faqatgina fayli bor qaydlar" +enableServiceworker: "Bildirish nomalarni olish" +antennaUsersDescription: "Har bir foydalunvchi nomini alohida qatorga yozing" +caseSensitive: "Katta-kichik harfni farqlash" +withReplies: "Javob yo'llash" connectedTo: "Quyidagi akkountlarga ulangan" -silence: "Sukunat" +notesAndReplies: "Qaydlar va javoblar" +withFiles: "Fayllar" +silence: "Jim qilish" +silenceConfirm: "Rostdan ham ushbu foydalanuvchini jim qilmoqchimisiz?" +unsilence: "Jim qilishni bekor qilish" +unsilenceConfirm: "Rostdan ham ushbu foydalanuvchini ovozsiz \nqilmoqchimisiz?" popularUsers: "Mashhur foydalanuvchilar." +recentlyUpdatedUsers: "Yaqinda ro'yxatdan o'tgan foydalanuvchilar" recentlyRegisteredUsers: "Yaqinda ro'yxatdan o'tgan foydalanuvchilar" +recentlyDiscoveredUsers: "Yangi foydalanuvchilar" exploreUsersCount: "{count} ta foydalanuvchi bor" +exploreFediverse: "Fediversni ko'rib chiqing" popularTags: "Ommabop teglar" userList: "Ro'yxatlar" about: "Haqida" @@ -381,12 +412,25 @@ token: "Tasdiqlash" totp: "Autentifikatsiya ilovasi" totpDescription: "Bir martalik parollarni kiritish uchun autentifikatsiya ilovasidan foydalaning" moderator: "Moderator" +moderation: "Moderatsiya" nUsersMentioned: "{n} tomonidan chop etilgan" +securityKeyAndPasskey: "Xavfsizlik kaliti va maxfiy so'z" +securityKey: "Xavfsizlik kaliti" +lastUsed: "Oxirgi marta foydalanilgan" +lastUsedAt: "Oxirgi marta {t} da foydalanilgan" +unregister: "ro'yxatdan chiqarish" +passwordLessLogin: "Parolsiz kirshni sozlash" +passwordLessLoginDescription: "Parolsiz kirish" resetPassword: "Parolni tiklash" +newPasswordIs: "Yangi parolingiz {password}" +reduceUiAnimation: "Interfeysdagi animatsiyani kamaytirish" share: "Yuborish" notFound: "Topilmadi" +notFoundDescription: "Ushbu sahifa topilmadi" uploadFolder: "Jildni yuklash" cacheClear: "Keshni tozalash" +markAsReadAllNotifications: "Bildirishnomalarni o'qilgan deb belgilash" +markAsReadAllUnreadNotes: "Barch xabarlarni oq'ilgan deb belgilash" markAsReadAllTalkMessages: "Barcha suhbatlarni o'qilgan deb belgilang" help: "Yordam" inputMessageHere: "Xabar kiriting" @@ -399,6 +443,11 @@ text: "Matn" enable: "Yoqish" next: "Keyingisi" retype: "Qayta kiriting" +noteOf: "{user} tomonidan joylandi\n" +quoteAttached: "Iqtibos" +quoteQuestion: "Iqtibos sifatida qo'shilsinmi?" +noMessagesYet: "Bu yerda xabarlar yo'q" +newMessageExists: "Yangi xabarlar bor" onlyOneFileCanBeAttached: "Faqat bitta faylni biriktirish mumkin" signinRequired: "Davom etishdan oldin ro'yhatdan o'tishingiz yoki tizimga kirishingiz kerak" invitations: "Taklif qilish" @@ -418,10 +467,16 @@ signinWith: "{x} bilan tizimga kirish" signinFailed: "Tizimga kirishda xatolik yuz berdi. Iltimos, foydalanuvchi nomingiz va parolingizni tekshiring." or: "yoki" language: "til" +uiLanguage: "Interfeys tili" aboutX: "{x} haqida" +emojiStyle: "Emoji ko'rinishi" +native: "Mahalliy" +disableDrawer: "Slayd menyusidan foydalanmang" showNoteActionsOnlyHover: "Eslatma amallarini faqat sichqonchani olib borganda ko‘rsatish" noHistory: "Tarix yo'q" signinHistory: "kirish tarixi" +enableAdvancedMfm: "MFMni faollashtirish" +doing: "Bajarilmoqda..." category: "kategoriya" tags: "teg" docSource: "Ushbu hujjatning manbasi" @@ -431,8 +486,10 @@ regenerate: "regeneratsiya" fontSize: "shrift hajmi" limitTo: "{x} gacha" noFollowRequests: "obuna uchun so'rov yo'q" +openImageInNewTab: "Rasmni boshqa oynada ochish" dashboard: "Boshqaruv paneli" local: "Mahalliy" +remote: "masofaviy" total: "Jami" weekOverWeekChanges: "Oxirgi haftadagi o'zgarishlar" dayOverDayChanges: "Kecha bo'lgan o'zgarishlar" @@ -444,18 +501,56 @@ promote: "targ'ib qilish" numberOfDays: "kunlar soni" hideThisNote: "bu eslatmani yashiring" showFeaturedNotesInTimeline: "Tanlangan qaydlarni Timelineda ko'rsatish" +objectStorage: "ob'ektni saqlash" +useObjectStorage: "Ob'ektni saqlashdan foydalaning" objectStorageBaseUrl: "Asosiy URL" +objectStorageBaseUrlDesc: "Malumot va foydalanish uchun URL. Agar siz CDN yoki proksi-serverdan foydalanayotgan bo'lsangiz, URL manzili, S3: 'https://.s3.amazonaws.com', GCS va boshqalar: 'https://storage.googleapis.com/'." +objectStorageBucket: "Bucket" +objectStorageBucketDesc: "Iltimos, foydalaniladigan xizmatning bucket nomini belgilang." +objectStoragePrefix: "Prefix" +objectStorageEndpoint: "Endpoint" objectStorageRegion: "Mintaqa" +objectStorageRegionDesc: "'xx-east-1' kabi mintaqani belgilang. Agar xizmatingizda mintaqa tushunchasi bo'lmasa, `us-east-1` dan foydalaning. AWS konfiguratsiya fayllari yoki muhit oʻzgaruvchilariga havola qilishda boʻsh qoldiring." objectStorageUseSSL: "SSL dan foydalaning" +objectStorageUseSSLDesc: "API ulanishlari uchun https dan foydalanmasangiz, belgini olib tashlang" +objectStorageUseProxy: "Proksi-serverdan foydalaning" +objectStorageUseProxyDesc: "Proksi-serverdan foydalanishni xohlamasangiz, uni o'chiring" +objectStorageSetPublicRead: "Yuklashda \"public-read\" ni o'rnating" +serverLogs: "Server protokoli" +deleteAll: "Hammasini o'chirib tashlash" +showFixedPostForm: "Taqdim etish shaklini vaqt jadvalining yuqori qismida ko'rsating" +newNoteRecived: "Yangi qaydlar mavjud emas" sounds: "Tovushlar" sound: "ovoz" +listen: "Eshitish" none: "Hechnima" +showInPage: "Sahifada ko'rsatish" +popout: "Oching" volume: "Ovoz balandligi" details: "Batafsil" +chooseEmoji: "Emojini tanlang" +unableToProcess: "Opertsiya bajarilmadi" +recentUsed: "Oxirgi ishlatilganlar" +install: "O‘rnatish" +uninstall: "O‘chirib tashlash" +installedApps: "O'rnatilgan ilovalar" +nothing: "Hech narsa yo'q" +installedDate: "O'rnatish sanasi" +lastUsedDate: "Oxirgi marta ishlatilgan sana" +state: "Holat" +sort: "saralamoq" +ascendingOrder: "O'sish bo'yicha" +descendingOrder: "Kamayish bo'yicha" +scratchpad: "Qoralama" output: "Chiqish" +script: "Skript" +disablePagesScript: "AiScriptni sahifalardan o'chirish" +updateRemoteUser: "Masofaviy foydalanuvchi ma'lumotlarini yangilash" deleteAllFiles: "barcha fayllarni o'chirish" deleteAllFilesConfirm: "Barcha fayllar oʻchirilsinmi?" +removeAllFollowing: "Barcha obunalarni o'chirish" userSuspended: "Bu foydalanuvchi muzlatilgan." +userSilenced: "Ushbu foydalanuvchi jim qilingan" yourAccountSuspendedTitle: "akkaunt muzlatilgan" yourAccountSuspendedDescription: "Ushbu akkaunt serverning xizmat ko'rsatish shartlarini buzish kabi sabablarga ko'ra to'xtatilgan. Tafsilotlar uchun administratoringizga murojaat qiling. Iltimos, yangi akkaunt yaratmang." tokenRevoked: "token yaroqsiz" @@ -465,51 +560,305 @@ accountDeletedDescription: "Bu akkaunt oʻchirildi." menu: "Menyu" divider: "Ajratrmoq" addItem: "Element qo'shish" +rearrange: "Qayta saralash" +inboxUrl: "Qabul qilingan xabarlar URL manzili" serviceworkerInfo: "bildirishnomalar uchun yoqilgan bo'lishi kerak." deletedNote: "Oʻchirilgan post" +visibility: "Ko'rinishi" +poll: "So'ro'vnoma" +useCw: "Kontentni yashirish" +enablePlayer: "Video pleyerni ochish" +disablePlayer: "Video pleyerni yopish" +expandTweet: "Xabarni kengaytirish" themeEditor: "Rang sxemasi muharriri" +description: "tavsif" describeFile: "sarlavha qo'shing" enterFileDescription: "sarlavha kiriting" author: "muallif" leaveConfirm: "Sizda saqlanmagan oʻzgarishlar bor. Bekor qilinsinmi?" +manage: "Administratsiya" +plugins: "Kengaytmalar, plaginlar" +preferencesBackups: "Sozlamalarni zahiralash" useBlurEffectForModal: "Modal uchun xiralashtirish effektidan foydalaning" +useFullReactionPicker: "Katta oynada reaksiya tanlash" width: "kengligi" height: "balandligi" large: "Katta" +medium: "O'rta" small: "kichik" +generateAccessToken: "Kirish tokenini yaratish" +permission: "Ruxsatlar" enableAll: "Yoqish" disableAll: "hammasini o'chirib qo'ying" +tokenRequested: "Hisobga kirish" +pluginTokenRequestedDescription: "Bu plagin shu yerda belgilanganlarga qodir bo'ladi" +notificationType: "Bildirishnoma turi" edit: "Tahrirlash" +emailServer: "Email server" email: "Email" +emailAddress: "E-pochtangiz:" +smtpConfig: "SMTP server sozlamalari" smtpHost: "Host" +smtpPort: "Port" smtpUser: "Foydalanuvchi nomi" smtpPass: "Parol" +testEmail: "Email jo'natmani testlash" +userSaysSomething: "{name} nimadir dedi" +makeActive: "Faol" +display: "Displey" copy: "Nusxa olish" +metrics: "Metrikalar" +overview: "Umumiy ma'lumot" +logs: "Jurnallar" +delayed: "Kechiktirildi" +database: "Ma'lumotlar bazasi" +channel: "Kanallar" +create: "Yaratish" notificationSetting: "Bildirishnoma sozlamalari" +notificationSettingDesc: "Ko'rsatish uchun bildirishnoma turlarini tanlang." +useGlobalSetting: "Global sozlamalardan foydalanish" other: "Qo‘shimcha" +regenerateLoginToken: "Kirish tokenini qayta yaratish" +setMultipleBySeparatingWithSpace: "Bo'sh joy qoldirib, bir necha ma'lumot kiritish mumkin" +fileIdOrUrl: "Fayl ID'si yoki URL havolasi" behavior: "Hatti-harakatlar" sample: "Namuna" +abuseReports: "Shikoyatlar" +reportAbuse: "Shikoyat qilish" +reportAbuseOf: "{name} ustidan shikoyat qilish" +abuseReported: "Shikoyatingiz yetkazildi. Ma'lumot uchun rahmat." +reporter: "Shikoyat qiluvchi" +reporteeOrigin: "Xabarning kelib chiqishi" +reporterOrigin: "Xabarchining joylashuvi" +forwardReport: "Xabarni masofadagi serverga yuborish" +forwardReportIsAnonymous: "Sizning yuborayotgan xabaringiz o'z akkountingiz emas balki anonim tarzda qoladi" +send: "Yuborish" +abuseMarkAsResolved: "Yuborilgan xabarni hal qilingan deb belgilash" +openInNewTab: "Yangi tab da ochish" +openInSideView: "Yon panelda ochish" +defaultNavigationBehaviour: "Standart navigatsiya harakati" +editTheseSettingsMayBreakAccount: "Bu sozlamalarni o'zgartirish hisobingizga zarar yetkazishi mumkin." +waitingFor: "{x}ni kutayapman" +random: "Tasodifiy" +system: "Tizim" +switchUi: "Interfeysni almashtirish" +desktop: "Brauzer rejimi" +clip: "Klip" +createNew: "Yangi yaratish" +optional: "Ixtiyoriy" +createNewClip: "Yangi klip yaratish" +unclip: "qirqish\n" +confirmToUnclipAlreadyClippedNote: "Ushbu xat allaqachon \"{name}\" klipga tegishli. Uni ushbu klipdan olib tashlashni xohlaysizmi?" public: "Ommaviy" +i18nInfo: "Misskey bir qancha volontyorlar yordamida bir qancha tillarga tarjima qilingan. Ushbu {link} orqali ularga yordam berishingiz mumkin." +manageAccessTokens: "Kirish tokenlarini boshqarish" +accountInfo: "Akkount haqida ma'lumot" +notesCount: "Xatlar soni" +repliesCount: "Yuborilgan javoblar soni" +renotesCount: "Qayta yuborilgan xatlar soni" +repliedCount: "Qabul qilingan javoblar soni" +renotedCount: "Qayta yuborilgan xatlar soni" +followingCount: "Obuna bo'lingan akkountlar soni" +followersCount: "Obunachilar soni" +sentReactionsCount: "Yuborilgan reaksiyalar soni" +receivedReactionsCount: "Qabul qilingan reaksiyalar soni" +pollVotesCount: "Berilgan ovozlar soni" +pollVotedCount: "Qabul qilingan ovozlar soni" +yes: "Ha" +no: "Yo'q" +driveFilesCount: "Diskdagi fayllar soni" +driveUsage: "Ishlatilgan disk joyi" +noCrawleDescription: "Qidiruv tizimlari sizning profilingiz, sahifalaringiz, xatlaringiz va hokazolarni belgilamasligi uchun so'rov yuborish" +lockedAccountInfo: "Xatlaringizni faqatgina obunachilaringizga ko'rsatishni xohlasangiz unda \"Faqat Obunachilar uchun\" xususiyatini yoqishingiz lozim. Bo'lmasa sizning yozgan xatlaringiz hammaga ko'rinadi." +alwaysMarkSensitive: "Avvaldan ta'sirchan kontent deb belgilash" +loadRawImages: "Thumbnaillarsiz Original rasmni yuklash" +disableShowingAnimatedImages: "Animatsiyali rasmlarni ko'rsatmaslik" +verificationEmailSent: "Emailingizga tasdiqlash xabari yuborildi. Iltimos linkda ko'rsatilgan amallarga rioya qiling" +notSet: "Sozlanmagan" +emailVerified: "Elektron pochta manzili tasdiqlandi" +pageLikesCount: "Sahifadagi like'lar soni" +contact: "aloqa uchun manzil" +useSystemFont: "Tizimdagi standart shriftidan foydalaning" +clips: "Klip" +experimentalFeatures: "eksperimental xususiyatlar" +experimental: "eksperimental" +developer: "Dasturchi" +makeExplorable: "Akkauntingizni topishni osonlashtiring" +duplicate: "Dublikat" +left: "Chap(dagi)" +center: "Markaz" +wide: "Keng" +narrow: "Tor" +reloadToApplySetting: "Bu sozlamalar sahifa yangilangandagina kuchga kiradi. Hozir yangilashni istaysizmi?" +needReloadToApply: "Sahifani yangilash talab etiladi." clearCache: "Keshni tozalash" onlineUsersCount: "Faol userlar" +nUsers: "{n} ta foydalanuvchi" myTheme: "Mening rang sxemam" backgroundColor: "Fon" accentColor: "Urg'u" textColor: "Matn" +saveAs: "Boshqacha saqlash" +advanced: "Murakkab" +advancedSettings: "Qo'shimcha sozlashlar" +value: "Qiymati" +createdAt: "Yaratilish vaqti" +updatedAt: "yangilangan sana" +saveConfirm: "O'zgartirishni saqlash?" +deleteConfirm: "o'chirishni tasdiqlash" +invalidValue: "noto'g'ri qiymat" +registry: "ro'yhatga olish" +closeAccount: "hisobni yopish / profilni yopish" +currentVersion: "joriy versiya" +latestVersion: "so'ngi versiya" +youAreRunningUpToDateClient: "siz so'ngi versiyali ilovani ishlatyapsiz" +newVersionOfClientAvailable: "Mijozning yangi versiyasi mavjud." +usageAmount: "foydalanish miqdori" +capacity: "sig'im" +inUse: "allaqachon band" +editCode: "kodni tahrirlash" +apply: "Ilova" +receiveAnnouncementFromInstance: "Serverdan bildirishnomalarni oling" +emailNotification: "E-mail xabarlari" +publish: "Chiqarish" +inChannelSearch: "Kanal qidirish" +useReactionPickerForContextMenu: "kontekst menyusi uchun reaktsiya tanlash vositasidan foydalaning" +typingUsers: "{users} yozmoqda" +jumpToSpecifiedDate: "Muayyan sanaga o'tish" +showingPastTimeline: "O'tgan vaqt jadvallarini ko'rsatish" +clear: "aniq" +markAllAsRead: "hammasini o'qilgan deb belgilang" +goBack: "qaytish" +unlikeConfirm: "Un like qilishni xohlaysizmi?" +fullView: "to'liq ko'rish" +quitFullView: "Toʻliq koʻrishdan chiqish" +addDescription: "Tavsif qo'shing" info: "Haqida" +userInfo: "Foydalanuvchi ma'lumotlari" +unknown: "aniq emas" +onlineStatus: "onlayn holat" +hideOnlineStatus: "onlayn holatni yashirish" +hideOnlineStatusDescription: "Onlayn statusingizni yashirish, qidiruv kabi baʼzi funksiyalardan foydalanish imkoniyatini kamaytirishi mumkin." +online: "onlayn" +active: "Aktiv" +offline: "oflayn" +notRecommended: "tavsiya etilmaydi" +selectAccount: "Akkauntni tanlang" +switchAccount: "akkauntni almashtirish" +enabled: "yaroqli" +disabled: "yaroqsiz" +quickAction: "tezkor harakat" user: "Foydalanuvchilar" +administration: "Administratsiya" +accounts: "akkaunt" +switch: "almashtirish" +noBotProtectionWarning: "Bot himoyasi sozlanmagan." +configure: "sozlamoq" +postToGallery: "Yangi galleriya posti" +gallery: "Galereya" +recentPosts: "So'nggi postlar" +popularPosts: "Mashhur postlar" +shareWithNote: "Eslatmani ulashish" +ads: "Reklama" +startingperiod: "Boshlanish davri" +memo: "Eslatma" +priority: "Ustuvorlik" +high: "Yuqori" +middle: "O'rta" +low: "Quyi" +ratio: "nisbat" +previewNoteText: "Razm solish" +customCss: "Maxsus CSS" global: "Global" squareAvatars: "Kvadrat avatarkalar" +sent: "Yuborish" +received: "Qabul qilingan" +searchResult: "Qidiruv natijalari" +hashtags: "Hashteglar" +troubleshooting: "Muammolarni bartaraf qilish" +useBlurEffect: "Interfeysda xiralashtiruvchi effektlardan foydalanish" +learnMore: "Batafsilroq" +misskeyUpdated: "Misskey yangilandi!" +whatIsNew: "O'zgarishlarni ko'rish" +translate: "Tarjima qilish" +translatedFrom: "{x} tilidan tarjima qilindi" +devMode: "Dasturchi rejimi" +lastCommunication: "Oxirgi muloqot" +resolved: "Hal qilingan" +unresolved: "Hal qilinmagan" +breakFollow: "Obunachini o'chirish" +breakFollowConfirm: "Obunachini o'chirmoqchimisiz?" +itsOn: "Yoqilgan" +itsOff: "O'chirilgan" +on: "Yoqish" +off: "O'chirish" +emailRequiredForSignup: "Ro'yxatdan o'tish uchun email talab qilish" +unread: "Oʻqilmagan xabarlar" +filter: "Filter" +controlPanel: "Boshqaruv paneli" +manageAccounts: "Hisobni boshqarish" +classic: "Klassik" +hide: "Yashirish" searchByGoogle: "Izlash" indefinitely: "Hech qachon" file: "Fayllar" +recommended: "Tavsiya qilingan" +check: "Tekshirish" +requireAdminForView: "Ko'rish uchun adminstrator hisobi bilan tizimga kirgan bo'lishingiz kerak." +isSystemAccount: "Ushbu hisob tizim tomonidan avtomatik tarzda yaratilgan va boshqariladi." +typeToConfirm: "Ushbu amalni bajarish uchun {x}ni kiriting" +deleteAccount: "Hisobni o'chirish" +document: "hujjat" +numberOfPageCache: "Sahifa keshlar soni" +logoutConfirm: "Chiqishni xohlaysizmi?" +lastActiveDate: "oxirgi foydalanish sanasi" +statusbar: "Holat paneli" +pleaseSelect: "Iltimos tanlang" +reverse: "Teskari" +colored: "rangli" +refreshInterval: "yangilash oralig'i" label: "Yorliq" +type: "turi" +speed: "tezlik" +slow: "Sekin" +fast: "Tez" +localOnly: "Faqat mahalliy" +remoteOnly: "faqat masofadan turib" +failedToUpload: "yuklanmadi" +cannotUploadBecauseInappropriate: "Faylni yuklab bo'lmaydi, chunki unda nomaqbul kontent borligi aniqlangan." +cannotUploadBecauseNoFreeSpace: "Yuklab bo'lmadi, chunki diskda bo'sh joy yo'q." +cannotUploadBecauseExceedsFileSizeLimit: "Faylni yuklash mumkin emas, chunki u fayl hajmi chegarasidan oshib ketgan." +beta: "beta" +account: "akkaunt" +show: "Displey" color: "Rang" +disableFederationConfirm: "Federatsiyani o'chirmoqchimisiz?" +disableFederationOk: "O'chirish" +emailNotSupported: "Bu server E-pochtalar yuborishni qo'llab-quvvatlamaydi" +postToTheChannel: "Kanalga joylash" +cannotBeChangedLater: "Buni keyinchalik o'zgartirishni iloji yo'q" +likeOnly: "Faqat like'lar" +nonSensitiveOnly: "Xavfsiz rejim" +rolesAssignedToMe: "Mening rollarim" +resetPasswordConfirm: "Qayta parol o'rnatmoqchimisiz?" +sensitiveWords: "Ta'sirchan so'zlar" +icon: "Avatar" +replies: "Javob berish" +renotes: "Qayta qayd etish" _achievements: _types: _viewInstanceChart: title: "Tahlilchi" +_role: + priority: "Ustuvorlik" + _priority: + low: "Quyi" + middle: "O'rta" + high: "Yuqori" +_ffVisibility: + public: "Chiqarish" _ad: + back: "qaytish" hide: "Boshqa ko'rsatilmasin" _email: _follow: @@ -566,6 +915,7 @@ _ago: minutesAgo: "{n} daqiqa oldin" hoursAgo: "{n} soat oldin" daysAgo: "{n} kun oldin" + invalid: "Hech narsa yo'q" _2fa: renewTOTPCancel: "Hozir emas" _permissions: diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index 898c478bf594..7b74e9714fa0 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -1,5 +1,5 @@ --- -_lang_: "Tiếng Việt" +_lang_: "Tiếng Nhật" headlineMisskey: "Mạng xã hội liên hợp" introMisskey: "Xin chào! Misskey là một nền tảng tiểu blog phi tập trung mã nguồn mở.\nViết \"tút\" để chia sẻ những suy nghĩ của bạn 📡\nBằng \"biểu cảm\", bạn có thể bày tỏ nhanh chóng cảm xúc của bạn với các tút 👍\nHãy khám phá một thế giới mới! 🚀" poweredByMisskeyDescription: "{name} là một trong những chủ máy của Misskey là nền tảng mã nguồn mở" @@ -20,6 +20,7 @@ noNotes: "Chưa có bài viết nào." noNotifications: "Chưa có thông báo" instance: "Máy chủ" settings: "Cài đặt" +notificationSettings: "Cài đặt thông báo" basicSettings: "Thiết lập chung" otherSettings: "Thiết lập khác" openInWindow: "Mở trong cửa sổ mới" @@ -44,13 +45,20 @@ pin: "Ghim" unpin: "Bỏ ghim" copyContent: "Chép nội dung" copyLink: "Chép liên kết" +copyLinkRenote: "Sao chép liên kết ghi chú" delete: "Xóa" deleteAndEdit: "Sửa" deleteAndEditConfirm: "Bạn có chắc muốn sửa tút này? Những biểu cảm, lượt trả lời và đăng lại sẽ bị mất." addToList: "Thêm vào danh sách" +addToAntenna: "Thêm vào Ăngten" sendMessage: "Gửi tin nhắn" copyRSS: "Sao chép RSS" copyUsername: "Chép tên người dùng" +copyUserId: "Sao chép ID người dùng" +copyNoteId: "Sao chép ID ghi chú" +copyFileId: "Sao chép ID tập tin" +copyFolderId: "Sao chép ID thư mục" +copyProfileUrl: "Sao chép URL hồ sơ" searchUser: "Tìm kiếm người dùng" reply: "Trả lời" loadMore: "Tải thêm" @@ -122,6 +130,8 @@ unmarkAsSensitive: "Bỏ đánh dấu nhạy cảm" enterFileName: "Nhập tên tập tin" mute: "Ẩn" unmute: "Bỏ ẩn" +renoteMute: "Mute Renotes" +renoteUnmute: "Unmute Renotes" block: "Chặn" unblock: "Bỏ chặn" suspend: "Vô hiệu hóa" @@ -131,8 +141,10 @@ unblockConfirm: "Bạn có chắc muốn bỏ chặn người này?" suspendConfirm: "Bạn có chắc muốn vô hiệu hóa người này?" unsuspendConfirm: "Bạn có chắc muốn bỏ vô hiệu hóa người này?" selectList: "Chọn danh sách" +editList: "Chỉnh sửa danh sách" selectChannel: "Lựa chọn kênh" selectAntenna: "Chọn một antenna" +editAntenna: "Chỉnh sửa Ăngten" selectWidget: "Chọn tiện ích" editWidgets: "Sửa tiện ích" editWidgetsExit: "Xong" @@ -145,6 +157,9 @@ addEmoji: "Thêm emoji" settingGuide: "Cài đặt đề xuất" cacheRemoteFiles: "Tập tin cache từ xa" cacheRemoteFilesDescription: "Khi tùy chọn này bị tắt, các tập tin từ xa sẽ được tải trực tiếp từ máy chủ khác. Điều này sẽ giúp giảm dung lượng lưu trữ nhưng lại tăng lưu lượng truy cập, vì hình thu nhỏ sẽ không được tạo." +youCanCleanRemoteFilesCache: "Bạn có thể xoá bộ nhớ đệm bằng cách nhấn vào nút🗑️ở trong phần quản lý tệp." +cacheRemoteSensitiveFiles: "Lưu các tập tin nhạy cảm vào bộ nhớ tạm từ xa" +cacheRemoteSensitiveFilesDescription: "Khi bạn tắt tính năng này, các tệp nhạy cảm sẽ được tải trực tiếp từ máy chủ và không được lưu vào bộ nhớ tạm" flagAsBot: "Đánh dấu đây là tài khoản bot" flagAsBotDescription: "Bật tùy chọn này nếu tài khoản này được kiểm soát bởi một chương trình. Nếu được bật, nó sẽ được đánh dấu để các nhà phát triển khác ngăn chặn chuỗi tương tác vô tận với các bot khác và điều chỉnh hệ thống nội bộ của Misskey để coi tài khoản này như một bot." flagAsCat: "Chế độ Mèeeeeeeeeeo!!" @@ -153,6 +168,7 @@ flagShowTimelineReplies: "Hiện lượt trả lời trong bảng tin" flagShowTimelineRepliesDescription: "Hiện lượt trả lời của người bạn theo dõi trên tút của những người khác." autoAcceptFollowed: "Tự động phê duyệt theo dõi từ những người mà bạn đang theo dõi" addAccount: "Thêm tài khoản" +reloadAccountsList: "Cập nhật danh sách tài khoản" loginFailed: "Đăng nhập không thành công" showOnRemote: "Truy cập trang của người này" general: "Tổng quan" @@ -259,8 +275,10 @@ noMoreHistory: "Không còn gì để đọc" startMessaging: "Bắt đầu trò chuyện" nUsersRead: "đọc bởi {n}" agreeTo: "Tôi đồng ý {0}" +agree: "Đồng ý" agreeBelow: "Đồng ý với nội dung dưới đây" basicNotesBeforeCreateAccount: "Những điều cơ bản cần chú ý " +termsOfService: "Điều khoản và Điều kiện" start: "Bắt đầu" home: "Trang chính" remoteUserCaution: "Vì người dùng này ở máy chủ khác, thông tin hiển thị có thể không đầy đủ." @@ -303,6 +321,7 @@ copyUrl: "Sao chép URL" rename: "Đổi tên" avatar: "Ảnh đại diện" banner: "Ảnh bìa" +displayOfSensitiveMedia: "Hiển thị nội dung nhạy cảm (NSFW)" whenServerDisconnected: "Khi mất kết nối tới máy chủ" disconnectedFromServer: "Mất kết nối tới máy chủ" reload: "Tải lại" @@ -337,7 +356,6 @@ invite: "Mời" driveCapacityPerLocalAccount: "Dung lượng ổ đĩa tối đa cho mỗi người dùng" driveCapacityPerRemoteAccount: "Dung lượng ổ đĩa tối đa cho mỗi người dùng từ xa" inMb: "Tính bằng MB" -iconUrl: "URL Icon" bannerUrl: "URL Ảnh bìa" backgroundImageUrl: "URL Ảnh nền" basicInfo: "Thông tin cơ bản" @@ -393,10 +411,13 @@ aboutMisskey: "Về Misskey" administrator: "Quản trị viên" token: "Token" 2fa: "Xác thực 2 yếu tố" +setupOf2fa: "Thiết lập xác thực 2 yếu tố" totp: "Ứng dụng xác thực" totpDescription: "Nhắn mã OTP bằng ứng dụng xác thực" moderator: "Kiểm duyệt viên" moderation: "Kiểm duyệt" +moderationNote: "Ghi chú kiểm duyệt" +addModerationNote: "Thêm ghi chú kiểm duyệt" nUsersMentioned: "Dùng bởi {n} người" securityKeyAndPasskey: "Mã bảo mật・Passkey" securityKey: "Khóa bảo mật" @@ -456,6 +477,7 @@ aboutX: "Giới thiệu {x}" emojiStyle: "Kiểu cách Emoji" native: "Bản xứ" disableDrawer: "Không dùng menu thanh bên" +showNoteActionsOnlyHover: "Chỉ hiển thị các hành động ghi chú khi di chuột" noHistory: "Không có dữ liệu" signinHistory: "Lịch sử đăng nhập" enableAdvancedMfm: "Xem bài MFM chất lượng cao." @@ -468,6 +490,7 @@ createAccount: "Tạo tài khoản" existingAccount: "Tài khoản hiện có" regenerate: "Tạo lại" fontSize: "Cỡ chữ" +limitTo: "Giới hạn tỷ lệ {x}" noFollowRequests: "Bạn không có yêu cầu theo dõi nào" openImageInNewTab: "Mở ảnh trong tab mới" dashboard: "Trang chính" @@ -504,6 +527,7 @@ objectStorageSetPublicRead: "Đặt \"public-read\" khi tải lên" serverLogs: "Nhật ký máy chủ" deleteAll: "Xóa tất cả" showFixedPostForm: "Hiện khung soạn tút ở phía trên bảng tin" +showFixedPostFormInChannel: "Hiển thị mẫu bài đăng ở phía trên bản tin" newNoteRecived: "Đã nhận tút mới" sounds: "Âm thanh" sound: "Âm thanh" @@ -541,9 +565,14 @@ userSuspended: "Người này đã bị vô hiệu hóa." userSilenced: "Người này đã bị ẩn" yourAccountSuspendedTitle: "Tài khoản bị vô hiệu hóa" yourAccountSuspendedDescription: "Tài khoản này đã bị vô hiệu hóa do vi phạm quy tắc máy chủ hoặc điều tương tự. Liên hệ với quản trị viên nếu bạn muốn biết lý do chi tiết hơn. Vui lòng không tạo tài khoản mới." +tokenRevoked: "Token đã bị từ chối" +tokenRevokedDescription: "Phiên đăng nhập đã hết hạn. Vui lòng đăng nhập lại." +accountDeleted: "Tài khoản đã bị xóa" +accountDeletedDescription: "Tài khoản này đã bị xóa." menu: "Menu" divider: "Phân chia" addItem: "Thêm mục" +rearrange: "Sắp xếp lại" relays: "Chuyển tiếp" addRelay: "Thêm chuyển tiếp" inboxUrl: "URL Hộp thư đến" @@ -653,6 +682,7 @@ createNewClip: "Tạo một ghim mới" unclip: "Bỏ ghim" confirmToUnclipAlreadyClippedNote: "Bài đăng này là một phần của \"{name}\" ghim. Bạn có muốn bỏ khỏi ghim?" public: "Công khai" +private: "Riêng tư" i18nInfo: "Misskey đang được các tình nguyện viên dịch sang nhiều thứ tiếng khác nhau. Bạn có thể hỗ trợ tại {link}." manageAccessTokens: "Tạo mã truy cập" accountInfo: "Thông tin tài khoản" @@ -687,6 +717,8 @@ contact: "Liên hệ" useSystemFont: "Dùng phông chữ mặc định của hệ thống" clips: "Lưu bài viết" experimentalFeatures: "Tính năng thử nghiệm" +experimental: "Thử nghiệm" +thisIsExperimentalFeature: "Tính năng này đang trong quá trình thử nghiệm. Tính năng có thể không hoạt động, hoặc đặc tính kỹ thuật có thể bị thay đổi sau này." developer: "Nhà phát triển" makeExplorable: "Không hiện tôi trong \"Khám phá\"" makeExplorableDescription: "Nếu bạn tắt, tài khoản của bạn sẽ không hiện trong mục \"Khám phá\"." @@ -771,6 +803,7 @@ noMaintainerInformationWarning: "Chưa thiết lập thông tin vận hành." noBotProtectionWarning: "Bảo vệ Bot chưa thiết lập." configure: "Thiết lập" postToGallery: "Tạo tút có ảnh" +postToHashtag: "Đăng bài với hashtag này" gallery: "Thư viện ảnh" recentPosts: "Tút gần đây" popularPosts: "Tút được xem nhiều nhất" @@ -804,6 +837,7 @@ translatedFrom: "Dịch từ {x}" accountDeletionInProgress: "Đang xử lý việc xóa tài khoản" usernameInfo: "Bạn có thể sử dụng chữ cái (a ~ z, A ~ Z), chữ số (0 ~ 9) hoặc dấu gạch dưới (_). Tên người dùng không thể thay đổi sau này." aiChanMode: "Chế độ Ai" +devMode: "Chế độ dành cho nhà phát triển" keepCw: "Giữ cảnh báo nội dung" pubSub: "Tài khoản Chính/Phụ" lastCommunication: "Lần giao tiếp cuối" @@ -813,6 +847,8 @@ breakFollow: "Xóa người theo dõi" breakFollowConfirm: "Bạn bỏ theo dõi tài khoản này không?" itsOn: "Đã bật" itsOff: "Đã tắt" +on: "Bật" +off: "Tắt" emailRequiredForSignup: "Yêu cầu địa chỉ email khi đăng ký" unread: "Chưa đọc" filter: "Bộ lọc" @@ -857,6 +893,7 @@ failedToFetchAccountInformation: "Không thể lấy thông tin tài khoản" rateLimitExceeded: "Giới hạn quá mức" cropImage: "Cắt hình ảnh" cropImageAsk: "Bạn có muốn cắt ảnh này?" +cropYes: "Cắt" cropNo: "Để nguyên" file: "Tập tin" recentNHours: "{n}h trước" @@ -892,6 +929,7 @@ remoteOnly: "Chỉ máy chủ từ xa" failedToUpload: "Tải lên thất bại" cannotUploadBecauseInappropriate: "Không thể tải lên tập tin này vì các phần của tập tin đã được phát hiện có khả năng là NSFW." cannotUploadBecauseNoFreeSpace: "Tải lên không thành công do thiếu dung lượng Drive." +cannotUploadBecauseExceedsFileSizeLimit: "Không thể tải lên tập tin vì kích thước quá lớn." beta: "Beta" enableAutoSensitive: "Tự động đánh dấu NSFW" enableAutoSensitiveDescription: "Cho phép tự động phát hiện và đánh dấu media NSFW thông qua học máy, nếu có thể. Ngay cả khi tùy chọn này bị tắt, nó vẫn có thể được bật trên toàn máy chủ." @@ -904,9 +942,11 @@ pushNotification: "Thông báo đẩy" subscribePushNotification: "Bật thông báo đẩy" unsubscribePushNotification: "Tắt thông báo đẩy" pushNotificationAlreadySubscribed: "Đang bật thông báo đẩy" +pushNotificationNotSupported: "Trình duyệt của bạn không hỗ trợ thông báo đẩy." sendPushNotificationReadMessage: "Xóa thông báo đẩy sau khi đọc thông báo hay tin nhắn" sendPushNotificationReadMessageCaption: "Thông báo như {emptyPushNotificationMessage} sẽ hiển thị trong giây phút. Tiêu tốn pin của máy bạn có thể tăng lên hơn nữa." windowMaximize: "Phóng to" +windowMinimize: "Thu nhỏ tối đa" windowRestore: "Khôi phục" caption: "Mô tả" loggedInAsBot: "Đang đăng nhập bằng tài khoản Bot" @@ -923,12 +963,22 @@ didYouLikeMisskey: "Bạn có ưa thích Mískey không?" pleaseDonate: "Misskey là phần mềm miễn phí mà {host} đang sử dụng. Xin mong bạn quyên góp cho chúng tôi để chúng tôi có thể tiếp tục phát triển dịch vụ này. Xin cảm ơn!!" roles: "Vai trò" role: "Vai trò" +noRole: "Bạn chưa được cấp quyền." normalUser: "Người dùng bình thường" undefined: "Chưa xác định" +assign: "Phân công" +unassign: "Hủy phân công" color: "Màu sắc" manageCustomEmojis: "Quản lý CustomEmoji" +youCannotCreateAnymore: "Bạn đã tới giới hạn tạo." cannotPerformTemporary: "Tạm thời không sử dụng được" cannotPerformTemporaryDescription: "Tạm thời không sử dụng được vì lần số điều kiện quá giới hạn. Thử lại sau mọt lát nữa." +invalidParamError: "Lỗi tham số" +invalidParamErrorDescription: "Có vấn đề với các tham số được request. Thông thường, đây là do bug, nhưng cũng có thể do bạn đã nhập vào quá nhiều ký tự." +permissionDeniedError: "Thao tác bị từ chối" +permissionDeniedErrorDescription: "Tài khoản này không có đủ quyền hạn để thực hiện thao tác này." +preset: "Mẫu thiết lập" +selectFromPresets: "Chọn từ mẫu" achievements: "Thành tích" gotInvalidResponseError: "Không nhận được trả lời chủ máy" gotInvalidResponseErrorDescription: "Chủ máy có lẻ ngừng hoạt động hoặc bảo trí. Thử lại sau một lát nữa. " @@ -943,8 +993,71 @@ copyErrorInfo: "Sao chép thông tin lỗi" joinThisServer: "Đăng ký trên chủ máy này" exploreOtherServers: "Tìm chủ máy khác" letsLookAtTimeline: "Thử xem Timeline" +emailNotSupported: "Máy chủ này không hỗ trợ gửi email" +postToTheChannel: "Đăng lên kênh" +cannotBeChangedLater: "Không thể thay đổi sau này." +rolesAssignedToMe: "Vai trò được giao cho tôi" +resetPasswordConfirm: "Bạn thực sự muốn đặt lại mật khẩu?" +sensitiveWords: "Các từ nhạy cảm" +license: "Giấy phép" +unfavoriteConfirm: "Bạn thực sự muốn xoá khỏi mục yêu thích?" +retryAllQueuesConfirmText: "Điều này sẽ tạm thời làm tăng mức độ tải của máy chủ." +enableChartsForRemoteUser: "Tạo biểu đồ người dùng từ xa" +video: "Video" +videos: "Các video" +dataSaver: "Tiết kiệm dung lượng" +accountMigration: "Gộp chung tài khoản" +accountMoved: "Người dùng này đã chuyển sang một tài khoản mới:" +accountMovedShort: "Tài khoản này đã được gộp" +operationForbidden: "Thao tác này không thể thực hiện" +forceShowAds: "Luôn hiện quảng cáo" +notificationDisplay: "Thông báo" +leftTop: "Phía trên bên tráí" +rightTop: "Phía trên bên phải" +leftBottom: "Phía dưới bên trái" +rightBottom: "Phía dưới bên phải" +stackAxis: "Hướng chồng" +vertical: "Dọc" horizontal: "Thanh bên" +position: "Vị trí" +serverRules: "Luật của máy chủ" youFollowing: "Đang theo dõi" +later: "Để sau" +goToMisskey: "Tới Misskey" +installed: "Đã tải xuống" +branding: "Thương hiệu" +turnOffToImprovePerformance: "Tắt mục này có thể cải thiện hiệu năng." +expirationDate: "Ngày hết hạn" +noExpirationDate: "Vô thời hạn" +waitingForMailAuth: "Đang chờ xác nhận email" +unused: "Chưa được sử dụng" +used: "Đã được sử dụng" +expired: "Đã hết hạn" +doYouAgree: "Đồng ý?" +iHaveReadXCarefullyAndAgree: "Tôi đã đọc và đồng ý với \"x\"." +dialog: "Hộp thoại" +icon: "Ảnh đại diện" +forYou: "Dành cho bạn" +currentAnnouncements: "Thông báo hiện tại" +pastAnnouncements: "Thông báo trước đó" +youHaveUnreadAnnouncements: "Có thông báo chưa đọc." +replies: "Trả lời" +renotes: "Đăng lại" +loadReplies: "Hiển thị các trả lời" +pinnedList: "Các mục đã được ghim" +keepScreenOn: "Giữ màn hình luôn bật" +_announcement: + forExistingUsers: "Chỉ những người dùng đã tồn tại" + end: "Lưu trữ thông báo" + readConfirmTitle: "Đánh dấu là đã đọc?" + readConfirmText: "Điều này sẽ đánh dấu nội dung của \"{title}\" là đã đọc." +_initialAccountSetting: + accountCreated: "Tài khoản của bạn đã được tạo thành công!" + letsStartAccountSetup: "Để bắt đầu, hãy cùng thiết lập tài khoản nhé." + letsFillYourProfile: "Đầu tiên, hãy thiết lập hồ sơ của bạn." + profileSetting: "Thiết lập hồ sơ" +_accountMigration: + movedTo: "Tài khoản mới:" _achievements: earnedAt: "Ngày thu nhận" _types: @@ -1104,6 +1217,7 @@ _achievements: _cookieClicked: flavor: "Bạn nhầm phầm mềm chứ?" _role: + assignTarget: "Phân công" priority: "Ưu tiên" _priority: low: "Thấp" @@ -1346,15 +1460,25 @@ _time: minute: "phút" hour: "giờ" day: "ngày" +_timelineTutorial: + step4_1: "Bạn có thể thêm \"Reaction\" vào ghi chú" + step4_2: "Khi thêm biểu cảm hãy nhấn dấu \"+\"" _2fa: alreadyRegistered: "Bạn đã đăng ký thiết bị xác minh 2 bước." + registerTOTP: "Đăng ký ứng dụng xác thực" passwordToTOTP: "Nhắn mật mã" step1: "Trước tiên, hãy cài đặt một ứng dụng xác minh (chẳng hạn như {a} hoặc {b}) trên thiết bị của bạn." step2: "Sau đó, quét mã QR hiển thị trên màn hình này." - step2Url: "Bạn cũng có thể nhập URL này nếu sử dụng một chương trình máy tính:" + step2Click: "Quét mã QR trên ứng dụng xác thực (Authy, Google authenticator, v.v.)" + step3Title: "Nhập mã xác thực" step3: "Nhập mã token do ứng dụng của bạn cung cấp để hoàn tất thiết lập." step4: "Kể từ bây giờ, những lần đăng nhập trong tương lai sẽ yêu cầu mã token đăng nhập đó." + securityKeyNotSupported: "Trình duyệt của bạn không hỗ trợ khóa bảo mật" + registerTOTPBeforeKey: "Vui lòng thiết lập một ứng dụng xác thực để đăng ký khóa bảo mật hoặc mật khẩu." securityKeyInfo: "Bên cạnh xác minh bằng vân tay hoặc mã PIN, bạn cũng có thể thiết lập xác minh thông qua khóa bảo mật phần cứng hỗ trợ FIDO2 để bảo mật hơn nữa cho tài khoản của mình." + registerSecurityKey: "Tạo khóa bảo mật hoặc mã bảo mật" + securityKeyName: "Nhập tên khóa bảo mật" + tapSecurityKey: "Vui lòng làm theo hướng dẫn của trình duyệt để đăng ký mã bảo mật hoặc mã khóa" removeKey: "Xóa mã bảo mật" removeKeyConfirm: "Xóa bản sao lưu {name}?" renewTOTP: "Cài đặt lại ứng dụng xác thực" @@ -1677,5 +1801,11 @@ _dialog: charactersExceeded: "Bạn nhắn quá giới hạn ký tự!! Hiện nay {current} / giới hạn {max}" charactersBelow: "Bạn nhắn quá ít tối thiểu ký tự!! Hiện nay {current} / Tối thiểu {min}" _webhookSettings: + createWebhook: "Tạo Webhook" name: "Tên" + secret: "Mã bí mật" + events: "Sự kiện Webhook" active: "Đã bật" + _events: + reaction: "Khi nhận được sự kiện" + mention: "Khi có người nhắc tới bạn" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index eff2df984905..ef32d1aa2341 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -15,7 +15,7 @@ gotIt: "我明白了" cancel: "取消" noThankYou: "不用,谢谢" enterUsername: "输入用户名" -renotedBy: "由 {user} 转贴" +renotedBy: "{user} 转发了" noNotes: "没有帖文" noNotifications: "无通知" instance: "服务器" @@ -45,6 +45,7 @@ pin: "置顶" unpin: "取消置顶" copyContent: "复制内容" copyLink: "复制链接" +copyLinkRenote: "复制转帖链接" delete: "删除" deleteAndEdit: "删除并编辑" deleteAndEditConfirm: "要删除此帖并再次编辑吗?对此帖的所有回应、转发和回复也将被删除。" @@ -155,13 +156,14 @@ emojiUrl: "emoji 地址" addEmoji: "添加表情符号" settingGuide: "推荐配置" cacheRemoteFiles: "缓存远程文件" -cacheRemoteFilesDescription: "当禁用此设定时远程文件将直接从远程服务器载入。禁用后会减小储存空间需求,但是会增加流量,因为缩略图不会被生成。" +cacheRemoteFilesDescription: "启用此设定时,将在此服务器上缓存远程文件。虽然可以加快图片显示的速度,但是相对的会消耗大量的服务器存储空间。用户角色内的网盘容量决定了这个远程用户能在服务器上保留保留多少缓存。当超出了这个限制时,旧的文件将从缓存中被删除,成为链接。当禁用此设定时,则是从一开始就将远程文件保留为链接。此时推荐将 default.yml 的 proxyRemoteFiles 设置为 true 以优化缩略图生成及保护用户隐私。" +youCanCleanRemoteFilesCache: "可以使用文件管理的🗑️按钮来删除所有的缓存。" cacheRemoteSensitiveFiles: "缓存远程敏感媒体文件" cacheRemoteSensitiveFilesDescription: "如果禁用这项设定,远程服务器的敏感媒体将不会被缓存,而是直接链接。" flagAsBot: "这是一个机器人账号" flagAsBotDescription: "如果此账户由程序控制,请启用此项。启用后,此标志可以帮助其他开发人员防止机器人之间产生无限互动的行为,并让 Misskey 的内部系统将此账户识别为机器人。" -flagAsCat: "将这个账户设定为一只猫" -flagAsCatDescription: "如果您想表明此帐户是一只猫,请打开此标志。\n开启后,会在您的头像上出现猫耳朵,并将你的帖子中的「na」替换为「nya」,日文同理。" +flagAsCat: "喵!!!!!!!!!!!!" +flagAsCatDescription: "喵喵喵??" flagShowTimelineReplies: "在时间线上显示帖子的回复" flagShowTimelineRepliesDescription: "启用时,时间线除了显示用户的帖子外,还会显示其他用户对帖子的回复。" autoAcceptFollowed: "自动允许来自我关注的用户对我的关注请求" @@ -177,7 +179,7 @@ searchWith: "搜索:{q}" youHaveNoLists: "列表为空" followConfirm: "你确定要关注 {name} 吗?" proxyAccount: "代理账户" -proxyAccountDescription: "代理账户是在某些情况下充当用户的远程关注者的账户。 例如,当一个用户列出一个远程用户时,如果没有人跟随该列出的用户,则该活动将不会传递到该服务器,因此将代之以代理账户。" +proxyAccountDescription: "代理账户是在某些情况下替代用户进行远程关注用的账户。 例如说,当用户将一位远程用户放入一个列表中时,如果本地服务器上没有任何人关注这位远程用户,则这位远程用户的账户活动将不会被送到本地服务器上。作为替代,此时将使用代理账户进行关注。" host: "主机名" selectUser: "选择用户" recipient: "收件人" @@ -211,7 +213,7 @@ clearQueueConfirmText: "未送达的帖子将不会投递。 通常,您不需 clearCachedFiles: "清除缓存" clearCachedFilesConfirm: "确定要清除缓存文件?" blockedInstances: "被封锁的服务器" -blockedInstancesDescription: "设定要封锁的服务器,以换行来进行分割。被封锁的服务器将无法与本服务器进行交换通讯。" +blockedInstancesDescription: "设定要封锁的服务器,以换行来进行分割。被封锁的服务器将无法与本服务器进行交换通讯。子域名也同样会被封锁。" muteAndBlock: "屏蔽/拉黑" mutedUsers: "已屏蔽用户" blockedUsers: "已拉黑的用户" @@ -354,7 +356,6 @@ invite: "邀请" driveCapacityPerLocalAccount: "每个用户的网盘容量" driveCapacityPerRemoteAccount: "每个远程用户的网盘容量" inMb: "以兆字节(MegaByte)为单位" -iconUrl: "图标 URL" bannerUrl: "横幅 URL" backgroundImageUrl: "背景图 URL" basicInfo: "基本信息" @@ -410,10 +411,13 @@ aboutMisskey: "关于 Misskey" administrator: "管理员" token: "Token (令牌)" 2fa: "双因素认证" +setupOf2fa: "设置双因素认证" totp: "身份验证应用" totpDescription: "使用认证应用输入一次性密码。" moderator: "监察员" moderation: "管理" +moderationNote: "管理笔记" +addModerationNote: "添加管理笔记" nUsersMentioned: "{n} 被提到" securityKeyAndPasskey: "安全密钥或 Passkey" securityKey: "安全密钥" @@ -507,13 +511,13 @@ showFeaturedNotesInTimeline: "在时间线上显示热门推荐" objectStorage: "对象存储" useObjectStorage: "使用对象存储" objectStorageBaseUrl: "Base URL" -objectStorageBaseUrlDesc: "这里是用于引用的 URL,如果您正在使用 CDN 或反向代理,请指定其 URL,例如S3:“https://.s3.amazonaws.com”,GCS:“https://storage.googleapis.com/”" +objectStorageBaseUrlDesc: "这里是用于参考的 URL,如果您正在使用 CDN 或反向代理,请指定其 URL,例如 S3:“https://.s3.amazonaws.com”,GCS:“https://storage.googleapis.com/”" objectStorageBucket: "存储桶" objectStorageBucketDesc: "请指定使用的对象存储服务的存储桶名称。" objectStoragePrefix: "前缀" objectStoragePrefixDesc: "文件将存储在此前缀的目录下。" -objectStorageEndpoint: "Endpoint" -objectStorageEndpointDesc: "如果你使用 AWS S3 请留空。否则请根据你使用的服务商的说明来进行设置,指定 Endpoint 形式为“”或“:”。" +objectStorageEndpoint: "端点" +objectStorageEndpointDesc: "如果你使用 AWS S3 请留空。否则请根据你使用的服务商的说明来进行设置,指定端点形式为“”或“:”。" objectStorageRegion: "可用区" objectStorageRegionDesc: "指定一个可用区,例如“xx-east-1”。 如果您的对象存储服务没有可用区概念,请将其留空或填写“us-east-1”。如果引用 AWS 的配置文件或环境变量,则留空。" objectStorageUseSSL: "使用 SSL" @@ -653,6 +657,7 @@ behavior: "行为" sample: "示例" abuseReports: "举报" reportAbuse: "举报" +reportAbuseRenote: "举报转帖" reportAbuseOf: "举报 {name}" fillAbuseReportDescription: "请填写举报的详细原因。如果有对方发的帖子,请同时填写 URL 地址。" abuseReported: "内容已发送。感谢您提交信息。" @@ -680,6 +685,7 @@ createNewClip: "新建便签" unclip: "移除便签" confirmToUnclipAlreadyClippedNote: "本帖已包含在便签 \"{name}\" 里。您想要将本帖从该便签中移除吗?" public: "公开" +private: "私密" i18nInfo: "Misskey 已经被志愿者们翻译成了各种语言。如果你也有兴趣,可以通过 {link} 帮助翻译。" manageAccessTokens: "管理 Access Tokens" accountInfo: "账户信息" @@ -1018,7 +1024,7 @@ retryAllQueuesConfirmText: "可能会使服务器负荷在一定时间内增加" enableChartsForRemoteUser: "生成远程用户的图表" enableChartsForFederatedInstances: "生成远程服务器的图表" showClipButtonInNoteFooter: "在贴文下方显示便签按钮" -largeNoteReactions: "使用大图标来显示回应" +reactionsDisplaySize: "回应显示大小" noteIdOrUrl: "帖子 ID 或 URL" video: "视频" videos: "视频" @@ -1094,6 +1100,28 @@ expired: "已过期" doYouAgree: "你同意吗?" beSureToReadThisAsItIsImportant: "请好好阅读,这真的很重要。" iHaveReadXCarefullyAndAgree: "我已经仔细阅读并同意了「{x}」的内容。" +dialog: "对话框" +icon: "头像" +forYou: "您的" +currentAnnouncements: "现在的公告" +pastAnnouncements: "过去的公告" +youHaveUnreadAnnouncements: "您有未读的公告" +useSecurityKey: "请根据浏览器或设备的提示,使用安全密钥或通行密钥。" +replies: "回复" +renotes: "转发" +loadReplies: "查看回复" +loadConversation: "查看对话" +pinnedList: "已置顶的列表" +keepScreenOn: "保持设备屏幕开启" +_announcement: + forExistingUsers: "仅限现有用户" + forExistingUsersDescription: "若启用,该公告将仅对创建此公告时存在的用户可见。 如果禁用,则在创建此公告后注册的用户也可以看到该公告。" + needConfirmationToRead: "需要确认才能标记为已读" + needConfirmationToReadDescription: "若启用,则会在标记已读时会显示确认对话框。此外,它也会不受批量已读操作的影响。" + end: "结束公告" + tooManyActiveAnnouncementDescription: "若有大量活动公告,可能会造成用户体验可能下降。请考虑归档已完成的公告。" + readConfirmTitle: "标记为已读?" + readConfirmText: "阅读“{title}”的内容并将其标记为已读。" _initialAccountSetting: accountCreated: "账户创建完成了!" letsStartAccountSetup: "来进行帐户的初始设置吧。" @@ -1111,6 +1139,13 @@ _initialAccountSetting: laterAreYouSure: "要稍后再进行初始设定吗?" _serverRules: description: "在新用户注册前显示服务器的简单规则。推荐显示服务条款的主要内容。" +_serverSettings: + iconUrl: "图标 URL" + appIconDescription: "指定当 {host} 显示为 app 时的图标。" + appIconUsageExample: "例如:作为书签添加到 PWA 或手机主屏幕的时候" + appIconStyleRecommendation: "因为有可能会被裁切为圆形或者圆角矩形,建议使用边缘带有留白背景的图标。" + appIconResolutionMustBe: "分辨率必须为 {resolution}。" + manifestJsonOverride: "覆盖 mainfest.json" _accountMigration: moveFrom: "从别的账号迁移到此账户" moveFromSub: "为另一个账户建立别名" @@ -1365,6 +1400,9 @@ _achievements: title: "Brain Diver" description: "发布了包含 Brain Diver 链接的帖子" flavor: "Misskey-Misskey La-Tu-Ma" + _smashTestNotificationButton: + title: "过度测试" + description: "短时间内连续测试通知" _role: new: "创建角色" edit: "编辑角色" @@ -1679,14 +1717,14 @@ _2fa: step1: "首先,在您的设备上安装验证应用,例如 {a} 或 {b}。" step2: "然后,扫描屏幕上显示的二维码。" step2Click: "通过点击二维码,您可以使用设备上安装的身份验证器应用程序或密钥环进行注册" - step2Url: "在桌面应用程序中输入以下 URL:" + step2Uri: "如果使用桌面应用程序的话,请输入下面的 URI" step3Title: "输入验证码" step3: "输入您的应用提供的动态口令以完成设置。" + setupCompleted: "设置完成" step4: "从现在开始,任何登录操作都将要求您提供动态口令。" securityKeyNotSupported: "您的浏览器不支持安全密钥。" registerTOTPBeforeKey: "要注册安全密钥或 Passkey,请先设置验证器应用程序。" securityKeyInfo: "注册兼容 WebAuthn 的密钥,例如支持 FIDO2 的硬件安全密钥、设备上的生物识别功能、PIN 码以及 Passkey 等。" - chromePasskeyNotSupported: "目前不支持 Chrome 的 Passkey。" registerSecurityKey: "注册安全密钥或 Passkey" securityKeyName: "输入密钥名称" tapSecurityKey: "请按照浏览器说明操作来注册安全密钥或 Passkey。" @@ -1697,6 +1735,11 @@ _2fa: renewTOTPConfirm: "当前验证器应用程序的验证码将不再有效" renewTOTPOk: "重新配置" renewTOTPCancel: "不用,谢谢" + checkBackupCodesBeforeCloseThisWizard: "在关闭此窗口前,请确认下面的备用代码" + backupCodes: "备用代码" + backupCodesDescription: "如果无法使用认证应用,可以使用以下的备用代码来访问账户。请务必将这些代码保存在安全的地方。每个代码仅可使用一次。" + backupCodeUsedWarning: "已使用备用代码。如果无法使用认证应用,请尽快重新设定。" + backupCodesExhaustedWarning: "已使用完所有的备用代码。如果无法使用认证应用,将无法再访问您的账户。请再次设定认证应用。" _permissions: "read:account": "查看账户信息" "write:account": "更改帐户信息" @@ -1730,6 +1773,10 @@ _permissions: "write:gallery": "操作图库" "read:gallery-likes": "读取喜欢的图片" "write:gallery-likes": "操作喜欢的图片" + "read:flash": "查看 Play" + "write:flash": "编辑 Play" + "read:flash-likes": "查看 Play 的点赞" + "write:flash-likes": "编辑 Play 的点赞列表" _auth: shareAccessTitle: "应用程序授权许可" shareAccess: "您要授权允许 “{name}” 访问您的帐户吗?" @@ -1965,6 +2012,10 @@ _notification: unreadAntennaNote: "天线 {name}" emptyPushNotificationMessage: "推送通知已更新" achievementEarned: "获得成就" + testNotification: "测试通知" + checkNotificationBehavior: "检查通知显示" + sendTestNotification: "发送测试通知" + notificationWillBeDisplayedLikeThis: "通知将会这样表示" _types: all: "全部" follow: "关注中" @@ -2000,6 +2051,8 @@ _deck: introduction2: "您可以随时通过屏幕右侧的 + 来添加列" widgetsIntroduction: "从列菜单中,选择“小工具编辑”来添加小工具" useSimpleUiForNonRootPages: "用简易UI表示非根页面" + usedAsMinWidthWhenFlexible: "「自适应宽度」被启用的时候,这就是最小的宽度" + flexible: "自适应宽度" _columns: main: "主列" widgets: "小工具" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index df4122ef3d23..39f2f2ff294a 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -1,9 +1,9 @@ --- _lang_: "繁體中文" headlineMisskey: "貼文連繫網絡" -introMisskey: "歡迎!Misskey 是一個開源且去中心化的社群網路服務。\n發佈「貼文」向身邊的人分享您的想法!📡\n利用「反應」表達您對貼文的感覺!👍\n讓我們一起探索新的世界吧!🚀" -poweredByMisskeyDescription: "{name}是使用開放原始碼平台Misskey的服務之一(稱為 Misskey 伺服器)。\n" -monthAndDay: "{month}月 {day}日" +introMisskey: "歡迎!Misskey 是一個開放原始碼且去中心化的社群網路服務。\n發布「貼文」向身邊的人分享您的想法!📡\n利用「反應」表達您對貼文的感覺!👍\n讓我們一起探索新的世界吧!🚀" +poweredByMisskeyDescription: "{name}是開放原始碼平臺 Misskey 的伺服器之一。" +monthAndDay: "{month} 月 {day} 日" search: "搜尋" notifications: "通知" username: "使用者名稱" @@ -15,8 +15,8 @@ gotIt: "知道了" cancel: "取消" noThankYou: "現在不要" enterUsername: "輸入使用者名稱" -renotedBy: "{user} 轉發了" -noNotes: "無貼文。" +renotedBy: "{user} 轉傳了" +noNotes: "無貼文" noNotifications: "沒有通知" instance: "伺服器" settings: "設定" @@ -26,7 +26,7 @@ otherSettings: "其他設定" openInWindow: "在新視窗開啟" profile: "個人檔案" timeline: "時間軸" -noAccountDescription: "此用戶還沒有自我介紹" +noAccountDescription: "此使用者尚未自我介紹" login: "登入" loggingIn: "登入中" logout: "登出" @@ -45,6 +45,7 @@ pin: "置頂" unpin: "取消置頂" copyContent: "複製內容" copyLink: "複製連結" +copyLinkRenote: "複製轉貼連結" delete: "刪除" deleteAndEdit: "刪除並編輯" deleteAndEditConfirm: "要刪除並再次編輯嗎?此貼文的所有反應、轉發和回覆也將會消失。" @@ -105,10 +106,10 @@ unfollow: "取消追隨" followRequestPending: "追隨許可待批准" enterEmoji: "輸入表情符號" renote: "轉發" -unrenote: "取消轉發" -renoted: "轉發成功" -cantRenote: "無法轉發此貼文。" -cantReRenote: "無法轉發之前已經轉發過的內容。" +unrenote: "取消轉傳" +renoted: "轉傳成功" +cantRenote: "無法轉傳此貼文。" +cantReRenote: "無法轉傳之前已經轉傳過的內容。" quote: "引用" inChannelRenote: "在頻道內轉發" inChannelQuote: "在頻道內引用" @@ -135,8 +136,8 @@ block: "封鎖" unblock: "解除封鎖" suspend: "凍結" unsuspend: "解除凍結" -blockConfirm: "確定要封鎖此用戶?" -unblockConfirm: "確定解除封鎖此用戶?" +blockConfirm: "確定要封鎖此使用者嗎?" +unblockConfirm: "確定要解除封鎖此使用者嗎?" suspendConfirm: "確定凍結此帳戶?" unsuspendConfirm: "確定解凍此帳戶?" selectList: "選擇清單" @@ -155,15 +156,16 @@ emojiUrl: "表情符號URL" addEmoji: "新增表情符號" settingGuide: "推薦設定" cacheRemoteFiles: "快取遠端檔案" -cacheRemoteFilesDescription: "禁用此設定會停止遠端檔案的緩存,從而節省儲存空間,但資料會因直接連線從而產生額外連接數據。" +cacheRemoteFilesDescription: "禁用此設定會停止建立遠端檔案快取,從而節省伺服器儲存空間,但會因從遠端讀取資料而增加網路數據用量。" +youCanCleanRemoteFilesCache: "按檔案管理的🗑️按鈕,將快取全部刪除。" cacheRemoteSensitiveFiles: "快取遠端的敏感檔案" cacheRemoteSensitiveFilesDescription: "若停用這個設定,則不會快取遠端的敏感檔案,而是直接連結。" flagAsBot: "此使用者是機器人" -flagAsBotDescription: "如果本帳戶是由程式控制,請啟用此選項。啟用後,會作為標示幫助其他開發者防止機器人之間產生無限互動的行為,並會調整Misskey內部系統將本帳戶識別為機器人。" +flagAsBotDescription: "標記本帳戶由程式控制,防止其他程式與本帳戶產生無限互動的行為。" flagAsCat: "此帳戶是一隻貓,喵~~~!!!" flagAsCatDescription: "如果想將本帳戶標示為一隻貓,請開啟此標示" flagShowTimelineReplies: "在時間軸上顯示貼文的回覆" -flagShowTimelineRepliesDescription: "啟用時,時間線除了顯示用戶的貼文以外,還會顯示用戶對其他貼文的回覆。" +flagShowTimelineRepliesDescription: "啟用時,時間軸除了顯示使用者的貼文以外,還會顯示使用者對其他貼文的回覆。" autoAcceptFollowed: "自動允許來自追隨中使用者的追隨請求" addAccount: "新增帳戶" reloadAccountsList: "更新帳戶清單的資訊" @@ -177,12 +179,12 @@ searchWith: "搜尋: {q}" youHaveNoLists: "你沒有任何清單" followConfirm: "你真的要追隨{name}嗎?" proxyAccount: "代理帳戶" -proxyAccountDescription: "代理帳戶是在某些情況下充當其他伺服器用戶的帳戶。例如,當使用者將一個來自其他伺服器的帳戶放在列表中時,由於沒有其他使用者追隨該帳戶,該指令不會傳送到該伺服器上,因此會由代理帳戶追隨。" +proxyAccountDescription: "代理帳戶是在特定條件下充當遠端追隨者的帳戶。例如,當使用者新增遠端使用者至其列表時,若沒有本地使用者追隨該遠端使用者,則其活動將不會傳送至伺服器,此時便會由代理帳戶代為追隨以解決問題。" host: "主機" selectUser: "選取使用者" recipient: "收件人" annotation: "註解" -federation: "站台聯邦" +federation: "聯邦宇宙" instances: "伺服器" registeredAt: "初次觀測" latestRequestReceivedAt: "上次收到的請求" @@ -197,10 +199,10 @@ operations: "操作" software: "軟體" version: "版本" metadata: "元資料" -withNFiles: "{n}個檔案" +withNFiles: "{n} 個檔案" monitor: "監視器" jobQueue: "佇列" -cpuAndMemory: "CPU及記憶體用量" +cpuAndMemory: "CPU 及記憶體" network: "網路" disk: "硬碟" instanceInfo: "伺服器資訊" @@ -213,8 +215,8 @@ clearCachedFilesConfirm: "確定要清除所有遠端暫存資料嗎?" blockedInstances: "已封鎖的伺服器" blockedInstancesDescription: "請逐行輸入需要封鎖的伺服器。已封鎖的伺服器將無法與本伺服器進行通訊。" muteAndBlock: "靜音和封鎖" -mutedUsers: "已靜音用戶" -blockedUsers: "已封鎖用戶" +mutedUsers: "被靜音的使用者" +blockedUsers: "被封鎖的使用者" noUsers: "沒有任何使用者" editProfile: "編輯個人檔案" noteDeleteConfirm: "確定刪除此貼文嗎?" @@ -236,7 +238,7 @@ publishing: "直播中" notResponding: "沒有回應" instanceFollowing: "追隨的伺服器" instanceFollowers: "伺服器的追隨者" -instanceUsers: "用戶" +instanceUsers: "伺服器使用者" changePassword: "修改密碼" security: "安全性" retypedNotMatch: "兩次輸入不一致。" @@ -246,7 +248,7 @@ newPasswordRetype: "確認密碼" attachFile: "上傳附件" more: "更多!" featured: "精選" -usernameOrUserId: "使用者名稱或使用者ID" +usernameOrUserId: "使用者名稱或使用者 ID" noSuchUser: "使用者不存在" lookup: "查詢" announcements: "公告" @@ -262,16 +264,16 @@ upload: "上傳" keepOriginalUploading: "保留原圖" keepOriginalUploadingDescription: "上傳圖片時保留原始圖片。關閉時,瀏覽器會在上傳時生成適用於網路傳送的版本。" fromDrive: "從雲端空間" -fromUrl: "從URL" +fromUrl: "從 URL" uploadFromUrl: "從網址上傳" -uploadFromUrlDescription: "您要上傳的文件的URL" +uploadFromUrlDescription: "您要上傳的檔案網址" uploadFromUrlRequested: "已請求上傳" uploadFromUrlMayTakeTime: "還需要一些時間才能完成上傳。" explore: "探索" messageRead: "已讀" noMoreHistory: "沒有更多歷史紀錄" startMessaging: "開始聊天" -nUsersRead: "{n}人已讀" +nUsersRead: "{n} 人已讀" agreeTo: "我同意{0}" agree: "同意" agreeBelow: "同意以下內容" @@ -284,7 +286,7 @@ activity: "動態" images: "圖片" image: "圖片" birthday: "生日" -yearsOld: "{age}歲" +yearsOld: "{age} 歲" registeredDate: "註冊日期" location: "位置" theme: "外觀主題" @@ -292,8 +294,8 @@ themeForLightMode: "在淺色模式下使用的主題" themeForDarkMode: "在深色模式下使用的主題" light: "淺色" dark: "深色" -lightThemes: "明亮主題" -darkThemes: "黑暗主題" +lightThemes: "淺色主題" +darkThemes: "深色主題" syncDeviceDarkMode: "同步至此裝置的深色模式設定" drive: "雲端硬碟" fileName: "檔案名稱" @@ -334,31 +336,30 @@ instanceName: "伺服器名稱" instanceDescription: "伺服器介紹" maintainerName: "管理員名稱" maintainerEmail: "管理員郵箱" -tosUrl: "服務條款URL" +tosUrl: "服務條款 URL" thisYear: "本年" thisMonth: "本月" today: "本日" -dayX: "{day}日" -monthX: "{month}月" -yearX: "{year}年" +dayX: "{day} 日" +monthX: "{month} 月" +yearX: "{year} 年" pages: "頁面" integration: "整合" connectService: "已連結" disconnectService: "已斷開 " enableLocalTimeline: "啟用本地時間軸" enableGlobalTimeline: "啟用全域時間軸" -disablingTimelinesInfo: "為了方便,即使您關閉了時間線功能,管理員和審查員仍可以繼續使用。" +disablingTimelinesInfo: "為了方便,即使您關閉了時間軸功能,管理員和審查員仍可以繼續使用。" registration: "註冊" enableRegistration: "開放新使用者註冊" invite: "邀請" -driveCapacityPerLocalAccount: "每個本地用戶的雲端空間大小" +driveCapacityPerLocalAccount: "每個本地使用者的雲端硬碟容量" driveCapacityPerRemoteAccount: "每個非本地用戶的雲端空間大小" inMb: "以Mbps為單位" -iconUrl: "圖標 URL(例如 favicon)" bannerUrl: "橫幅圖片URL" backgroundImageUrl: "背景圖片的來源網址 " basicInfo: "基本資訊" -pinnedUsers: "置頂用戶" +pinnedUsers: "置頂使用者" pinnedUsersDescription: "在「探索」頁面中使用換行標記想要置頂的使用者。" pinnedPages: "釘選頁面" pinnedPagesDescription: "輸入要固定至實例首頁的頁面路徑,以換行符分隔。" @@ -386,7 +387,7 @@ antennaExcludeKeywords: "排除關鍵字" antennaKeywordsDescription: "空格代表「以及」(AND),換行代表「或者」(OR)" notifyAntenna: "通知有新貼文" withFileAntenna: "僅帶有附件的貼文" -enableServiceworker: "啟用 ServiceWorker" +enableServiceworker: "啟用瀏覽器的推播通知" antennaUsersDescription: "填寫使用者名稱,以換行分隔" caseSensitive: "區分大小寫" withReplies: "包含回覆" @@ -394,7 +395,7 @@ connectedTo: "您的帳戶已連接到以下社交帳戶" notesAndReplies: "貼文與回覆" withFiles: "附件" silence: "禁言" -silenceConfirm: "確定要禁言此帳戶嗎?" +silenceConfirm: "確定要禁言此使用者嗎?" unsilence: "解除禁言" unsilenceConfirm: "確定要解除禁言嗎?" popularUsers: "熱門使用者" @@ -410,10 +411,13 @@ aboutMisskey: "關於 Misskey" administrator: "管理員" token: "權杖" 2fa: "雙重驗證" +setupOf2fa: "設定雙重驗證" totp: "驗證應用程式" totpDescription: "以驗證應用程式輸入一次性密碼" moderator: "審查員" moderation: "審查" +moderationNote: "管理筆記" +addModerationNote: "新增管理筆記" nUsersMentioned: "被提及到 {n} 次" securityKeyAndPasskey: "安全金鑰、Passkey" securityKey: "安全金鑰" @@ -426,8 +430,8 @@ resetPassword: "重設密碼" newPasswordIs: "新密碼為「{password}」" reduceUiAnimation: "減少介面的動態視覺" share: "分享" -notFound: "找不到" -notFoundDescription: "找不到該 URL 的頁面" +notFound: "查無項目" +notFoundDescription: "查無此頁" uploadFolder: "預設上傳資料夾" cacheClear: "清除快取" markAsReadAllNotifications: "標記所有通知為已讀" @@ -476,7 +480,7 @@ disableDrawer: "不顯示下拉式選單" showNoteActionsOnlyHover: "僅在游標停留時顯示貼文的操作選項" noHistory: "沒有歷史紀錄" signinHistory: "登入歷史" -enableAdvancedMfm: "啟用高級 MFM" +enableAdvancedMfm: "啟用進階 MFM" enableAnimatedMfm: "啟用 MFM 動畫" doing: "正在進行" category: "類別" @@ -487,7 +491,7 @@ existingAccount: "現有帳戶" regenerate: "再次生成" fontSize: "字體大小" mediaListWithOneImageAppearance: "只有一張圖片時的媒體列表高度" -limitTo: "上限為{x}" +limitTo: "上限為 {x}" noFollowRequests: "沒有追隨您的請求" openImageInNewTab: "於新分頁中開啟圖片" dashboard: "儀表板" @@ -504,10 +508,10 @@ promote: "推廣" numberOfDays: "有效天數" hideThisNote: "隱藏此貼文" showFeaturedNotesInTimeline: "在時間軸上顯示熱門推薦" -objectStorage: "Object Storage (物件儲存)" -useObjectStorage: "使用Object Storage" +objectStorage: "對象存儲" +useObjectStorage: "使用對象存儲" objectStorageBaseUrl: "Base URL" -objectStorageBaseUrlDesc: "引用時的 URL。如果您使用的是 CDN 或反向代理,請指定其 URL,例如 S3(https://.s3.amazonaws.com)、GCS(https://storage.googleapis.com/)。" +objectStorageBaseUrlDesc: "用於引用的 URL。如果您使用的是 CDN 或反向代理,請指定其 URL,例如 S3(https://.s3.amazonaws.com)、GCS(https://storage.googleapis.com/)。" objectStorageBucket: "儲存空間(Bucket)" objectStorageBucketDesc: "請填寫所用服務的儲存空間(Bucket)名稱。 " objectStoragePrefix: "前綴" @@ -532,7 +536,7 @@ sound: "音效" listen: "聆聽" none: "無" showInPage: "在頁面中顯示" -popout: "彈出型窗口" +popout: "彈出式視窗" volume: "音量" masterVolume: "主音量" details: "詳細資訊" @@ -542,7 +546,7 @@ recentUsed: "最近使用" install: "安裝" uninstall: "解除安裝" installedApps: "已授權的應用程式" -nothing: "未發現" +nothing: "無" installedDate: "安裝時間" lastUsedDate: "最後上線日期" state: "狀態" @@ -550,7 +554,7 @@ sort: "排序" ascendingOrder: "昇冪" descendingOrder: "降冪" scratchpad: "暫存記憶體" -scratchpadDescription: "AiScript 控制台為 AiScript 的實驗環境。您可以在此編寫、執行和確認程式碼與 Misskey 互動的结果。" +scratchpadDescription: "AiScript 控制臺為 AiScript 的實驗環境。您可以在此編寫、執行和確認程式碼與 Misskey 互動的結果。" output: "輸出" script: "腳本" disablePagesScript: "停用頁面的 AiScript 腳本" @@ -559,8 +563,8 @@ deleteAllFiles: "刪除所有檔案" deleteAllFilesConfirm: "要刪除所有檔案嗎?" removeAllFollowing: "解除所有追隨" removeAllFollowingDescription: "解除{host}所有的追隨。在伺服器不再存在時執行。" -userSuspended: "該使用者已被停用" -userSilenced: "該用戶已被禁言。" +userSuspended: "該使用者已被停用。" +userSilenced: "該使用者已被禁言。" yourAccountSuspendedTitle: "帳戶已被凍結" yourAccountSuspendedDescription: "該帳戶已因違反伺服器服務條款或其他原因而被凍結。您可以向管理員查詢更多資訊。請不要建立新帳戶。" tokenRevoked: "權杖無效" @@ -653,6 +657,7 @@ behavior: "行為" sample: "範例" abuseReports: "檢舉" reportAbuse: "檢舉" +reportAbuseRenote: "檢舉轉貼" reportAbuseOf: "檢舉{name}" fillAbuseReportDescription: "請填寫檢舉的詳細理由。如有需要,請附上相關 URL。" abuseReported: "檢舉完成。感謝您的報告。" @@ -680,7 +685,8 @@ createNewClip: "建立新摘錄" unclip: "解除摘錄" confirmToUnclipAlreadyClippedNote: "此貼文已包含在摘錄「{name}」中。 你想將貼文從這個摘錄中排除嗎?" public: "公開" -i18nInfo: "Misskey 已被志願者們翻譯成各種語言版本。您可以瀏覽{link}幫助翻譯。" +private: "私密" +i18nInfo: "Misskey 已被志願者們翻譯成各種語言版本。您可以瀏覽 {link} 幫助翻譯。" manageAccessTokens: "管理存取權杖" accountInfo: "帳戶資訊" notesCount: "貼文數量" @@ -688,7 +694,7 @@ repliesCount: "回覆數量" renotesCount: "轉發數量" repliedCount: "回覆數量" renotedCount: "轉發次數" -followingCount: "正在追隨的用戶數量" +followingCount: "正在追隨的使用者數量" followersCount: "追隨者數量" sentReactionsCount: "反應發送次數" receivedReactionsCount: "收到反應次數" @@ -719,7 +725,7 @@ thisIsExperimentalFeature: "這是實驗性的功能。可能會有變更規格 developer: "開發者" makeExplorable: "使自己的帳戶能夠在「探索」頁面中顯示" makeExplorableDescription: "如果關閉,帳戶將不會被顯示在「探索」頁面中。" -showGapBetweenNotesInTimeline: "分開顯示時間線上的貼文。" +showGapBetweenNotesInTimeline: "分開顯示時間軸上的貼文。" duplicate: "複製" left: "左" center: "置中" @@ -729,11 +735,11 @@ reloadToApplySetting: "設定將會在頁面重新載入之後生效。要現在 needReloadToApply: "必須重新載入才會生效。" showTitlebar: "顯示標題列" clearCache: "清除快取資料" -onlineUsersCount: "{n}人正在線上" -nUsers: "{n}用戶" -nNotes: "{n}貼文" +onlineUsersCount: "{n} 人上線" +nUsers: "{n} 使用者" +nNotes: "{n} 貼文" sendErrorReports: "傳送錯誤報告" -sendErrorReportsDescription: "啟用後,問題報告將傳送至開發者以提升軟體品質。問題報告可能包括OS版本,瀏覽器類型,行為歷史記錄等。" +sendErrorReportsDescription: "傳送問題報告至開發者以提升軟體品質。問題報告可能包括作業系統版本,瀏覽器類型,行為歷史記錄等。" myTheme: "我的佈景主題" backgroundColor: "背景" accentColor: "重點色彩" @@ -765,18 +771,18 @@ inChannelSearch: "頻道内搜尋" useReactionPickerForContextMenu: "點擊右鍵開啟反應工具欄" typingUsers: "{users}輸入中" jumpToSpecifiedDate: "跳轉到特定日期" -showingPastTimeline: "顯示過往的時間線" +showingPastTimeline: "顯示過往的時間軸" clear: "清除" markAllAsRead: "全部標示為已讀" goBack: "返回" unlikeConfirm: "要取消按讚嗎?" -fullView: "全熒幕顯示" +fullView: "全螢幕顯示" quitFullView: "退出全螢幕顯示" addDescription: "新增描述" -userPagePinTip: "在貼文的選單中選擇\"置頂\",即可置頂該貼文至您的個人檔案頁面。" +userPagePinTip: "在貼文的選單中選擇「置頂」,即可置頂該貼文至您的個人檔案頁面。" notSpecifiedMentionWarning: "此貼文有未指定的提及" info: "資訊" -userInfo: "用戶資料" +userInfo: "使用者資訊" unknown: "未知" onlineStatus: "上線狀態" hideOnlineStatus: "隱藏上線狀態" @@ -835,7 +841,7 @@ accountDeletionInProgress: "正在刪除帳戶" usernameInfo: "在伺服器上您的帳戶是唯一的識別名稱。您可以使用字母 (a ~ z, A ~ Z)、數字 (0 ~ 9) 和下底線 (_)。之後帳戶名是不能更改的。" aiChanMode: "小藍模式" devMode: "開發者模式" -keepCw: "保持 CW" +keepCw: "保持隱藏內容" pubSub: "Pub/Sub 帳戶" lastCommunication: "最近的通信" resolved: "已解決" @@ -849,7 +855,7 @@ off: "關閉" emailRequiredForSignup: "註冊帳戶需要電子郵件地址" unread: "未讀" filter: "篩選" -controlPanel: "控制台" +controlPanel: "控制臺" manageAccounts: "管理帳戶" makeReactionsPublic: "將反應設為公開" makeReactionsPublicDescription: "將您做過的反應設為公開可見。" @@ -893,8 +899,8 @@ cropImageAsk: "要剪裁圖片嗎?" cropYes: "裁剪" cropNo: "使用原圖" file: "檔案" -recentNHours: "過去{n}小時" -recentNDays: "過去{n}天" +recentNHours: "過去 {n} 小時" +recentNDays: "過去 {n} 天" noEmailServerWarning: "尚未設定電子郵件伺服器。" thereIsUnresolvedAbuseReportWarning: "有尚未處理的檢舉。" recommended: "推薦" @@ -930,7 +936,7 @@ cannotUploadBecauseExceedsFileSizeLimit: "由於超過了檔案大小的限制 beta: "測試版" enableAutoSensitive: "自動 NSFW 判定" enableAutoSensitiveDescription: "如果可用,它將使用機器學習技術判斷多媒體內容是否需要標記 NSFW。即使關閉此功能,也可能會依實例規則而自動啟用。" -activeEmailValidationDescription: "積極驗證帳戶的電郵地址,以判斷它是否可以通訊。關閉此選項代表只會檢查地址是否符合格式。" +activeEmailValidationDescription: "積極驗證使用者的電郵地址,以判斷它是否可以通訊。關閉此選項代表只會檢查地址是否符合格式。" navbar: "導覽列" shuffle: "隨機" account: "帳戶" @@ -1015,10 +1021,10 @@ drivecleaner: "雲端硬碟清掃器" retryAllQueuesNow: "立刻重試所有佇列" retryAllQueuesConfirmTitle: "要現在重試嗎?" retryAllQueuesConfirmText: "伺服器的負荷可能會暫時增加。" -enableChartsForRemoteUser: "生成遠端用戶的圖表" +enableChartsForRemoteUser: "生成遠端使用者的圖表" enableChartsForFederatedInstances: "生成遠端伺服器的圖表" showClipButtonInNoteFooter: "新增摘錄至貼文" -largeNoteReactions: "放大顯示貼文反應" +reactionsDisplaySize: "表情回應的顯示尺寸" noteIdOrUrl: "貼文ID或URL" video: "影片" videos: "影片" @@ -1094,6 +1100,27 @@ expired: "過期" doYouAgree: "你同意嗎?" beSureToReadThisAsItIsImportant: "重要,請務必閱讀。" iHaveReadXCarefullyAndAgree: "我已仔細閱讀並同意「{x}」的内容。" +dialog: "對話方塊" +icon: "圖示" +forYou: "給您" +currentAnnouncements: "最新公告" +pastAnnouncements: "歷史公告" +youHaveUnreadAnnouncements: "有未讀的公告。" +useSecurityKey: "請按照瀏覽器或設備上的說明使用安全金鑰或 Passkey。" +replies: "回覆" +renotes: "轉發" +loadReplies: "閱覽回覆" +loadConversation: "閱覽對話" +pinnedList: "已置頂的清單" +_announcement: + forExistingUsers: "僅限既有的使用者" + forExistingUsersDescription: "啟用代表僅向現存使用者顯示;停用代表張貼後註冊的新使用者也會看到。" + needConfirmationToRead: "必須確認才能標記為已讀" + needConfirmationToReadDescription: "啟用代表此公告將顯示對話方塊以確認是否標記為已讀,同時不會受「標記所有公告為已讀」功能影響。" + end: "結束公告" + tooManyActiveAnnouncementDescription: "有過多公告可能會影響使用者體驗。請考慮歸檔已結束的公告。" + readConfirmTitle: "標記為已讀嗎?" + readConfirmText: "閱讀「{title}」的內容並標記為已讀。" _initialAccountSetting: accountCreated: "帳戶已建立完成!" letsStartAccountSetup: "來進行帳戶的初始設定吧。" @@ -1106,11 +1133,18 @@ _initialAccountSetting: pushNotificationDescription: "啟用推送通知,就可以在設備上接收{name}的通知。" initialAccountSettingCompleted: "初始設定完成了!" haveFun: "盡情享受{name}吧!" - ifYouNeedLearnMore: "關於如何使用{name}(Misskey)的詳細資訊,請見{link}。" + ifYouNeedLearnMore: "請瀏覽{link}以更瞭解{name}(Misskey)的使用方法。" skipAreYouSure: "要略過初始設定嗎?" laterAreYouSure: "稍後再重新進行初始設定嗎?" _serverRules: description: "設定在註冊頁面顯示的伺服器簡要規則。建議是服務條款的摘要。" +_serverSettings: + iconUrl: "圖示的 URL" + appIconDescription: "指定顯示 {host} 為應用程式時的圖示。" + appIconUsageExample: "例如:漸進式網路應用程式(PWA)、於手機桌面新增書籤" + appIconStyleRecommendation: "因為可能會裁剪成圓形或圓角,所以建議用單色填滿邊框及背景。" + appIconResolutionMustBe: "解析度必須為 {resolution}。" + manifestJsonOverride: "覆寫 manifest.json" _accountMigration: moveFrom: "從其他帳戶遷移到這個帳戶" moveFromSub: "為另一個帳戶建立別名" @@ -1119,7 +1153,7 @@ _accountMigration: moveTo: "將這個帳戶遷移至新的帳戶" moveToLabel: "要遷移到的帳戶:" moveCannotBeUndone: "一旦遷移帳戶,就無法取消。" - moveAccountDescription: "這個操作不可撤銷。首先,請確認已在要遷移到的帳戶中為這個帳戶建立了一個別名。建立別名之後,像這樣輸入你要遷移到的帳戶:@person@instance.com" + moveAccountDescription: "遷移至新帳戶。\n ・此帳戶的追隨者將自動追隨新帳戶;\n ・此帳戶的所有追隨者將被取消追隨;\n ・此帳戶不能再發文。\n\n雖然會自動遷移您追隨者,但必須手動遷移您追隨的帳戶。請在遷移前匯出此帳戶的「追隨中」名單,並在遷移後自行匯入。\n列表名單、靜音名單及封鎖名單也必須如此處理。\n\n(此說明適用於本伺服器,以及運行 Misskey v13.12.0 或更新版本的其他伺服器;如 Mastodon 等使用 ActivityPub 協定的其他軟體或有不同的處理方式。)" moveAccountHowTo: "要遷移帳戶,首先要在目標帳戶中為此帳戶建立一個別名。\n 建立別名後,像這樣輸入目標帳戶:@username@server.example.com" startMigration: "遷移" migrationConfirm: "確定要將這個帳戶遷移至 {account} 嗎?一旦遷移就無法撤銷,也就無法以原來的狀態使用這個帳戶。\n另外,請確認在要遷移到的帳戶已經建立了一個別名。" @@ -1242,7 +1276,7 @@ _achievements: title: "有備而來" description: "設定了個人檔案" _markedAsCat: - title: "吾輩乃貓是也" + title: "我是貓" description: "已將帳戶設定為貓" flavor: "還沒有名字。" _following1: @@ -1264,7 +1298,7 @@ _achievements: title: "第一個追隨者" description: "第一次被追隨" _followers10: - title: "Follow me!" + title: "追隨我吧!" description: "追隨者超過10人了" _followers50: title: "成群結隊" @@ -1273,20 +1307,20 @@ _achievements: title: "熱門人物" description: "追隨者超過100人了" _followers300: - title: "請排成一排" + title: "請排隊" description: "追隨者超過300人了" _followers500: - title: "基地台" + title: "基地臺" description: "超過五百名追隨者了" _followers1000: - title: "影響者" + title: "星光熠熠" description: "超過一千名追隨者了" _collectAchievements30: title: "成就收藏家" description: "獲得三十個以上的成就" _viewAchievements3min: - title: "喜愛成就" - description: "看成就列表要花三分鐘以上" + title: "成就發燒友" + description: "看著成就列表超過三分鐘" _iLoveMisskey: title: "I Love Misskey" description: "發佈「I ❤ #Misskey」" @@ -1298,34 +1332,34 @@ _achievements: title: "休息一下" description: "客戶端啟動已超過30分鐘" _client60min: - title: "Misskey看太多" + title: "Misskey 看太多" description: "客戶端啟動已超過60分鐘" _noteDeletedWithin1min: - title: "現在沒有了" - description: "發文後1分鐘內刪文" + title: "欲言又止" + description: "發文後一分鐘內刪文" _postedAtLateNight: - title: "夜行性" + title: "夜貓子" description: "在深夜發佈貼文" flavor: "該去睡覺了。" _postedAt0min0sec: title: "報時" - description: "在0分0秒發佈貼文" - flavor: "啵.啵.啵.嗶ー" + description: "在零分零秒發佈貼文" + flavor: "啵、啵、啵、嗶ーー" _selfQuote: title: "自我引用" description: "引用了自己的貼文" _htl20npm: - title: "流動的TL" - description: "在首頁時間軸的流速超過20npm" + title: "源源不絕" + description: "首頁時間軸在一分鐘內出現超過二十篇貼文" _viewInstanceChart: title: "分析師" description: "顯示了實例的圖表" _outputHelloWorldOnScratchpad: - title: "Hello world!" - description: "在暫存記憶體輸出了 hello world" + title: "Hello, world!" + description: "在 AiScript 控制臺輸出了「hello world」" _open3windows: title: "多重視窗" - description: "開啟了3個以上的視窗" + description: "開啟過三個以上的視窗" _driveFolderCircularReference: title: "循環引用" description: "試圖遞迴套入雲端硬碟資料夾" @@ -1337,34 +1371,37 @@ _achievements: description: "已點擊這裡了" _justPlainLucky: title: "只是運氣好" - description: "每10秒有0.01%的機率獲得" + description: "每十秒有二萬分之一(0.005%)的機率獲得" _setNameToSyuilo: - title: "神的情結" + title: "神與您同在" description: "將名稱設定為 syuilo" _passedSinceAccountCreated1: - title: "一周年" - description: "自建立帳戶開始過了1年" + title: "一週年" + description: "帳戶加入時間已超過一年" _passedSinceAccountCreated2: - title: "二周年" - description: "自建立帳戶開始過了2年" + title: "二週年" + description: "帳戶加入時間已超過兩年" _passedSinceAccountCreated3: - title: "三周年" - description: "自建立帳戶開始過了3年" + title: "三週年" + description: "帳戶加入時間已超過三年" _loggedInOnBirthday: title: "生日快樂" description: "在生日當天登入了" _loggedInOnNewYearsDay: title: "新年快樂" description: "在元旦當天登入了" - flavor: "今年也請對敝實例多多指教" + flavor: "今年也請您多多指教!" _cookieClicked: title: "點擊餅乾的遊戲" description: "點擊了餅乾" flavor: "是不是軟體有問題?" _brainDiver: title: "Brain Driver" - description: "發佈了Brain Driver的連結" + description: "發佈一篇含歌曲《Brain Driver》連結的貼文" flavor: "Misskey-Misskey La-Tu-Ma" + _smashTestNotificationButton: + title: "過度測試" + description: "極短時間內連續測試通知" _role: new: "建立角色" edit: "編輯角色" @@ -1387,7 +1424,7 @@ _role: chooseRoleToAssign: "選擇要指派的角色" iconUrl: "圖示的URL" asBadge: "顯示為徽章" - descriptionOfAsBadge: "開啟的話,角色圖示會顯示在用戶名旁邊。" + descriptionOfAsBadge: "開啟的話,角色圖示會顯示在使用者名稱旁邊。" isExplorable: "讓使用者更容易找到您" descriptionOfIsExplorable: "若開啟則公開角色時間軸。若角色不是公開的,則無法公開時間軸。" displayOrder: "顯示順序" @@ -1425,8 +1462,8 @@ _role: _condition: isLocal: "本地使用者" isRemote: "遠端使用者" - createdLessThan: "自建立帳戶開始~以內" - createdMoreThan: "自建立帳戶開始~經過" + createdLessThan: "帳戶加入時間不超過" + createdMoreThan: "帳戶加入時間已超過" followersLessThanOrEq: "追隨者人數在~以下" followersMoreThanOrEq: "追隨者人數在~以上" followingLessThanOrEq: "追隨人數在~以下" @@ -1452,7 +1489,7 @@ _emailUnavailable: smtp: "郵件伺服器沒有應答" _ffVisibility: public: "公開" - followers: "只有關注你的用戶能看到" + followers: "只有關注您的使用者能看到" private: "私密" _signup: almostThere: "即將完成" @@ -1513,7 +1550,7 @@ _registry: domain: "域" createKey: "新增機碼" _aboutMisskey: - about: "Misskey是由syuilo自2014年起開發的開源軟體。" + about: "Misskey 是由 syuilo 自 2014 年起開發的開放原始碼軟體。" contributors: "主要貢獻者" allContributors: "全體貢獻人員" source: "原始碼" @@ -1541,8 +1578,8 @@ _channel: featured: "熱門貼文" owned: "管理中" following: "追隨中" - usersCount: "有{n}人參與" - notesCount: "有{n}個貼文" + usersCount: "有 {n} 人參與" + notesCount: "有 {n} 篇貼文" nameAndDescription: "名稱與說明" nameOnly: "僅名稱" _menuDisplay: @@ -1560,7 +1597,7 @@ _wordMute: hard: "硬性靜音" mutedNotes: "已靜音的貼文" _instanceMute: - instanceMuteDescription: "包括對被靜音實例上的用戶的回覆,被設定的實例上所有貼文及轉發都會被靜音。" + instanceMuteDescription: "包括對被靜音實例上的使用者的回覆,被設定的實例上所有貼文及轉發都會被靜音。" instanceMuteDescription2: "換行以分隔" title: "將隱藏被設定的實例貼文。" heading: "將實例靜音" @@ -1622,9 +1659,9 @@ _theme: infoFg: "資訊內容" infoWarnBg: "警告背景" infoWarnFg: "警告文字" - cwBg: "CW 按鈕背景" - cwFg: "CW 按鈕文字" - cwHoverBg: "CW 按鈕背景(懸浮)" + cwBg: "隱藏內容按鈕背景" + cwFg: "隱藏內容按鈕文字" + cwHoverBg: "隱藏內容按鈕背景(懸浮)" toastBg: "通知背景" toastFg: "通知文本" buttonBg: "按鈕背景" @@ -1649,14 +1686,14 @@ _sfx: _ago: future: "未來" justNow: "剛剛" - secondsAgo: "{n}秒前" - minutesAgo: "{n}分鐘前" - hoursAgo: "{n}小時前" - daysAgo: "{n}天前" - weeksAgo: "{n}週前" - monthsAgo: "{n}個月前" - yearsAgo: "{n}年前" - invalid: "未發現" + secondsAgo: "{n} 秒前" + minutesAgo: "{n}分鐘前 " + hoursAgo: "{n} 小時前" + daysAgo: "{n} 天前" + weeksAgo: "{n} 週前" + monthsAgo: "{n} 個月前" + yearsAgo: "{n} 年前" + invalid: "無" _time: second: "秒" minute: "分鐘" @@ -1679,14 +1716,14 @@ _2fa: step1: "首先,在您的裝置上安裝驗證程式,例如 {a} 或 {b}。" step2: "然後,掃描螢幕上的 QR 碼。" step2Click: "您可以點擊 QR 碼,以使用裝置上的驗證應用程式或金鑰環註冊。" - step2Url: "請在桌面版應用程式中輸入以下的 URL:" + step2Uri: "使用桌面版應用程式時,請輸入以下的 URI" step3Title: "輸入驗證碼" step3: "輸入應用程式所提供的權杖以完成設定。" + setupCompleted: "設定完成" step4: "從現在開始,任何登入操作都將要求您提供權杖。" securityKeyNotSupported: "您的瀏覽器不支援安全金鑰。" registerTOTPBeforeKey: "如要註冊安全金鑰或 Passkey,請先設定驗證應用程式。" securityKeyInfo: "您可以設定使用支援 FIDO2 的硬體安全鎖、終端設備的指紋認證,或者 PIN 碼來登入。" - chromePasskeyNotSupported: "目前不支援 Chrome 的 Passkey。" registerSecurityKey: "註冊安全金鑰或 Passkey" securityKeyName: "輸入金鑰名稱" tapSecurityKey: "按照瀏覽器的說明註冊安全金鑰或 Passkey。" @@ -1697,16 +1734,21 @@ _2fa: renewTOTPConfirm: "目前驗證應用程式的驗證碼將無法使用。" renewTOTPOk: "重設" renewTOTPCancel: "現在不要" + checkBackupCodesBeforeCloseThisWizard: "請先確認下列備用驗證碼,再關閉此精靈視窗。" + backupCodes: "備用驗證碼" + backupCodesDescription: "如果驗證應用程式不能用了,可以使用以下的備用驗證碼存取您的帳戶。請務必妥善保管這個驗證碼。每個驗證碼只能使用一次。" + backupCodeUsedWarning: "已使用備用驗證碼。如果無法使用驗證應用程式,請盡快重新設定。" + backupCodesExhaustedWarning: "已使用所有備用驗證碼。如果無法使用驗證應用程式,則將無法再存取您的帳戶。請重新設定您的驗證應用程式。" _permissions: "read:account": "查看我的帳戶資訊" "write:account": "更改我的帳戶資訊" - "read:blocks": "已封鎖用戶名單" - "write:blocks": "編輯已封鎖用戶名單" + "read:blocks": "查看封鎖名單" + "write:blocks": "編輯封鎖名單" "read:drive": "存取雲端硬碟" "write:drive": "編輯雲端硬碟的檔案" "read:favorites": "瀏覽我的最愛" "write:favorites": "編輯我的最愛列表" - "read:following": "查看追隨中的用戶資訊" + "read:following": "查看追隨中的使用者資訊" "write:following": "追隨/解除追隨" "read:messaging": "顯示訊息" "write:messaging": "撰寫或刪除訊息" @@ -1730,10 +1772,14 @@ _permissions: "write:gallery": "操作圖庫" "read:gallery-likes": "讀取喜歡的圖片" "write:gallery-likes": "操作喜歡的圖片" + "read:flash": "檢視 Play" + "write:flash": "編輯 Play" + "read:flash-likes": "檢視 Play 的讚" + "write:flash-likes": "編輯 Play 的讚" _auth: shareAccessTitle: "應用程式的存取權限" shareAccess: "要授權「“{name}”」存取您的帳戶嗎?" - shareAccessAsk: "您確定要授權這個應用程式使用您的帳戶嗎?" + shareAccessAsk: "您確定要授權這個應用程式存取您的帳戶嗎?" permission: "{name}要求以下的權限" permissionAsk: "此應用程式需要以下權限" pleaseGoBack: "請返回至應用程式" @@ -1760,7 +1806,7 @@ _widgets: notifications: "通知" timeline: "時間軸" calendar: "行事曆" - trends: "發燒貼文" + trends: "熱門貼文" clock: "時鐘" rss: "RSS 閱讀器" rssTicker: "RSS 跑馬燈" @@ -1768,15 +1814,15 @@ _widgets: photos: "照片" digitalClock: "電子時鐘" unixClock: "UNIX 時間" - federation: "站台聯邦" + federation: "聯邦宇宙" instanceCloud: "實例雲" - postForm: "發佈窗口" + postForm: "發文視窗" slideshow: "幻燈片" button: "按鈕" - onlineUsers: "線上的用戶" + onlineUsers: "上線使用者" jobQueue: "佇列" serverMetric: "伺服器指標 " - aiscript: "AiScript 控制台" + aiscript: "AiScript 控制臺" aiscriptApp: "AiScript App" aichan: "小藍" userList: "使用者列表" @@ -1789,8 +1835,8 @@ _cw: chars: "{count} 個字元" files: "{count} 個檔案" _poll: - noOnlyOneChoice: "至少需要兩個選項。" - choiceN: "選擇 {n}" + noOnlyOneChoice: "需要至少兩個選項。" + choiceN: "選項 {n}" noMore: "沒辦法再添加選項了" canMultipleVote: "可以多次投票" expiration: "期限" @@ -1812,7 +1858,7 @@ _poll: remainingSeconds: "{s} 秒後截止" _visibility: public: "公開" - publicDescription: "發佈給所有帳戶" + publicDescription: "發佈給所有使用者" home: "首頁" homeDescription: "僅發布至首頁的時間軸" followers: "追隨者" @@ -1837,8 +1883,8 @@ _profile: username: "使用者名稱" description: "關於我" youCanIncludeHashtags: "你也可以在「關於我」中加上 #tag" - metadata: "進階資訊" - metadataEdit: "編輯進階資訊" + metadata: "附加資訊" + metadataEdit: "編輯附加資訊" metadataDescription: "可以在個人資料中以表格形式顯示其他資訊。" metadataLabel: "標籤" metadataContent: "内容" @@ -1851,10 +1897,10 @@ _exportOrImport: muteList: "靜音" blockingList: "封鎖" userLists: "清單" - excludeMutingUsers: "排除被靜音的用戶" + excludeMutingUsers: "排除被靜音的使用者" excludeInactiveUsers: "排除不活躍帳戶" _charts: - federation: "站台聯邦" + federation: "聯邦宇宙" apRequest: "請求" usersIncDec: "使用者增減" usersTotal: "使用者合共" @@ -1895,7 +1941,7 @@ _play: viewSource: "檢視原始碼" my: "自己的 Play" liked: "按讚的 Play" - featured: "人氣" + featured: "熱門" title: "標題" script: "腳本" summary: "描述" @@ -1917,7 +1963,7 @@ _pages: unlike: "收回讚好" my: "我的頁面" liked: "已讚好的頁面" - featured: "人氣" + featured: "熱門" inspector: "面板檢查" contents: "內容" content: "頁面方塊" @@ -1965,6 +2011,10 @@ _notification: unreadAntennaNote: "天線 {name}" emptyPushNotificationMessage: "推送通知已更新" achievementEarned: "獲得成就" + testNotification: "通知測試" + checkNotificationBehavior: "確認通知的顯示行為" + sendTestNotification: "發送測試通知" + notificationWillBeDisplayedLikeThis: "通知會以這樣的方式顯示" _types: all: "全部 " follow: "追隨中" @@ -1999,7 +2049,9 @@ _deck: introduction: "組合多個欄位,製作屬於自己的介面吧!" introduction2: "您可以隨時按畫面右方的「+」新增欄位。" widgetsIntroduction: "請從欄位選單中選擇「編輯小工具」新增小工具。" - useSimpleUiForNonRootPages: "用簡易 UI 顯示非根頁面" + useSimpleUiForNonRootPages: "用簡易介面顯示非根頁面" + usedAsMinWidthWhenFlexible: "如果啟用「自動調整寬度」,此為最小寬度" + flexible: "自動調整寬度" _columns: main: "主列" widgets: "小工具" diff --git a/package.json b/package.json index 283894f934be..8b5f57494708 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "misskey", - "version": "13.14.2", + "version": "2023.9.0-beta.10", "codename": "nasubi", "repository": { "type": "git", "url": "https://github.com/misskey-dev/misskey.git" }, - "packageManager": "pnpm@8.6.10", + "packageManager": "pnpm@8.7.6", "workspaces": [ "packages/frontend", "packages/backend", @@ -15,15 +15,15 @@ "private": true, "scripts": { "build-pre": "node ./scripts/build-pre.js", - "build": "pnpm build-pre && pnpm -r build && pnpm gulp", + "build-assets": "node ./scripts/build-assets.mjs", + "build": "pnpm build-pre && pnpm -r build && pnpm build-assets", "build-storybook": "pnpm --filter frontend build-storybook", - "start": "pnpm check:connect && cd packages/backend && node ./built/boot/index.js", - "start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/index.js", + "start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js", + "start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js", "init": "pnpm migrate", "migrate": "cd packages/backend && pnpm migrate", "check:connect": "cd packages/backend && pnpm check:connect", "migrateandstart": "pnpm migrate && pnpm start", - "gulp": "pnpm exec gulp build", "watch": "pnpm dev", "dev": "node ./scripts/dev.mjs", "lint": "pnpm -r lint", @@ -34,7 +34,6 @@ "jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage", "test": "pnpm -r test", "test-and-coverage": "pnpm -r test-and-coverage", - "format": "pnpm exec gulp format", "clean": "node ./scripts/clean.js", "clean-all": "node ./scripts/clean-all.js", "cleanall": "pnpm clean-all" @@ -44,23 +43,19 @@ "lodash": "4.17.21" }, "dependencies": { - "execa": "7.2.0", - "gulp": "4.0.2", - "gulp-cssnano": "2.1.3", - "gulp-rename": "2.0.0", - "gulp-replace": "1.1.4", - "gulp-terser": "2.1.0", + "execa": "8.0.1", + "cssnano": "6.0.1", "js-yaml": "4.1.0", - "typescript": "5.1.6" + "postcss": "8.4.30", + "terser": "5.20.0", + "typescript": "5.2.2" }, "devDependencies": { - "@types/gulp": "4.0.13", - "@types/gulp-rename": "2.0.2", - "@typescript-eslint/eslint-plugin": "6.2.0", - "@typescript-eslint/parser": "6.2.0", + "@typescript-eslint/eslint-plugin": "6.7.2", + "@typescript-eslint/parser": "6.7.2", "cross-env": "7.0.3", - "cypress": "12.17.2", - "eslint": "8.46.0", + "cypress": "13.2.0", + "eslint": "8.49.0", "start-server-and-test": "2.0.0" }, "optionalDependencies": { diff --git a/packages/backend/.swcrc b/packages/backend/.swcrc index 0504a2d38933..d9f047b6ac41 100644 --- a/packages/backend/.swcrc +++ b/packages/backend/.swcrc @@ -11,7 +11,7 @@ "decoratorMetadata": true }, "experimental": { - "keepImportAssertions": true + "keepImportAttributes": true }, "baseUrl": "src", "paths": { diff --git a/packages/backend/migration/1665091090561-add-renote-muting.js b/packages/backend/migration/1665091090561-add-renote-muting.js index 575a63703cc8..a22d7037f30e 100644 --- a/packages/backend/migration/1665091090561-add-renote-muting.js +++ b/packages/backend/migration/1665091090561-add-renote-muting.js @@ -16,5 +16,9 @@ export class addRenoteMuting1665091090561 { } async down(queryRunner) { + await queryRunner.query(`DROP INDEX "IDX_renote_muting_muterId"`); + await queryRunner.query(`DROP INDEX "IDX_renote_muting_muteeId"`); + await queryRunner.query(`DROP INDEX "IDX_renote_muting_createdAt"`); + await queryRunner.query(`DROP TABLE "renote_muting"`); } } diff --git a/packages/backend/migration/1690417561185-fix-renote-muting.js b/packages/backend/migration/1690417561185-fix-renote-muting.js new file mode 100644 index 000000000000..14150b036237 --- /dev/null +++ b/packages/backend/migration/1690417561185-fix-renote-muting.js @@ -0,0 +1,12 @@ +export class FixRenoteMuting1690417561185 { + name = 'FixRenoteMuting1690417561185' + + async up(queryRunner) { + await queryRunner.query(`DELETE FROM "renote_muting" WHERE "muteeId" NOT IN (SELECT "id" FROM "user")`); + await queryRunner.query(`DELETE FROM "renote_muting" WHERE "muterId" NOT IN (SELECT "id" FROM "user")`); + } + + async down(queryRunner) { + + } +} diff --git a/packages/backend/migration/1690569881926-user-2fa-backup-codes.js b/packages/backend/migration/1690569881926-user-2fa-backup-codes.js new file mode 100644 index 000000000000..2049df8ea2e4 --- /dev/null +++ b/packages/backend/migration/1690569881926-user-2fa-backup-codes.js @@ -0,0 +1,11 @@ +export class User2faBackupCodes1690569881926 { + name = 'User2faBackupCodes1690569881926' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_profile" ADD "twoFactorBackupSecret" character varying array`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "twoFactorBackupSecret"`); + } +} diff --git a/packages/backend/migration/1690796169261-play-visibility.js b/packages/backend/migration/1690796169261-play-visibility.js new file mode 100644 index 000000000000..c57fa7a10986 --- /dev/null +++ b/packages/backend/migration/1690796169261-play-visibility.js @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class PlayVisibility1689102832143 { + name = 'PlayVisibility1690796169261' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "public"."flash" ADD "visibility" character varying(512) DEFAULT 'public'`, undefined); + } + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "public"."flash" DROP COLUMN "visibility"`, undefined); + } +} diff --git a/packages/backend/migration/1691959191872-passkey-support.js b/packages/backend/migration/1691959191872-passkey-support.js new file mode 100644 index 000000000000..55b571d60d83 --- /dev/null +++ b/packages/backend/migration/1691959191872-passkey-support.js @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class PasskeySupport1691959191872 { + name = 'PasskeySupport1691959191872' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_security_key" ADD "counter" bigint NOT NULL DEFAULT '0'`); + await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."counter" IS 'The number of times the UserSecurityKey was validated.'`); + await queryRunner.query(`ALTER TABLE "user_security_key" ADD "credentialDeviceType" character varying(32)`); + await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."credentialDeviceType" IS 'The type of Backup Eligibility in authenticator data'`); + await queryRunner.query(`ALTER TABLE "user_security_key" ADD "credentialBackedUp" boolean`); + await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."credentialBackedUp" IS 'Whether or not the credential has been backed up'`); + await queryRunner.query(`ALTER TABLE "user_security_key" ADD "transports" character varying(32) array`); + await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."transports" IS 'The type of the credential returned by the browser'`); + await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."publicKey" IS 'The public key of the UserSecurityKey, hex-encoded.'`); + await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."lastUsed" IS 'Timestamp of the last time the UserSecurityKey was used.'`); + await queryRunner.query(`ALTER TABLE "user_security_key" ALTER COLUMN "lastUsed" SET DEFAULT now()`); + await queryRunner.query(`UPDATE "user_security_key" SET "id" = REPLACE(REPLACE(REPLACE(REPLACE(ENCODE(DECODE("id", 'hex'), 'base64'), E'\\n', ''), '+', '-'), '/', '_'), '=', ''), "publicKey" = REPLACE(REPLACE(REPLACE(REPLACE(ENCODE(DECODE("publicKey", 'hex'), 'base64'), E'\\n', ''), '+', '-'), '/', '_'), '=', '')`); + await queryRunner.query(`ALTER TABLE "attestation_challenge" DROP CONSTRAINT "FK_f1a461a618fa1755692d0e0d592"`); + await queryRunner.query(`DROP INDEX "IDX_47efb914aed1f72dd39a306c7b"`); + await queryRunner.query(`DROP INDEX "IDX_f1a461a618fa1755692d0e0d59"`); + await queryRunner.query(`DROP TABLE "attestation_challenge"`); + } + + async down(queryRunner) { + await queryRunner.query(`CREATE TABLE "attestation_challenge" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "challenge" character varying(64) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "registrationChallenge" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_d0ba6786e093f1bcb497572a6b5" PRIMARY KEY ("id", "userId"))`); + await queryRunner.query(`CREATE INDEX "IDX_f1a461a618fa1755692d0e0d59" ON "attestation_challenge" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_47efb914aed1f72dd39a306c7b" ON "attestation_challenge" ("challenge") `); + await queryRunner.query(`ALTER TABLE "attestation_challenge" ADD CONSTRAINT "FK_f1a461a618fa1755692d0e0d592" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."challenge" IS 'Hex-encoded sha256 hash of the challenge.'`); + await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."createdAt" IS 'The date challenge was created for expiry purposes.'`); + await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."registrationChallenge" IS 'Indicates that the challenge is only for registration purposes if true to prevent the challenge for being used as authentication.'`); + await queryRunner.query(`UPDATE "user_security_key" SET "id" = ENCODE(DECODE(REPLACE(REPLACE("id" || CASE WHEN LENGTH("id") % 4 = 2 THEN '==' WHEN LENGTH("id") % 4 = 3 THEN '=' ELSE '' END, '-', '+'), '_', '/'), 'base64'), 'hex'), "publicKey" = ENCODE(DECODE(REPLACE(REPLACE("publicKey" || CASE WHEN LENGTH("publicKey") % 4 = 2 THEN '==' WHEN LENGTH("publicKey") % 4 = 3 THEN '=' ELSE '' END, '-', '+'), '_', '/'), 'base64'), 'hex')`); + await queryRunner.query(`ALTER TABLE "user_security_key" ALTER COLUMN "lastUsed" DROP DEFAULT`); + await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."lastUsed" IS 'The date of the last time the UserSecurityKey was successfully validated.'`); + await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."publicKey" IS 'Variable-length public key used to verify attestations (hex-encoded).'`); + await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."transports" IS 'The type of the credential returned by the browser'`); + await queryRunner.query(`ALTER TABLE "user_security_key" DROP COLUMN "transports"`); + await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."credentialBackedUp" IS 'Whether or not the credential has been backed up'`); + await queryRunner.query(`ALTER TABLE "user_security_key" DROP COLUMN "credentialBackedUp"`); + await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."credentialDeviceType" IS 'The type of Backup Eligibility in authenticator data'`); + await queryRunner.query(`ALTER TABLE "user_security_key" DROP COLUMN "credentialDeviceType"`); + await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."counter" IS 'The number of times the UserSecurityKey was validated.'`); + await queryRunner.query(`ALTER TABLE "user_security_key" DROP COLUMN "counter"`); + } +} diff --git a/packages/backend/migration/1694850832075-server-icons-and-manifest.js b/packages/backend/migration/1694850832075-server-icons-and-manifest.js new file mode 100644 index 000000000000..1bd8979d9b90 --- /dev/null +++ b/packages/backend/migration/1694850832075-server-icons-and-manifest.js @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class ServerIconsAndManifest1694850832075 { + name = 'ServerIconsAndManifest1694850832075' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "app192IconUrl" character varying(1024)`); + await queryRunner.query(`ALTER TABLE "meta" ADD "app512IconUrl" character varying(1024)`); + await queryRunner.query(`ALTER TABLE "meta" ADD "manifestJsonOverride" character varying(8192) NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "manifestJsonOverride"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "app512IconUrl"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "app192IconUrl"`); + } +} diff --git a/packages/backend/migration/1694915420864-clipped-count.js b/packages/backend/migration/1694915420864-clipped-count.js new file mode 100644 index 000000000000..1ad8e04ce083 --- /dev/null +++ b/packages/backend/migration/1694915420864-clipped-count.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class ClippedCount1694915420864 { + name = 'ClippedCount1694915420864' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "note" ADD "clippedCount" smallint NOT NULL DEFAULT '0'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "clippedCount"`); + } +} diff --git a/packages/backend/migration/1695260774117-verified-links.js b/packages/backend/migration/1695260774117-verified-links.js new file mode 100644 index 000000000000..18e0571d81d0 --- /dev/null +++ b/packages/backend/migration/1695260774117-verified-links.js @@ -0,0 +1,11 @@ +export class VerifiedLinks1695260774117 { + name = 'VerifiedLinks1695260774117' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_profile" ADD "verifiedLinks" character varying array NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "verifiedLinks"`); + } +} diff --git a/packages/backend/migration/1695288787870-following-notify.js b/packages/backend/migration/1695288787870-following-notify.js new file mode 100644 index 000000000000..e7e2194b156f --- /dev/null +++ b/packages/backend/migration/1695288787870-following-notify.js @@ -0,0 +1,13 @@ +export class FollowingNotify1695288787870 { + name = 'FollowingNotify1695288787870' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "following" ADD "notify" character varying(32)`); + await queryRunner.query(`CREATE INDEX "IDX_5108098457488634a4768e1d12" ON "following" ("notify") `); + } + + async down(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_5108098457488634a4768e1d12"`); + await queryRunner.query(`ALTER TABLE "following" DROP COLUMN "notify"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index 8c5b67ced64c..466416393c25 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -56,36 +56,37 @@ "utf-8-validate": "^6.0.3" }, "dependencies": { - "@aws-sdk/client-s3": "3.367.0", - "@aws-sdk/lib-storage": "3.367.0", - "@aws-sdk/node-http-handler": "3.360.0", - "@bull-board/api": "5.7.1", - "@bull-board/fastify": "5.7.1", - "@bull-board/ui": "5.7.1", + "@aws-sdk/client-s3": "3.412.0", + "@aws-sdk/lib-storage": "3.412.0", + "@smithy/node-http-handler": "2.1.5", + "@bull-board/api": "5.8.4", + "@bull-board/fastify": "5.8.4", + "@bull-board/ui": "5.8.4", "@discordapp/twemoji": "14.1.2", "@fastify/accepts": "4.2.0", - "@fastify/cookie": "8.3.0", - "@fastify/cors": "8.3.0", + "@fastify/cookie": "9.1.0", + "@fastify/cors": "8.4.0", "@fastify/express": "2.3.0", "@fastify/http-proxy": "9.2.1", "@fastify/multipart": "7.7.3", - "@fastify/static": "6.10.2", - "@fastify/view": "8.0.0", - "@nestjs/common": "10.1.2", - "@nestjs/core": "10.1.2", - "@nestjs/testing": "10.1.2", + "@fastify/static": "6.11.2", + "@fastify/view": "8.2.0", + "@nestjs/common": "10.2.5", + "@nestjs/core": "10.2.5", + "@nestjs/testing": "10.2.5", "@peertube/http-signature": "1.7.0", - "@sinonjs/fake-timers": "10.3.0", + "@simplewebauthn/server": "8.1.1", + "@sinonjs/fake-timers": "11.1.0", "@swc/cli": "0.1.62", - "@swc/core": "1.3.72", + "@swc/core": "1.3.86", "accepts": "1.3.8", "ajv": "8.12.0", - "archiver": "5.3.1", - "async-mutex": "^0.4.0", + "archiver": "6.0.1", + "async-mutex": "0.4.0", "bcryptjs": "2.4.3", "blurhash": "2.0.5", "body-parser": "1.20.2", - "bullmq": "4.6.3", + "bullmq": "4.11.2", "cacheable-lookup": "7.0.0", "cbor": "9.0.1", "chalk": "5.3.0", @@ -96,7 +97,7 @@ "content-disposition": "0.5.4", "date-fns": "2.30.0", "deep-email-validator": "0.1.21", - "fastify": "4.21.0", + "fastify": "4.23.2", "feed": "4.2.2", "file-type": "18.5.0", "fluent-ffmpeg": "2.1.2", @@ -112,17 +113,18 @@ "js-yaml": "4.1.0", "jsdom": "22.1.0", "json5": "2.2.3", - "jsonld": "8.2.0", + "jsonld": "8.3.1", "jsrsasign": "10.8.6", - "meilisearch": "0.33.0", + "meilisearch": "0.34.2", "mfm-js": "0.23.3", - "microformats-parser": "1.4.1", + "microformats-parser": "1.5.2", "mime-types": "2.1.35", "misskey-js": "workspace:*", "ms": "3.0.0-canary.1", + "nanoid": "5.0.1", "nested-property": "4.0.0", "node-fetch": "3.3.2", - "nodemailer": "6.9.4", + "nodemailer": "6.9.5", "nsfwjs": "2.4.2", "oauth": "0.10.0", "oauth2orize": "1.11.1", @@ -130,7 +132,7 @@ "os-utils": "0.0.14", "otpauth": "9.1.4", "parse5": "7.1.2", - "pg": "8.11.1", + "pg": "8.11.3", "pkce-challenge": "4.0.1", "probe-image-size": "7.2.3", "promise-limit": "2.7.0", @@ -140,84 +142,85 @@ "qrcode": "1.5.3", "random-seed": "0.3.0", "ratelimiter": "3.4.1", - "re2": "1.20.1", + "re2": "1.20.3", "redis-lock": "0.1.4", "reflect-metadata": "0.1.13", "rename": "1.0.4", "rss-parser": "3.13.0", "rxjs": "7.8.1", "sanitize-html": "2.11.0", - "sharp": "0.32.4", + "sharp": "0.32.6", "sharp-read-bmp": "github:misskey-dev/sharp-read-bmp", "slacc": "0.0.10", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", "summaly": "github:misskey-dev/summaly", - "systeminformation": "5.18.9", + "systeminformation": "5.21.8", "tinycolor2": "1.6.0", "tmp": "0.2.1", - "tsc-alias": "1.8.7", + "tsc-alias": "1.8.8", "tsconfig-paths": "4.2.0", "twemoji-parser": "14.0.0", "typeorm": "0.3.17", - "typescript": "5.1.6", + "typescript": "5.2.2", "ulid": "2.3.0", "vary": "1.1.2", - "web-push": "3.6.4", - "ws": "8.13.0", + "web-push": "3.6.6", + "ws": "8.14.2", "xev": "3.0.2" }, "devDependencies": { - "@jest/globals": "29.6.2", - "@swc/jest": "0.2.27", + "@jest/globals": "29.7.0", + "@simplewebauthn/typescript-types": "8.0.0", + "@swc/jest": "0.2.29", "@types/accepts": "1.3.5", "@types/archiver": "5.3.2", - "@types/bcryptjs": "2.4.2", - "@types/body-parser": "1.19.2", + "@types/bcryptjs": "2.4.4", + "@types/body-parser": "1.19.3", "@types/cbor": "6.0.0", - "@types/color-convert": "2.0.0", - "@types/content-disposition": "0.5.5", - "@types/fluent-ffmpeg": "2.1.21", + "@types/color-convert": "2.0.1", + "@types/content-disposition": "0.5.6", + "@types/fluent-ffmpeg": "2.1.22", "@types/http-link-header": "1.0.3", - "@types/jest": "29.5.3", - "@types/js-yaml": "4.0.5", - "@types/jsdom": "21.1.1", - "@types/jsonld": "1.5.9", - "@types/jsrsasign": "10.5.8", + "@types/jest": "29.5.5", + "@types/js-yaml": "4.0.6", + "@types/jsdom": "21.1.3", + "@types/jsonld": "1.5.10", + "@types/jsrsasign": "10.5.9", "@types/mime-types": "2.1.1", "@types/ms": "0.7.31", - "@types/node": "20.4.5", + "@types/node": "20.6.3", "@types/node-fetch": "3.0.3", - "@types/nodemailer": "6.4.9", - "@types/oauth": "0.9.1", - "@types/oauth2orize": "1.11.0", + "@types/nodemailer": "6.4.10", + "@types/oauth": "0.9.2", + "@types/oauth2orize": "1.11.1", "@types/oauth2orize-pkce": "0.1.0", "@types/pg": "8.10.2", "@types/pug": "2.0.6", "@types/punycode": "2.1.0", - "@types/qrcode": "1.5.1", + "@types/qrcode": "1.5.2", "@types/random-seed": "0.3.3", "@types/ratelimiter": "3.4.4", "@types/rename": "1.0.4", "@types/sanitize-html": "2.9.0", - "@types/semver": "7.5.0", + "@types/semver": "7.5.2", "@types/sharp": "0.32.0", "@types/simple-oauth2": "5.0.4", "@types/sinonjs__fake-timers": "8.1.2", - "@types/tinycolor2": "1.4.3", - "@types/tmp": "0.2.3", + "@types/tinycolor2": "1.4.4", + "@types/tmp": "0.2.4", "@types/vary": "1.1.0", - "@types/web-push": "3.3.2", + "@types/web-push": "3.6.0", "@types/ws": "8.5.5", - "@typescript-eslint/eslint-plugin": "6.2.0", - "@typescript-eslint/parser": "6.2.0", + "@typescript-eslint/eslint-plugin": "6.7.2", + "@typescript-eslint/parser": "6.7.2", "aws-sdk-client-mock": "3.0.0", "cross-env": "7.0.3", - "eslint": "8.46.0", - "eslint-plugin-import": "2.28.0", - "execa": "7.2.0", - "jest": "29.6.2", - "jest-mock": "29.6.2", + "eslint": "8.49.0", + "eslint-plugin-import": "2.28.1", + "execa": "8.0.1", + "jest": "29.7.0", + "jest-mock": "29.7.0", "simple-oauth2": "5.0.0" } } diff --git a/packages/backend/src/boot/common.ts b/packages/backend/src/boot/common.ts index ca2b729156c5..4783a2b2dae5 100644 --- a/packages/backend/src/boot/common.ts +++ b/packages/backend/src/boot/common.ts @@ -8,7 +8,6 @@ import { ChartManagementService } from '@/core/chart/ChartManagementService.js'; import { QueueProcessorService } from '@/queue/QueueProcessorService.js'; import { NestLogger } from '@/NestLogger.js'; import { QueueProcessorModule } from '@/queue/QueueProcessorModule.js'; -import { JanitorService } from '@/daemons/JanitorService.js'; import { QueueStatsService } from '@/daemons/QueueStatsService.js'; import { ServerStatsService } from '@/daemons/ServerStatsService.js'; import { ServerService } from '@/server/ServerService.js'; @@ -25,7 +24,6 @@ export async function server() { if (process.env.NODE_ENV !== 'test') { app.get(ChartManagementService).start(); - app.get(JanitorService).start(); app.get(QueueStatsService).start(); app.get(ServerStatsService).start(); } diff --git a/packages/backend/src/boot/index.ts b/packages/backend/src/boot/entry.ts similarity index 100% rename from packages/backend/src/boot/index.ts rename to packages/backend/src/boot/entry.ts diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index 0bf23c770b96..a45ea2bb8f14 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -68,21 +68,34 @@ export async function masterMain() { process.exit(1); } - if (envOption.onlyServer) { - await server(); - } else if (envOption.onlyQueue) { - await jobQueue(); - } else { - await server(); - } - bootLogger.succ('Misskey initialized'); - if (!envOption.disableClustering) { + if (envOption.disableClustering) { + if (envOption.onlyServer) { + await server(); + } else if (envOption.onlyQueue) { + await jobQueue(); + } else { + await server(); + await jobQueue(); + } + } else { + if (envOption.onlyServer) { + // nop + } else if (envOption.onlyQueue) { + // nop + } else { + await server(); + } + await spawnWorkers(config.clusterLimit); } - bootLogger.succ(config.socket ? `Now listening on socket ${config.socket} on ${config.url}` : `Now listening on port ${config.port} on ${config.url}`, null, true); + if (envOption.onlyQueue) { + bootLogger.succ('Queue started', null, true); + } else { + bootLogger.succ(config.socket ? `Now listening on socket ${config.socket} on ${config.url}` : `Now listening on port ${config.port} on ${config.url}`, null, true); + } } function showEnvironment(): void { diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 29f511842cd9..abbfdfed8fe5 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -3,10 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -/** - * Config loader - */ - import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, resolve } from 'node:path'; @@ -23,11 +19,9 @@ type RedisOptionsSource = Partial & { }; /** - * ユーザーが設定する必要のある情報 + * 設定ファイルの型 */ -export type Source = { - repository_url?: string; - feedback_url?: string; +type Source = { url: string; port?: number; socket?: string; @@ -70,12 +64,11 @@ export type Source = { maxFileSize?: number; - accesslog?: string; - clusterLimit?: number; id: string; + outgoingAddress?: string; outgoingAddressFamily?: 'ipv4' | 'ipv6' | 'dual'; deliverJobConcurrency?: number; @@ -92,12 +85,63 @@ export type Source = { videoThumbnailGenerator?: string; signToActivityPubGet?: boolean; + + perChannelMaxNoteCacheCount?: number; + perUserNotificationsMaxCount?: number; + deactivateAntennaThreshold?: number; }; -/** - * Misskeyが自動的に(ユーザーが設定した情報から推論して)設定する情報 - */ -export type Mixin = { +export type Config = { + url: string; + port: number; + socket: string | undefined; + chmodSocket: string | undefined; + disableHsts: boolean | undefined; + db: { + host: string; + port: number; + db: string; + user: string; + pass: string; + disableCache?: boolean; + extra?: { [x: string]: string }; + }; + dbReplications: boolean | undefined; + dbSlaves: { + host: string; + port: number; + db: string; + user: string; + pass: string; + }[] | undefined; + meilisearch: { + host: string; + port: string; + apiKey: string; + ssl?: boolean; + index: string; + scope?: 'local' | 'global' | string[]; + } | undefined; + proxy: string | undefined; + proxySmtp: string | undefined; + proxyBypassHosts: string[] | undefined; + allowedPrivateNetworks: string[] | undefined; + maxFileSize: number | undefined; + clusterLimit: number | undefined; + id: string; + outgoingAddress: string | undefined; + outgoingAddressFamily: 'ipv4' | 'ipv6' | 'dual' | undefined; + deliverJobConcurrency: number | undefined; + inboxJobConcurrency: number | undefined; + relashionshipJobConcurrency: number | undefined; + deliverJobPerSec: number | undefined; + inboxJobPerSec: number | undefined; + relashionshipJobPerSec: number | undefined; + deliverJobMaxAttempts: number | undefined; + inboxJobMaxAttempts: number | undefined; + proxyRemoteFiles: boolean | undefined; + signToActivityPubGet: boolean | undefined; + version: string; host: string; hostname: string; @@ -116,10 +160,11 @@ export type Mixin = { redis: RedisOptions & RedisOptionsSource; redisForPubsub: RedisOptions & RedisOptionsSource; redisForJobQueue: RedisOptions & RedisOptionsSource; + perChannelMaxNoteCacheCount: number; + perUserNotificationsMaxCount: number; + deactivateAntennaThreshold: number; }; -export type Config = Source & Mixin; - const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); @@ -137,7 +182,7 @@ const path = process.env.MISSKEY_CONFIG_YML ? resolve(dir, 'test.yml') : resolve(dir, 'default.yml'); -export function loadConfig() { +export function loadConfig(): Config { const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../built/meta.json`, 'utf-8')); const clientManifestExists = fs.existsSync(_dirname + '/../../../built/_vite_/manifest.json'); const clientManifest = clientManifestExists ? @@ -145,43 +190,72 @@ export function loadConfig() { : { 'src/_boot_.ts': { file: 'src/_boot_.ts' } }; const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source; - const mixin = {} as Mixin; - const url = tryCreateUrl(config.url); - - config.url = url.origin; - - config.port = config.port ?? parseInt(process.env.PORT ?? '', 10); - - mixin.version = meta.version; - mixin.host = url.host; - mixin.hostname = url.hostname; - mixin.scheme = url.protocol.replace(/:$/, ''); - mixin.wsScheme = mixin.scheme.replace('http', 'ws'); - mixin.wsUrl = `${mixin.wsScheme}://${mixin.host}`; - mixin.apiUrl = `${mixin.scheme}://${mixin.host}/api`; - mixin.authUrl = `${mixin.scheme}://${mixin.host}/auth`; - mixin.driveUrl = `${mixin.scheme}://${mixin.host}/files`; - mixin.userAgent = `Misskey/${meta.version} (${config.url})`; - mixin.clientEntry = clientManifest['src/_boot_.ts']; - mixin.clientManifestExists = clientManifestExists; + const version = meta.version; + const host = url.host; + const hostname = url.hostname; + const scheme = url.protocol.replace(/:$/, ''); + const wsScheme = scheme.replace('http', 'ws'); const externalMediaProxy = config.mediaProxy ? config.mediaProxy.endsWith('/') ? config.mediaProxy.substring(0, config.mediaProxy.length - 1) : config.mediaProxy : null; - const internalMediaProxy = `${mixin.scheme}://${mixin.host}/proxy`; - mixin.mediaProxy = externalMediaProxy ?? internalMediaProxy; - mixin.externalMediaProxyEnabled = externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy; + const internalMediaProxy = `${scheme}://${host}/proxy`; + const redis = convertRedisOptions(config.redis, host); - mixin.videoThumbnailGenerator = config.videoThumbnailGenerator ? - config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator - : null; - - mixin.redis = convertRedisOptions(config.redis, mixin.host); - mixin.redisForPubsub = config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, mixin.host) : mixin.redis; - mixin.redisForJobQueue = config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, mixin.host) : mixin.redis; - - return Object.assign(config, mixin); + return { + version, + url: url.origin, + port: config.port ?? parseInt(process.env.PORT ?? '', 10), + socket: config.socket, + chmodSocket: config.chmodSocket, + disableHsts: config.disableHsts, + host, + hostname, + scheme, + wsScheme, + wsUrl: `${wsScheme}://${host}`, + apiUrl: `${scheme}://${host}/api`, + authUrl: `${scheme}://${host}/auth`, + driveUrl: `${scheme}://${host}/files`, + db: config.db, + dbReplications: config.dbReplications, + dbSlaves: config.dbSlaves, + meilisearch: config.meilisearch, + redis, + redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis, + redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis, + id: config.id, + proxy: config.proxy, + proxySmtp: config.proxySmtp, + proxyBypassHosts: config.proxyBypassHosts, + allowedPrivateNetworks: config.allowedPrivateNetworks, + maxFileSize: config.maxFileSize, + clusterLimit: config.clusterLimit, + outgoingAddress: config.outgoingAddress, + outgoingAddressFamily: config.outgoingAddressFamily, + deliverJobConcurrency: config.deliverJobConcurrency, + inboxJobConcurrency: config.inboxJobConcurrency, + relashionshipJobConcurrency: config.relashionshipJobConcurrency, + deliverJobPerSec: config.deliverJobPerSec, + inboxJobPerSec: config.inboxJobPerSec, + relashionshipJobPerSec: config.relashionshipJobPerSec, + deliverJobMaxAttempts: config.deliverJobMaxAttempts, + inboxJobMaxAttempts: config.inboxJobMaxAttempts, + proxyRemoteFiles: config.proxyRemoteFiles, + signToActivityPubGet: config.signToActivityPubGet, + mediaProxy: externalMediaProxy ?? internalMediaProxy, + externalMediaProxyEnabled: externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy, + videoThumbnailGenerator: config.videoThumbnailGenerator ? + config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator + : null, + userAgent: `Misskey/${version} (${config.url})`, + clientEntry: clientManifest['src/_boot_.ts'], + clientManifestExists: clientManifestExists, + perChannelMaxNoteCacheCount: config.perChannelMaxNoteCacheCount ?? 1000, + perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 300, + deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7), + }; } function tryCreateUrl(url: string) { diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts index 2d96dce9460f..ec1d013922b7 100644 --- a/packages/backend/src/core/AccountMoveService.ts +++ b/packages/backend/src/core/AccountMoveService.ts @@ -8,8 +8,8 @@ import { IsNull, In, MoreThan, Not } from 'typeorm'; import { bindThis } from '@/decorators.js'; import { DI } from '@/di-symbols.js'; -import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js'; -import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListJoiningsRepository, UsersRepository } from '@/models/index.js'; +import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js'; +import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListJoiningsRepository, UsersRepository } from '@/models/_.js'; import type { RelationshipJobData, ThinUser } from '@/queue/types.js'; import { IdService } from '@/core/IdService.js'; @@ -71,12 +71,12 @@ export class AccountMoveService { * After delivering Move activity, its local followers unfollow the old account and then follow the new one. */ @bindThis - public async moveFromLocal(src: LocalUser, dst: LocalUser | RemoteUser): Promise { + public async moveFromLocal(src: MiLocalUser, dst: MiLocalUser | MiRemoteUser): Promise { const srcUri = this.userEntityService.getUserUri(src); const dstUri = this.userEntityService.getUserUri(dst); // add movedToUri to indicate that the user has moved - const update = {} as Partial; + const update = {} as Partial; update.alsoKnownAs = src.alsoKnownAs?.includes(dstUri) ? src.alsoKnownAs : src.alsoKnownAs?.concat([dstUri]) ?? [dstUri]; update.movedToUri = dstUri; update.movedAt = new Date(); @@ -114,7 +114,7 @@ export class AccountMoveService { } @bindThis - public async postMoveProcess(src: User, dst: User): Promise { + public async postMoveProcess(src: MiUser, dst: MiUser): Promise { // Copy blockings and mutings, and update lists try { await Promise.all([ @@ -213,7 +213,7 @@ export class AccountMoveService { * @returns Promise */ @bindThis - public async updateLists(src: ThinUser, dst: User): Promise { + public async updateLists(src: ThinUser, dst: MiUser): Promise { // Return if there is no list to be updated. const oldJoinings = await this.userListJoiningsRepository.find({ where: { @@ -260,7 +260,7 @@ export class AccountMoveService { } @bindThis - private async adjustFollowingCounts(localFollowerIds: string[], oldAccount: User): Promise { + private async adjustFollowingCounts(localFollowerIds: string[], oldAccount: MiUser): Promise { if (localFollowerIds.length === 0) return; // Set the old account's following and followers counts to 0. @@ -301,11 +301,11 @@ export class AccountMoveService { */ @bindThis public async validateAlsoKnownAs( - dst: LocalUser | RemoteUser, - check: (oldUser: LocalUser | RemoteUser | null, newUser: LocalUser | RemoteUser) => boolean | Promise = () => true, + dst: MiLocalUser | MiRemoteUser, + check: (oldUser: MiLocalUser | MiRemoteUser | null, newUser: MiLocalUser | MiRemoteUser) => boolean | Promise = () => true, instant = false, - ): Promise { - let resultUser: LocalUser | RemoteUser | null = null; + ): Promise { + let resultUser: MiLocalUser | MiRemoteUser | null = null; if (this.userEntityService.isRemoteUser(dst)) { if ((new Date()).getTime() - (dst.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) { diff --git a/packages/backend/src/core/AccountUpdateService.ts b/packages/backend/src/core/AccountUpdateService.ts index ae5dcf8b5233..664700ea6b06 100644 --- a/packages/backend/src/core/AccountUpdateService.ts +++ b/packages/backend/src/core/AccountUpdateService.ts @@ -5,8 +5,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository } from '@/models/index.js'; -import type { User } from '@/models/entities/User.js'; +import type { UsersRepository } from '@/models/_.js'; +import type { MiUser } from '@/models/User.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { RelayService } from '@/core/RelayService.js'; import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; @@ -27,7 +27,7 @@ export class AccountUpdateService { } @bindThis - public async publishToFollowers(userId: User['id']) { + public async publishToFollowers(userId: MiUser['id']) { const user = await this.usersRepository.findOneBy({ id: userId }); if (user == null) throw new Error('user not found'); diff --git a/packages/backend/src/core/AchievementService.ts b/packages/backend/src/core/AchievementService.ts index cc71bc2b991a..1b8718335bde 100644 --- a/packages/backend/src/core/AchievementService.ts +++ b/packages/backend/src/core/AchievementService.ts @@ -4,8 +4,8 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserProfilesRepository } from '@/models/index.js'; -import type { User } from '@/models/entities/User.js'; +import type { UserProfilesRepository } from '@/models/_.js'; +import type { MiUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { NotificationService } from '@/core/NotificationService.js'; @@ -85,6 +85,7 @@ export const ACHIEVEMENT_TYPES = [ 'setNameToSyuilo', 'cookieClicked', 'brainDiver', + 'smashTestNotificationButton', ] as const; @Injectable() @@ -99,7 +100,7 @@ export class AchievementService { @bindThis public async create( - userId: User['id'], + userId: MiUser['id'], type: typeof ACHIEVEMENT_TYPES[number], ): Promise { if (!ACHIEVEMENT_TYPES.includes(type)) return; diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts index 482aeee39f68..70f37516a43f 100644 --- a/packages/backend/src/core/AnnouncementService.ts +++ b/packages/backend/src/core/AnnouncementService.ts @@ -6,8 +6,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { Brackets } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { User } from '@/models/entities/User.js'; -import type { AnnouncementReadsRepository, AnnouncementsRepository, Announcement, AnnouncementRead } from '@/models/index.js'; +import type { MiUser } from '@/models/User.js'; +import type { AnnouncementReadsRepository, AnnouncementsRepository, MiAnnouncement, MiAnnouncementRead } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { Packed } from '@/misc/json-schema.js'; import { IdService } from '@/core/IdService.js'; @@ -28,14 +28,14 @@ export class AnnouncementService { } @bindThis - public async getReads(userId: User['id']): Promise { + public async getReads(userId: MiUser['id']): Promise { return this.announcementReadsRepository.findBy({ userId: userId, }); } @bindThis - public async getUnreadAnnouncements(user: User): Promise { + public async getUnreadAnnouncements(user: MiUser): Promise { const readsQuery = this.announcementReadsRepository.createQueryBuilder('read') .select('read.announcementId') .where('read.userId = :userId', { userId: user.id }); @@ -58,7 +58,7 @@ export class AnnouncementService { } @bindThis - public async create(values: Partial): Promise<{ raw: Announcement; packed: Packed<'Announcement'> }> { + public async create(values: Partial): Promise<{ raw: MiAnnouncement; packed: Packed<'Announcement'> }> { const announcement = await this.announcementsRepository.insert({ id: this.idService.genId(), createdAt: new Date(), @@ -92,7 +92,7 @@ export class AnnouncementService { } @bindThis - public async read(user: User, announcementId: Announcement['id']): Promise { + public async read(user: MiUser, announcementId: MiAnnouncement['id']): Promise { try { await this.announcementReadsRepository.insert({ id: this.idService.genId(), @@ -111,10 +111,10 @@ export class AnnouncementService { @bindThis public async packMany( - announcements: Announcement[], - me?: { id: User['id'] } | null | undefined, + announcements: MiAnnouncement[], + me?: { id: MiUser['id'] } | null | undefined, options?: { - reads?: AnnouncementRead[]; + reads?: MiAnnouncementRead[]; }, ): Promise[]> { const reads = me ? (options?.reads ?? await this.getReads(me.id)) : []; diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index 53b497a78a09..a7e74eeef077 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -5,14 +5,14 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; -import type { Antenna } from '@/models/entities/Antenna.js'; -import type { Note } from '@/models/entities/Note.js'; -import type { User } from '@/models/entities/User.js'; +import type { MiAntenna } from '@/models/Antenna.js'; +import type { MiNote } from '@/models/Note.js'; +import type { MiUser } from '@/models/User.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import * as Acct from '@/misc/acct.js'; import type { Packed } from '@/misc/json-schema.js'; import { DI } from '@/di-symbols.js'; -import type { AntennasRepository, UserListJoiningsRepository } from '@/models/index.js'; +import type { AntennasRepository, UserListJoiningsRepository } from '@/models/_.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; import { StreamMessages } from '@/server/api/stream/types.js'; @@ -21,7 +21,7 @@ import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() export class AntennaService implements OnApplicationShutdown { private antennasFetched: boolean; - private antennas: Antenna[]; + private antennas: MiAntenna[]; constructor( @Inject(DI.redis) @@ -76,7 +76,7 @@ export class AntennaService implements OnApplicationShutdown { } @bindThis - public async addNoteToAntennas(note: Note, noteUser: { id: User['id']; username: string; host: string | null; }): Promise { + public async addNoteToAntennas(note: MiNote, noteUser: { id: MiUser['id']; username: string; host: string | null; }): Promise { const antennas = await this.getAntennas(); const antennasWithMatchResult = await Promise.all(antennas.map(antenna => this.checkHitAntenna(antenna, note, noteUser).then(hit => [antenna, hit] as const))); const matchedAntennas = antennasWithMatchResult.filter(([, hit]) => hit).map(([antenna]) => antenna); @@ -99,7 +99,7 @@ export class AntennaService implements OnApplicationShutdown { // NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている @bindThis - public async checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { id: User['id']; username: string; host: string | null; }): Promise { + public async checkHitAntenna(antenna: MiAntenna, note: (MiNote | Packed<'Note'>), noteUser: { id: MiUser['id']; username: string; host: string | null; }): Promise { if (note.visibility === 'specified') return false; if (note.visibility === 'followers') return false; diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts index 02bda436cfe0..6ca684d53c9b 100644 --- a/packages/backend/src/core/CacheService.ts +++ b/packages/backend/src/core/CacheService.ts @@ -5,9 +5,9 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; -import type { BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, UserProfile, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js'; -import type { LocalUser, User } from '@/models/entities/User.js'; +import type { MiLocalUser, MiUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; @@ -16,11 +16,11 @@ import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() export class CacheService implements OnApplicationShutdown { - public userByIdCache: MemoryKVCache; - public localUserByNativeTokenCache: MemoryKVCache; - public localUserByIdCache: MemoryKVCache; - public uriPersonCache: MemoryKVCache; - public userProfileCache: RedisKVCache; + public userByIdCache: MemoryKVCache; + public localUserByNativeTokenCache: MemoryKVCache; + public localUserByIdCache: MemoryKVCache; + public uriPersonCache: MemoryKVCache; + public userProfileCache: RedisKVCache; public userMutingsCache: RedisKVCache>; public userBlockingCache: RedisKVCache>; public userBlockedCache: RedisKVCache>; // NOTE: 「被」Blockキャッシュ @@ -60,14 +60,14 @@ export class CacheService implements OnApplicationShutdown { ) { //this.onMessage = this.onMessage.bind(this); - const localUserByIdCache = new MemoryKVCache(1000 * 60 * 60 * 6 /* 6h */); + const localUserByIdCache = new MemoryKVCache(1000 * 60 * 60 * 6 /* 6h */); this.localUserByIdCache = localUserByIdCache; // ローカルユーザーならlocalUserByIdCacheにデータを追加し、こちらにはid(文字列)だけを追加する - const userByIdCache = new MemoryKVCache(1000 * 60 * 60 * 6 /* 6h */, { + const userByIdCache = new MemoryKVCache(1000 * 60 * 60 * 6 /* 6h */, { toMapConverter: user => { if (user.host === null) { - localUserByIdCache.set(user.id, user as LocalUser); + localUserByIdCache.set(user.id, user as MiLocalUser); return user.id; } @@ -77,7 +77,7 @@ export class CacheService implements OnApplicationShutdown { }); this.userByIdCache = userByIdCache; - this.localUserByNativeTokenCache = new MemoryKVCache(Infinity, { + this.localUserByNativeTokenCache = new MemoryKVCache(Infinity, { toMapConverter: user => { if (user === null) return null; @@ -86,7 +86,7 @@ export class CacheService implements OnApplicationShutdown { }, fromMapConverter: id => id === null ? null : localUserByIdCache.get(id), }); - this.uriPersonCache = new MemoryKVCache(Infinity, { + this.uriPersonCache = new MemoryKVCache(Infinity, { toMapConverter: user => { if (user === null) return null; @@ -96,7 +96,7 @@ export class CacheService implements OnApplicationShutdown { fromMapConverter: id => id === null ? null : userByIdCache.get(id), }); - this.userProfileCache = new RedisKVCache(this.redisClient, 'userProfile', { + this.userProfileCache = new RedisKVCache(this.redisClient, 'userProfile', { lifetime: 1000 * 60 * 30, // 30m memoryCacheLifetime: 1000 * 60, // 1m fetcher: (key) => this.userProfilesRepository.findOneByOrFail({ userId: key }), @@ -178,7 +178,7 @@ export class CacheService implements OnApplicationShutdown { break; } case 'userTokenRegenerated': { - const user = await this.usersRepository.findOneByOrFail({ id: body.id }) as LocalUser; + const user = await this.usersRepository.findOneByOrFail({ id: body.id }) as MiLocalUser; this.localUserByNativeTokenCache.delete(body.oldToken); this.localUserByNativeTokenCache.set(body.newToken, user); break; @@ -197,7 +197,7 @@ export class CacheService implements OnApplicationShutdown { } @bindThis - public findUserById(userId: User['id']) { + public findUserById(userId: MiUser['id']) { return this.userByIdCache.fetch(userId, () => this.usersRepository.findOneByOrFail({ id: userId })); } diff --git a/packages/backend/src/core/ClipService.ts b/packages/backend/src/core/ClipService.ts new file mode 100644 index 000000000000..3d9982e80f7a --- /dev/null +++ b/packages/backend/src/core/ClipService.ts @@ -0,0 +1,159 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { QueryFailedError } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { ClipsRepository, MiNote, MiClip, ClipNotesRepository, NotesRepository } from '@/models/_.js'; +import { bindThis } from '@/decorators.js'; +import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; +import { RoleService } from '@/core/RoleService.js'; +import { IdService } from '@/core/IdService.js'; +import type { MiLocalUser } from '@/models/User.js'; + +@Injectable() +export class ClipService { + public static NoSuchNoteError = class extends Error {}; + public static NoSuchClipError = class extends Error {}; + public static AlreadyAddedError = class extends Error {}; + public static TooManyClipNotesError = class extends Error {}; + public static TooManyClipsError = class extends Error {}; + + constructor( + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, + + @Inject(DI.clipNotesRepository) + private clipNotesRepository: ClipNotesRepository, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private roleService: RoleService, + private idService: IdService, + ) { + } + + @bindThis + public async create(me: MiLocalUser, name: string, isPublic: boolean, description: string | null): Promise { + const currentCount = await this.clipsRepository.countBy({ + userId: me.id, + }); + if (currentCount > (await this.roleService.getUserPolicies(me.id)).clipLimit) { + throw new ClipService.TooManyClipsError(); + } + + const clip = await this.clipsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + name: name, + isPublic: isPublic, + description: description, + }).then(x => this.clipsRepository.findOneByOrFail(x.identifiers[0])); + + return clip; + } + + @bindThis + public async update(me: MiLocalUser, clipId: MiClip['id'], name: string | undefined, isPublic: boolean | undefined, description: string | null | undefined): Promise { + const clip = await this.clipsRepository.findOneBy({ + id: clipId, + userId: me.id, + }); + + if (clip == null) { + throw new ClipService.NoSuchClipError(); + } + + await this.clipsRepository.update(clip.id, { + name: name, + description: description, + isPublic: isPublic, + }); + } + + @bindThis + public async delete(me: MiLocalUser, clipId: MiClip['id']): Promise { + const clip = await this.clipsRepository.findOneBy({ + id: clipId, + userId: me.id, + }); + + if (clip == null) { + throw new ClipService.NoSuchClipError(); + } + + await this.clipsRepository.delete(clip.id); + } + + @bindThis + public async addNote(me: MiLocalUser, clipId: MiClip['id'], noteId: MiNote['id']): Promise { + const clip = await this.clipsRepository.findOneBy({ + id: clipId, + userId: me.id, + }); + + if (clip == null) { + throw new ClipService.NoSuchClipError(); + } + + const currentCount = await this.clipNotesRepository.countBy({ + clipId: clip.id, + }); + if (currentCount > (await this.roleService.getUserPolicies(me.id)).noteEachClipsLimit) { + throw new ClipService.TooManyClipNotesError(); + } + + try { + await this.clipNotesRepository.insert({ + id: this.idService.genId(), + noteId: noteId, + clipId: clip.id, + }); + } catch (e: unknown) { + if (e instanceof QueryFailedError) { + if (isDuplicateKeyValueError(e)) { + throw new ClipService.AlreadyAddedError(); + } else if (e.driverError.detail.includes('is not present in table "note".')) { + throw new ClipService.NoSuchNoteError(); + } + } + + throw e; + } + + this.clipsRepository.update(clip.id, { + lastClippedAt: new Date(), + }); + + this.notesRepository.increment({ id: noteId }, 'clippedCount', 1); + } + + @bindThis + public async removeNote(me: MiLocalUser, clipId: MiClip['id'], noteId: MiNote['id']): Promise { + const clip = await this.clipsRepository.findOneBy({ + id: clipId, + userId: me.id, + }); + + if (clip == null) { + throw new ClipService.NoSuchClipError(); + } + + const note = await this.notesRepository.findOneBy({ id: noteId }); + + if (note == null) { + throw new ClipService.NoSuchNoteError(); + } + + await this.clipNotesRepository.delete({ + noteId: noteId, + clipId: clip.id, + }); + + this.notesRepository.decrement({ id: noteId }, 'clippedCount', 1); + } +} diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index 51d4f9cfa977..78333e70a5e0 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -43,7 +43,7 @@ import { RelayService } from './RelayService.js'; import { RoleService } from './RoleService.js'; import { S3Service } from './S3Service.js'; import { SignupService } from './SignupService.js'; -import { TwoFactorAuthenticationService } from './TwoFactorAuthenticationService.js'; +import { WebAuthnService } from './WebAuthnService.js'; import { UserBlockingService } from './UserBlockingService.js'; import { CacheService } from './CacheService.js'; import { UserFollowingService } from './UserFollowingService.js'; @@ -51,12 +51,14 @@ import { UserKeypairService } from './UserKeypairService.js'; import { UserListService } from './UserListService.js'; import { UserMutingService } from './UserMutingService.js'; import { UserSuspendService } from './UserSuspendService.js'; +import { UserAuthService } from './UserAuthService.js'; import { VideoProcessingService } from './VideoProcessingService.js'; import { WebhookService } from './WebhookService.js'; import { ProxyAccountService } from './ProxyAccountService.js'; import { UtilityService } from './UtilityService.js'; import { FileInfoService } from './FileInfoService.js'; import { SearchService } from './SearchService.js'; +import { ClipService } from './ClipService.js'; import { ChartLoggerService } from './chart/ChartLoggerService.js'; import FederationChart from './chart/charts/federation.js'; import NotesChart from './chart/charts/notes.js'; @@ -168,7 +170,7 @@ const $RelayService: Provider = { provide: 'RelayService', useExisting: RelaySer const $RoleService: Provider = { provide: 'RoleService', useExisting: RoleService }; const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service }; const $SignupService: Provider = { provide: 'SignupService', useExisting: SignupService }; -const $TwoFactorAuthenticationService: Provider = { provide: 'TwoFactorAuthenticationService', useExisting: TwoFactorAuthenticationService }; +const $WebAuthnService: Provider = { provide: 'WebAuthnService', useExisting: WebAuthnService }; const $UserBlockingService: Provider = { provide: 'UserBlockingService', useExisting: UserBlockingService }; const $CacheService: Provider = { provide: 'CacheService', useExisting: CacheService }; const $UserFollowingService: Provider = { provide: 'UserFollowingService', useExisting: UserFollowingService }; @@ -176,11 +178,13 @@ const $UserKeypairService: Provider = { provide: 'UserKeypairService', useExisti const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService }; const $UserMutingService: Provider = { provide: 'UserMutingService', useExisting: UserMutingService }; const $UserSuspendService: Provider = { provide: 'UserSuspendService', useExisting: UserSuspendService }; +const $UserAuthService: Provider = { provide: 'UserAuthService', useExisting: UserAuthService }; const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', useExisting: VideoProcessingService }; const $WebhookService: Provider = { provide: 'WebhookService', useExisting: WebhookService }; const $UtilityService: Provider = { provide: 'UtilityService', useExisting: UtilityService }; const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: FileInfoService }; const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService }; +const $ClipService: Provider = { provide: 'ClipService', useExisting: ClipService }; const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService }; const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart }; @@ -296,7 +300,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting RoleService, S3Service, SignupService, - TwoFactorAuthenticationService, + WebAuthnService, UserBlockingService, CacheService, UserFollowingService, @@ -304,11 +308,13 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting UserListService, UserMutingService, UserSuspendService, + UserAuthService, VideoProcessingService, WebhookService, UtilityService, FileInfoService, SearchService, + ClipService, ChartLoggerService, FederationChart, NotesChart, @@ -417,7 +423,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $RoleService, $S3Service, $SignupService, - $TwoFactorAuthenticationService, + $WebAuthnService, $UserBlockingService, $CacheService, $UserFollowingService, @@ -425,11 +431,13 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $UserListService, $UserMutingService, $UserSuspendService, + $UserAuthService, $VideoProcessingService, $WebhookService, $UtilityService, $FileInfoService, $SearchService, + $ClipService, $ChartLoggerService, $FederationChart, $NotesChart, @@ -539,7 +547,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting RoleService, S3Service, SignupService, - TwoFactorAuthenticationService, + WebAuthnService, UserBlockingService, CacheService, UserFollowingService, @@ -547,11 +555,13 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting UserListService, UserMutingService, UserSuspendService, + UserAuthService, VideoProcessingService, WebhookService, UtilityService, FileInfoService, SearchService, + ClipService, FederationChart, NotesChart, UsersChart, @@ -659,7 +669,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $RoleService, $S3Service, $SignupService, - $TwoFactorAuthenticationService, + $WebAuthnService, $UserBlockingService, $CacheService, $UserFollowingService, @@ -667,11 +677,13 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $UserListService, $UserMutingService, $UserSuspendService, + $UserAuthService, $VideoProcessingService, $WebhookService, $UtilityService, $FileInfoService, $SearchService, + $ClipService, $FederationChart, $NotesChart, $UsersChart, diff --git a/packages/backend/src/core/CreateSystemUserService.ts b/packages/backend/src/core/CreateSystemUserService.ts index 5eece8cd4657..3419d0b497ca 100644 --- a/packages/backend/src/core/CreateSystemUserService.ts +++ b/packages/backend/src/core/CreateSystemUserService.ts @@ -8,11 +8,11 @@ import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; import { IsNull, DataSource } from 'typeorm'; import { genRsaKeyPair } from '@/misc/gen-key-pair.js'; -import { User } from '@/models/entities/User.js'; -import { UserProfile } from '@/models/entities/UserProfile.js'; +import { MiUser } from '@/models/User.js'; +import { MiUserProfile } from '@/models/UserProfile.js'; import { IdService } from '@/core/IdService.js'; -import { UserKeypair } from '@/models/entities/UserKeypair.js'; -import { UsedUsername } from '@/models/entities/UsedUsername.js'; +import { MiUserKeypair } from '@/models/UserKeypair.js'; +import { MiUsedUsername } from '@/models/UsedUsername.js'; import { DI } from '@/di-symbols.js'; import generateNativeUserToken from '@/misc/generate-native-user-token.js'; import { bindThis } from '@/decorators.js'; @@ -28,7 +28,7 @@ export class CreateSystemUserService { } @bindThis - public async createSystemUser(username: string): Promise { + public async createSystemUser(username: string): Promise { const password = randomUUID(); // Generate hash of password @@ -40,18 +40,18 @@ export class CreateSystemUserService { const keyPair = await genRsaKeyPair(); - let account!: User; + let account!: MiUser; // Start transaction await this.db.transaction(async transactionalEntityManager => { - const exist = await transactionalEntityManager.findOneBy(User, { + const exist = await transactionalEntityManager.findOneBy(MiUser, { usernameLower: username.toLowerCase(), host: IsNull(), }); if (exist) throw new Error('the user is already exists'); - account = await transactionalEntityManager.insert(User, { + account = await transactionalEntityManager.insert(MiUser, { id: this.idService.genId(), createdAt: new Date(), username: username, @@ -62,21 +62,21 @@ export class CreateSystemUserService { isLocked: true, isExplorable: false, isBot: true, - }).then(x => transactionalEntityManager.findOneByOrFail(User, x.identifiers[0])); + }).then(x => transactionalEntityManager.findOneByOrFail(MiUser, x.identifiers[0])); - await transactionalEntityManager.insert(UserKeypair, { + await transactionalEntityManager.insert(MiUserKeypair, { publicKey: keyPair.publicKey, privateKey: keyPair.privateKey, userId: account.id, }); - await transactionalEntityManager.insert(UserProfile, { + await transactionalEntityManager.insert(MiUserProfile, { userId: account.id, autoAcceptFollowed: false, password: hash, }); - await transactionalEntityManager.insert(UsedUsername, { + await transactionalEntityManager.insert(MiUsedUsername, { createdAt: new Date(), username: username.toLowerCase(), }); diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index f24a880914df..aa5490eba725 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -10,9 +10,9 @@ import { DI } from '@/di-symbols.js'; import { IdService } from '@/core/IdService.js'; import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; -import type { Emoji } from '@/models/entities/Emoji.js'; -import type { EmojisRepository, Role } from '@/models/index.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; +import type { MiEmoji } from '@/models/Emoji.js'; +import type { EmojisRepository, MiRole } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js'; import { UtilityService } from '@/core/UtilityService.js'; @@ -23,8 +23,8 @@ const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/; @Injectable() export class CustomEmojiService implements OnApplicationShutdown { - private cache: MemoryKVCache; - public localEmojisCache: RedisSingleCache>; + private cache: MemoryKVCache; + public localEmojisCache: RedisSingleCache>; constructor( @Inject(DI.redis) @@ -38,16 +38,16 @@ export class CustomEmojiService implements OnApplicationShutdown { private emojiEntityService: EmojiEntityService, private globalEventService: GlobalEventService, ) { - this.cache = new MemoryKVCache(1000 * 60 * 60 * 12); + this.cache = new MemoryKVCache(1000 * 60 * 60 * 12); - this.localEmojisCache = new RedisSingleCache>(this.redisClient, 'localEmojis', { + this.localEmojisCache = new RedisSingleCache>(this.redisClient, 'localEmojis', { lifetime: 1000 * 60 * 30, // 30m memoryCacheLifetime: 1000 * 60 * 3, // 3m fetcher: () => this.emojisRepository.find({ where: { host: IsNull() } }).then(emojis => new Map(emojis.map(emoji => [emoji.name, emoji]))), toRedisConverter: (value) => JSON.stringify(Array.from(value.values())), fromRedisConverter: (value) => { if (!Array.isArray(JSON.parse(value))) return undefined; // 古いバージョンの壊れたキャッシュが残っていることがある(そのうち消す) - return new Map(JSON.parse(value).map((x: Serialized) => [x.name, { + return new Map(JSON.parse(value).map((x: Serialized) => [x.name, { ...x, updatedAt: x.updatedAt ? new Date(x.updatedAt) : null, }])); @@ -57,7 +57,7 @@ export class CustomEmojiService implements OnApplicationShutdown { @bindThis public async add(data: { - driveFile: DriveFile; + driveFile: MiDriveFile; name: string; category: string | null; aliases: string[]; @@ -65,8 +65,8 @@ export class CustomEmojiService implements OnApplicationShutdown { license: string | null; isSensitive: boolean; localOnly: boolean; - roleIdsThatCanBeUsedThisEmojiAsReaction: Role['id'][]; - }): Promise { + roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][]; + }): Promise { const emoji = await this.emojisRepository.insert({ id: this.idService.genId(), updatedAt: new Date(), @@ -95,15 +95,15 @@ export class CustomEmojiService implements OnApplicationShutdown { } @bindThis - public async update(id: Emoji['id'], data: { - driveFile?: DriveFile; + public async update(id: MiEmoji['id'], data: { + driveFile?: MiDriveFile; name?: string; category?: string | null; aliases?: string[]; license?: string | null; isSensitive?: boolean; localOnly?: boolean; - roleIdsThatCanBeUsedThisEmojiAsReaction?: Role['id'][]; + roleIdsThatCanBeUsedThisEmojiAsReaction?: MiRole['id'][]; }): Promise { const emoji = await this.emojisRepository.findOneByOrFail({ id: id }); const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() }); @@ -143,7 +143,7 @@ export class CustomEmojiService implements OnApplicationShutdown { } @bindThis - public async addAliasesBulk(ids: Emoji['id'][], aliases: string[]) { + public async addAliasesBulk(ids: MiEmoji['id'][], aliases: string[]) { const emojis = await this.emojisRepository.findBy({ id: In(ids), }); @@ -163,7 +163,7 @@ export class CustomEmojiService implements OnApplicationShutdown { } @bindThis - public async setAliasesBulk(ids: Emoji['id'][], aliases: string[]) { + public async setAliasesBulk(ids: MiEmoji['id'][], aliases: string[]) { await this.emojisRepository.update({ id: In(ids), }, { @@ -179,7 +179,7 @@ export class CustomEmojiService implements OnApplicationShutdown { } @bindThis - public async removeAliasesBulk(ids: Emoji['id'][], aliases: string[]) { + public async removeAliasesBulk(ids: MiEmoji['id'][], aliases: string[]) { const emojis = await this.emojisRepository.findBy({ id: In(ids), }); @@ -199,7 +199,7 @@ export class CustomEmojiService implements OnApplicationShutdown { } @bindThis - public async setCategoryBulk(ids: Emoji['id'][], category: string | null) { + public async setCategoryBulk(ids: MiEmoji['id'][], category: string | null) { await this.emojisRepository.update({ id: In(ids), }, { @@ -215,7 +215,7 @@ export class CustomEmojiService implements OnApplicationShutdown { } @bindThis - public async setLicenseBulk(ids: Emoji['id'][], license: string | null) { + public async setLicenseBulk(ids: MiEmoji['id'][], license: string | null) { await this.emojisRepository.update({ id: In(ids), }, { @@ -231,7 +231,7 @@ export class CustomEmojiService implements OnApplicationShutdown { } @bindThis - public async delete(id: Emoji['id']) { + public async delete(id: MiEmoji['id']) { const emoji = await this.emojisRepository.findOneByOrFail({ id: id }); await this.emojisRepository.delete(emoji.id); @@ -244,7 +244,7 @@ export class CustomEmojiService implements OnApplicationShutdown { } @bindThis - public async deleteBulk(ids: Emoji['id'][]) { + public async deleteBulk(ids: MiEmoji['id'][]) { const emojis = await this.emojisRepository.findBy({ id: In(ids), }); diff --git a/packages/backend/src/core/DeleteAccountService.ts b/packages/backend/src/core/DeleteAccountService.ts index 3e90d39455ad..570bd440e448 100644 --- a/packages/backend/src/core/DeleteAccountService.ts +++ b/packages/backend/src/core/DeleteAccountService.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/_.js'; import { QueueService } from '@/core/QueueService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 8c5c41ca0122..e015d3dc416f 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -11,12 +11,12 @@ import { sharpBmp } from 'sharp-read-bmp'; import { IsNull } from 'typeorm'; import { DeleteObjectCommandInput, PutObjectCommandInput, NoSuchKey } from '@aws-sdk/client-s3'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; import Logger from '@/logger.js'; -import type { RemoteUser, User } from '@/models/entities/User.js'; +import type { MiRemoteUser, MiUser } from '@/models/User.js'; import { MetaService } from '@/core/MetaService.js'; -import { DriveFile } from '@/models/entities/DriveFile.js'; +import { MiDriveFile } from '@/models/DriveFile.js'; import { IdService } from '@/core/IdService.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; @@ -27,7 +27,7 @@ import { VideoProcessingService } from '@/core/VideoProcessingService.js'; import { ImageProcessingService } from '@/core/ImageProcessingService.js'; import type { IImage } from '@/core/ImageProcessingService.js'; import { QueueService } from '@/core/QueueService.js'; -import type { DriveFolder } from '@/models/entities/DriveFolder.js'; +import type { MiDriveFolder } from '@/models/DriveFolder.js'; import { createTemp } from '@/misc/create-temp.js'; import DriveChart from '@/core/chart/charts/drive.js'; import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js'; @@ -45,7 +45,7 @@ import { isMimeImage } from '@/misc/is-mime-image.js'; type AddFileArgs = { /** User who wish to add file */ - user: { id: User['id']; host: User['host'] } | null; + user: { id: MiUser['id']; host: MiUser['host'] } | null; /** File path */ path: string; /** Name */ @@ -73,8 +73,8 @@ type AddFileArgs = { type UploadFromUrlArgs = { url: string; - user: { id: User['id']; host: User['host'] } | null; - folderId?: DriveFolder['id'] | null; + user: { id: MiUser['id']; host: MiUser['host'] } | null; + folderId?: MiDriveFolder['id'] | null; uri?: string | null; sensitive?: boolean; force?: boolean; @@ -138,7 +138,7 @@ export class DriveService { * @param size Size for original */ @bindThis - private async save(file: DriveFile, path: string, name: string, type: string, hash: string, size: number): Promise { + private async save(file: MiDriveFile, path: string, name: string, type: string, hash: string, size: number): Promise { // thunbnail, webpublic を必要なら生成 const alts = await this.generateAlts(path, type, !file.uri); @@ -405,7 +405,7 @@ export class DriveService { // Expire oldest file (without avatar or banner) of remote user @bindThis - private async expireOldFile(user: RemoteUser, driveCapacity: number) { + private async expireOldFile(user: MiRemoteUser, driveCapacity: number) { const q = this.driveFilesRepository.createQueryBuilder('file') .where('file.userId = :userId', { userId: user.id }) .andWhere('file.isLink = FALSE'); @@ -451,7 +451,7 @@ export class DriveService { requestIp = null, requestHeaders = null, ext = null, - }: AddFileArgs): Promise { + }: AddFileArgs): Promise { let skipNsfwCheck = false; const instance = await this.metaService.fetch(); const userRoleNSFW = user && (await this.roleService.getUserPolicies(user.id)).alwaysMarkNsfw; @@ -520,7 +520,7 @@ export class DriveService { if (isLocalUser) { throw new IdentifiableError('c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6', 'No free space.'); } - await this.expireOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as RemoteUser, driveCapacity - info.size); + await this.expireOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as MiRemoteUser, driveCapacity - info.size); } } //#endregion @@ -558,7 +558,7 @@ export class DriveService { const folder = await fetchFolder(); - let file = new DriveFile(); + let file = new MiDriveFile(); file.id = this.idService.genId(); file.createdAt = new Date(); file.userId = user ? user.id : null; @@ -614,7 +614,7 @@ export class DriveService { file = await this.driveFilesRepository.findOneBy({ uri: file.uri!, userId: user ? user.id : IsNull(), - }) as DriveFile; + }) as MiDriveFile; } else { this.registerLogger.error(err as Error); throw err; @@ -648,7 +648,7 @@ export class DriveService { } @bindThis - public async deleteFile(file: DriveFile, isExpired = false) { + public async deleteFile(file: MiDriveFile, isExpired = false) { if (file.storedInternal) { this.internalStorageService.del(file.accessKey!); @@ -675,7 +675,7 @@ export class DriveService { } @bindThis - public async deleteFileSync(file: DriveFile, isExpired = false) { + public async deleteFileSync(file: MiDriveFile, isExpired = false) { if (file.storedInternal) { this.internalStorageService.del(file.accessKey!); @@ -706,7 +706,7 @@ export class DriveService { } @bindThis - private async deletePostProcess(file: DriveFile, isExpired = false) { + private async deletePostProcess(file: MiDriveFile, isExpired = false) { // リモートファイル期限切れ削除後は直リンクにする if (isExpired && file.userHost !== null && file.uri != null) { this.driveFilesRepository.update(file.id, { @@ -769,7 +769,7 @@ export class DriveService { comment = null, requestIp = null, requestHeaders = null, - }: UploadFromUrlArgs): Promise { + }: UploadFromUrlArgs): Promise { // Create temp file const [path, cleanup] = await createTemp(); diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts index 162634593e6c..c9da3f77c006 100644 --- a/packages/backend/src/core/EmailService.ts +++ b/packages/backend/src/core/EmailService.ts @@ -10,7 +10,7 @@ import { MetaService } from '@/core/MetaService.js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; -import type { UserProfilesRepository } from '@/models/index.js'; +import type { UserProfilesRepository } from '@/models/_.js'; import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts index 8c3272adea54..61806583c6ee 100644 --- a/packages/backend/src/core/FederatedInstanceService.ts +++ b/packages/backend/src/core/FederatedInstanceService.ts @@ -5,8 +5,8 @@ import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import * as Redis from 'ioredis'; -import type { InstancesRepository } from '@/models/index.js'; -import type { Instance } from '@/models/entities/Instance.js'; +import type { InstancesRepository } from '@/models/_.js'; +import type { MiInstance } from '@/models/Instance.js'; import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; @@ -15,7 +15,7 @@ import { bindThis } from '@/decorators.js'; @Injectable() export class FederatedInstanceService implements OnApplicationShutdown { - public federatedInstanceCache: RedisKVCache; + public federatedInstanceCache: RedisKVCache; constructor( @Inject(DI.redis) @@ -27,7 +27,7 @@ export class FederatedInstanceService implements OnApplicationShutdown { private utilityService: UtilityService, private idService: IdService, ) { - this.federatedInstanceCache = new RedisKVCache(this.redisClient, 'federatedInstance', { + this.federatedInstanceCache = new RedisKVCache(this.redisClient, 'federatedInstance', { lifetime: 1000 * 60 * 30, // 30m memoryCacheLifetime: 1000 * 60 * 3, // 3m fetcher: (key) => this.instancesRepository.findOneBy({ host: key }), @@ -46,7 +46,7 @@ export class FederatedInstanceService implements OnApplicationShutdown { } @bindThis - public async fetch(host: string): Promise { + public async fetch(host: string): Promise { host = this.utilityService.toPuny(host); const cached = await this.federatedInstanceCache.get(host); @@ -70,7 +70,7 @@ export class FederatedInstanceService implements OnApplicationShutdown { } @bindThis - public async update(id: Instance['id'], data: Partial): Promise { + public async update(id: MiInstance['id'], data: Partial): Promise { const result = await this.instancesRepository.createQueryBuilder().update() .set(data) .where('id = :id', { id }) diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index 22309f230f27..682acef15b96 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -8,7 +8,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { JSDOM } from 'jsdom'; import tinycolor from 'tinycolor2'; import * as Redis from 'ioredis'; -import type { Instance } from '@/models/entities/Instance.js'; +import type { MiInstance } from '@/models/Instance.js'; import type Logger from '@/logger.js'; import { DI } from '@/di-symbols.js'; import { LoggerService } from '@/core/LoggerService.js'; @@ -62,7 +62,7 @@ export class FetchInstanceMetadataService { } @bindThis - public async fetchInstanceMetadata(instance: Instance, force = false): Promise { + public async fetchInstanceMetadata(instance: MiInstance, force = false): Promise { const host = instance.host; // Acquire mutex to ensure no parallel runs if (!await this.tryLock(host)) return; @@ -123,7 +123,7 @@ export class FetchInstanceMetadataService { } @bindThis - private async fetchNodeinfo(instance: Instance): Promise { + private async fetchNodeinfo(instance: MiInstance): Promise { this.logger.info(`Fetching nodeinfo of ${instance.host} ...`); try { @@ -142,10 +142,10 @@ export class FetchInstanceMetadataService { const links = wellknown.links as any[]; - const lnik1_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/1.0'); - const lnik2_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.0'); - const lnik2_1 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.1'); - const link = lnik2_1 ?? lnik2_0 ?? lnik1_0; + const link1_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/1.0'); + const link2_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.0'); + const link2_1 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.1'); + const link = link2_1 ?? link2_0 ?? link1_0; if (link == null) { throw new Error('No nodeinfo link provided'); @@ -167,7 +167,7 @@ export class FetchInstanceMetadataService { } @bindThis - private async fetchDom(instance: Instance): Promise { + private async fetchDom(instance: MiInstance): Promise { this.logger.info(`Fetching HTML of ${instance.host} ...`); const url = 'https://' + instance.host; @@ -181,7 +181,7 @@ export class FetchInstanceMetadataService { } @bindThis - private async fetchManifest(instance: Instance): Promise | null> { + private async fetchManifest(instance: MiInstance): Promise | null> { const url = 'https://' + instance.host; const manifestUrl = url + '/manifest.json'; @@ -192,7 +192,7 @@ export class FetchInstanceMetadataService { } @bindThis - private async fetchFaviconUrl(instance: Instance, doc: DOMWindow['document'] | null): Promise { + private async fetchFaviconUrl(instance: MiInstance, doc: DOMWindow['document'] | null): Promise { const url = 'https://' + instance.host; if (doc) { @@ -218,7 +218,7 @@ export class FetchInstanceMetadataService { } @bindThis - private async fetchIconUrl(instance: Instance, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { + private async fetchIconUrl(instance: MiInstance, doc: DOMWindow['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; diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index f216b79ba355..4bc4f54c21d3 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -5,10 +5,10 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; -import type { User } from '@/models/entities/User.js'; -import type { Note } from '@/models/entities/Note.js'; -import type { UserList } from '@/models/entities/UserList.js'; -import type { Antenna } from '@/models/entities/Antenna.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiNote } from '@/models/Note.js'; +import type { MiUserList } from '@/models/UserList.js'; +import type { MiAntenna } from '@/models/Antenna.js'; import type { StreamChannels, AdminStreamTypes, @@ -25,7 +25,7 @@ import type { Packed } from '@/misc/json-schema.js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { bindThis } from '@/decorators.js'; -import { Role } from '@/models/index.js'; +import { MiRole } from '@/models/_.js'; @Injectable() export class GlobalEventService { @@ -61,17 +61,17 @@ export class GlobalEventService { } @bindThis - public publishMainStream(userId: User['id'], type: K, value?: MainStreamTypes[K]): void { + public publishMainStream(userId: MiUser['id'], type: K, value?: MainStreamTypes[K]): void { this.publish(`mainStream:${userId}`, type, typeof value === 'undefined' ? null : value); } @bindThis - public publishDriveStream(userId: User['id'], type: K, value?: DriveStreamTypes[K]): void { + public publishDriveStream(userId: MiUser['id'], type: K, value?: DriveStreamTypes[K]): void { this.publish(`driveStream:${userId}`, type, typeof value === 'undefined' ? null : value); } @bindThis - public publishNoteStream(noteId: Note['id'], type: K, value?: NoteStreamTypes[K]): void { + public publishNoteStream(noteId: MiNote['id'], type: K, value?: NoteStreamTypes[K]): void { this.publish(`noteStream:${noteId}`, type, { id: noteId, body: value, @@ -79,17 +79,17 @@ export class GlobalEventService { } @bindThis - public publishUserListStream(listId: UserList['id'], type: K, value?: UserListStreamTypes[K]): void { + public publishUserListStream(listId: MiUserList['id'], type: K, value?: UserListStreamTypes[K]): void { this.publish(`userListStream:${listId}`, type, typeof value === 'undefined' ? null : value); } @bindThis - public publishAntennaStream(antennaId: Antenna['id'], type: K, value?: AntennaStreamTypes[K]): void { + public publishAntennaStream(antennaId: MiAntenna['id'], type: K, value?: AntennaStreamTypes[K]): void { this.publish(`antennaStream:${antennaId}`, type, typeof value === 'undefined' ? null : value); } @bindThis - public publishRoleTimelineStream(roleId: Role['id'], type: K, value?: RoleTimelineStreamTypes[K]): void { + public publishRoleTimelineStream(roleId: MiRole['id'], type: K, value?: RoleTimelineStreamTypes[K]): void { this.publish(`roleTimelineStream:${roleId}`, type, typeof value === 'undefined' ? null : value); } @@ -99,7 +99,7 @@ export class GlobalEventService { } @bindThis - public publishAdminStream(userId: User['id'], type: K, value?: AdminStreamTypes[K]): void { + public publishAdminStream(userId: MiUser['id'], type: K, value?: AdminStreamTypes[K]): void { this.publish(`adminStream:${userId}`, type, typeof value === 'undefined' ? null : value); } } diff --git a/packages/backend/src/core/HashtagService.ts b/packages/backend/src/core/HashtagService.ts index 383bed2f4100..c72c7460ff21 100644 --- a/packages/backend/src/core/HashtagService.ts +++ b/packages/backend/src/core/HashtagService.ts @@ -5,11 +5,11 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { User } from '@/models/entities/User.js'; +import type { MiUser } from '@/models/User.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { IdService } from '@/core/IdService.js'; -import type { Hashtag } from '@/models/entities/Hashtag.js'; -import type { HashtagsRepository } from '@/models/index.js'; +import type { MiHashtag } from '@/models/Hashtag.js'; +import type { HashtagsRepository } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; @@ -25,14 +25,14 @@ export class HashtagService { } @bindThis - public async updateHashtags(user: { id: User['id']; host: User['host']; }, tags: string[]) { + public async updateHashtags(user: { id: MiUser['id']; host: MiUser['host']; }, tags: string[]) { for (const tag of tags) { await this.updateHashtag(user, tag); } } @bindThis - public async updateUsertags(user: User, tags: string[]) { + public async updateUsertags(user: MiUser, tags: string[]) { for (const tag of tags) { await this.updateHashtag(user, tag, true, true); } @@ -43,7 +43,7 @@ export class HashtagService { } @bindThis - public async updateHashtag(user: { id: User['id']; host: User['host']; }, tag: string, isUserAttached = false, inc = true) { + public async updateHashtag(user: { id: MiUser['id']; host: MiUser['host']; }, tag: string, isUserAttached = false, inc = true) { tag = normalizeForSearch(tag); const index = await this.hashtagsRepository.findOneBy({ name: tag }); @@ -123,7 +123,7 @@ export class HashtagService { attachedLocalUsersCount: this.userEntityService.isLocalUser(user) ? 1 : 0, attachedRemoteUserIds: this.userEntityService.isRemoteUser(user) ? [user.id] : [], attachedRemoteUsersCount: this.userEntityService.isRemoteUser(user) ? 1 : 0, - } as Hashtag); + } as MiHashtag); } else { this.hashtagsRepository.insert({ id: this.idService.genId(), @@ -140,7 +140,7 @@ export class HashtagService { attachedLocalUsersCount: 0, attachedRemoteUserIds: [], attachedRemoteUsersCount: 0, - } as Hashtag); + } as MiHashtag); } } } diff --git a/packages/backend/src/core/HttpRequestService.ts b/packages/backend/src/core/HttpRequestService.ts index 487172deae4c..73bb3dc7e971 100644 --- a/packages/backend/src/core/HttpRequestService.ts +++ b/packages/backend/src/core/HttpRequestService.ts @@ -53,12 +53,14 @@ export class HttpRequestService { keepAlive: true, keepAliveMsecs: 30 * 1000, lookup: cache.lookup as unknown as net.LookupFunction, + localAddress: config.outgoingAddress, }); this.https = new https.Agent({ keepAlive: true, keepAliveMsecs: 30 * 1000, lookup: cache.lookup as unknown as net.LookupFunction, + localAddress: config.outgoingAddress, }); const maxSockets = Math.max(256, config.deliverJobConcurrency ?? 128); @@ -71,6 +73,7 @@ export class HttpRequestService { maxFreeSockets: 256, scheduling: 'lifo', proxy: config.proxy, + localAddress: config.outgoingAddress, }) : this.http; @@ -82,6 +85,7 @@ export class HttpRequestService { maxFreeSockets: 256, scheduling: 'lifo', proxy: config.proxy, + localAddress: config.outgoingAddress, }) : this.https; } diff --git a/packages/backend/src/core/IdService.ts b/packages/backend/src/core/IdService.ts index 186fd36b42ef..06c58ad8a129 100644 --- a/packages/backend/src/core/IdService.ts +++ b/packages/backend/src/core/IdService.ts @@ -8,6 +8,7 @@ import { ulid } from 'ulid'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { genAid, parseAid } from '@/misc/id/aid.js'; +import { genAidx, parseAidx } from '@/misc/id/aidx.js'; import { genMeid, parseMeid } from '@/misc/id/meid.js'; import { genMeidg, parseMeidg } from '@/misc/id/meidg.js'; import { genObjectId, parseObjectId } from '@/misc/id/object-id.js'; @@ -31,6 +32,7 @@ export class IdService { switch (this.method) { case 'aid': return genAid(date); + case 'aidx': return genAidx(date); case 'meid': return genMeid(date); case 'meidg': return genMeidg(date); case 'ulid': return ulid(date.getTime()); @@ -43,6 +45,7 @@ export class IdService { public parse(id: string): { date: Date; } { switch (this.method) { case 'aid': return parseAid(id); + case 'aidx': return parseAidx(id); case 'objectid': return parseObjectId(id); case 'meid': return parseMeid(id); case 'meidg': return parseMeidg(id); diff --git a/packages/backend/src/core/InstanceActorService.ts b/packages/backend/src/core/InstanceActorService.ts index c6b18925bca5..b40fd46291ec 100644 --- a/packages/backend/src/core/InstanceActorService.ts +++ b/packages/backend/src/core/InstanceActorService.ts @@ -5,8 +5,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; -import type { LocalUser } from '@/models/entities/User.js'; -import type { UsersRepository } from '@/models/index.js'; +import type { MiLocalUser } from '@/models/User.js'; +import type { UsersRepository } from '@/models/_.js'; import { MemorySingleCache } from '@/misc/cache.js'; import { DI } from '@/di-symbols.js'; import { CreateSystemUserService } from '@/core/CreateSystemUserService.js'; @@ -16,7 +16,7 @@ const ACTOR_USERNAME = 'instance.actor' as const; @Injectable() export class InstanceActorService { - private cache: MemorySingleCache; + private cache: MemorySingleCache; constructor( @Inject(DI.usersRepository) @@ -24,24 +24,24 @@ export class InstanceActorService { private createSystemUserService: CreateSystemUserService, ) { - this.cache = new MemorySingleCache(Infinity); + this.cache = new MemorySingleCache(Infinity); } @bindThis - public async getInstanceActor(): Promise { + public async getInstanceActor(): Promise { const cached = this.cache.get(); if (cached) return cached; const user = await this.usersRepository.findOneBy({ host: IsNull(), username: ACTOR_USERNAME, - }) as LocalUser | undefined; + }) as MiLocalUser | undefined; if (user) { this.cache.set(user); return user; } else { - const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME) as LocalUser; + const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME) as MiLocalUser; this.cache.set(created); return created; } diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts index 3adc83ab8001..00e1e3c1fc2b 100644 --- a/packages/backend/src/core/MetaService.ts +++ b/packages/backend/src/core/MetaService.ts @@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; -import { Meta } from '@/models/entities/Meta.js'; +import { MiMeta } from '@/models/Meta.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { bindThis } from '@/decorators.js'; import { StreamMessages } from '@/server/api/stream/types.js'; @@ -15,8 +15,8 @@ import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() export class MetaService implements OnApplicationShutdown { - private cache: Meta | undefined; - private intervalId: NodeJS.Timer; + private cache: MiMeta | undefined; + private intervalId: NodeJS.Timeout; constructor( @Inject(DI.redisForSub) @@ -59,12 +59,12 @@ export class MetaService implements OnApplicationShutdown { } @bindThis - public async fetch(noCache = false): Promise { + public async fetch(noCache = false): Promise { if (!noCache && this.cache) return this.cache; return await this.db.transaction(async transactionalEntityManager => { // 過去のバグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する - const metas = await transactionalEntityManager.find(Meta, { + const metas = await transactionalEntityManager.find(MiMeta, { order: { id: 'DESC', }, @@ -79,13 +79,13 @@ export class MetaService implements OnApplicationShutdown { // metaが空のときfetchMetaが同時に呼ばれるとここが同時に呼ばれてしまうことがあるのでフェイルセーフなupsertを使う const saved = await transactionalEntityManager .upsert( - Meta, + MiMeta, { id: 'x', }, ['id'], ) - .then((x) => transactionalEntityManager.findOneByOrFail(Meta, x.identifiers[0])); + .then((x) => transactionalEntityManager.findOneByOrFail(MiMeta, x.identifiers[0])); this.cache = saved; return saved; @@ -94,9 +94,9 @@ export class MetaService implements OnApplicationShutdown { } @bindThis - public async update(data: Partial): Promise { + public async update(data: Partial): Promise { const updated = await this.db.transaction(async transactionalEntityManager => { - const metas = await transactionalEntityManager.find(Meta, { + const metas = await transactionalEntityManager.find(MiMeta, { order: { id: 'DESC', }, @@ -105,9 +105,9 @@ export class MetaService implements OnApplicationShutdown { const meta = metas[0]; if (meta) { - await transactionalEntityManager.update(Meta, meta.id, data); + await transactionalEntityManager.update(MiMeta, meta.id, data); - const metas = await transactionalEntityManager.find(Meta, { + const metas = await transactionalEntityManager.find(MiMeta, { order: { id: 'DESC', }, @@ -115,7 +115,7 @@ export class MetaService implements OnApplicationShutdown { return metas[0]; } else { - return await transactionalEntityManager.save(Meta, data); + return await transactionalEntityManager.save(MiMeta, data); } }); diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts index b6f84da47550..b275d1b14203 100644 --- a/packages/backend/src/core/MfmService.ts +++ b/packages/backend/src/core/MfmService.ts @@ -10,7 +10,7 @@ import { Window } from 'happy-dom'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { intersperse } from '@/misc/prelude/array.js'; -import type { IMentionedRemoteUsers } from '@/models/entities/Note.js'; +import type { IMentionedRemoteUsers } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; import * as TreeAdapter from '../../node_modules/parse5/dist/tree-adapters/default.js'; import type * as mfm from 'mfm-js'; diff --git a/packages/backend/src/core/ModerationLogService.ts b/packages/backend/src/core/ModerationLogService.ts index 2f09f6f4803e..b0e5b794d0b5 100644 --- a/packages/backend/src/core/ModerationLogService.ts +++ b/packages/backend/src/core/ModerationLogService.ts @@ -5,8 +5,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { ModerationLogsRepository } from '@/models/index.js'; -import type { User } from '@/models/entities/User.js'; +import type { ModerationLogsRepository } from '@/models/_.js'; +import type { MiUser } from '@/models/User.js'; import { IdService } from '@/core/IdService.js'; import { bindThis } from '@/decorators.js'; @@ -21,7 +21,7 @@ export class ModerationLogService { } @bindThis - public async insertModerationLog(moderator: { id: User['id'] }, type: string, info?: Record) { + public async insertModerationLog(moderator: { id: MiUser['id'] }, type: string, info?: Record) { await this.moderationLogsRepository.insert({ id: this.idService.genId(), createdAt: new Date(), diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 001947322d5d..972319ddcf75 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -12,22 +12,22 @@ import RE2 from 're2'; import { extractMentions } from '@/misc/extract-mentions.js'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; import { extractHashtags } from '@/misc/extract-hashtags.js'; -import type { IMentionedRemoteUsers } from '@/models/entities/Note.js'; -import { Note } from '@/models/entities/Note.js'; -import type { ChannelsRepository, InstancesRepository, MutedNotesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; -import type { App } from '@/models/entities/App.js'; +import type { IMentionedRemoteUsers } from '@/models/Note.js'; +import { MiNote } from '@/models/Note.js'; +import type { ChannelsRepository, FollowingsRepository, InstancesRepository, MutedNotesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; +import type { MiApp } from '@/models/App.js'; import { concat } from '@/misc/prelude/array.js'; import { IdService } from '@/core/IdService.js'; -import type { User, LocalUser, RemoteUser } from '@/models/entities/User.js'; -import type { IPoll } from '@/models/entities/Poll.js'; -import { Poll } from '@/models/entities/Poll.js'; +import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js'; +import type { IPoll } from '@/models/Poll.js'; +import { MiPoll } from '@/models/Poll.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; import { checkWordMute } from '@/misc/check-word-mute.js'; -import type { Channel } from '@/models/entities/Channel.js'; +import type { MiChannel } from '@/models/Channel.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { MemorySingleCache } from '@/misc/cache.js'; -import type { UserProfile } from '@/models/entities/UserProfile.js'; +import type { MiUserProfile } from '@/models/UserProfile.js'; import { RelayService } from '@/core/RelayService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { DI } from '@/di-symbols.js'; @@ -54,23 +54,23 @@ import { RoleService } from '@/core/RoleService.js'; import { MetaService } from '@/core/MetaService.js'; import { SearchService } from '@/core/SearchService.js'; -const mutedWordsCache = new MemorySingleCache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5); +const mutedWordsCache = new MemorySingleCache<{ userId: MiUserProfile['userId']; mutedWords: MiUserProfile['mutedWords']; }[]>(1000 * 60 * 5); type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; class NotificationManager { - private notifier: { id: User['id']; }; - private note: Note; + private notifier: { id: MiUser['id']; }; + private note: MiNote; private queue: { - target: LocalUser['id']; + target: MiLocalUser['id']; reason: NotificationType; }[]; constructor( private mutingsRepository: MutingsRepository, private notificationService: NotificationService, - notifier: { id: User['id']; }, - note: Note, + notifier: { id: MiUser['id']; }, + note: MiNote, ) { this.notifier = notifier; this.note = note; @@ -78,7 +78,7 @@ class NotificationManager { } @bindThis - public push(notifiee: LocalUser['id'], reason: NotificationType) { + public push(notifiee: MiLocalUser['id'], reason: NotificationType) { // 自分自身へは通知しない if (this.notifier.id === notifiee) return; @@ -119,32 +119,32 @@ class NotificationManager { } type MinimumUser = { - id: User['id']; - host: User['host']; - username: User['username']; - uri: User['uri']; + id: MiUser['id']; + host: MiUser['host']; + username: MiUser['username']; + uri: MiUser['uri']; }; type Option = { createdAt?: Date | null; name?: string | null; text?: string | null; - reply?: Note | null; - renote?: Note | null; - files?: DriveFile[] | null; + reply?: MiNote | null; + renote?: MiNote | null; + files?: MiDriveFile[] | null; poll?: IPoll | null; localOnly?: boolean | null; - reactionAcceptance?: Note['reactionAcceptance']; + reactionAcceptance?: MiNote['reactionAcceptance']; cw?: string | null; visibility?: string; visibleUsers?: MinimumUser[] | null; - channel?: Channel | null; + channel?: MiChannel | null; apMentions?: MinimumUser[] | null; apHashtags?: string[] | null; apEmojis?: string[] | null; uri?: string | null; url?: string | null; - app?: App | null; + app?: MiApp | null; }; @Injectable() @@ -185,6 +185,9 @@ export class NoteCreateService implements OnApplicationShutdown { @Inject(DI.noteThreadMutingsRepository) private noteThreadMutingsRepository: NoteThreadMutingsRepository, + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + private userEntityService: UserEntityService, private noteEntityService: NoteEntityService, private idService: IdService, @@ -211,12 +214,12 @@ export class NoteCreateService implements OnApplicationShutdown { @bindThis public async create(user: { - id: User['id']; - username: User['username']; - host: User['host']; - createdAt: User['createdAt']; - isBot: User['isBot']; - }, data: Option, silent = false): Promise { + id: MiUser['id']; + username: MiUser['username']; + host: MiUser['host']; + createdAt: MiUser['createdAt']; + isBot: MiUser['isBot']; + }, data: Option, silent = false): Promise { // チャンネル外にリプライしたら対象のスコープに合わせる // (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで) if (data.reply && data.channel && data.reply.channelId !== data.channel.id) { @@ -334,7 +337,7 @@ export class NoteCreateService implements OnApplicationShutdown { if (data.channel) { this.redisClient.xadd( `channelTimeline:${data.channel.id}`, - 'MAXLEN', '~', '1000', + 'MAXLEN', '~', this.config.perChannelMaxNoteCacheCount.toString(), '*', 'note', note.id); } @@ -348,8 +351,8 @@ export class NoteCreateService implements OnApplicationShutdown { } @bindThis - private async insertNote(user: { id: User['id']; host: User['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]) { - const insert = new Note({ + private async insertNote(user: { id: MiUser['id']; host: MiUser['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]) { + const insert = new MiNote({ id: this.idService.genId(data.createdAt!), createdAt: data.createdAt!, fileIds: data.files ? data.files.map(file => file.id) : [], @@ -411,9 +414,9 @@ export class NoteCreateService implements OnApplicationShutdown { if (insert.hasPoll) { // Start transaction await this.db.transaction(async transactionalEntityManager => { - await transactionalEntityManager.insert(Note, insert); + await transactionalEntityManager.insert(MiNote, insert); - const poll = new Poll({ + const poll = new MiPoll({ noteId: insert.id, choices: data.poll!.choices, expiresAt: data.poll!.expiresAt, @@ -424,7 +427,7 @@ export class NoteCreateService implements OnApplicationShutdown { userHost: user.host, }); - await transactionalEntityManager.insert(Poll, poll); + await transactionalEntityManager.insert(MiPoll, poll); }); } else { await this.notesRepository.insert(insert); @@ -446,12 +449,12 @@ export class NoteCreateService implements OnApplicationShutdown { } @bindThis - private async postNoteCreated(note: Note, user: { - id: User['id']; - username: User['username']; - host: User['host']; - createdAt: User['createdAt']; - isBot: User['isBot']; + private async postNoteCreated(note: MiNote, user: { + id: MiUser['id']; + username: MiUser['username']; + host: MiUser['host']; + createdAt: MiUser['createdAt']; + isBot: MiUser['isBot']; }, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) { const meta = await this.metaService.fetch(); @@ -505,6 +508,20 @@ export class NoteCreateService implements OnApplicationShutdown { this.saveReply(data.reply, note); } + if (data.reply == null) { + this.followingsRepository.findBy({ + followeeId: user.id, + notify: 'normal', + }).then(followings => { + for (const following of followings) { + this.notificationService.createNotification(following.followerId, 'note', { + notifierId: user.id, + noteId: note.id, + }); + } + }); + } + // この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき if (data.renote && (await this.noteEntityService.countSameRenotes(user.id, data.renote.id, note.id) === 0)) { if (!user.isBot) this.incRenoteCount(data.renote); @@ -625,7 +642,7 @@ export class NoteCreateService implements OnApplicationShutdown { // メンションされたリモートユーザーに配送 for (const u of mentionedUsers.filter(u => this.userEntityService.isRemoteUser(u))) { - dm.addDirectRecipe(u as RemoteUser); + dm.addDirectRecipe(u as MiRemoteUser); } // 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送 @@ -703,7 +720,7 @@ export class NoteCreateService implements OnApplicationShutdown { } @bindThis - private incRenoteCount(renote: Note) { + private incRenoteCount(renote: MiNote) { this.notesRepository.createQueryBuilder().update() .set({ renoteCount: () => '"renoteCount" + 1', @@ -714,7 +731,7 @@ export class NoteCreateService implements OnApplicationShutdown { } @bindThis - private async createMentionedEvents(mentionedUsers: MinimumUser[], note: Note, nm: NotificationManager) { + private async createMentionedEvents(mentionedUsers: MinimumUser[], note: MiNote, nm: NotificationManager) { for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) { const isThreadMuted = await this.noteThreadMutingsRepository.exist({ where: { @@ -746,12 +763,12 @@ export class NoteCreateService implements OnApplicationShutdown { } @bindThis - private saveReply(reply: Note, note: Note) { + private saveReply(reply: MiNote, note: MiNote) { this.notesRepository.increment({ id: reply.id }, 'repliesCount', 1); } @bindThis - private async renderNoteOrRenoteActivity(data: Option, note: Note) { + private async renderNoteOrRenoteActivity(data: Option, note: MiNote) { if (data.localOnly) return null; const content = data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length === 0) @@ -762,14 +779,14 @@ export class NoteCreateService implements OnApplicationShutdown { } @bindThis - private index(note: Note) { + private index(note: MiNote) { if (note.text == null && note.cw == null) return; this.searchService.indexNote(note); } @bindThis - private incNotesCountOfUser(user: { id: User['id']; }) { + private incNotesCountOfUser(user: { id: MiUser['id']; }) { this.usersRepository.createQueryBuilder().update() .set({ updatedAt: new Date(), @@ -780,13 +797,13 @@ export class NoteCreateService implements OnApplicationShutdown { } @bindThis - private async extractMentionedUsers(user: { host: User['host']; }, tokens: mfm.MfmNode[]): Promise { + private async extractMentionedUsers(user: { host: MiUser['host']; }, tokens: mfm.MfmNode[]): Promise { if (tokens == null) return []; const mentions = extractMentions(tokens); let mentionedUsers = (await Promise.all(mentions.map(m => this.remoteUserResolveService.resolveUser(m.username, m.host ?? user.host).catch(() => null), - ))).filter(x => x != null) as User[]; + ))).filter(x => x != null) as MiUser[]; // Drop duplicate users mentionedUsers = mentionedUsers.filter((u, i, self) => diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index 7a44916f5ec6..69fff36a02d1 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -5,9 +5,9 @@ import { Brackets, In } from 'typeorm'; import { Injectable, Inject } from '@nestjs/common'; -import type { User, LocalUser, RemoteUser } from '@/models/entities/User.js'; -import type { Note, IMentionedRemoteUsers } from '@/models/entities/Note.js'; -import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/index.js'; +import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js'; +import type { MiNote, IMentionedRemoteUsers } from '@/models/Note.js'; +import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/_.js'; import { RelayService } from '@/core/RelayService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { DI } from '@/di-symbols.js'; @@ -58,7 +58,7 @@ export class NoteDeleteService { * @param user 投稿者 * @param note 投稿 */ - async delete(user: { id: User['id']; uri: User['uri']; host: User['host']; isBot: User['isBot']; }, note: Note, quiet = false) { + async delete(user: { id: MiUser['id']; uri: MiUser['uri']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote, quiet = false) { const deletedAt = new Date(); const cascadingNotes = await this.findCascadingNotes(note); @@ -79,7 +79,7 @@ export class NoteDeleteService { //#region ローカルの投稿なら削除アクティビティを配送 if (this.userEntityService.isLocalUser(user) && !note.localOnly) { - let renote: Note | null = null; + let renote: MiNote | null = null; // if deletd note is renote if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) { @@ -134,8 +134,8 @@ export class NoteDeleteService { } @bindThis - private async findCascadingNotes(note: Note): Promise { - const recursive = async (noteId: string): Promise => { + private async findCascadingNotes(note: MiNote): Promise { + const recursive = async (noteId: string): Promise => { const query = this.notesRepository.createQueryBuilder('note') .where('note.replyId = :noteId', { noteId }) .orWhere(new Brackets(q => { @@ -151,13 +151,13 @@ export class NoteDeleteService { ].flat(); }; - const cascadingNotes: Note[] = await recursive(note.id); + const cascadingNotes: MiNote[] = await recursive(note.id); return cascadingNotes; } @bindThis - private async getMentionedRemoteUsers(note: Note) { + private async getMentionedRemoteUsers(note: MiNote) { const where = [] as any[]; // mention / reply / dm @@ -179,11 +179,11 @@ export class NoteDeleteService { return await this.usersRepository.find({ where, - }) as RemoteUser[]; + }) as MiRemoteUser[]; } @bindThis - private async deliverToConcerned(user: { id: LocalUser['id']; host: null; }, note: Note, content: any) { + private async deliverToConcerned(user: { id: MiLocalUser['id']; host: null; }, note: MiNote, content: any) { this.apDeliverManagerService.deliverToFollowers(user, content); this.relayService.deliverToRelays(user, content); const remoteUsers = await this.getMentionedRemoteUsers(note); diff --git a/packages/backend/src/core/NotePiningService.ts b/packages/backend/src/core/NotePiningService.ts index d35b5dc41118..147554ee9ace 100644 --- a/packages/backend/src/core/NotePiningService.ts +++ b/packages/backend/src/core/NotePiningService.ts @@ -5,12 +5,12 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { NotesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js'; +import type { NotesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/_.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; -import type { User } from '@/models/entities/User.js'; -import type { Note } from '@/models/entities/Note.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiNote } from '@/models/Note.js'; import { IdService } from '@/core/IdService.js'; -import type { UserNotePining } from '@/models/entities/UserNotePining.js'; +import type { MiUserNotePining } from '@/models/UserNotePining.js'; import { RelayService } from '@/core/RelayService.js'; import type { Config } from '@/config.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -49,7 +49,7 @@ export class NotePiningService { * @param noteId */ @bindThis - public async addPinned(user: { id: User['id']; host: User['host']; }, noteId: Note['id']) { + public async addPinned(user: { id: MiUser['id']; host: MiUser['host']; }, noteId: MiNote['id']) { // Fetch pinee const note = await this.notesRepository.findOneBy({ id: noteId, @@ -75,7 +75,7 @@ export class NotePiningService { createdAt: new Date(), userId: user.id, noteId: note.id, - } as UserNotePining); + } as MiUserNotePining); // Deliver to remote followers if (this.userEntityService.isLocalUser(user)) { @@ -89,7 +89,7 @@ export class NotePiningService { * @param noteId */ @bindThis - public async removePinned(user: { id: User['id']; host: User['host']; }, noteId: Note['id']) { + public async removePinned(user: { id: MiUser['id']; host: MiUser['host']; }, noteId: MiNote['id']) { // Fetch unpinee const note = await this.notesRepository.findOneBy({ id: noteId, @@ -112,7 +112,7 @@ export class NotePiningService { } @bindThis - public async deliverPinnedChange(userId: User['id'], noteId: Note['id'], isAddition: boolean) { + public async deliverPinnedChange(userId: MiUser['id'], noteId: MiNote['id'], isAddition: boolean) { const user = await this.usersRepository.findOneBy({ id: userId }); if (user == null) throw new Error('user not found'); diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts index a6a66f2653fc..422e0192cfc6 100644 --- a/packages/backend/src/core/NoteReadService.ts +++ b/packages/backend/src/core/NoteReadService.ts @@ -7,12 +7,12 @@ import { setTimeout } from 'node:timers/promises'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { User } from '@/models/entities/User.js'; +import type { MiUser } from '@/models/User.js'; import type { Packed } from '@/misc/json-schema.js'; -import type { Note } from '@/models/entities/Note.js'; +import type { MiNote } from '@/models/Note.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; -import type { NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository } from '@/models/index.js'; +import type { NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; @Injectable() @@ -35,7 +35,7 @@ export class NoteReadService implements OnApplicationShutdown { } @bindThis - public async insertNoteUnread(userId: User['id'], note: Note, params: { + public async insertNoteUnread(userId: MiUser['id'], note: MiNote, params: { // NOTE: isSpecifiedがtrueならisMentionedは必ずfalse isSpecified: boolean; isMentioned: boolean; @@ -84,11 +84,11 @@ export class NoteReadService implements OnApplicationShutdown { @bindThis public async read( - userId: User['id'], - notes: (Note | Packed<'Note'>)[], + userId: MiUser['id'], + notes: (MiNote | Packed<'Note'>)[], ): Promise { - const readMentions: (Note | Packed<'Note'>)[] = []; - const readSpecifiedNotes: (Note | Packed<'Note'>)[] = []; + const readMentions: (MiNote | Packed<'Note'>)[] = []; + const readSpecifiedNotes: (MiNote | Packed<'Note'>)[] = []; for (const note of notes) { if (note.mentions && note.mentions.includes(userId)) { diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index a708a5400424..258ae44f7d12 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -8,21 +8,25 @@ import * as Redis from 'ioredis'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository } from '@/models/index.js'; -import type { User } from '@/models/entities/User.js'; -import type { Notification } from '@/models/entities/Notification.js'; +import type { UsersRepository } from '@/models/_.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiNotification } from '@/models/Notification.js'; import { bindThis } from '@/decorators.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { PushNotificationService } from '@/core/PushNotificationService.js'; import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js'; import { IdService } from '@/core/IdService.js'; import { CacheService } from '@/core/CacheService.js'; +import type { Config } from '@/config.js'; @Injectable() export class NotificationService implements OnApplicationShutdown { #shutdownController = new AbortController(); constructor( + @Inject(DI.config) + private config: Config, + @Inject(DI.redis) private redisClient: Redis.Redis, @@ -39,7 +43,7 @@ export class NotificationService implements OnApplicationShutdown { @bindThis public async readAllNotification( - userId: User['id'], + userId: MiUser['id'], force = false, ) { const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${userId}`); @@ -61,17 +65,17 @@ export class NotificationService implements OnApplicationShutdown { } @bindThis - private postReadAllNotifications(userId: User['id']) { + private postReadAllNotifications(userId: MiUser['id']) { this.globalEventService.publishMainStream(userId, 'readAllNotifications'); this.pushNotificationService.pushNotification(userId, 'readAllNotifications', undefined); } @bindThis public async createNotification( - notifieeId: User['id'], - type: Notification['type'], - data: Partial, - ): Promise { + notifieeId: MiUser['id'], + type: MiNotification['type'], + data: Partial, + ): Promise { const profile = await this.cacheService.userProfileCache.fetch(notifieeId); const isMuted = profile.mutingNotificationTypes.includes(type); if (isMuted) return null; @@ -92,11 +96,11 @@ export class NotificationService implements OnApplicationShutdown { createdAt: new Date(), type: type, ...data, - } as Notification; + } as MiNotification; const redisIdPromise = this.redisClient.xadd( `notificationTimeline:${notifieeId}`, - 'MAXLEN', '~', '300', + 'MAXLEN', '~', this.config.perUserNotificationsMaxCount.toString(), '*', 'data', JSON.stringify(notification)); @@ -126,7 +130,7 @@ export class NotificationService implements OnApplicationShutdown { // TODO: locale ファイルをクライアント用とサーバー用で分けたい @bindThis - private async emailNotificationFollow(userId: User['id'], follower: User) { + private async emailNotificationFollow(userId: MiUser['id'], follower: MiUser) { /* const userProfile = await UserProfiles.findOneByOrFail({ userId: userId }); if (!userProfile.email || !userProfile.emailNotificationTypes.includes('follow')) return; @@ -138,7 +142,7 @@ export class NotificationService implements OnApplicationShutdown { } @bindThis - private async emailNotificationReceiveFollowRequest(userId: User['id'], follower: User) { + private async emailNotificationReceiveFollowRequest(userId: MiUser['id'], follower: MiUser) { /* const userProfile = await UserProfiles.findOneByOrFail({ userId: userId }); if (!userProfile.email || !userProfile.emailNotificationTypes.includes('receiveFollowRequest')) return; diff --git a/packages/backend/src/core/PollService.ts b/packages/backend/src/core/PollService.ts index f317087b4175..940aa98347eb 100644 --- a/packages/backend/src/core/PollService.ts +++ b/packages/backend/src/core/PollService.ts @@ -5,8 +5,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { NotesRepository, UsersRepository, PollsRepository, PollVotesRepository, User } from '@/models/index.js'; -import type { Note } from '@/models/entities/Note.js'; +import type { NotesRepository, UsersRepository, PollsRepository, PollVotesRepository, MiUser } from '@/models/_.js'; +import type { MiNote } from '@/models/Note.js'; import { RelayService } from '@/core/RelayService.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; @@ -42,7 +42,7 @@ export class PollService { } @bindThis - public async vote(user: User, note: Note, choice: number) { + public async vote(user: MiUser, note: MiNote, choice: number) { const poll = await this.pollsRepository.findOneBy({ noteId: note.id }); if (poll == null) throw new Error('poll not found'); @@ -92,7 +92,7 @@ export class PollService { } @bindThis - public async deliverQuestionUpdate(noteId: Note['id']) { + public async deliverQuestionUpdate(noteId: MiNote['id']) { const note = await this.notesRepository.findOneBy({ id: noteId }); if (note == null) throw new Error('note not found'); diff --git a/packages/backend/src/core/ProxyAccountService.ts b/packages/backend/src/core/ProxyAccountService.ts index c682754fb9b2..b1bc60701ba0 100644 --- a/packages/backend/src/core/ProxyAccountService.ts +++ b/packages/backend/src/core/ProxyAccountService.ts @@ -4,8 +4,8 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository } from '@/models/index.js'; -import type { LocalUser } from '@/models/entities/User.js'; +import type { UsersRepository } from '@/models/_.js'; +import type { MiLocalUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; import { MetaService } from '@/core/MetaService.js'; import { bindThis } from '@/decorators.js'; @@ -21,9 +21,9 @@ export class ProxyAccountService { } @bindThis - public async fetch(): Promise { + public async fetch(): Promise { const meta = await this.metaService.fetch(); if (meta.proxyAccountId == null) return null; - return await this.usersRepository.findOneByOrFail({ id: meta.proxyAccountId }) as LocalUser; + return await this.usersRepository.findOneByOrFail({ id: meta.proxyAccountId }) as MiLocalUser; } } diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index d5a727b3e4c2..40d1deceebb0 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -10,7 +10,7 @@ import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import type { Packed } from '@/misc/json-schema.js'; import { getNoteSummary } from '@/misc/get-note-summary.js'; -import type { SwSubscription, SwSubscriptionsRepository } from '@/models/index.js'; +import type { MiSwSubscription, SwSubscriptionsRepository } from '@/models/_.js'; import { MetaService } from '@/core/MetaService.js'; import { bindThis } from '@/decorators.js'; import { RedisKVCache } from '@/misc/cache.js'; @@ -48,7 +48,7 @@ function truncateBody(type: T, body: Pus @Injectable() export class PushNotificationService implements OnApplicationShutdown { - private subscriptionsCache: RedisKVCache; + private subscriptionsCache: RedisKVCache; constructor( @Inject(DI.config) @@ -62,7 +62,7 @@ export class PushNotificationService implements OnApplicationShutdown { private metaService: MetaService, ) { - this.subscriptionsCache = new RedisKVCache(this.redisClient, 'userSwSubscriptions', { + this.subscriptionsCache = new RedisKVCache(this.redisClient, 'userSwSubscriptions', { lifetime: 1000 * 60 * 60 * 1, // 1h memoryCacheLifetime: 1000 * 60 * 3, // 3m fetcher: (key) => this.swSubscriptionsRepository.findBy({ userId: key }), diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts index c6ebc423b5a6..9145726f86f9 100644 --- a/packages/backend/src/core/QueryService.ts +++ b/packages/backend/src/core/QueryService.ts @@ -6,8 +6,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { Brackets, ObjectLiteral } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { User } from '@/models/entities/User.js'; -import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, MutedNotesRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository, RenoteMutingsRepository } from '@/models/index.js'; +import type { MiUser } from '@/models/User.js'; +import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, MutedNotesRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository, RenoteMutingsRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import type { SelectQueryBuilder } from 'typeorm'; @@ -69,7 +69,7 @@ export class QueryService { // ここでいうBlockedは被Blockedの意 @bindThis - public generateBlockedUserQuery(q: SelectQueryBuilder, me: { id: User['id'] }): void { + public generateBlockedUserQuery(q: SelectQueryBuilder, me: { id: MiUser['id'] }): void { const blockingQuery = this.blockingsRepository.createQueryBuilder('blocking') .select('blocking.blockerId') .where('blocking.blockeeId = :blockeeId', { blockeeId: me.id }); @@ -92,7 +92,7 @@ export class QueryService { } @bindThis - public generateBlockQueryForUsers(q: SelectQueryBuilder, me: { id: User['id'] }): void { + public generateBlockQueryForUsers(q: SelectQueryBuilder, me: { id: MiUser['id'] }): void { const blockingQuery = this.blockingsRepository.createQueryBuilder('blocking') .select('blocking.blockeeId') .where('blocking.blockerId = :blockerId', { blockerId: me.id }); @@ -109,7 +109,7 @@ export class QueryService { } @bindThis - public generateChannelQuery(q: SelectQueryBuilder, me?: { id: User['id'] } | null): void { + public generateChannelQuery(q: SelectQueryBuilder, me?: { id: MiUser['id'] } | null): void { if (me == null) { q.andWhere('note.channelId IS NULL'); } else { @@ -131,7 +131,7 @@ export class QueryService { } @bindThis - public generateMutedNoteQuery(q: SelectQueryBuilder, me: { id: User['id'] }): void { + public generateMutedNoteQuery(q: SelectQueryBuilder, me: { id: MiUser['id'] }): void { const mutedQuery = this.mutedNotesRepository.createQueryBuilder('muted') .select('muted.noteId') .where('muted.userId = :userId', { userId: me.id }); @@ -142,7 +142,7 @@ export class QueryService { } @bindThis - public generateMutedNoteThreadQuery(q: SelectQueryBuilder, me: { id: User['id'] }): void { + public generateMutedNoteThreadQuery(q: SelectQueryBuilder, me: { id: MiUser['id'] }): void { const mutedQuery = this.noteThreadMutingsRepository.createQueryBuilder('threadMuted') .select('threadMuted.threadId') .where('threadMuted.userId = :userId', { userId: me.id }); @@ -157,7 +157,7 @@ export class QueryService { } @bindThis - public generateMutedUserQuery(q: SelectQueryBuilder, me: { id: User['id'] }, exclude?: User): void { + public generateMutedUserQuery(q: SelectQueryBuilder, me: { id: MiUser['id'] }, exclude?: MiUser): void { const mutingQuery = this.mutingsRepository.createQueryBuilder('muting') .select('muting.muteeId') .where('muting.muterId = :muterId', { muterId: me.id }); @@ -202,7 +202,7 @@ export class QueryService { } @bindThis - public generateMutedUserQueryForUsers(q: SelectQueryBuilder, me: { id: User['id'] }): void { + public generateMutedUserQueryForUsers(q: SelectQueryBuilder, me: { id: MiUser['id'] }): void { const mutingQuery = this.mutingsRepository.createQueryBuilder('muting') .select('muting.muteeId') .where('muting.muterId = :muterId', { muterId: me.id }); @@ -213,7 +213,7 @@ export class QueryService { } @bindThis - public generateRepliesQuery(q: SelectQueryBuilder, withReplies: boolean, me?: Pick | null): void { + public generateRepliesQuery(q: SelectQueryBuilder, withReplies: boolean, me?: Pick | null): void { if (me == null) { q.andWhere(new Brackets(qb => { qb .where('note.replyId IS NULL') // 返信ではない @@ -239,7 +239,7 @@ export class QueryService { } @bindThis - public generateVisibilityQuery(q: SelectQueryBuilder, me?: { id: User['id'] } | null): void { + public generateVisibilityQuery(q: SelectQueryBuilder, me?: { id: MiUser['id'] } | null): void { // This code must always be synchronized with the checks in Notes.isVisibleForMe. if (me == null) { q.andWhere(new Brackets(qb => { qb @@ -279,7 +279,7 @@ export class QueryService { } @bindThis - public generateMutedUserRenotesQueryForNotes(q: SelectQueryBuilder, me: { id: User['id'] }): void { + public generateMutedUserRenotesQueryForNotes(q: SelectQueryBuilder, me: { id: MiUser['id'] }): void { const mutingQuery = this.renoteMutingsRepository.createQueryBuilder('renote_muting') .select('renote_muting.muteeId') .where('renote_muting.muterId = :muterId', { muterId: me.id }); diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index c70b49ca499b..d8c725003496 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -6,8 +6,8 @@ import { randomUUID } from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; import type { IActivity } from '@/core/activitypub/type.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; -import type { Webhook, webhookEventTypes } from '@/models/entities/Webhook.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; +import type { MiWebhook, webhookEventTypes } from '@/models/Webhook.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; @@ -237,7 +237,7 @@ export class QueueService { } @bindThis - public createImportFollowingJob(user: ThinUser, fileId: DriveFile['id']) { + public createImportFollowingJob(user: ThinUser, fileId: MiDriveFile['id']) { return this.dbQueue.add('importFollowing', { user: { id: user.id }, fileId: fileId, @@ -254,7 +254,7 @@ export class QueueService { } @bindThis - public createImportMutingJob(user: ThinUser, fileId: DriveFile['id']) { + public createImportMutingJob(user: ThinUser, fileId: MiDriveFile['id']) { return this.dbQueue.add('importMuting', { user: { id: user.id }, fileId: fileId, @@ -265,7 +265,7 @@ export class QueueService { } @bindThis - public createImportBlockingJob(user: ThinUser, fileId: DriveFile['id']) { + public createImportBlockingJob(user: ThinUser, fileId: MiDriveFile['id']) { return this.dbQueue.add('importBlocking', { user: { id: user.id }, fileId: fileId, @@ -298,7 +298,7 @@ export class QueueService { } @bindThis - public createImportUserListsJob(user: ThinUser, fileId: DriveFile['id']) { + public createImportUserListsJob(user: ThinUser, fileId: MiDriveFile['id']) { return this.dbQueue.add('importUserLists', { user: { id: user.id }, fileId: fileId, @@ -309,7 +309,7 @@ export class QueueService { } @bindThis - public createImportCustomEmojisJob(user: ThinUser, fileId: DriveFile['id']) { + public createImportCustomEmojisJob(user: ThinUser, fileId: MiDriveFile['id']) { return this.dbQueue.add('importCustomEmojis', { user: { id: user.id }, fileId: fileId, @@ -412,7 +412,7 @@ export class QueueService { } @bindThis - public webhookDeliver(webhook: Webhook, type: typeof webhookEventTypes[number], content: unknown) { + public webhookDeliver(webhook: MiWebhook, type: typeof webhookEventTypes[number], content: unknown) { const data = { type, content, diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index 7d97c8745e3c..d9bde502c8b8 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -5,12 +5,12 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository } from '@/models/index.js'; +import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository } from '@/models/_.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; -import type { RemoteUser, User } from '@/models/entities/User.js'; -import type { Note } from '@/models/entities/Note.js'; +import type { MiRemoteUser, MiUser } from '@/models/User.js'; +import type { MiNote } from '@/models/Note.js'; import { IdService } from '@/core/IdService.js'; -import type { NoteReaction } from '@/models/entities/NoteReaction.js'; +import type { MiNoteReaction } from '@/models/NoteReaction.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { NotificationService } from '@/core/NotificationService.js'; @@ -95,7 +95,7 @@ export class ReactionService { } @bindThis - public async create(user: { id: User['id']; host: User['host']; isBot: User['isBot'] }, note: Note, _reaction?: string | null) { + public async create(user: { id: MiUser['id']; host: MiUser['host']; isBot: MiUser['isBot'] }, note: MiNote, _reaction?: string | null) { // Check blocking if (note.userId !== user.id) { const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id); @@ -146,7 +146,7 @@ export class ReactionService { } } - const record: NoteReaction = { + const record: MiNoteReaction = { id: this.idService.genId(), createdAt: new Date(), noteId: note.id, @@ -231,7 +231,7 @@ export class ReactionService { const dm = this.apDeliverManagerService.createDeliverManager(user, content); if (note.userHost !== null) { const reactee = await this.usersRepository.findOneBy({ id: note.userId }); - dm.addDirectRecipe(reactee as RemoteUser); + dm.addDirectRecipe(reactee as MiRemoteUser); } if (['public', 'home', 'followers'].includes(note.visibility)) { @@ -239,7 +239,7 @@ export class ReactionService { } else if (note.visibility === 'specified') { const visibleUsers = await Promise.all(note.visibleUserIds.map(id => this.usersRepository.findOneBy({ id }))); for (const u of visibleUsers.filter(u => u && this.userEntityService.isRemoteUser(u))) { - dm.addDirectRecipe(u as RemoteUser); + dm.addDirectRecipe(u as MiRemoteUser); } } @@ -249,7 +249,7 @@ export class ReactionService { } @bindThis - public async delete(user: { id: User['id']; host: User['host']; isBot: User['isBot']; }, note: Note) { + public async delete(user: { id: MiUser['id']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote) { // if already unreacted const exist = await this.noteReactionsRepository.findOneBy({ noteId: note.id, @@ -289,7 +289,7 @@ export class ReactionService { const dm = this.apDeliverManagerService.createDeliverManager(user, content); if (note.userHost !== null) { const reactee = await this.usersRepository.findOneBy({ id: note.userId }); - dm.addDirectRecipe(reactee as RemoteUser); + dm.addDirectRecipe(reactee as MiRemoteUser); } dm.addFollowersRecipe(); dm.execute(); diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts index 1ac906991bf9..7171bf84c5b4 100644 --- a/packages/backend/src/core/RelayService.ts +++ b/packages/backend/src/core/RelayService.ts @@ -5,11 +5,11 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; -import type { LocalUser, User } from '@/models/entities/User.js'; -import type { RelaysRepository, UsersRepository } from '@/models/index.js'; +import type { MiLocalUser, MiUser } from '@/models/User.js'; +import type { RelaysRepository, UsersRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { MemorySingleCache } from '@/misc/cache.js'; -import type { Relay } from '@/models/entities/Relay.js'; +import type { MiRelay } from '@/models/Relay.js'; import { QueueService } from '@/core/QueueService.js'; import { CreateSystemUserService } from '@/core/CreateSystemUserService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; @@ -21,7 +21,7 @@ const ACTOR_USERNAME = 'relay.actor' as const; @Injectable() export class RelayService { - private relaysCache: MemorySingleCache; + private relaysCache: MemorySingleCache; constructor( @Inject(DI.usersRepository) @@ -35,24 +35,24 @@ export class RelayService { private createSystemUserService: CreateSystemUserService, private apRendererService: ApRendererService, ) { - this.relaysCache = new MemorySingleCache(1000 * 60 * 10); + this.relaysCache = new MemorySingleCache(1000 * 60 * 10); } @bindThis - private async getRelayActor(): Promise { + private async getRelayActor(): Promise { const user = await this.usersRepository.findOneBy({ host: IsNull(), username: ACTOR_USERNAME, }); - if (user) return user as LocalUser; + if (user) return user as MiLocalUser; const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME); - return created as LocalUser; + return created as MiLocalUser; } @bindThis - public async addRelay(inbox: string): Promise { + public async addRelay(inbox: string): Promise { const relay = await this.relaysRepository.insert({ id: this.idService.genId(), inbox, @@ -87,7 +87,7 @@ export class RelayService { } @bindThis - public async listRelay(): Promise { + public async listRelay(): Promise { const relays = await this.relaysRepository.find(); return relays; } @@ -111,7 +111,7 @@ export class RelayService { } @bindThis - public async deliverToRelays(user: { id: User['id']; host: null; }, activity: any): Promise { + public async deliverToRelays(user: { id: MiUser['id']; host: null; }, activity: any): Promise { if (activity == null) return; const relays = await this.relaysCache.fetch(() => this.relaysRepository.findBy({ diff --git a/packages/backend/src/core/RemoteUserResolveService.ts b/packages/backend/src/core/RemoteUserResolveService.ts index 31682ea98d89..75c5f14aa413 100644 --- a/packages/backend/src/core/RemoteUserResolveService.ts +++ b/packages/backend/src/core/RemoteUserResolveService.ts @@ -8,8 +8,8 @@ import { Inject, Injectable } from '@nestjs/common'; import chalk from 'chalk'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository } from '@/models/index.js'; -import type { LocalUser, RemoteUser } from '@/models/entities/User.js'; +import type { UsersRepository } from '@/models/_.js'; +import type { MiLocalUser, MiRemoteUser } from '@/models/User.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { UtilityService } from '@/core/UtilityService.js'; @@ -40,7 +40,7 @@ export class RemoteUserResolveService { } @bindThis - public async resolveUser(username: string, host: string | null): Promise { + public async resolveUser(username: string, host: string | null): Promise { const usernameLower = username.toLowerCase(); if (host == null) { @@ -51,7 +51,7 @@ export class RemoteUserResolveService { } else { return u; } - }) as LocalUser; + }) as MiLocalUser; } host = this.utilityService.toPuny(host); @@ -64,10 +64,10 @@ export class RemoteUserResolveService { } else { return u; } - }) as LocalUser; + }) as MiLocalUser; } - const user = await this.usersRepository.findOneBy({ usernameLower, host }) as RemoteUser | null; + const user = await this.usersRepository.findOneBy({ usernameLower, host }) as MiRemoteUser | null; const acctLower = `${usernameLower}@${host}`; @@ -86,7 +86,7 @@ export class RemoteUserResolveService { } else { return u; } - })) as LocalUser; + })) as MiLocalUser; } } @@ -132,7 +132,7 @@ export class RemoteUserResolveService { if (u == null) { throw new Error('user not found'); } else { - return u as LocalUser | RemoteUser; + return u as MiLocalUser | MiRemoteUser; } }); } diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 70454a84c263..518f28369508 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -6,14 +6,14 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; import { In } from 'typeorm'; -import type { Role, RoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/index.js'; +import type { MiRole, MiRoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/_.js'; import { MemoryKVCache, MemorySingleCache } from '@/misc/cache.js'; -import type { User } from '@/models/entities/User.js'; +import type { MiUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { MetaService } from '@/core/MetaService.js'; import { CacheService } from '@/core/CacheService.js'; -import type { RoleCondFormulaValue } from '@/models/entities/Role.js'; +import type { RoleCondFormulaValue } from '@/models/Role.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { StreamMessages } from '@/server/api/stream/types.js'; import { IdService } from '@/core/IdService.js'; @@ -71,8 +71,8 @@ export const DEFAULT_POLICIES: RolePolicies = { @Injectable() export class RoleService implements OnApplicationShutdown { - private rolesCache: MemorySingleCache; - private roleAssignmentByUserIdCache: MemoryKVCache; + private rolesCache: MemorySingleCache; + private roleAssignmentByUserIdCache: MemoryKVCache; public static AlreadyAssignedError = class extends Error {}; public static NotAssignedError = class extends Error {}; @@ -101,8 +101,8 @@ export class RoleService implements OnApplicationShutdown { ) { //this.onMessage = this.onMessage.bind(this); - this.rolesCache = new MemorySingleCache(1000 * 60 * 60 * 1); - this.roleAssignmentByUserIdCache = new MemoryKVCache(1000 * 60 * 60 * 1); + this.rolesCache = new MemorySingleCache(1000 * 60 * 60 * 1); + this.roleAssignmentByUserIdCache = new MemoryKVCache(1000 * 60 * 60 * 1); this.redisForSub.on('message', this.onMessage); } @@ -173,7 +173,7 @@ export class RoleService implements OnApplicationShutdown { } @bindThis - private evalCond(user: User, value: RoleCondFormulaValue): boolean { + private evalCond(user: MiUser, value: RoleCondFormulaValue): boolean { try { switch (value.type) { case 'and': { @@ -225,7 +225,7 @@ export class RoleService implements OnApplicationShutdown { } @bindThis - public async getUserAssigns(userId: User['id']) { + public async getUserAssigns(userId: MiUser['id']) { const now = Date.now(); let assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId })); // 期限切れのロールを除外 @@ -234,7 +234,7 @@ export class RoleService implements OnApplicationShutdown { } @bindThis - public async getUserRoles(userId: User['id']) { + public async getUserRoles(userId: MiUser['id']) { const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({})); const assigns = await this.getUserAssigns(userId); const assignedRoles = roles.filter(r => assigns.map(x => x.roleId).includes(r.id)); @@ -247,7 +247,7 @@ export class RoleService implements OnApplicationShutdown { * 指定ユーザーのバッジロール一覧取得 */ @bindThis - public async getUserBadgeRoles(userId: User['id']) { + public async getUserBadgeRoles(userId: MiUser['id']) { const now = Date.now(); let assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId })); // 期限切れのロールを除外 @@ -266,7 +266,7 @@ export class RoleService implements OnApplicationShutdown { } @bindThis - public async getUserPolicies(userId: User['id'] | null): Promise { + public async getUserPolicies(userId: MiUser['id'] | null): Promise { const meta = await this.metaService.fetch(); const basePolicies = { ...DEFAULT_POLICIES, ...meta.policies }; @@ -314,19 +314,19 @@ export class RoleService implements OnApplicationShutdown { } @bindThis - public async isModerator(user: { id: User['id']; isRoot: User['isRoot'] } | null): Promise { + public async isModerator(user: { id: MiUser['id']; isRoot: MiUser['isRoot'] } | null): Promise { if (user == null) return false; return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isModerator || r.isAdministrator); } @bindThis - public async isAdministrator(user: { id: User['id']; isRoot: User['isRoot'] } | null): Promise { + public async isAdministrator(user: { id: MiUser['id']; isRoot: MiUser['isRoot'] } | null): Promise { if (user == null) return false; return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isAdministrator); } @bindThis - public async isExplorable(role: { id: Role['id']} | null): Promise { + public async isExplorable(role: { id: MiRole['id']} | null): Promise { if (role == null) return false; const check = await this.rolesRepository.findOneBy({ id: role.id }); if (check == null) return false; @@ -334,7 +334,7 @@ export class RoleService implements OnApplicationShutdown { } @bindThis - public async getModeratorIds(includeAdmins = true): Promise { + public async getModeratorIds(includeAdmins = true): Promise { const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({})); const moderatorRoles = includeAdmins ? roles.filter(r => r.isModerator || r.isAdministrator) : roles.filter(r => r.isModerator); const assigns = moderatorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({ @@ -345,7 +345,7 @@ export class RoleService implements OnApplicationShutdown { } @bindThis - public async getModerators(includeAdmins = true): Promise { + public async getModerators(includeAdmins = true): Promise { const ids = await this.getModeratorIds(includeAdmins); const users = ids.length > 0 ? await this.usersRepository.findBy({ id: In(ids), @@ -354,7 +354,7 @@ export class RoleService implements OnApplicationShutdown { } @bindThis - public async getAdministratorIds(): Promise { + public async getAdministratorIds(): Promise { const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({})); const administratorRoles = roles.filter(r => r.isAdministrator); const assigns = administratorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({ @@ -365,7 +365,7 @@ export class RoleService implements OnApplicationShutdown { } @bindThis - public async getAdministrators(): Promise { + public async getAdministrators(): Promise { const ids = await this.getAdministratorIds(); const users = ids.length > 0 ? await this.usersRepository.findBy({ id: In(ids), @@ -374,7 +374,7 @@ export class RoleService implements OnApplicationShutdown { } @bindThis - public async assign(userId: User['id'], roleId: Role['id'], expiresAt: Date | null = null): Promise { + public async assign(userId: MiUser['id'], roleId: MiRole['id'], expiresAt: Date | null = null): Promise { const now = new Date(); const existing = await this.roleAssignmentsRepository.findOneBy({ @@ -409,7 +409,7 @@ export class RoleService implements OnApplicationShutdown { } @bindThis - public async unassign(userId: User['id'], roleId: Role['id']): Promise { + public async unassign(userId: MiUser['id'], roleId: MiRole['id']): Promise { const now = new Date(); const existing = await this.roleAssignmentsRepository.findOneBy({ roleId, userId }); diff --git a/packages/backend/src/core/S3Service.ts b/packages/backend/src/core/S3Service.ts index 0010aaa37a6e..df0991539d5a 100644 --- a/packages/backend/src/core/S3Service.ts +++ b/packages/backend/src/core/S3Service.ts @@ -9,8 +9,8 @@ import * as https from 'node:https'; import { Injectable } from '@nestjs/common'; import { DeleteObjectCommand, S3Client } from '@aws-sdk/client-s3'; import { Upload } from '@aws-sdk/lib-storage'; -import { NodeHttpHandler, NodeHttpHandlerOptions } from '@aws-sdk/node-http-handler'; -import type { Meta } from '@/models/entities/Meta.js'; +import { NodeHttpHandler, NodeHttpHandlerOptions } from '@smithy/node-http-handler'; +import type { MiMeta } from '@/models/Meta.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { bindThis } from '@/decorators.js'; import type { DeleteObjectCommandInput, PutObjectCommandInput } from '@aws-sdk/client-s3'; @@ -23,7 +23,7 @@ export class S3Service { } @bindThis - public getS3Client(meta: Meta): S3Client { + public getS3Client(meta: MiMeta): S3Client { const u = meta.objectStorageEndpoint ? `${meta.objectStorageUseSSL ? 'https' : 'http'}://${meta.objectStorageEndpoint}` : `${meta.objectStorageUseSSL ? 'https' : 'http'}://example.net`; // dummy url to select http(s) agent @@ -50,7 +50,7 @@ export class S3Service { } @bindThis - public async upload(meta: Meta, input: PutObjectCommandInput) { + public async upload(meta: MiMeta, input: PutObjectCommandInput) { const client = this.getS3Client(meta); return new Upload({ client, @@ -62,7 +62,7 @@ export class S3Service { } @bindThis - public delete(meta: Meta, input: DeleteObjectCommandInput) { + public delete(meta: MiMeta, input: DeleteObjectCommandInput) { const client = this.getS3Client(meta); return client.send(new DeleteObjectCommand(input)); } diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index 88b368dd2230..3ef321dd32c7 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -8,9 +8,9 @@ import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { bindThis } from '@/decorators.js'; -import { Note } from '@/models/entities/Note.js'; -import { User } from '@/models/index.js'; -import type { NotesRepository } from '@/models/index.js'; +import { MiNote } from '@/models/Note.js'; +import { MiUser } from '@/models/_.js'; +import type { NotesRepository } from '@/models/_.js'; import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; import { QueryService } from '@/core/QueryService.js'; import { IdService } from '@/core/IdService.js'; @@ -25,6 +25,8 @@ 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: 'and', qs: Q[] } | { op: 'or', qs: Q[] } | { op: 'not', q: Q }; @@ -50,6 +52,8 @@ function compileQuery(q: Q): string { case '<=': return `(${q.k} <= ${compileValue(q.v)})`; case 'and': return q.qs.length === 0 ? '' : `(${ q.qs.map(_q => compileQuery(_q)).join(' AND ') })`; case 'or': return q.qs.length === 0 ? '' : `(${ q.qs.map(_q => compileQuery(_q)).join(' OR ') })`; + case 'is null': return `(${q.k} IS NULL)`; + case 'is not null': return `(${q.k} IS NOT NULL)`; case 'not': return `(NOT ${compileQuery(q.q)})`; default: throw new Error('unrecognized query operator'); } @@ -105,7 +109,7 @@ export class SearchService { } @bindThis - public async indexNote(note: Note): Promise { + public async indexNote(note: MiNote): Promise { if (note.text == null && note.cw == null) return; if (!['home', 'public'].includes(note.visibility)) return; @@ -141,7 +145,7 @@ export class SearchService { } @bindThis - public async unindexNote(note: Note): Promise { + public async unindexNote(note: MiNote): Promise { if (!['home', 'public'].includes(note.visibility)) return; if (this.meilisearch) { @@ -150,15 +154,15 @@ export class SearchService { } @bindThis - public async searchNote(q: string, me: User | null, opts: { - userId?: Note['userId'] | null; - channelId?: Note['channelId'] | null; + public async searchNote(q: string, me: MiUser | null, opts: { + userId?: MiNote['userId'] | null; + channelId?: MiNote['channelId'] | null; host?: string | null; }, pagination: { - untilId?: Note['id']; - sinceId?: Note['id']; + untilId?: MiNote['id']; + sinceId?: MiNote['id']; limit?: number; - }): Promise { + }): Promise { if (this.meilisearch) { const filter: Q = { op: 'and', @@ -170,7 +174,7 @@ export class SearchService { if (opts.channelId) filter.qs.push({ op: '=', k: 'channelId', v: opts.channelId }); if (opts.host) { if (opts.host === '.') { - // TODO: Meilisearchが2023/05/07現在値がNULLかどうかのクエリが書けない + filter.qs.push({ op: 'is null', k: 'userHost' }); } else { filter.qs.push({ op: '=', k: 'userHost', v: opts.host }); } @@ -204,6 +208,14 @@ export class SearchService { .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); diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index 2a19daff4c3e..dfec0cfcfe15 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -8,12 +8,12 @@ import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; import { DataSource, IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { UsedUsernamesRepository, UsersRepository } from '@/models/index.js'; -import { User } from '@/models/entities/User.js'; -import { UserProfile } from '@/models/entities/UserProfile.js'; +import type { UsedUsernamesRepository, UsersRepository } from '@/models/_.js'; +import { MiUser } from '@/models/User.js'; +import { MiUserProfile } from '@/models/UserProfile.js'; import { IdService } from '@/core/IdService.js'; -import { UserKeypair } from '@/models/entities/UserKeypair.js'; -import { UsedUsername } from '@/models/entities/UsedUsername.js'; +import { MiUserKeypair } from '@/models/UserKeypair.js'; +import { MiUsedUsername } from '@/models/UsedUsername.js'; import generateUserToken from '@/misc/generate-native-user-token.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; @@ -43,9 +43,9 @@ export class SignupService { @bindThis public async signup(opts: { - username: User['username']; + username: MiUser['username']; password?: string | null; - passwordHash?: UserProfile['password'] | null; + passwordHash?: MiUserProfile['password'] | null; host?: string | null; ignorePreservedUsernames?: boolean; }) { @@ -108,18 +108,18 @@ export class SignupService { err ? rej(err) : res([publicKey, privateKey]), )); - let account!: User; + let account!: MiUser; // Start transaction await this.db.transaction(async transactionalEntityManager => { - const exist = await transactionalEntityManager.findOneBy(User, { + const exist = await transactionalEntityManager.findOneBy(MiUser, { usernameLower: username.toLowerCase(), host: IsNull(), }); if (exist) throw new Error(' the username is already used'); - account = await transactionalEntityManager.save(new User({ + account = await transactionalEntityManager.save(new MiUser({ id: this.idService.genId(), createdAt: new Date(), username: username, @@ -129,19 +129,19 @@ export class SignupService { isRoot: isTheFirstUser, })); - await transactionalEntityManager.save(new UserKeypair({ + await transactionalEntityManager.save(new MiUserKeypair({ publicKey: keyPair[0], privateKey: keyPair[1], userId: account.id, })); - await transactionalEntityManager.save(new UserProfile({ + await transactionalEntityManager.save(new MiUserProfile({ userId: account.id, autoAcceptFollowed: true, password: hash, })); - await transactionalEntityManager.save(new UsedUsername({ + await transactionalEntityManager.save(new MiUsedUsername({ createdAt: new Date(), username: username.toLowerCase(), })); diff --git a/packages/backend/src/core/TwoFactorAuthenticationService.ts b/packages/backend/src/core/TwoFactorAuthenticationService.ts deleted file mode 100644 index ecf7676f4b8a..000000000000 --- a/packages/backend/src/core/TwoFactorAuthenticationService.ts +++ /dev/null @@ -1,446 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and other misskey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import * as crypto from 'node:crypto'; -import { Inject, Injectable } from '@nestjs/common'; -import * as jsrsasign from 'jsrsasign'; -import { DI } from '@/di-symbols.js'; -import type { Config } from '@/config.js'; -import { bindThis } from '@/decorators.js'; - -const ECC_PRELUDE = Buffer.from([0x04]); -const NULL_BYTE = Buffer.from([0]); -const PEM_PRELUDE = Buffer.from( - '3059301306072a8648ce3d020106082a8648ce3d030107034200', - 'hex', -); - -// Android Safetynet attestations are signed with this cert: -const GSR2 = `-----BEGIN CERTIFICATE----- -MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G -A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp -Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1 -MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG -A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL -v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8 -eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq -tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd -C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa -zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB -mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH -V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n -bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG -3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs -J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO -291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS -ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd -AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 -TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== ------END CERTIFICATE-----\n`; - -function base64URLDecode(source: string) { - return Buffer.from(source.replace(/\-/g, '+').replace(/_/g, '/'), 'base64'); -} - -function getCertSubject(certificate: string) { - const subjectCert = new jsrsasign.X509(); - subjectCert.readCertPEM(certificate); - - const subjectString = subjectCert.getSubjectString(); - const subjectFields = subjectString.slice(1).split('/'); - - const fields = {} as Record; - for (const field of subjectFields) { - const eqIndex = field.indexOf('='); - fields[field.substring(0, eqIndex)] = field.substring(eqIndex + 1); - } - - return fields; -} - -function verifyCertificateChain(certificates: string[]) { - let valid = true; - - for (let i = 0; i < certificates.length; i++) { - const Cert = certificates[i]; - const certificate = new jsrsasign.X509(); - certificate.readCertPEM(Cert); - - const CACert = i + 1 >= certificates.length ? Cert : certificates[i + 1]; - - const certStruct = jsrsasign.ASN1HEX.getTLVbyList(certificate.hex!, 0, [0]); - if (certStruct == null) throw new Error('certStruct is null'); - - const algorithm = certificate.getSignatureAlgorithmField(); - const signatureHex = certificate.getSignatureValueHex(); - - // Verify against CA - const Signature = new jsrsasign.KJUR.crypto.Signature({ alg: algorithm }); - Signature.init(CACert); - Signature.updateHex(certStruct); - valid = valid && !!Signature.verify(signatureHex); // true if CA signed the certificate - } - - return valid; -} - -function PEMString(pemBuffer: Buffer, type = 'CERTIFICATE') { - if (pemBuffer.length === 65 && pemBuffer[0] === 0x04) { - pemBuffer = Buffer.concat([PEM_PRELUDE, pemBuffer], 91); - type = 'PUBLIC KEY'; - } - const cert = pemBuffer.toString('base64'); - - const keyParts = []; - const max = Math.ceil(cert.length / 64); - let start = 0; - for (let i = 0; i < max; i++) { - keyParts.push(cert.substring(start, start + 64)); - start += 64; - } - - return ( - `-----BEGIN ${type}-----\n` + - keyParts.join('\n') + - `\n-----END ${type}-----\n` - ); -} - -@Injectable() -export class TwoFactorAuthenticationService { - constructor( - @Inject(DI.config) - private config: Config, - ) { - } - - @bindThis - public hash(data: Buffer) { - return crypto - .createHash('sha256') - .update(data) - .digest(); - } - - @bindThis - public verifySignin({ - publicKey, - authenticatorData, - clientDataJSON, - clientData, - signature, - challenge, - }: { - publicKey: Buffer, - authenticatorData: Buffer, - clientDataJSON: Buffer, - clientData: any, - signature: Buffer, - challenge: string - }) { - if (clientData.type !== 'webauthn.get') { - throw new Error('type is not webauthn.get'); - } - - if (this.hash(clientData.challenge).toString('hex') !== challenge) { - throw new Error('challenge mismatch'); - } - if (clientData.origin !== this.config.scheme + '://' + this.config.host) { - throw new Error('origin mismatch'); - } - - const verificationData = Buffer.concat( - [authenticatorData, this.hash(clientDataJSON)], - 32 + authenticatorData.length, - ); - - return crypto - .createVerify('SHA256') - .update(verificationData) - .verify(PEMString(publicKey), signature); - } - - @bindThis - public getProcedures() { - return { - none: { - verify({ publicKey }: { publicKey: Map }) { - const negTwo = publicKey.get(-2); - - if (!negTwo || negTwo.length !== 32) { - throw new Error('invalid or no -2 key given'); - } - const negThree = publicKey.get(-3); - if (!negThree || negThree.length !== 32) { - throw new Error('invalid or no -3 key given'); - } - - const publicKeyU2F = Buffer.concat( - [ECC_PRELUDE, negTwo, negThree], - 1 + 32 + 32, - ); - - return { - publicKey: publicKeyU2F, - valid: true, - }; - }, - }, - 'android-key': { - verify({ - attStmt, - authenticatorData, - clientDataHash, - publicKey, - rpIdHash, - credentialId, - }: { - attStmt: any, - authenticatorData: Buffer, - clientDataHash: Buffer, - publicKey: Map; - rpIdHash: Buffer, - credentialId: Buffer, - }) { - if (attStmt.alg !== -7) { - throw new Error('alg mismatch'); - } - - const verificationData = Buffer.concat([ - authenticatorData, - clientDataHash, - ]); - - const attCert: Buffer = attStmt.x5c[0]; - - const negTwo = publicKey.get(-2); - - if (!negTwo || negTwo.length !== 32) { - throw new Error('invalid or no -2 key given'); - } - const negThree = publicKey.get(-3); - if (!negThree || negThree.length !== 32) { - throw new Error('invalid or no -3 key given'); - } - - const publicKeyData = Buffer.concat( - [ECC_PRELUDE, negTwo, negThree], - 1 + 32 + 32, - ); - - if (!attCert.equals(publicKeyData)) { - throw new Error('public key mismatch'); - } - - const isValid = crypto - .createVerify('SHA256') - .update(verificationData) - .verify(PEMString(attCert), attStmt.sig); - - // TODO: Check 'attestationChallenge' field in extension of cert matches hash(clientDataJSON) - - return { - valid: isValid, - publicKey: publicKeyData, - }; - }, - }, - // what a stupid attestation - 'android-safetynet': { - verify: ({ - attStmt, - authenticatorData, - clientDataHash, - publicKey, - rpIdHash, - credentialId, - }: { - attStmt: any, - authenticatorData: Buffer, - clientDataHash: Buffer, - publicKey: Map; - rpIdHash: Buffer, - credentialId: Buffer, - }) => { - const verificationData = this.hash( - Buffer.concat([authenticatorData, clientDataHash]), - ); - - const jwsParts = attStmt.response.toString('utf-8').split('.'); - - const header = JSON.parse(base64URLDecode(jwsParts[0]).toString('utf-8')); - const response = JSON.parse( - base64URLDecode(jwsParts[1]).toString('utf-8'), - ); - const signature = jwsParts[2]; - - if (!verificationData.equals(Buffer.from(response.nonce, 'base64'))) { - throw new Error('invalid nonce'); - } - - const certificateChain = header.x5c - .map((key: any) => PEMString(key)) - .concat([GSR2]); - - if (getCertSubject(certificateChain[0]).CN !== 'attest.android.com') { - throw new Error('invalid common name'); - } - - if (!verifyCertificateChain(certificateChain)) { - throw new Error('Invalid certificate chain!'); - } - - const signatureBase = Buffer.from( - jwsParts[0] + '.' + jwsParts[1], - 'utf-8', - ); - - const valid = crypto - .createVerify('sha256') - .update(signatureBase) - .verify(certificateChain[0], base64URLDecode(signature)); - - const negTwo = publicKey.get(-2); - - if (!negTwo || negTwo.length !== 32) { - throw new Error('invalid or no -2 key given'); - } - const negThree = publicKey.get(-3); - if (!negThree || negThree.length !== 32) { - throw new Error('invalid or no -3 key given'); - } - - const publicKeyData = Buffer.concat( - [ECC_PRELUDE, negTwo, negThree], - 1 + 32 + 32, - ); - return { - valid, - publicKey: publicKeyData, - }; - }, - }, - packed: { - verify({ - attStmt, - authenticatorData, - clientDataHash, - publicKey, - rpIdHash, - credentialId, - }: { - attStmt: any, - authenticatorData: Buffer, - clientDataHash: Buffer, - publicKey: Map; - rpIdHash: Buffer, - credentialId: Buffer, - }) { - const verificationData = Buffer.concat([ - authenticatorData, - clientDataHash, - ]); - - if (attStmt.x5c) { - const attCert = attStmt.x5c[0]; - - const validSignature = crypto - .createVerify('SHA256') - .update(verificationData) - .verify(PEMString(attCert), attStmt.sig); - - const negTwo = publicKey.get(-2); - - if (!negTwo || negTwo.length !== 32) { - throw new Error('invalid or no -2 key given'); - } - const negThree = publicKey.get(-3); - if (!negThree || negThree.length !== 32) { - throw new Error('invalid or no -3 key given'); - } - - const publicKeyData = Buffer.concat( - [ECC_PRELUDE, negTwo, negThree], - 1 + 32 + 32, - ); - - return { - valid: validSignature, - publicKey: publicKeyData, - }; - } else if (attStmt.ecdaaKeyId) { - // https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-ecdaa-algorithm-v2.0-id-20180227.html#ecdaa-verify-operation - throw new Error('ECDAA-Verify is not supported'); - } else { - if (attStmt.alg !== -7) throw new Error('alg mismatch'); - - throw new Error('self attestation is not supported'); - } - }, - }, - - 'fido-u2f': { - verify({ - attStmt, - authenticatorData, - clientDataHash, - publicKey, - rpIdHash, - credentialId, - }: { - attStmt: any, - authenticatorData: Buffer, - clientDataHash: Buffer, - publicKey: Map, - rpIdHash: Buffer, - credentialId: Buffer - }) { - const x5c: Buffer[] = attStmt.x5c; - if (x5c.length !== 1) { - throw new Error('x5c length does not match expectation'); - } - - const attCert = x5c[0]; - - // TODO: make sure attCert is an Elliptic Curve (EC) public key over the P-256 curve - - const negTwo: Buffer = publicKey.get(-2); - - if (!negTwo || negTwo.length !== 32) { - throw new Error('invalid or no -2 key given'); - } - const negThree: Buffer = publicKey.get(-3); - if (!negThree || negThree.length !== 32) { - throw new Error('invalid or no -3 key given'); - } - - const publicKeyU2F = Buffer.concat( - [ECC_PRELUDE, negTwo, negThree], - 1 + 32 + 32, - ); - - const verificationData = Buffer.concat([ - NULL_BYTE, - rpIdHash, - clientDataHash, - credentialId, - publicKeyU2F, - ]); - - const validSignature = crypto - .createVerify('SHA256') - .update(verificationData) - .verify(PEMString(attCert), attStmt.sig); - - return { - valid: validSignature, - publicKey: publicKeyU2F, - }; - }, - }, - }; - } -} diff --git a/packages/backend/src/core/UserAuthService.ts b/packages/backend/src/core/UserAuthService.ts new file mode 100644 index 000000000000..ccf4dfc6bdfb --- /dev/null +++ b/packages/backend/src/core/UserAuthService.ts @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { QueryFailedError } from 'typeorm'; +import * as OTPAuth from 'otpauth'; +import { DI } from '@/di-symbols.js'; +import type { MiUserProfile, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import { bindThis } from '@/decorators.js'; +import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; +import type { MiLocalUser } from '@/models/User.js'; + +@Injectable() +export class UserAuthService { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + ) { + } + + @bindThis + public async twoFactorAuthenticate(profile: MiUserProfile, token: string): Promise { + if (profile.twoFactorBackupSecret?.includes(token)) { + await this.userProfilesRepository.update({ userId: profile.userId }, { + twoFactorBackupSecret: profile.twoFactorBackupSecret.filter((secret) => secret !== token), + }); + } else { + const delta = OTPAuth.TOTP.validate({ + secret: OTPAuth.Secret.fromBase32(profile.twoFactorSecret!), + digits: 6, + token, + window: 5, + }); + + if (delta === null) { + throw new Error('authentication failed'); + } + } + } +} diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index 28ecf81a13f7..37031e341e4f 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -6,12 +6,12 @@ import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import { IdService } from '@/core/IdService.js'; -import type { User } from '@/models/entities/User.js'; -import type { Blocking } from '@/models/entities/Blocking.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiBlocking } from '@/models/Blocking.js'; import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; -import type { FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; +import type { FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/_.js'; import Logger from '@/logger.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; @@ -58,7 +58,7 @@ export class UserBlockingService implements OnModuleInit { } @bindThis - public async block(blocker: User, blockee: User, silent = false) { + public async block(blocker: MiUser, blockee: MiUser, silent = false) { await Promise.all([ this.cancelRequest(blocker, blockee, silent), this.cancelRequest(blockee, blocker, silent), @@ -74,7 +74,7 @@ export class UserBlockingService implements OnModuleInit { blockerId: blocker.id, blockee, blockeeId: blockee.id, - } as Blocking; + } as MiBlocking; await this.blockingsRepository.insert(blocking); @@ -93,7 +93,7 @@ export class UserBlockingService implements OnModuleInit { } @bindThis - private async cancelRequest(follower: User, followee: User, silent = false) { + private async cancelRequest(follower: MiUser, followee: MiUser, silent = false) { const request = await this.followRequestsRepository.findOneBy({ followeeId: followee.id, followerId: follower.id, @@ -143,7 +143,7 @@ export class UserBlockingService implements OnModuleInit { } @bindThis - private async removeFromList(listOwner: User, user: User) { + private async removeFromList(listOwner: MiUser, user: MiUser) { const userLists = await this.userListsRepository.findBy({ userId: listOwner.id, }); @@ -157,7 +157,7 @@ export class UserBlockingService implements OnModuleInit { } @bindThis - public async unblock(blocker: User, blockee: User) { + public async unblock(blocker: MiUser, blockee: MiUser) { const blocking = await this.blockingsRepository.findOneBy({ blockerId: blocker.id, blockeeId: blockee.id, @@ -191,7 +191,7 @@ export class UserBlockingService implements OnModuleInit { } @bindThis - public async checkBlocked(blockerId: User['id'], blockeeId: User['id']): Promise { + public async checkBlocked(blockerId: MiUser['id'], blockeeId: MiUser['id']): Promise { return (await this.cacheService.userBlockingCache.fetch(blockerId)).has(blockeeId); } } diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index 8e356d19bb78..5b2b0205d96e 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -6,7 +6,7 @@ import { Inject, Injectable, OnModuleInit, forwardRef } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import { IsNull } from 'typeorm'; -import type { LocalUser, PartialLocalUser, PartialRemoteUser, RemoteUser, User } from '@/models/entities/User.js'; +import type { MiLocalUser, MiPartialLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { QueueService } from '@/core/QueueService.js'; import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js'; @@ -19,7 +19,7 @@ import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { WebhookService } from '@/core/WebhookService.js'; import { NotificationService } from '@/core/NotificationService.js'; import { DI } from '@/di-symbols.js'; -import type { FollowingsRepository, FollowRequestsRepository, InstancesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { FollowingsRepository, FollowRequestsRepository, InstancesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { bindThis } from '@/decorators.js'; @@ -32,16 +32,16 @@ import Logger from '../logger.js'; const logger = new Logger('following/create'); -type Local = LocalUser | { - id: LocalUser['id']; - host: LocalUser['host']; - uri: LocalUser['uri'] +type Local = MiLocalUser | { + id: MiLocalUser['id']; + host: MiLocalUser['host']; + uri: MiLocalUser['uri'] }; -type Remote = RemoteUser | { - id: RemoteUser['id']; - host: RemoteUser['host']; - uri: RemoteUser['uri']; - inbox: RemoteUser['inbox']; +type Remote = MiRemoteUser | { + id: MiRemoteUser['id']; + host: MiRemoteUser['host']; + uri: MiRemoteUser['uri']; + inbox: MiRemoteUser['inbox']; }; type Both = Local | Remote; @@ -91,11 +91,11 @@ export class UserFollowingService implements OnModuleInit { } @bindThis - public async follow(_follower: { id: User['id'] }, _followee: { id: User['id'] }, requestId?: string, silent = false): Promise { + public async follow(_follower: { id: MiUser['id'] }, _followee: { id: MiUser['id'] }, requestId?: string, silent = false): Promise { const [follower, followee] = await Promise.all([ this.usersRepository.findOneByOrFail({ id: _follower.id }), this.usersRepository.findOneByOrFail({ id: _followee.id }), - ]) as [LocalUser | RemoteUser, LocalUser | RemoteUser]; + ]) as [MiLocalUser | MiRemoteUser, MiLocalUser | MiRemoteUser]; // check blocking const [blocking, blocked] = await Promise.all([ @@ -180,10 +180,10 @@ export class UserFollowingService implements OnModuleInit { @bindThis private async insertFollowingDoc( followee: { - id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox'] + id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox'] }, follower: { - id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox'] + id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox'] }, silent = false, ): Promise { @@ -312,10 +312,10 @@ export class UserFollowingService implements OnModuleInit { @bindThis public async unfollow( follower: { - id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; + id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox']; }, followee: { - id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; + id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox']; }, silent = false, ): Promise { @@ -358,21 +358,21 @@ export class UserFollowingService implements OnModuleInit { } if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { - const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower as PartialLocalUser, followee as PartialRemoteUser), follower)); + const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower as MiPartialLocalUser, followee as MiPartialRemoteUser), follower)); this.queueService.deliver(follower, content, followee.inbox, false); } if (this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower)) { // local user has null host - const content = this.apRendererService.addContext(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower as PartialRemoteUser, followee as PartialLocalUser), followee)); + const content = this.apRendererService.addContext(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower as MiPartialRemoteUser, followee as MiPartialLocalUser), followee)); this.queueService.deliver(followee, content, follower.inbox, false); } } @bindThis private async decrementFollowing( - follower: User, - followee: User, + follower: MiUser, + followee: MiUser, ): Promise { this.globalEventService.publishInternalEvent('unfollow', { followerId: follower.id, followeeId: followee.id }); @@ -444,10 +444,10 @@ export class UserFollowingService implements OnModuleInit { @bindThis public async createFollowRequest( follower: { - id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; + id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox']; }, followee: { - id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; + id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox']; }, requestId?: string, ): Promise { @@ -494,7 +494,7 @@ export class UserFollowingService implements OnModuleInit { } if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { - const content = this.apRendererService.addContext(this.apRendererService.renderFollow(follower as PartialLocalUser, followee as PartialRemoteUser, requestId ?? `${this.config.url}/follows/${followRequest.id}`)); + const content = this.apRendererService.addContext(this.apRendererService.renderFollow(follower as MiPartialLocalUser, followee as MiPartialRemoteUser, requestId ?? `${this.config.url}/follows/${followRequest.id}`)); this.queueService.deliver(follower, content, followee.inbox, false); } } @@ -502,14 +502,14 @@ export class UserFollowingService implements OnModuleInit { @bindThis public async cancelFollowRequest( followee: { - id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox'] + id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox'] }, follower: { - id: User['id']; host: User['host']; uri: User['host'] + id: MiUser['id']; host: MiUser['host']; uri: MiUser['host'] }, ): Promise { if (this.userEntityService.isRemoteUser(followee)) { - const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower as PartialLocalUser | PartialRemoteUser, followee as PartialRemoteUser), follower)); + const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower as MiPartialLocalUser | MiPartialRemoteUser, followee as MiPartialRemoteUser), follower)); if (this.userEntityService.isLocalUser(follower)) { // 本来このチェックは不要だけどTSに怒られるので this.queueService.deliver(follower, content, followee.inbox, false); @@ -540,9 +540,9 @@ export class UserFollowingService implements OnModuleInit { @bindThis public async acceptFollowRequest( followee: { - id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; + id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox']; }, - follower: User, + follower: MiUser, ): Promise { const request = await this.followRequestsRepository.findOneBy({ followeeId: followee.id, @@ -556,7 +556,7 @@ export class UserFollowingService implements OnModuleInit { await this.insertFollowingDoc(followee, follower); if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { - const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee as PartialLocalUser, request.requestId!), followee)); + const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee as MiPartialLocalUser, request.requestId!), followee)); this.queueService.deliver(followee, content, follower.inbox, false); } @@ -568,7 +568,7 @@ export class UserFollowingService implements OnModuleInit { @bindThis public async acceptAllFollowRequests( user: { - id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; + id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox']; }, ): Promise { const requests = await this.followRequestsRepository.findBy({ diff --git a/packages/backend/src/core/UserKeypairService.ts b/packages/backend/src/core/UserKeypairService.ts index ec5d867bdbb7..425a97f3f128 100644 --- a/packages/backend/src/core/UserKeypairService.ts +++ b/packages/backend/src/core/UserKeypairService.ts @@ -5,16 +5,16 @@ import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import * as Redis from 'ioredis'; -import type { User } from '@/models/entities/User.js'; -import type { UserKeypairsRepository } from '@/models/index.js'; +import type { MiUser } from '@/models/User.js'; +import type { UserKeypairsRepository } from '@/models/_.js'; import { RedisKVCache } from '@/misc/cache.js'; -import type { UserKeypair } from '@/models/entities/UserKeypair.js'; +import type { MiUserKeypair } from '@/models/UserKeypair.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; @Injectable() export class UserKeypairService implements OnApplicationShutdown { - private cache: RedisKVCache; + private cache: RedisKVCache; constructor( @Inject(DI.redis) @@ -23,7 +23,7 @@ export class UserKeypairService implements OnApplicationShutdown { @Inject(DI.userKeypairsRepository) private userKeypairsRepository: UserKeypairsRepository, ) { - this.cache = new RedisKVCache(this.redisClient, 'userKeypair', { + this.cache = new RedisKVCache(this.redisClient, 'userKeypair', { lifetime: 1000 * 60 * 60 * 24, // 24h memoryCacheLifetime: Infinity, fetcher: (key) => this.userKeypairsRepository.findOneByOrFail({ userId: key }), @@ -33,7 +33,7 @@ export class UserKeypairService implements OnApplicationShutdown { } @bindThis - public async getUserKeypair(userId: User['id']): Promise { + public async getUserKeypair(userId: MiUser['id']): Promise { return await this.cache.fetch(userId); } diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index e065635d5d4e..a71d50bba515 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -4,10 +4,10 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserListJoiningsRepository } from '@/models/index.js'; -import type { User } from '@/models/entities/User.js'; -import type { UserList } from '@/models/entities/UserList.js'; -import type { UserListJoining } from '@/models/entities/UserListJoining.js'; +import type { UserListJoiningsRepository } from '@/models/_.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiUserList } from '@/models/UserList.js'; +import type { MiUserListJoining } from '@/models/UserListJoining.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; @@ -35,7 +35,7 @@ export class UserListService { } @bindThis - public async push(target: User, list: UserList, me: User) { + public async push(target: MiUser, list: MiUserList, me: MiUser) { const currentCount = await this.userListJoiningsRepository.countBy({ userListId: list.id, }); @@ -48,7 +48,7 @@ export class UserListService { createdAt: new Date(), userId: target.id, userListId: list.id, - } as UserListJoining); + } as MiUserListJoining); this.globalEventService.publishUserListStream(list.id, 'userAdded', await this.userEntityService.pack(target)); diff --git a/packages/backend/src/core/UserMutingService.ts b/packages/backend/src/core/UserMutingService.ts index 7472d31dcebc..2387c9d64839 100644 --- a/packages/backend/src/core/UserMutingService.ts +++ b/packages/backend/src/core/UserMutingService.ts @@ -5,9 +5,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; -import type { MutingsRepository, Muting } from '@/models/index.js'; +import type { MutingsRepository, MiMuting } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; -import type { User } from '@/models/entities/User.js'; +import type { MiUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { CacheService } from '@/core/CacheService.js'; @@ -24,7 +24,7 @@ export class UserMutingService { } @bindThis - public async mute(user: User, target: User, expiresAt: Date | null = null): Promise { + public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise { await this.mutingsRepository.insert({ id: this.idService.genId(), createdAt: new Date(), @@ -37,7 +37,7 @@ export class UserMutingService { } @bindThis - public async unmute(mutings: Muting[]): Promise { + public async unmute(mutings: MiMuting[]): Promise { if (mutings.length === 0) return; await this.mutingsRepository.delete({ diff --git a/packages/backend/src/core/UserSuspendService.ts b/packages/backend/src/core/UserSuspendService.ts index 4ec6a71f7540..8940a142d10f 100644 --- a/packages/backend/src/core/UserSuspendService.ts +++ b/packages/backend/src/core/UserSuspendService.ts @@ -5,8 +5,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { Not, IsNull } from 'typeorm'; -import type { FollowingsRepository } from '@/models/index.js'; -import type { User } from '@/models/entities/User.js'; +import type { FollowingsRepository } from '@/models/_.js'; +import type { MiUser } from '@/models/User.js'; import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; @@ -28,7 +28,7 @@ export class UserSuspendService { } @bindThis - public async doPostSuspend(user: { id: User['id']; host: User['host'] }): Promise { + public async doPostSuspend(user: { id: MiUser['id']; host: MiUser['host'] }): Promise { this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true }); if (this.userEntityService.isLocalUser(user)) { @@ -58,7 +58,7 @@ export class UserSuspendService { } @bindThis - public async doPostUnsuspend(user: User): Promise { + public async doPostUnsuspend(user: MiUser): Promise { this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: false }); if (this.userEntityService.isLocalUser(user)) { diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts new file mode 100644 index 000000000000..5945dc29197a --- /dev/null +++ b/packages/backend/src/core/WebAuthnService.ts @@ -0,0 +1,252 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import * as Redis from 'ioredis'; +import { + generateAuthenticationOptions, + generateRegistrationOptions, verifyAuthenticationResponse, + verifyRegistrationResponse, +} from '@simplewebauthn/server'; +import { AttestationFormat, isoCBOR } from '@simplewebauthn/server/helpers'; +import { DI } from '@/di-symbols.js'; +import type { UserSecurityKeysRepository } from '@/models/_.js'; +import type { Config } from '@/config.js'; +import { bindThis } from '@/decorators.js'; +import { MetaService } from '@/core/MetaService.js'; +import { MiUser } from '@/models/_.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; +import type { + AuthenticationResponseJSON, + AuthenticatorTransportFuture, + CredentialDeviceType, + PublicKeyCredentialCreationOptionsJSON, + PublicKeyCredentialDescriptorFuture, + PublicKeyCredentialRequestOptionsJSON, + RegistrationResponseJSON, +} from '@simplewebauthn/typescript-types'; + +@Injectable() +export class WebAuthnService { + constructor( + @Inject(DI.redis) + private redisClient: Redis.Redis, + + @Inject(DI.config) + private config: Config, + + @Inject(DI.userSecurityKeysRepository) + private userSecurityKeysRepository: UserSecurityKeysRepository, + + private metaService: MetaService, + ) { + } + + @bindThis + public async getRelyingParty(): Promise<{ origin: string; rpId: string; rpName: string; rpIcon?: string; }> { + const instance = await this.metaService.fetch(); + return { + origin: this.config.url, + rpId: this.config.host, + rpName: instance.name ?? this.config.host, + rpIcon: instance.iconUrl ?? undefined, + }; + } + + @bindThis + public async initiateRegistration(userId: MiUser['id'], userName: string, userDisplayName?: string): Promise { + const relyingParty = await this.getRelyingParty(); + const keys = await this.userSecurityKeysRepository.findBy({ + userId: userId, + }); + + const registrationOptions = await generateRegistrationOptions({ + rpName: relyingParty.rpName, + rpID: relyingParty.rpId, + userID: userId, + userName: userName, + userDisplayName: userDisplayName, + attestationType: 'indirect', + excludeCredentials: keys.map(key => ({ + id: Buffer.from(key.id, 'base64url'), + type: 'public-key', + transports: key.transports ?? undefined, + })), + authenticatorSelection: { + residentKey: 'required', + userVerification: 'preferred', + }, + }); + + await this.redisClient.setex(`webauthn:challenge:${userId}`, 90, registrationOptions.challenge); + + return registrationOptions; + } + + @bindThis + public async verifyRegistration(userId: MiUser['id'], response: RegistrationResponseJSON): Promise<{ + credentialID: Uint8Array; + credentialPublicKey: Uint8Array; + attestationObject: Uint8Array; + fmt: AttestationFormat; + counter: number; + userVerified: boolean; + credentialDeviceType: CredentialDeviceType; + credentialBackedUp: boolean; + transports?: AuthenticatorTransportFuture[]; + }> { + const challenge = await this.redisClient.get(`webauthn:challenge:${userId}`); + + if (!challenge) { + throw new IdentifiableError('7dbfb66c-9216-4e2b-9c27-cef2ac8efb84', 'challenge not found'); + } + + await this.redisClient.del(`webauthn:challenge:${userId}`); + + const relyingParty = await this.getRelyingParty(); + + let verification; + try { + verification = await verifyRegistrationResponse({ + response: response, + expectedChallenge: challenge, + expectedOrigin: relyingParty.origin, + expectedRPID: relyingParty.rpId, + requireUserVerification: true, + }); + } catch (error) { + console.error(error); + throw new IdentifiableError('5c1446f8-8ca7-4d31-9f39-656afe9c5d87', 'verification failed'); + } + + const { verified } = verification; + + if (!verified || !verification.registrationInfo) { + throw new IdentifiableError('bb333667-3832-4a80-8bb5-c505be7d710d', 'verification failed'); + } + + const { registrationInfo } = verification; + + return { + credentialID: registrationInfo.credentialID, + credentialPublicKey: registrationInfo.credentialPublicKey, + attestationObject: registrationInfo.attestationObject, + fmt: registrationInfo.fmt, + counter: registrationInfo.counter, + userVerified: registrationInfo.userVerified, + credentialDeviceType: registrationInfo.credentialDeviceType, + credentialBackedUp: registrationInfo.credentialBackedUp, + transports: response.response.transports, + }; + } + + @bindThis + public async initiateAuthentication(userId: MiUser['id']): Promise { + const keys = await this.userSecurityKeysRepository.findBy({ + userId: userId, + }); + + if (keys.length === 0) { + throw new IdentifiableError('f27fd449-9af4-4841-9249-1f989b9fa4a4', 'no keys found'); + } + + const authenticationOptions = await generateAuthenticationOptions({ + allowCredentials: keys.map(key => ({ + id: Buffer.from(key.id, 'base64url'), + type: 'public-key', + transports: key.transports ?? undefined, + })), + userVerification: 'preferred', + }); + + await this.redisClient.setex(`webauthn:challenge:${userId}`, 90, authenticationOptions.challenge); + + return authenticationOptions; + } + + @bindThis + public async verifyAuthentication(userId: MiUser['id'], response: AuthenticationResponseJSON): Promise { + const challenge = await this.redisClient.get(`webauthn:challenge:${userId}`); + + if (!challenge) { + throw new IdentifiableError('2d16e51c-007b-4edd-afd2-f7dd02c947f6', 'challenge not found'); + } + + await this.redisClient.del(`webauthn:challenge:${userId}`); + + const key = await this.userSecurityKeysRepository.findOneBy({ + id: response.id, + userId: userId, + }); + + if (!key) { + throw new IdentifiableError('36b96a7d-b547-412d-aeed-2d611cdc8cdc', 'unknown key'); + } + + // マイグレーション + if (key.counter === 0 && key.publicKey.length === 87) { + const cert = new Uint8Array(Buffer.from(key.publicKey, 'base64url')); + if (cert[0] === 0x04) { // 前の実装ではいつも 0x04 で始まっていた + const halfLength = (cert.length - 1) / 2; + + const cborMap = new Map(); + cborMap.set(1, 2); // kty, EC2 + cborMap.set(3, -7); // alg, ES256 + cborMap.set(-1, 1); // crv, P256 + cborMap.set(-2, cert.slice(1, halfLength + 1)); // x + cborMap.set(-3, cert.slice(halfLength + 1)); // y + + const cborPubKey = Buffer.from(isoCBOR.encode(cborMap)).toString('base64url'); + await this.userSecurityKeysRepository.update({ + id: response.id, + userId: userId, + }, { + publicKey: cborPubKey, + }); + key.publicKey = cborPubKey; + } + } + + const relyingParty = await this.getRelyingParty(); + + let verification; + try { + verification = await verifyAuthenticationResponse({ + response: response, + expectedChallenge: challenge, + expectedOrigin: relyingParty.origin, + expectedRPID: relyingParty.rpId, + authenticator: { + credentialID: Buffer.from(key.id, 'base64url'), + credentialPublicKey: Buffer.from(key.publicKey, 'base64url'), + counter: key.counter, + transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined, + }, + requireUserVerification: true, + }); + } catch (error) { + console.error(error); + throw new IdentifiableError('b18c89a7-5b5e-4cec-bb5b-0419f332d430', 'verification failed'); + } + + const { verified, authenticationInfo } = verification; + + if (!verified) { + return false; + } + + await this.userSecurityKeysRepository.update({ + id: response.id, + userId: userId, + }, { + lastUsed: new Date(), + counter: authenticationInfo.newCounter, + credentialDeviceType: authenticationInfo.credentialDeviceType, + credentialBackedUp: authenticationInfo.credentialBackedUp, + }); + + return verified; + } +} diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts index 8919897c4e26..1344f0ac9709 100644 --- a/packages/backend/src/core/WebhookService.ts +++ b/packages/backend/src/core/WebhookService.ts @@ -5,8 +5,8 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; -import type { WebhooksRepository } from '@/models/index.js'; -import type { Webhook } from '@/models/entities/Webhook.js'; +import type { WebhooksRepository } from '@/models/_.js'; +import type { MiWebhook } from '@/models/Webhook.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { StreamMessages } from '@/server/api/stream/types.js'; @@ -15,7 +15,7 @@ import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() export class WebhookService implements OnApplicationShutdown { private webhooksFetched = false; - private webhooks: Webhook[] = []; + private webhooks: MiWebhook[] = []; constructor( @Inject(DI.redisForSub) diff --git a/packages/backend/src/core/activitypub/ApAudienceService.ts b/packages/backend/src/core/activitypub/ApAudienceService.ts index f90f4a48df10..440852bdf370 100644 --- a/packages/backend/src/core/activitypub/ApAudienceService.ts +++ b/packages/backend/src/core/activitypub/ApAudienceService.ts @@ -5,7 +5,7 @@ import { Injectable } from '@nestjs/common'; import promiseLimit from 'promise-limit'; -import type { RemoteUser, User } from '@/models/entities/User.js'; +import type { MiRemoteUser, MiUser } from '@/models/User.js'; import { concat, unique } from '@/misc/prelude/array.js'; import { bindThis } from '@/decorators.js'; import { getApIds } from './type.js'; @@ -17,8 +17,8 @@ type Visibility = 'public' | 'home' | 'followers' | 'specified'; type AudienceInfo = { visibility: Visibility, - mentionedUsers: User[], - visibleUsers: User[], + mentionedUsers: MiUser[], + visibleUsers: MiUser[], }; type GroupedAudience = Record<'public' | 'followers' | 'other', string[]>; @@ -31,16 +31,16 @@ export class ApAudienceService { } @bindThis - public async parseAudience(actor: RemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise { + public async parseAudience(actor: MiRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise { const toGroups = this.groupingAudience(getApIds(to), actor); const ccGroups = this.groupingAudience(getApIds(cc), actor); const others = unique(concat([toGroups.other, ccGroups.other])); - const limit = promiseLimit(2); + const limit = promiseLimit(2); const mentionedUsers = (await Promise.all( others.map(id => limit(() => this.apPersonService.resolvePerson(id, resolver).catch(() => null))), - )).filter((x): x is User => x != null); + )).filter((x): x is MiUser => x != null); if (toGroups.public.length > 0) { return { @@ -74,7 +74,7 @@ export class ApAudienceService { } @bindThis - private groupingAudience(ids: string[], actor: RemoteUser): GroupedAudience { + private groupingAudience(ids: string[], actor: MiRemoteUser): GroupedAudience { const groups: GroupedAudience = { public: [], followers: [], @@ -106,7 +106,7 @@ export class ApAudienceService { } @bindThis - private isFollowers(id: string, actor: RemoteUser): boolean { + private isFollowers(id: string, actor: MiRemoteUser): boolean { return id === (actor.followersUri ?? `${actor.uri}/followers`); } } diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts index 418252f6bdb9..995c5dcd5f88 100644 --- a/packages/backend/src/core/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts @@ -5,14 +5,14 @@ import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; +import type { NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; import { MemoryKVCache } from '@/misc/cache.js'; -import type { UserPublickey } from '@/models/entities/UserPublickey.js'; +import type { MiUserPublickey } from '@/models/UserPublickey.js'; import { CacheService } from '@/core/CacheService.js'; -import type { Note } from '@/models/entities/Note.js'; +import type { MiNote } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; -import { LocalUser, RemoteUser } from '@/models/entities/User.js'; +import { MiLocalUser, MiRemoteUser } from '@/models/User.js'; import { getApId } from './type.js'; import { ApPersonService } from './models/ApPersonService.js'; import type { IObject } from './type.js'; @@ -35,8 +35,8 @@ export type UriParseResult = { @Injectable() export class ApDbResolverService implements OnApplicationShutdown { - private publicKeyCache: MemoryKVCache; - private publicKeyByUserIdCache: MemoryKVCache; + private publicKeyCache: MemoryKVCache; + private publicKeyByUserIdCache: MemoryKVCache; constructor( @Inject(DI.config) @@ -54,8 +54,8 @@ export class ApDbResolverService implements OnApplicationShutdown { private cacheService: CacheService, private apPersonService: ApPersonService, ) { - this.publicKeyCache = new MemoryKVCache(Infinity); - this.publicKeyByUserIdCache = new MemoryKVCache(Infinity); + this.publicKeyCache = new MemoryKVCache(Infinity); + this.publicKeyByUserIdCache = new MemoryKVCache(Infinity); } @bindThis @@ -78,7 +78,7 @@ export class ApDbResolverService implements OnApplicationShutdown { * AP Note => Misskey Note in DB */ @bindThis - public async getNoteFromApId(value: string | IObject): Promise { + public async getNoteFromApId(value: string | IObject): Promise { const parsed = this.parseUri(value); if (parsed.local) { @@ -98,7 +98,7 @@ export class ApDbResolverService implements OnApplicationShutdown { * AP Person => Misskey User in DB */ @bindThis - public async getUserFromApId(value: string | IObject): Promise { + public async getUserFromApId(value: string | IObject): Promise { const parsed = this.parseUri(value); if (parsed.local) { @@ -107,12 +107,12 @@ export class ApDbResolverService implements OnApplicationShutdown { return await this.cacheService.userByIdCache.fetchMaybe( parsed.id, () => this.usersRepository.findOneBy({ id: parsed.id }).then(x => x ?? undefined), - ) as LocalUser | undefined ?? null; + ) as MiLocalUser | undefined ?? null; } else { return await this.cacheService.uriPersonCache.fetch( parsed.uri, () => this.usersRepository.findOneBy({ uri: parsed.uri }), - ) as RemoteUser | null; + ) as MiRemoteUser | null; } } @@ -121,8 +121,8 @@ export class ApDbResolverService implements OnApplicationShutdown { */ @bindThis public async getAuthUserFromKeyId(keyId: string): Promise<{ - user: RemoteUser; - key: UserPublickey; + user: MiRemoteUser; + key: MiUserPublickey; } | null> { const key = await this.publicKeyCache.fetch(keyId, async () => { const key = await this.userPublickeysRepository.findOneBy({ @@ -137,7 +137,7 @@ export class ApDbResolverService implements OnApplicationShutdown { if (key == null) return null; return { - user: await this.cacheService.findUserById(key.userId) as RemoteUser, + user: await this.cacheService.findUserById(key.userId) as MiRemoteUser, key, }; } @@ -147,10 +147,10 @@ export class ApDbResolverService implements OnApplicationShutdown { */ @bindThis public async getAuthUserFromApId(uri: string): Promise<{ - user: RemoteUser; - key: UserPublickey | null; + user: MiRemoteUser; + key: MiUserPublickey | null; } | null> { - const user = await this.apPersonService.resolvePerson(uri) as RemoteUser; + const user = await this.apPersonService.resolvePerson(uri) as MiRemoteUser; const key = await this.publicKeyByUserIdCache.fetch( user.id, diff --git a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts index 0f2dcaead92c..81003bcf1c79 100644 --- a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts +++ b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts @@ -6,8 +6,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { FollowingsRepository } from '@/models/index.js'; -import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js'; +import type { FollowingsRepository } from '@/models/_.js'; +import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js'; import { QueueService } from '@/core/QueueService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; @@ -24,7 +24,7 @@ interface IFollowersRecipe extends IRecipe { interface IDirectRecipe extends IRecipe { type: 'Direct'; - to: RemoteUser; + to: MiRemoteUser; } const isFollowers = (recipe: IRecipe): recipe is IFollowersRecipe => @@ -51,7 +51,7 @@ class DeliverManager { private followingsRepository: FollowingsRepository, private queueService: QueueService, - actor: { id: User['id']; host: null; }, + actor: { id: MiUser['id']; host: null; }, activity: IActivity | null, ) { // 型で弾いてはいるが一応ローカルユーザーかチェック @@ -82,7 +82,7 @@ class DeliverManager { * @param to To */ @bindThis - public addDirectRecipe(to: RemoteUser): void { + public addDirectRecipe(to: MiRemoteUser): void { const recipe: IDirectRecipe = { type: 'Direct', to, @@ -165,7 +165,7 @@ export class ApDeliverManagerService { * @param activity Activity */ @bindThis - public async deliverToFollowers(actor: { id: LocalUser['id']; host: null; }, activity: IActivity): Promise { + public async deliverToFollowers(actor: { id: MiLocalUser['id']; host: null; }, activity: IActivity): Promise { const manager = new DeliverManager( this.userEntityService, this.followingsRepository, @@ -184,7 +184,7 @@ export class ApDeliverManagerService { * @param to Target user */ @bindThis - public async deliverToUser(actor: { id: LocalUser['id']; host: null; }, activity: IActivity, to: RemoteUser): Promise { + public async deliverToUser(actor: { id: MiLocalUser['id']; host: null; }, activity: IActivity, to: MiRemoteUser): Promise { const manager = new DeliverManager( this.userEntityService, this.followingsRepository, @@ -197,7 +197,7 @@ export class ApDeliverManagerService { } @bindThis - public createDeliverManager(actor: { id: User['id']; host: null; }, activity: IActivity | null): DeliverManager { + public createDeliverManager(actor: { id: MiUser['id']; host: null; }, activity: IActivity | null): DeliverManager { return new DeliverManager( this.userEntityService, this.followingsRepository, diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index ac2940ce94bb..b921ee745455 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -24,9 +24,9 @@ import { UtilityService } from '@/core/UtilityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { QueueService } from '@/core/QueueService.js'; -import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.js'; +import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; -import type { RemoteUser } from '@/models/entities/User.js'; +import type { MiRemoteUser } from '@/models/User.js'; import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; import { ApNoteService } from './models/ApNoteService.js'; import { ApLoggerService } from './ApLoggerService.js'; @@ -87,7 +87,7 @@ export class ApInboxService { } @bindThis - public async performActivity(actor: RemoteUser, activity: IObject): Promise { + public async performActivity(actor: MiRemoteUser, activity: IObject): Promise { if (isCollectionOrOrderedCollection(activity)) { const resolver = this.apResolverService.createResolver(); for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) { @@ -115,7 +115,7 @@ export class ApInboxService { } @bindThis - public async performOneActivity(actor: RemoteUser, activity: IObject): Promise { + public async performOneActivity(actor: MiRemoteUser, activity: IObject): Promise { if (actor.isSuspended) return; if (isCreate(activity)) { @@ -152,7 +152,7 @@ export class ApInboxService { } @bindThis - private async follow(actor: RemoteUser, activity: IFollow): Promise { + private async follow(actor: MiRemoteUser, activity: IFollow): Promise { const followee = await this.apDbResolverService.getUserFromApId(activity.object); if (followee == null) { @@ -169,7 +169,7 @@ export class ApInboxService { } @bindThis - private async like(actor: RemoteUser, activity: ILike): Promise { + private async like(actor: MiRemoteUser, activity: ILike): Promise { const targetUri = getApId(activity.object); const note = await this.apNoteService.fetchNote(targetUri); @@ -187,7 +187,7 @@ export class ApInboxService { } @bindThis - private async accept(actor: RemoteUser, activity: IAccept): Promise { + private async accept(actor: MiRemoteUser, activity: IAccept): Promise { const uri = activity.id ?? activity; this.logger.info(`Accept: ${uri}`); @@ -205,7 +205,7 @@ export class ApInboxService { } @bindThis - private async acceptFollow(actor: RemoteUser, activity: IFollow): Promise { + private async acceptFollow(actor: MiRemoteUser, activity: IFollow): Promise { // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある const follower = await this.apDbResolverService.getUserFromApId(activity.actor); @@ -229,7 +229,7 @@ export class ApInboxService { } @bindThis - private async add(actor: RemoteUser, activity: IAdd): Promise { + private async add(actor: MiRemoteUser, activity: IAdd): Promise { if (actor.uri !== activity.actor) { throw new Error('invalid actor'); } @@ -249,7 +249,7 @@ export class ApInboxService { } @bindThis - private async announce(actor: RemoteUser, activity: IAnnounce): Promise { + private async announce(actor: MiRemoteUser, activity: IAnnounce): Promise { const uri = getApId(activity); this.logger.info(`Announce: ${uri}`); @@ -260,7 +260,7 @@ export class ApInboxService { } @bindThis - private async announceNote(actor: RemoteUser, activity: IAnnounce, targetUri: string): Promise { + private async announceNote(actor: MiRemoteUser, activity: IAnnounce, targetUri: string): Promise { const uri = getApId(activity); if (actor.isSuspended) { @@ -320,7 +320,7 @@ export class ApInboxService { } @bindThis - private async block(actor: RemoteUser, activity: IBlock): Promise { + private async block(actor: MiRemoteUser, activity: IBlock): Promise { // ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず const blockee = await this.apDbResolverService.getUserFromApId(activity.object); @@ -338,7 +338,7 @@ export class ApInboxService { } @bindThis - private async create(actor: RemoteUser, activity: ICreate): Promise { + private async create(actor: MiRemoteUser, activity: ICreate): Promise { const uri = getApId(activity); this.logger.info(`Create: ${uri}`); @@ -374,7 +374,7 @@ export class ApInboxService { } @bindThis - private async createNote(resolver: Resolver, actor: RemoteUser, note: IObject, silent = false, activity?: ICreate): Promise { + private async createNote(resolver: Resolver, actor: MiRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise { const uri = getApId(note); if (typeof note === 'object') { @@ -409,7 +409,7 @@ export class ApInboxService { } @bindThis - private async delete(actor: RemoteUser, activity: IDelete): Promise { + private async delete(actor: MiRemoteUser, activity: IDelete): Promise { if (actor.uri !== activity.actor) { throw new Error('invalid actor'); } @@ -451,7 +451,7 @@ export class ApInboxService { } @bindThis - private async deleteActor(actor: RemoteUser, uri: string): Promise { + private async deleteActor(actor: MiRemoteUser, uri: string): Promise { this.logger.info(`Deleting the Actor: ${uri}`); if (actor.uri !== uri) { @@ -475,7 +475,7 @@ export class ApInboxService { } @bindThis - private async deleteNote(actor: RemoteUser, uri: string): Promise { + private async deleteNote(actor: MiRemoteUser, uri: string): Promise { this.logger.info(`Deleting the Note: ${uri}`); const unlock = await this.appLockService.getApLock(uri); @@ -499,7 +499,7 @@ export class ApInboxService { } @bindThis - private async flag(actor: RemoteUser, activity: IFlag): Promise { + private async flag(actor: MiRemoteUser, activity: IFlag): Promise { // objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので // 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する const uris = getApIds(activity.object); @@ -527,7 +527,7 @@ export class ApInboxService { } @bindThis - private async reject(actor: RemoteUser, activity: IReject): Promise { + private async reject(actor: MiRemoteUser, activity: IReject): Promise { const uri = activity.id ?? activity; this.logger.info(`Reject: ${uri}`); @@ -545,7 +545,7 @@ export class ApInboxService { } @bindThis - private async rejectFollow(actor: RemoteUser, activity: IFollow): Promise { + private async rejectFollow(actor: MiRemoteUser, activity: IFollow): Promise { // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある const follower = await this.apDbResolverService.getUserFromApId(activity.actor); @@ -569,7 +569,7 @@ export class ApInboxService { } @bindThis - private async remove(actor: RemoteUser, activity: IRemove): Promise { + private async remove(actor: MiRemoteUser, activity: IRemove): Promise { if (actor.uri !== activity.actor) { throw new Error('invalid actor'); } @@ -589,7 +589,7 @@ export class ApInboxService { } @bindThis - private async undo(actor: RemoteUser, activity: IUndo): Promise { + private async undo(actor: MiRemoteUser, activity: IUndo): Promise { if (actor.uri !== activity.actor) { throw new Error('invalid actor'); } @@ -616,7 +616,7 @@ export class ApInboxService { } @bindThis - private async undoAccept(actor: RemoteUser, activity: IAccept): Promise { + private async undoAccept(actor: MiRemoteUser, activity: IAccept): Promise { const follower = await this.apDbResolverService.getUserFromApId(activity.object); if (follower == null) { return 'skip: follower not found'; @@ -638,7 +638,7 @@ export class ApInboxService { } @bindThis - private async undoAnnounce(actor: RemoteUser, activity: IAnnounce): Promise { + private async undoAnnounce(actor: MiRemoteUser, activity: IAnnounce): Promise { const uri = getApId(activity); const note = await this.notesRepository.findOneBy({ @@ -653,7 +653,7 @@ export class ApInboxService { } @bindThis - private async undoBlock(actor: RemoteUser, activity: IBlock): Promise { + private async undoBlock(actor: MiRemoteUser, activity: IBlock): Promise { const blockee = await this.apDbResolverService.getUserFromApId(activity.object); if (blockee == null) { @@ -669,7 +669,7 @@ export class ApInboxService { } @bindThis - private async undoFollow(actor: RemoteUser, activity: IFollow): Promise { + private async undoFollow(actor: MiRemoteUser, activity: IFollow): Promise { const followee = await this.apDbResolverService.getUserFromApId(activity.object); if (followee == null) { return 'skip: followee not found'; @@ -707,7 +707,7 @@ export class ApInboxService { } @bindThis - private async undoLike(actor: RemoteUser, activity: ILike): Promise { + private async undoLike(actor: MiRemoteUser, activity: ILike): Promise { const targetUri = getApId(activity.object); const note = await this.apNoteService.fetchNote(targetUri); @@ -722,7 +722,7 @@ export class ApInboxService { } @bindThis - private async update(actor: RemoteUser, activity: IUpdate): Promise { + private async update(actor: MiRemoteUser, activity: IUpdate): Promise { if (actor.uri !== activity.actor) { return 'skip: invalid actor'; } @@ -748,7 +748,7 @@ export class ApInboxService { } @bindThis - private async move(actor: RemoteUser, activity: IMove): Promise { + private async move(actor: MiRemoteUser, activity: IMove): Promise { // fetch the new and old accounts const targetUri = getApHrefNullable(activity.target); if (!targetUri) return 'skip: invalid activity target'; diff --git a/packages/backend/src/core/activitypub/ApMfmService.ts b/packages/backend/src/core/activitypub/ApMfmService.ts index 8775eba5768c..60868627a2c0 100644 --- a/packages/backend/src/core/activitypub/ApMfmService.ts +++ b/packages/backend/src/core/activitypub/ApMfmService.ts @@ -6,7 +6,7 @@ import { Injectable } from '@nestjs/common'; import * as mfm from 'mfm-js'; import { MfmService } from '@/core/MfmService.js'; -import type { Note } from '@/models/entities/Note.js'; +import type { MiNote } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; import { extractApHashtagObjects } from './models/tag.js'; import type { IObject } from './type.js'; @@ -25,7 +25,7 @@ export class ApMfmService { } @bindThis - public getNoteHtml(note: Note): string | null { + public getNoteHtml(note: MiNote): string | null { if (!note.text) return ''; return this.mfmService.toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers)); } diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index f31d5f84e549..7a9d2e21d8d6 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -9,21 +9,21 @@ import { In } from 'typeorm'; import * as mfm from 'mfm-js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; -import type { PartialLocalUser, LocalUser, PartialRemoteUser, RemoteUser, User } from '@/models/entities/User.js'; -import type { IMentionedRemoteUsers, Note } from '@/models/entities/Note.js'; -import type { Blocking } from '@/models/entities/Blocking.js'; -import type { Relay } from '@/models/entities/Relay.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; -import type { NoteReaction } from '@/models/entities/NoteReaction.js'; -import type { Emoji } from '@/models/entities/Emoji.js'; -import type { Poll } from '@/models/entities/Poll.js'; -import type { PollVote } from '@/models/entities/PollVote.js'; +import type { MiPartialLocalUser, MiLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js'; +import type { IMentionedRemoteUsers, MiNote } from '@/models/Note.js'; +import type { MiBlocking } from '@/models/Blocking.js'; +import type { MiRelay } from '@/models/Relay.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; +import type { MiNoteReaction } from '@/models/NoteReaction.js'; +import type { MiEmoji } from '@/models/Emoji.js'; +import type { MiPoll } from '@/models/Poll.js'; +import type { MiPollVote } from '@/models/PollVote.js'; import { UserKeypairService } from '@/core/UserKeypairService.js'; import { MfmService } from '@/core/MfmService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; -import type { UserKeypair } from '@/models/entities/UserKeypair.js'; -import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository } from '@/models/index.js'; +import type { MiUserKeypair } from '@/models/UserKeypair.js'; +import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { isNotNull } from '@/misc/is-not-null.js'; @@ -63,7 +63,7 @@ export class ApRendererService { } @bindThis - public renderAccept(object: string | IObject, user: { id: User['id']; host: null }): IAccept { + public renderAccept(object: string | IObject, user: { id: MiUser['id']; host: null }): IAccept { return { type: 'Accept', actor: this.userEntityService.genLocalUserUri(user.id), @@ -72,7 +72,7 @@ export class ApRendererService { } @bindThis - public renderAdd(user: LocalUser, target: string | IObject | undefined, object: string | IObject): IAdd { + public renderAdd(user: MiLocalUser, target: string | IObject | undefined, object: string | IObject): IAdd { return { type: 'Add', actor: this.userEntityService.genLocalUserUri(user.id), @@ -82,7 +82,7 @@ export class ApRendererService { } @bindThis - public renderAnnounce(object: string | IObject, note: Note): IAnnounce { + public renderAnnounce(object: string | IObject, note: MiNote): IAnnounce { const attributedTo = this.userEntityService.genLocalUserUri(note.userId); let to: string[] = []; @@ -118,7 +118,7 @@ export class ApRendererService { * @param block The block to be rendered. The blockee relation must be loaded. */ @bindThis - public renderBlock(block: Blocking): IBlock { + public renderBlock(block: MiBlocking): IBlock { if (block.blockee?.uri == null) { throw new Error('renderBlock: missing blockee uri'); } @@ -132,7 +132,7 @@ export class ApRendererService { } @bindThis - public renderCreate(object: IObject, note: Note): ICreate { + public renderCreate(object: IObject, note: MiNote): ICreate { const activity: ICreate = { id: `${this.config.url}/notes/${note.id}/activity`, actor: this.userEntityService.genLocalUserUri(note.userId), @@ -148,7 +148,7 @@ export class ApRendererService { } @bindThis - public renderDelete(object: IObject | string, user: { id: User['id']; host: null }): IDelete { + public renderDelete(object: IObject | string, user: { id: MiUser['id']; host: null }): IDelete { return { type: 'Delete', actor: this.userEntityService.genLocalUserUri(user.id), @@ -158,7 +158,7 @@ export class ApRendererService { } @bindThis - public renderDocument(file: DriveFile): IApDocument { + public renderDocument(file: MiDriveFile): IApDocument { return { type: 'Document', mediaType: file.webpublicType ?? file.type, @@ -168,7 +168,7 @@ export class ApRendererService { } @bindThis - public renderEmoji(emoji: Emoji): IApEmoji { + public renderEmoji(emoji: MiEmoji): IApEmoji { return { id: `${this.config.url}/emojis/${emoji.name}`, type: 'Emoji', @@ -185,7 +185,7 @@ export class ApRendererService { // to anonymise reporters, the reporting actor must be a system user @bindThis - public renderFlag(user: LocalUser, object: IObject | string, content: string): IFlag { + public renderFlag(user: MiLocalUser, object: IObject | string, content: string): IFlag { return { type: 'Flag', actor: this.userEntityService.genLocalUserUri(user.id), @@ -195,7 +195,7 @@ export class ApRendererService { } @bindThis - public renderFollowRelay(relay: Relay, relayActor: LocalUser): IFollow { + public renderFollowRelay(relay: MiRelay, relayActor: MiLocalUser): IFollow { return { id: `${this.config.url}/activities/follow-relay/${relay.id}`, type: 'Follow', @@ -209,15 +209,15 @@ export class ApRendererService { * @param id Follower|Followee ID */ @bindThis - public async renderFollowUser(id: User['id']): Promise { - const user = await this.usersRepository.findOneByOrFail({ id: id }) as PartialLocalUser | PartialRemoteUser; + public async renderFollowUser(id: MiUser['id']): Promise { + const user = await this.usersRepository.findOneByOrFail({ id: id }) as MiPartialLocalUser | MiPartialRemoteUser; return this.userEntityService.getUserUri(user); } @bindThis public renderFollow( - follower: PartialLocalUser | PartialRemoteUser, - followee: PartialLocalUser | PartialRemoteUser, + follower: MiPartialLocalUser | MiPartialRemoteUser, + followee: MiPartialLocalUser | MiPartialRemoteUser, requestId?: string, ): IFollow { return { @@ -238,7 +238,7 @@ export class ApRendererService { } @bindThis - public renderImage(file: DriveFile): IApImage { + public renderImage(file: MiDriveFile): IApImage { return { type: 'Image', url: this.driveFileEntityService.getPublicUrl(file), @@ -248,7 +248,7 @@ export class ApRendererService { } @bindThis - public renderKey(user: LocalUser, key: UserKeypair, postfix?: string): IKey { + public renderKey(user: MiLocalUser, key: MiUserKeypair, postfix?: string): IKey { return { id: `${this.config.url}/users/${user.id}${postfix ?? '/publickey'}`, type: 'Key', @@ -261,7 +261,7 @@ export class ApRendererService { } @bindThis - public async renderLike(noteReaction: NoteReaction, note: { uri: string | null }): Promise { + public async renderLike(noteReaction: MiNoteReaction, note: { uri: string | null }): Promise { const reaction = noteReaction.reaction; const object: ILike = { @@ -284,18 +284,18 @@ export class ApRendererService { } @bindThis - public renderMention(mention: PartialLocalUser | PartialRemoteUser): IApMention { + public renderMention(mention: MiPartialLocalUser | MiPartialRemoteUser): IApMention { return { type: 'Mention', href: this.userEntityService.getUserUri(mention), - name: this.userEntityService.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as LocalUser).username}`, + name: this.userEntityService.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as MiLocalUser).username}`, }; } @bindThis public renderMove( - src: PartialLocalUser | PartialRemoteUser, - dst: PartialLocalUser | PartialRemoteUser, + src: MiPartialLocalUser | MiPartialRemoteUser, + dst: MiPartialLocalUser | MiPartialRemoteUser, ): IMove { const actor = this.userEntityService.getUserUri(src); const target = this.userEntityService.getUserUri(dst); @@ -309,15 +309,15 @@ export class ApRendererService { } @bindThis - public async renderNote(note: Note, dive = true): Promise { - const getPromisedFiles = async (ids: string[]): Promise => { + public async renderNote(note: MiNote, dive = true): Promise { + const getPromisedFiles = async (ids: string[]): Promise => { if (ids.length === 0) return []; const items = await this.driveFilesRepository.findBy({ id: In(ids) }); - return ids.map(id => items.find(item => item.id === id)).filter((item): item is DriveFile => item != null); + return ids.map(id => items.find(item => item.id === id)).filter((item): item is MiDriveFile => item != null); }; let inReplyTo; - let inReplyToNote: Note | null; + let inReplyToNote: MiNote | null; if (note.replyId) { inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId }); @@ -376,12 +376,12 @@ export class ApRendererService { }) : []; const hashtagTags = note.tags.map(tag => this.renderHashtag(tag)); - const mentionTags = mentionedUsers.map(u => this.renderMention(u as LocalUser | RemoteUser)); + const mentionTags = mentionedUsers.map(u => this.renderMention(u as MiLocalUser | MiRemoteUser)); const files = await getPromisedFiles(note.fileIds); const text = note.text ?? ''; - let poll: Poll | null = null; + let poll: MiPoll | null = null; if (note.hasPoll) { poll = await this.pollsRepository.findOneBy({ noteId: note.id }); @@ -449,7 +449,7 @@ export class ApRendererService { } @bindThis - public async renderPerson(user: LocalUser) { + public async renderPerson(user: MiLocalUser) { const id = this.userEntityService.genLocalUserUri(user.id); const isSystem = user.username.includes('.'); @@ -523,7 +523,7 @@ export class ApRendererService { } @bindThis - public renderQuestion(user: { id: User['id'] }, note: Note, poll: Poll): IQuestion { + public renderQuestion(user: { id: MiUser['id'] }, note: MiNote, poll: MiPoll): IQuestion { return { type: 'Question', id: `${this.config.url}/questions/${note.id}`, @@ -541,7 +541,7 @@ export class ApRendererService { } @bindThis - public renderReject(object: string | IObject, user: { id: User['id'] }): IReject { + public renderReject(object: string | IObject, user: { id: MiUser['id'] }): IReject { return { type: 'Reject', actor: this.userEntityService.genLocalUserUri(user.id), @@ -550,7 +550,7 @@ export class ApRendererService { } @bindThis - public renderRemove(user: { id: User['id'] }, target: string | IObject | undefined, object: string | IObject): IRemove { + public renderRemove(user: { id: MiUser['id'] }, target: string | IObject | undefined, object: string | IObject): IRemove { return { type: 'Remove', actor: this.userEntityService.genLocalUserUri(user.id), @@ -568,7 +568,7 @@ export class ApRendererService { } @bindThis - public renderUndo(object: string | IObject, user: { id: User['id'] }): IUndo { + public renderUndo(object: string | IObject, user: { id: MiUser['id'] }): IUndo { const id = typeof object !== 'string' && typeof object.id === 'string' && object.id.startsWith(this.config.url) ? `${object.id}/undo` : undefined; return { @@ -581,7 +581,7 @@ export class ApRendererService { } @bindThis - public renderUpdate(object: string | IObject, user: { id: User['id'] }): IUpdate { + public renderUpdate(object: string | IObject, user: { id: MiUser['id'] }): IUpdate { return { id: `${this.config.url}/users/${user.id}#updates/${new Date().getTime()}`, actor: this.userEntityService.genLocalUserUri(user.id), @@ -593,7 +593,7 @@ export class ApRendererService { } @bindThis - public renderVote(user: { id: User['id'] }, vote: PollVote, note: Note, poll: Poll, pollOwner: RemoteUser): ICreate { + public renderVote(user: { id: MiUser['id'] }, vote: MiPollVote, note: MiNote, poll: MiPoll, pollOwner: MiRemoteUser): ICreate { return { id: `${this.config.url}/users/${user.id}#votes/${vote.id}/activity`, actor: this.userEntityService.genLocalUserUri(user.id), @@ -651,7 +651,7 @@ export class ApRendererService { } @bindThis - public async attachLdSignature(activity: any, user: { id: User['id']; host: null; }): Promise { + public async attachLdSignature(activity: any, user: { id: MiUser['id']; host: null; }): Promise { const keypair = await this.userKeypairService.getUserKeypair(user.id); const ldSignature = this.ldSignatureService.use(); @@ -710,7 +710,7 @@ export class ApRendererService { } @bindThis - private async getEmojis(names: string[]): Promise { + private async getEmojis(names: string[]): Promise { if (names.length === 0) return []; const allEmojis = await this.customEmojiService.localEmojisCache.fetch(); diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts index b2d98f25e02a..b59ce5241fb1 100644 --- a/packages/backend/src/core/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/activitypub/ApRequestService.ts @@ -8,7 +8,7 @@ import { URL } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; -import type { User } from '@/models/entities/User.js'; +import type { MiUser } from '@/models/User.js'; import { UserKeypairService } from '@/core/UserKeypairService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { LoggerService } from '@/core/LoggerService.js'; @@ -145,7 +145,7 @@ export class ApRequestService { } @bindThis - public async signedPost(user: { id: User['id'] }, url: string, object: unknown): Promise { + public async signedPost(user: { id: MiUser['id'] }, url: string, object: unknown): Promise { const body = JSON.stringify(object); const keypair = await this.userKeypairService.getUserKeypair(user.id); @@ -174,7 +174,7 @@ export class ApRequestService { * @param url URL to fetch */ @bindThis - public async signedGet(url: string, user: { id: User['id'] }): Promise { + public async signedGet(url: string, user: { id: MiUser['id'] }): Promise { const keypair = await this.userKeypairService.getUserKeypair(user.id); const req = ApRequestCreator.createSignedGet({ diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts index 31bea744e2b2..058869ca77ae 100644 --- a/packages/backend/src/core/activitypub/ApResolverService.ts +++ b/packages/backend/src/core/activitypub/ApResolverService.ts @@ -4,9 +4,9 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { LocalUser, RemoteUser } from '@/models/entities/User.js'; +import type { MiLocalUser, MiRemoteUser } from '@/models/User.js'; import { InstanceActorService } from '@/core/InstanceActorService.js'; -import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js'; +import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; @@ -23,7 +23,7 @@ import type { IObject, ICollection, IOrderedCollection } from './type.js'; export class Resolver { private history: Set; - private user?: LocalUser; + private user?: MiLocalUser; private logger: Logger; constructor( @@ -134,7 +134,7 @@ export class Resolver { }); case 'users': return this.usersRepository.findOneByOrFail({ id: parsed.id }) - .then(user => this.apRendererService.renderPerson(user as LocalUser)); + .then(user => this.apRendererService.renderPerson(user as MiLocalUser)); case 'questions': // Polls are indexed by the note they are attached to. return Promise.all([ @@ -152,7 +152,7 @@ export class Resolver { return Promise.all( [parsed.id, parsed.rest].map(id => this.usersRepository.findOneByOrFail({ id })), ) - .then(([follower, followee]) => this.apRendererService.addContext(this.apRendererService.renderFollow(follower as LocalUser | RemoteUser, followee as LocalUser | RemoteUser, url))); + .then(([follower, followee]) => this.apRendererService.addContext(this.apRendererService.renderFollow(follower as MiLocalUser | MiRemoteUser, followee as MiLocalUser | MiRemoteUser, url))); default: throw new Error(`resolveLocal: type ${parsed.type} unhandled`); } diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts index cc4a352f0271..a4cd5338926b 100644 --- a/packages/backend/src/core/activitypub/models/ApImageService.ts +++ b/packages/backend/src/core/activitypub/models/ApImageService.ts @@ -5,9 +5,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository } from '@/models/index.js'; -import type { RemoteUser } from '@/models/entities/User.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; +import type { DriveFilesRepository } from '@/models/_.js'; +import type { MiRemoteUser } from '@/models/User.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; import { MetaService } from '@/core/MetaService.js'; import { truncate } from '@/misc/truncate.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js'; @@ -39,7 +39,7 @@ export class ApImageService { * Imageを作成します。 */ @bindThis - public async createImage(actor: RemoteUser, value: string | IObject): Promise { + public async createImage(actor: MiRemoteUser, value: string | IObject): Promise { // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { throw new Error('actor has been suspended'); @@ -90,7 +90,7 @@ export class ApImageService { * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ @bindThis - public async resolveImage(actor: RemoteUser, value: string | IObject): Promise { + public async resolveImage(actor: MiRemoteUser, value: string | IObject): Promise { // TODO // リモートサーバーからフェッチしてきて登録 diff --git a/packages/backend/src/core/activitypub/models/ApMentionService.ts b/packages/backend/src/core/activitypub/models/ApMentionService.ts index ead2b38c22bf..9aa8ba5ede93 100644 --- a/packages/backend/src/core/activitypub/models/ApMentionService.ts +++ b/packages/backend/src/core/activitypub/models/ApMentionService.ts @@ -5,7 +5,7 @@ import { Injectable } from '@nestjs/common'; import promiseLimit from 'promise-limit'; -import type { User } from '@/models/index.js'; +import type { MiUser } from '@/models/_.js'; import { toArray, unique } from '@/misc/prelude/array.js'; import { bindThis } from '@/decorators.js'; import { isMention } from '../type.js'; @@ -21,13 +21,13 @@ export class ApMentionService { } @bindThis - public async extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver): Promise { + public async extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver): Promise { const hrefs = unique(this.extractApMentionObjects(tags).map(x => x.href)); - const limit = promiseLimit(2); + const limit = promiseLimit(2); const mentionedUsers = (await Promise.all( hrefs.map(x => limit(() => this.apPersonService.resolvePerson(x, resolver).catch(() => null))), - )).filter((x): x is User => x != null); + )).filter((x): x is MiUser => x != null); return mentionedUsers; } diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index 41d1bc48a7ca..573dff5b9107 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -7,15 +7,15 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; import promiseLimit from 'promise-limit'; import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { PollsRepository, EmojisRepository } from '@/models/index.js'; +import type { PollsRepository, EmojisRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; -import type { RemoteUser } from '@/models/entities/User.js'; -import type { Note } from '@/models/entities/Note.js'; +import type { MiRemoteUser } from '@/models/User.js'; +import type { MiNote } from '@/models/Note.js'; import { toArray, toSingle, unique } from '@/misc/prelude/array.js'; -import type { Emoji } from '@/models/entities/Emoji.js'; +import type { MiEmoji } from '@/models/Emoji.js'; import { MetaService } from '@/core/MetaService.js'; import { AppLockService } from '@/core/AppLockService.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; import { NoteCreateService } from '@/core/NoteCreateService.js'; import type Logger from '@/logger.js'; import { IdService } from '@/core/IdService.js'; @@ -101,7 +101,7 @@ export class ApNoteService { * Misskeyに対象のNoteが登録されていればそれを返します。 */ @bindThis - public async fetchNote(object: string | IObject): Promise { + public async fetchNote(object: string | IObject): Promise { return await this.apDbResolverService.getNoteFromApId(object); } @@ -109,7 +109,7 @@ export class ApNoteService { * Noteを作成します。 */ @bindThis - public async createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise { + public async createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise { // eslint-disable-next-line no-param-reassign if (resolver == null) resolver = this.apResolverService.createResolver(); @@ -147,7 +147,7 @@ export class ApNoteService { throw new Error('invalid note.attributedTo: ' + note.attributedTo); } - const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo), resolver) as RemoteUser; + const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo), resolver) as MiRemoteUser; // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { @@ -172,7 +172,7 @@ export class ApNoteService { // 添付ファイル // TODO: attachmentは必ずしもImageではない // TODO: attachmentは必ずしも配列ではない - const limit = promiseLimit(2); + const limit = promiseLimit(2); const files = (await Promise.all(toArray(note.attachment).map(attach => ( limit(() => this.apImageService.resolveImage(actor, { ...attach, @@ -181,7 +181,7 @@ export class ApNoteService { )))); // リプライ - const reply: Note | null = note.inReplyTo + const reply: MiNote | null = note.inReplyTo ? await this.resolveNote(note.inReplyTo, { resolver }) .then(x => { if (x == null) { @@ -198,11 +198,11 @@ export class ApNoteService { : null; // 引用 - let quote: Note | undefined | null = null; + let quote: MiNote | undefined | null = null; if (note._misskey_quote ?? note.quoteUrl) { const tryResolveNote = async (uri: string): Promise< - | { status: 'ok'; res: Note } + | { status: 'ok'; res: MiNote } | { status: 'permerror' | 'temperror' } > => { if (!/^https?:/.test(uri)) return { status: 'permerror' }; @@ -220,7 +220,7 @@ export class ApNoteService { const uris = unique([note._misskey_quote, note.quoteUrl].filter((x): x is string => typeof x === 'string')); const results = await Promise.all(uris.map(tryResolveNote)); - quote = results.filter((x): x is { status: 'ok', res: Note } => x.status === 'ok').map(x => x.res).at(0); + quote = results.filter((x): x is { status: 'ok', res: MiNote } => x.status === 'ok').map(x => x.res).at(0); if (!quote) { if (results.some(x => x.status === 'temperror')) { throw new Error('quote resolve failed'); @@ -310,7 +310,7 @@ export class ApNoteService { * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ @bindThis - public async resolveNote(value: string | IObject, options: { sentFrom?: URL, resolver?: Resolver } = {}): Promise { + public async resolveNote(value: string | IObject, options: { sentFrom?: URL, resolver?: Resolver } = {}): Promise { const uri = getApId(value); // ブロックしていたら中断 @@ -342,7 +342,7 @@ export class ApNoteService { } @bindThis - public async extractEmojis(tags: IObject | IObject[], host: string): Promise { + public async extractEmojis(tags: IObject | IObject[], host: string): Promise { // eslint-disable-next-line no-param-reassign host = this.utilityService.toPuny(host); diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index fda03e73a065..ea6488339576 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -8,28 +8,28 @@ import promiseLimit from 'promise-limit'; import { DataSource } from 'typeorm'; import { ModuleRef } from '@nestjs/core'; import { DI } from '@/di-symbols.js'; -import type { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; +import type { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; -import type { LocalUser, RemoteUser } from '@/models/entities/User.js'; -import { User } from '@/models/entities/User.js'; +import type { MiLocalUser, MiRemoteUser } from '@/models/User.js'; +import { MiUser } from '@/models/User.js'; import { truncate } from '@/misc/truncate.js'; import type { CacheService } from '@/core/CacheService.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; import type Logger from '@/logger.js'; -import type { Note } from '@/models/entities/Note.js'; +import type { MiNote } from '@/models/Note.js'; import type { IdService } from '@/core/IdService.js'; import type { MfmService } from '@/core/MfmService.js'; import { toArray } from '@/misc/prelude/array.js'; import type { GlobalEventService } from '@/core/GlobalEventService.js'; import type { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import type { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; -import { UserProfile } from '@/models/entities/UserProfile.js'; -import { UserPublickey } from '@/models/entities/UserPublickey.js'; +import { MiUserProfile } from '@/models/UserProfile.js'; +import { MiUserPublickey } from '@/models/UserPublickey.js'; import type UsersChart from '@/core/chart/charts/users.js'; import type InstanceChart from '@/core/chart/charts/instance.js'; import type { HashtagService } from '@/core/HashtagService.js'; -import { UserNotePining } from '@/models/entities/UserNotePining.js'; +import { MiUserNotePining } from '@/models/UserNotePining.js'; import { StatusError } from '@/misc/status-error.js'; import type { UtilityService } from '@/core/UtilityService.js'; import type { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -201,20 +201,20 @@ export class ApPersonService implements OnModuleInit { * Misskeyに対象のPersonが登録されていればそれを返し、登録がなければnullを返します。 */ @bindThis - public async fetchPerson(uri: string): Promise { - const cached = this.cacheService.uriPersonCache.get(uri) as LocalUser | RemoteUser | null | undefined; + public async fetchPerson(uri: string): Promise { + const cached = this.cacheService.uriPersonCache.get(uri) as MiLocalUser | MiRemoteUser | null | undefined; if (cached) return cached; // URIがこのサーバーを指しているならデータベースからフェッチ if (uri.startsWith(`${this.config.url}/`)) { const id = uri.split('/').pop(); - const u = await this.usersRepository.findOneBy({ id }) as LocalUser | null; + const u = await this.usersRepository.findOneBy({ id }) as MiLocalUser | null; if (u) this.cacheService.uriPersonCache.set(uri, u); return u; } //#region このサーバーに既に登録されていたらそれを返す - const exist = await this.usersRepository.findOneBy({ uri }) as LocalUser | RemoteUser | null; + const exist = await this.usersRepository.findOneBy({ uri }) as MiLocalUser | MiRemoteUser | null; if (exist) { this.cacheService.uriPersonCache.set(uri, exist); @@ -225,7 +225,7 @@ export class ApPersonService implements OnModuleInit { return null; } - private async resolveAvatarAndBanner(user: RemoteUser, icon: any, image: any): Promise> { + private async resolveAvatarAndBanner(user: MiRemoteUser, icon: any, image: any): Promise> { const [avatar, banner] = await Promise.all([icon, image].map(img => { if (img == null) return null; if (user == null) throw new Error('failed to create user: user is null'); @@ -246,7 +246,7 @@ export class ApPersonService implements OnModuleInit { * Personを作成します。 */ @bindThis - public async createPerson(uri: string, resolver?: Resolver): Promise { + public async createPerson(uri: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); if (uri.startsWith(this.config.url)) { @@ -280,7 +280,7 @@ export class ApPersonService implements OnModuleInit { } // Create user - let user: RemoteUser | null = null; + let user: MiRemoteUser | null = null; //#region カスタム絵文字取得 const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host) @@ -294,7 +294,7 @@ export class ApPersonService implements OnModuleInit { try { // Start transaction await this.db.transaction(async transactionalEntityManager => { - user = await transactionalEntityManager.save(new User({ + user = await transactionalEntityManager.save(new MiUser({ id: this.idService.genId(), avatarId: null, bannerId: null, @@ -318,9 +318,9 @@ export class ApPersonService implements OnModuleInit { isBot, isCat: (person as any).isCat === true, emojis, - })) as RemoteUser; + })) as MiRemoteUser; - await transactionalEntityManager.save(new UserProfile({ + await transactionalEntityManager.save(new MiUserProfile({ userId: user.id, description: person.summary ? this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null, url, @@ -331,7 +331,7 @@ export class ApPersonService implements OnModuleInit { })); if (person.publicKey) { - await transactionalEntityManager.save(new UserPublickey({ + await transactionalEntityManager.save(new MiUserPublickey({ userId: user.id, keyId: person.publicKey.id, keyPem: person.publicKey.publicKeyPem, @@ -345,7 +345,7 @@ export class ApPersonService implements OnModuleInit { const u = await this.usersRepository.findOneBy({ uri: person.id }); if (u == null) throw new Error('already registered'); - user = u as RemoteUser; + user = u as MiRemoteUser; } else { this.logger.error(e instanceof Error ? e : new Error(e as string)); throw e; @@ -407,7 +407,7 @@ export class ApPersonService implements OnModuleInit { if (uri.startsWith(`${this.config.url}/`)) return; //#region このサーバーに既に登録されているか - const exist = await this.fetchPerson(uri) as RemoteUser | null; + const exist = await this.fetchPerson(uri) as MiRemoteUser | null; if (exist === null) return; //#endregion @@ -456,7 +456,7 @@ export class ApPersonService implements OnModuleInit { alsoKnownAs: person.alsoKnownAs ?? null, isExplorable: person.discoverable, ...(await this.resolveAvatarAndBanner(exist, person.icon, person.image).catch(() => ({}))), - } as Partial & Pick; + } as Partial & Pick; const moving = ((): boolean => { // 移行先がない→ある @@ -542,7 +542,7 @@ export class ApPersonService implements OnModuleInit { * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ @bindThis - public async resolvePerson(uri: string, resolver?: Resolver): Promise { + public async resolvePerson(uri: string, resolver?: Resolver): Promise { //#region このサーバーに既に登録されていたらそれを返す const exist = await this.fetchPerson(uri); if (exist) return exist; @@ -572,7 +572,7 @@ export class ApPersonService implements OnModuleInit { } @bindThis - public async updateFeatured(userId: User['id'], resolver?: Resolver): Promise { + public async updateFeatured(userId: MiUser['id'], resolver?: Resolver): Promise { const user = await this.usersRepository.findOneByOrFail({ id: userId }); if (!this.userEntityService.isRemoteUser(user)) return; if (!user.featured) return; @@ -590,7 +590,7 @@ export class ApPersonService implements OnModuleInit { const items = await Promise.all(toArray(unresolvedItems).map(x => _resolver.resolve(x))); // Resolve and regist Notes - const limit = promiseLimit(2); + const limit = promiseLimit(2); const featuredNotes = await Promise.all(items .filter(item => getApType(item) === 'Note') // TODO: Noteでなくてもいいかも .slice(0, 5) @@ -600,13 +600,13 @@ export class ApPersonService implements OnModuleInit { })))); await this.db.transaction(async transactionalEntityManager => { - await transactionalEntityManager.delete(UserNotePining, { userId: user.id }); + await transactionalEntityManager.delete(MiUserNotePining, { userId: user.id }); // とりあえずidを別の時間で生成して順番を維持 let td = 0; - for (const note of featuredNotes.filter((note): note is Note => note != null)) { + for (const note of featuredNotes.filter((note): note is MiNote => note != null)) { td -= 1000; - transactionalEntityManager.insert(UserNotePining, { + transactionalEntityManager.insert(MiUserNotePining, { id: this.idService.genId(new Date(Date.now() + td)), createdAt: new Date(), userId: user.id, @@ -622,7 +622,7 @@ export class ApPersonService implements OnModuleInit { * @param movePreventUris ここに列挙されたURIにsrc.movedToUriが含まれる場合、移行処理はしない(無限ループ防止) */ @bindThis - private async processRemoteMove(src: RemoteUser, movePreventUris: string[] = []): Promise { + private async processRemoteMove(src: MiRemoteUser, movePreventUris: string[] = []): Promise { if (!src.movedToUri) return 'skip: no movedToUri'; if (src.uri === src.movedToUri) return 'skip: movedTo itself (src)'; // ??? if (movePreventUris.length > 10) return 'skip: too many moves'; @@ -632,7 +632,7 @@ export class ApPersonService implements OnModuleInit { if (dst && this.userEntityService.isLocalUser(dst)) { // targetがローカルユーザーだった場合データベースから引っ張ってくる - dst = await this.usersRepository.findOneByOrFail({ uri: src.movedToUri }) as LocalUser; + dst = await this.usersRepository.findOneByOrFail({ uri: src.movedToUri }) as MiLocalUser; } else if (dst) { if (movePreventUris.includes(src.movedToUri)) return 'skip: circular move'; diff --git a/packages/backend/src/core/activitypub/models/ApQuestionService.ts b/packages/backend/src/core/activitypub/models/ApQuestionService.ts index 8d8c0a581137..27bd62268b3c 100644 --- a/packages/backend/src/core/activitypub/models/ApQuestionService.ts +++ b/packages/backend/src/core/activitypub/models/ApQuestionService.ts @@ -5,9 +5,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { NotesRepository, PollsRepository } from '@/models/index.js'; +import type { NotesRepository, PollsRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; -import type { IPoll } from '@/models/entities/Poll.js'; +import type { IPoll } from '@/models/Poll.js'; import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import { isQuestion } from '../type.js'; diff --git a/packages/backend/src/core/chart/ChartManagementService.ts b/packages/backend/src/core/chart/ChartManagementService.ts index 6b626d398038..f751a68cb41e 100644 --- a/packages/backend/src/core/chart/ChartManagementService.ts +++ b/packages/backend/src/core/chart/ChartManagementService.ts @@ -23,7 +23,7 @@ import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() export class ChartManagementService implements OnApplicationShutdown { private charts; - private saveIntervalId: NodeJS.Timer; + private saveIntervalId: NodeJS.Timeout; constructor( private federationChart: FederationChart, diff --git a/packages/backend/src/core/chart/charts/active-users.ts b/packages/backend/src/core/chart/charts/active-users.ts index 4b2ded9e82d7..55da1469e57c 100644 --- a/packages/backend/src/core/chart/charts/active-users.ts +++ b/packages/backend/src/core/chart/charts/active-users.ts @@ -6,7 +6,7 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { AppLockService } from '@/core/AppLockService.js'; -import type { User } from '@/models/entities/User.js'; +import type { MiUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; @@ -21,9 +21,8 @@ const year = 1000 * 60 * 60 * 24 * 365; /** * アクティブユーザーに関するチャート */ -// eslint-disable-next-line import/no-default-export @Injectable() -export default class ActiveUsersChart extends Chart { +export default class ActiveUsersChart extends Chart { // eslint-disable-line import/no-default-export constructor( @Inject(DI.db) private db: DataSource, @@ -43,7 +42,7 @@ export default class ActiveUsersChart extends Chart { } @bindThis - public async read(user: { id: User['id'], host: null, createdAt: User['createdAt'] }): Promise { + public async read(user: { id: MiUser['id'], host: null, createdAt: MiUser['createdAt'] }): Promise { await this.commit({ 'read': [user.id], 'registeredWithinWeek': (Date.now() - user.createdAt.getTime() < week) ? [user.id] : [], @@ -56,7 +55,7 @@ export default class ActiveUsersChart extends Chart { } @bindThis - public async write(user: { id: User['id'], host: null, createdAt: User['createdAt'] }): Promise { + public async write(user: { id: MiUser['id'], host: null, createdAt: MiUser['createdAt'] }): Promise { await this.commit({ 'write': [user.id], }); diff --git a/packages/backend/src/core/chart/charts/ap-request.ts b/packages/backend/src/core/chart/charts/ap-request.ts index 8bcbd97b4868..03c9b42be17c 100644 --- a/packages/backend/src/core/chart/charts/ap-request.ts +++ b/packages/backend/src/core/chart/charts/ap-request.ts @@ -16,9 +16,8 @@ import type { KVs } from '../core.js'; /** * Chart about ActivityPub requests */ -// eslint-disable-next-line import/no-default-export @Injectable() -export default class ApRequestChart extends Chart { +export default class ApRequestChart extends Chart { // eslint-disable-line import/no-default-export constructor( @Inject(DI.db) private db: DataSource, diff --git a/packages/backend/src/core/chart/charts/drive.ts b/packages/backend/src/core/chart/charts/drive.ts index eff541667019..bbcbf1a95589 100644 --- a/packages/backend/src/core/chart/charts/drive.ts +++ b/packages/backend/src/core/chart/charts/drive.ts @@ -5,7 +5,7 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; @@ -17,9 +17,8 @@ import type { KVs } from '../core.js'; /** * ドライブに関するチャート */ -// eslint-disable-next-line import/no-default-export @Injectable() -export default class DriveChart extends Chart { +export default class DriveChart extends Chart { // eslint-disable-line import/no-default-export constructor( @Inject(DI.db) private db: DataSource, @@ -39,7 +38,7 @@ export default class DriveChart extends Chart { } @bindThis - public async update(file: DriveFile, isAdditional: boolean): Promise { + public async update(file: MiDriveFile, isAdditional: boolean): Promise { const fileSizeKb = file.size / 1000; await this.commit(file.userHost === null ? { 'local.incCount': isAdditional ? 1 : 0, diff --git a/packages/backend/src/core/chart/charts/federation.ts b/packages/backend/src/core/chart/charts/federation.ts index 6d42dab9b4bd..fc474b002b22 100644 --- a/packages/backend/src/core/chart/charts/federation.ts +++ b/packages/backend/src/core/chart/charts/federation.ts @@ -5,7 +5,7 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import type { FollowingsRepository, InstancesRepository } from '@/models/index.js'; +import type { FollowingsRepository, InstancesRepository } from '@/models/_.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { MetaService } from '@/core/MetaService.js'; @@ -18,9 +18,8 @@ import type { KVs } from '../core.js'; /** * フェデレーションに関するチャート */ -// eslint-disable-next-line import/no-default-export @Injectable() -export default class FederationChart extends Chart { +export default class FederationChart extends Chart { // eslint-disable-line import/no-default-export constructor( @Inject(DI.db) private db: DataSource, diff --git a/packages/backend/src/core/chart/charts/instance.ts b/packages/backend/src/core/chart/charts/instance.ts index 08592855dc28..9df0afb02e5e 100644 --- a/packages/backend/src/core/chart/charts/instance.ts +++ b/packages/backend/src/core/chart/charts/instance.ts @@ -5,9 +5,9 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import type { DriveFilesRepository, FollowingsRepository, UsersRepository, NotesRepository } from '@/models/index.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; -import type { Note } from '@/models/entities/Note.js'; +import type { DriveFilesRepository, FollowingsRepository, UsersRepository, NotesRepository } from '@/models/_.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; +import type { MiNote } from '@/models/Note.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UtilityService } from '@/core/UtilityService.js'; @@ -20,9 +20,8 @@ import type { KVs } from '../core.js'; /** * インスタンスごとのチャート */ -// eslint-disable-next-line import/no-default-export @Injectable() -export default class InstanceChart extends Chart { +export default class InstanceChart extends Chart { // eslint-disable-line import/no-default-export constructor( @Inject(DI.db) private db: DataSource, @@ -98,7 +97,7 @@ export default class InstanceChart extends Chart { } @bindThis - public async updateNote(host: string, note: Note, isAdditional: boolean): Promise { + public async updateNote(host: string, note: MiNote, isAdditional: boolean): Promise { await this.commit({ 'notes.total': isAdditional ? 1 : -1, 'notes.inc': isAdditional ? 1 : 0, @@ -129,7 +128,7 @@ export default class InstanceChart extends Chart { } @bindThis - public async updateDrive(file: DriveFile, isAdditional: boolean): Promise { + public async updateDrive(file: MiDriveFile, isAdditional: boolean): Promise { const fileSizeKb = file.size / 1000; await this.commit({ 'drive.totalFiles': isAdditional ? 1 : -1, diff --git a/packages/backend/src/core/chart/charts/notes.ts b/packages/backend/src/core/chart/charts/notes.ts index 5b18bcff5a4e..df3295dbac21 100644 --- a/packages/backend/src/core/chart/charts/notes.ts +++ b/packages/backend/src/core/chart/charts/notes.ts @@ -5,8 +5,8 @@ import { Injectable, Inject } from '@nestjs/common'; import { Not, IsNull, DataSource } from 'typeorm'; -import type { NotesRepository } from '@/models/index.js'; -import type { Note } from '@/models/entities/Note.js'; +import type { NotesRepository } from '@/models/_.js'; +import type { MiNote } from '@/models/Note.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; @@ -18,9 +18,8 @@ import type { KVs } from '../core.js'; /** * ノートに関するチャート */ -// eslint-disable-next-line import/no-default-export @Injectable() -export default class NotesChart extends Chart { +export default class NotesChart extends Chart { // eslint-disable-line import/no-default-export constructor( @Inject(DI.db) private db: DataSource, @@ -51,7 +50,7 @@ export default class NotesChart extends Chart { } @bindThis - public async update(note: Note, isAdditional: boolean): Promise { + public async update(note: MiNote, isAdditional: boolean): Promise { const prefix = note.userHost === null ? 'local' : 'remote'; await this.commit({ diff --git a/packages/backend/src/core/chart/charts/per-user-drive.ts b/packages/backend/src/core/chart/charts/per-user-drive.ts index a3ee4c2958b8..18354359c836 100644 --- a/packages/backend/src/core/chart/charts/per-user-drive.ts +++ b/packages/backend/src/core/chart/charts/per-user-drive.ts @@ -5,8 +5,8 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import type { DriveFilesRepository } from '@/models/index.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; +import type { DriveFilesRepository } from '@/models/_.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; @@ -19,9 +19,8 @@ import type { KVs } from '../core.js'; /** * ユーザーごとのドライブに関するチャート */ -// eslint-disable-next-line import/no-default-export @Injectable() -export default class PerUserDriveChart extends Chart { +export default class PerUserDriveChart extends Chart { // eslint-disable-line import/no-default-export constructor( @Inject(DI.db) private db: DataSource, @@ -53,7 +52,7 @@ export default class PerUserDriveChart extends Chart { } @bindThis - public async update(file: DriveFile, isAdditional: boolean): Promise { + public async update(file: MiDriveFile, isAdditional: boolean): Promise { const fileSizeKb = file.size / 1000; await this.commit({ 'totalCount': isAdditional ? 1 : -1, diff --git a/packages/backend/src/core/chart/charts/per-user-following.ts b/packages/backend/src/core/chart/charts/per-user-following.ts index ee629772b224..79bff2cb6689 100644 --- a/packages/backend/src/core/chart/charts/per-user-following.ts +++ b/packages/backend/src/core/chart/charts/per-user-following.ts @@ -5,11 +5,11 @@ import { Injectable, Inject } from '@nestjs/common'; import { Not, IsNull, DataSource } from 'typeorm'; -import type { User } from '@/models/entities/User.js'; +import type { MiUser } from '@/models/User.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import type { FollowingsRepository } from '@/models/index.js'; +import type { FollowingsRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; @@ -19,9 +19,8 @@ import type { KVs } from '../core.js'; /** * ユーザーごとのフォローに関するチャート */ -// eslint-disable-next-line import/no-default-export @Injectable() -export default class PerUserFollowingChart extends Chart { +export default class PerUserFollowingChart extends Chart { // eslint-disable-line import/no-default-export constructor( @Inject(DI.db) private db: DataSource, @@ -62,7 +61,7 @@ export default class PerUserFollowingChart extends Chart { } @bindThis - public async update(follower: { id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }, isFollow: boolean): Promise { + public async update(follower: { id: MiUser['id']; host: MiUser['host']; }, followee: { id: MiUser['id']; host: MiUser['host']; }, isFollow: boolean): Promise { const prefixFollower = this.userEntityService.isLocalUser(follower) ? 'local' : 'remote'; const prefixFollowee = this.userEntityService.isLocalUser(followee) ? 'local' : 'remote'; diff --git a/packages/backend/src/core/chart/charts/per-user-notes.ts b/packages/backend/src/core/chart/charts/per-user-notes.ts index 796fe29eac0e..0db0e6f07fc9 100644 --- a/packages/backend/src/core/chart/charts/per-user-notes.ts +++ b/packages/backend/src/core/chart/charts/per-user-notes.ts @@ -5,11 +5,11 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import type { User } from '@/models/entities/User.js'; -import type { Note } from '@/models/entities/Note.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiNote } from '@/models/Note.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; -import type { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; @@ -19,9 +19,8 @@ import type { KVs } from '../core.js'; /** * ユーザーごとのノートに関するチャート */ -// eslint-disable-next-line import/no-default-export @Injectable() -export default class PerUserNotesChart extends Chart { +export default class PerUserNotesChart extends Chart { // eslint-disable-line import/no-default-export constructor( @Inject(DI.db) private db: DataSource, @@ -50,7 +49,7 @@ export default class PerUserNotesChart extends Chart { } @bindThis - public update(user: { id: User['id'] }, note: Note, isAdditional: boolean): void { + public update(user: { id: MiUser['id'] }, note: MiNote, isAdditional: boolean): void { this.commit({ 'total': isAdditional ? 1 : -1, 'inc': isAdditional ? 1 : 0, diff --git a/packages/backend/src/core/chart/charts/per-user-pv.ts b/packages/backend/src/core/chart/charts/per-user-pv.ts index efab633fa21e..cf1b4c71f6ac 100644 --- a/packages/backend/src/core/chart/charts/per-user-pv.ts +++ b/packages/backend/src/core/chart/charts/per-user-pv.ts @@ -5,7 +5,7 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import type { User } from '@/models/entities/User.js'; +import type { MiUser } from '@/models/User.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; @@ -17,9 +17,8 @@ import type { KVs } from '../core.js'; /** * ユーザーごとのプロフィール被閲覧数に関するチャート */ -// eslint-disable-next-line import/no-default-export @Injectable() -export default class PerUserPvChart extends Chart { +export default class PerUserPvChart extends Chart { // eslint-disable-line import/no-default-export constructor( @Inject(DI.db) private db: DataSource, @@ -39,7 +38,7 @@ export default class PerUserPvChart extends Chart { } @bindThis - public async commitByUser(user: { id: User['id'] }, key: string): Promise { + public async commitByUser(user: { id: MiUser['id'] }, key: string): Promise { await this.commit({ 'upv.user': [key], 'pv.user': 1, @@ -47,7 +46,7 @@ export default class PerUserPvChart extends Chart { } @bindThis - public async commitByVisitor(user: { id: User['id'] }, key: string): Promise { + public async commitByVisitor(user: { id: MiUser['id'] }, key: string): Promise { await this.commit({ 'upv.visitor': [key], 'pv.visitor': 1, diff --git a/packages/backend/src/core/chart/charts/per-user-reactions.ts b/packages/backend/src/core/chart/charts/per-user-reactions.ts index b6e89f217722..9f4f6e965125 100644 --- a/packages/backend/src/core/chart/charts/per-user-reactions.ts +++ b/packages/backend/src/core/chart/charts/per-user-reactions.ts @@ -5,8 +5,8 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import type { User } from '@/models/entities/User.js'; -import type { Note } from '@/models/entities/Note.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiNote } from '@/models/Note.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -19,9 +19,8 @@ import type { KVs } from '../core.js'; /** * ユーザーごとのリアクションに関するチャート */ -// eslint-disable-next-line import/no-default-export @Injectable() -export default class PerUserReactionsChart extends Chart { +export default class PerUserReactionsChart extends Chart { // eslint-disable-line import/no-default-export constructor( @Inject(DI.db) private db: DataSource, @@ -42,7 +41,7 @@ export default class PerUserReactionsChart extends Chart { } @bindThis - public async update(user: { id: User['id'], host: User['host'] }, note: Note): Promise { + public async update(user: { id: MiUser['id'], host: MiUser['host'] }, note: MiNote): Promise { const prefix = this.userEntityService.isLocalUser(user) ? 'local' : 'remote'; this.commit({ [`${prefix}.count`]: 1, diff --git a/packages/backend/src/core/chart/charts/test-grouped.ts b/packages/backend/src/core/chart/charts/test-grouped.ts index 452f54330e51..00fb87223761 100644 --- a/packages/backend/src/core/chart/charts/test-grouped.ts +++ b/packages/backend/src/core/chart/charts/test-grouped.ts @@ -16,9 +16,8 @@ import type { KVs } from '../core.js'; /** * For testing */ -// eslint-disable-next-line import/no-default-export @Injectable() -export default class TestGroupedChart extends Chart { +export default class TestGroupedChart extends Chart { // eslint-disable-line import/no-default-export private total = {} as Record; constructor( diff --git a/packages/backend/src/core/chart/charts/test-intersection.ts b/packages/backend/src/core/chart/charts/test-intersection.ts index 0898f281281f..45a7e805c557 100644 --- a/packages/backend/src/core/chart/charts/test-intersection.ts +++ b/packages/backend/src/core/chart/charts/test-intersection.ts @@ -16,9 +16,8 @@ import type { KVs } from '../core.js'; /** * For testing */ -// eslint-disable-next-line import/no-default-export @Injectable() -export default class TestIntersectionChart extends Chart { +export default class TestIntersectionChart extends Chart { // eslint-disable-line import/no-default-export constructor( @Inject(DI.db) private db: DataSource, diff --git a/packages/backend/src/core/chart/charts/test-unique.ts b/packages/backend/src/core/chart/charts/test-unique.ts index 96cc171945d3..e9d38eaf136c 100644 --- a/packages/backend/src/core/chart/charts/test-unique.ts +++ b/packages/backend/src/core/chart/charts/test-unique.ts @@ -16,9 +16,8 @@ import type { KVs } from '../core.js'; /** * For testing */ -// eslint-disable-next-line import/no-default-export @Injectable() -export default class TestUniqueChart extends Chart { +export default class TestUniqueChart extends Chart { // eslint-disable-line import/no-default-export constructor( @Inject(DI.db) private db: DataSource, diff --git a/packages/backend/src/core/chart/charts/test.ts b/packages/backend/src/core/chart/charts/test.ts index ce2eeb0d5a5e..4dd6063b5bfb 100644 --- a/packages/backend/src/core/chart/charts/test.ts +++ b/packages/backend/src/core/chart/charts/test.ts @@ -16,9 +16,8 @@ import type { KVs } from '../core.js'; /** * For testing */ -// eslint-disable-next-line import/no-default-export @Injectable() -export default class TestChart extends Chart { +export default class TestChart extends Chart { // eslint-disable-line import/no-default-export public total = 0; // publicにするのはテストのため constructor( diff --git a/packages/backend/src/core/chart/charts/users.ts b/packages/backend/src/core/chart/charts/users.ts index 347b1d24f71a..c2026c2aeafb 100644 --- a/packages/backend/src/core/chart/charts/users.ts +++ b/packages/backend/src/core/chart/charts/users.ts @@ -5,11 +5,11 @@ import { Injectable, Inject } from '@nestjs/common'; import { Not, IsNull, DataSource } from 'typeorm'; -import type { User } from '@/models/entities/User.js'; +import type { MiUser } from '@/models/User.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import type { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; @@ -19,9 +19,8 @@ import type { KVs } from '../core.js'; /** * ユーザー数に関するチャート */ -// eslint-disable-next-line import/no-default-export @Injectable() -export default class UsersChart extends Chart { +export default class UsersChart extends Chart { // eslint-disable-line import/no-default-export constructor( @Inject(DI.db) private db: DataSource, @@ -53,7 +52,7 @@ export default class UsersChart extends Chart { } @bindThis - public async update(user: { id: User['id'], host: User['host'] }, isAdditional: boolean): Promise { + public async update(user: { id: MiUser['id'], host: MiUser['host'] }, isAdditional: boolean): Promise { const prefix = this.userEntityService.isLocalUser(user) ? 'local' : 'remote'; await this.commit({ diff --git a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts index 557174605c02..0e65a10d26bd 100644 --- a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts +++ b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts @@ -5,9 +5,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { AbuseUserReportsRepository } from '@/models/index.js'; +import type { AbuseUserReportsRepository } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; -import type { AbuseUserReport } from '@/models/entities/AbuseUserReport.js'; +import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js'; import { bindThis } from '@/decorators.js'; import { UserEntityService } from './UserEntityService.js'; @@ -23,7 +23,7 @@ export class AbuseUserReportEntityService { @bindThis public async pack( - src: AbuseUserReport['id'] | AbuseUserReport, + src: MiAbuseUserReport['id'] | MiAbuseUserReport, ) { const report = typeof src === 'object' ? src : await this.abuseUserReportsRepository.findOneByOrFail({ id: src }); diff --git a/packages/backend/src/core/entities/AntennaEntityService.ts b/packages/backend/src/core/entities/AntennaEntityService.ts index 662510603b9e..ed108f2ce5b3 100644 --- a/packages/backend/src/core/entities/AntennaEntityService.ts +++ b/packages/backend/src/core/entities/AntennaEntityService.ts @@ -5,9 +5,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { AntennasRepository } from '@/models/index.js'; +import type { AntennasRepository } from '@/models/_.js'; import type { Packed } from '@/misc/json-schema.js'; -import type { Antenna } from '@/models/entities/Antenna.js'; +import type { MiAntenna } from '@/models/Antenna.js'; import { bindThis } from '@/decorators.js'; @Injectable() @@ -20,7 +20,7 @@ export class AntennaEntityService { @bindThis public async pack( - src: Antenna['id'] | Antenna, + src: MiAntenna['id'] | MiAntenna, ): Promise> { const antenna = typeof src === 'object' ? src : await this.antennasRepository.findOneByOrFail({ id: src }); diff --git a/packages/backend/src/core/entities/AppEntityService.ts b/packages/backend/src/core/entities/AppEntityService.ts index 1a0c5e2c5695..14a93cda5ba0 100644 --- a/packages/backend/src/core/entities/AppEntityService.ts +++ b/packages/backend/src/core/entities/AppEntityService.ts @@ -5,10 +5,10 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { AccessTokensRepository, AppsRepository } from '@/models/index.js'; +import type { AccessTokensRepository, AppsRepository } from '@/models/_.js'; import type { Packed } from '@/misc/json-schema.js'; -import type { App } from '@/models/entities/App.js'; -import type { User } from '@/models/entities/User.js'; +import type { MiApp } from '@/models/App.js'; +import type { MiUser } from '@/models/User.js'; import { bindThis } from '@/decorators.js'; @Injectable() @@ -24,8 +24,8 @@ export class AppEntityService { @bindThis public async pack( - src: App['id'] | App, - me?: { id: User['id'] } | null | undefined, + src: MiApp['id'] | MiApp, + me?: { id: MiUser['id'] } | null | undefined, options?: { detail?: boolean, includeSecret?: boolean, diff --git a/packages/backend/src/core/entities/AuthSessionEntityService.ts b/packages/backend/src/core/entities/AuthSessionEntityService.ts index a24495b5592c..fd356cc89d17 100644 --- a/packages/backend/src/core/entities/AuthSessionEntityService.ts +++ b/packages/backend/src/core/entities/AuthSessionEntityService.ts @@ -5,10 +5,10 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { AuthSessionsRepository } from '@/models/index.js'; +import type { AuthSessionsRepository } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; -import type { AuthSession } from '@/models/entities/AuthSession.js'; -import type { User } from '@/models/entities/User.js'; +import type { MiAuthSession } from '@/models/AuthSession.js'; +import type { MiUser } from '@/models/User.js'; import { bindThis } from '@/decorators.js'; import { AppEntityService } from './AppEntityService.js'; @@ -24,8 +24,8 @@ export class AuthSessionEntityService { @bindThis public async pack( - src: AuthSession['id'] | AuthSession, - me?: { id: User['id'] } | null | undefined, + src: MiAuthSession['id'] | MiAuthSession, + me?: { id: MiUser['id'] } | null | undefined, ) { const session = typeof src === 'object' ? src : await this.authSessionsRepository.findOneByOrFail({ id: src }); diff --git a/packages/backend/src/core/entities/BlockingEntityService.ts b/packages/backend/src/core/entities/BlockingEntityService.ts index 1b11450669ab..44466e24e86a 100644 --- a/packages/backend/src/core/entities/BlockingEntityService.ts +++ b/packages/backend/src/core/entities/BlockingEntityService.ts @@ -5,11 +5,11 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { BlockingsRepository } from '@/models/index.js'; +import type { BlockingsRepository } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/json-schema.js'; -import type { Blocking } from '@/models/entities/Blocking.js'; -import type { User } from '@/models/entities/User.js'; +import type { MiBlocking } from '@/models/Blocking.js'; +import type { MiUser } from '@/models/User.js'; import { bindThis } from '@/decorators.js'; import { UserEntityService } from './UserEntityService.js'; @@ -25,8 +25,8 @@ export class BlockingEntityService { @bindThis public async pack( - src: Blocking['id'] | Blocking, - me?: { id: User['id'] } | null | undefined, + src: MiBlocking['id'] | MiBlocking, + me?: { id: MiUser['id'] } | null | undefined, ): Promise> { const blocking = typeof src === 'object' ? src : await this.blockingsRepository.findOneByOrFail({ id: src }); @@ -43,7 +43,7 @@ export class BlockingEntityService { @bindThis public packMany( blockings: any[], - me: { id: User['id'] }, + me: { id: MiUser['id'] }, ) { return Promise.all(blockings.map(x => this.pack(x, me))); } diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts index 042e7c3005ca..094de4d2d504 100644 --- a/packages/backend/src/core/entities/ChannelEntityService.ts +++ b/packages/backend/src/core/entities/ChannelEntityService.ts @@ -5,11 +5,11 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { ChannelFavoritesRepository, ChannelFollowingsRepository, ChannelsRepository, DriveFilesRepository, NoteUnreadsRepository, NotesRepository } from '@/models/index.js'; +import type { ChannelFavoritesRepository, ChannelFollowingsRepository, ChannelsRepository, DriveFilesRepository, NoteUnreadsRepository, NotesRepository } from '@/models/_.js'; import type { Packed } from '@/misc/json-schema.js'; -import type { } from '@/models/entities/Blocking.js'; -import type { User } from '@/models/entities/User.js'; -import type { Channel } from '@/models/entities/Channel.js'; +import type { } from '@/models/Blocking.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiChannel } from '@/models/Channel.js'; import { bindThis } from '@/decorators.js'; import { DriveFileEntityService } from './DriveFileEntityService.js'; import { NoteEntityService } from './NoteEntityService.js'; @@ -43,8 +43,8 @@ export class ChannelEntityService { @bindThis public async pack( - src: Channel['id'] | Channel, - me?: { id: User['id'] } | null | undefined, + src: MiChannel['id'] | MiChannel, + me?: { id: MiUser['id'] } | null | undefined, detailed?: boolean, ): Promise> { const channel = typeof src === 'object' ? src : await this.channelsRepository.findOneByOrFail({ id: src }); diff --git a/packages/backend/src/core/entities/ClipEntityService.ts b/packages/backend/src/core/entities/ClipEntityService.ts index 32009a8aadf6..e141db03f115 100644 --- a/packages/backend/src/core/entities/ClipEntityService.ts +++ b/packages/backend/src/core/entities/ClipEntityService.ts @@ -5,11 +5,11 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { ClipFavoritesRepository, ClipsRepository, User } from '@/models/index.js'; +import type { ClipFavoritesRepository, ClipsRepository, MiUser } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/json-schema.js'; -import type { } from '@/models/entities/Blocking.js'; -import type { Clip } from '@/models/entities/Clip.js'; +import type { } from '@/models/Blocking.js'; +import type { MiClip } from '@/models/Clip.js'; import { bindThis } from '@/decorators.js'; import { UserEntityService } from './UserEntityService.js'; @@ -28,8 +28,8 @@ export class ClipEntityService { @bindThis public async pack( - src: Clip['id'] | Clip, - me?: { id: User['id'] } | null | undefined, + src: MiClip['id'] | MiClip, + me?: { id: MiUser['id'] } | null | undefined, ): Promise> { const meId = me ? me.id : null; const clip = typeof src === 'object' ? src : await this.clipsRepository.findOneByOrFail({ id: src }); @@ -50,8 +50,8 @@ export class ClipEntityService { @bindThis public packMany( - clips: Clip[], - me?: { id: User['id'] } | null | undefined, + clips: MiClip[], + me?: { id: MiUser['id'] } | null | undefined, ) { return Promise.all(clips.map(x => this.pack(x, me))); } diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index 9e5a1d02ccd0..23273b041327 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -6,14 +6,17 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; import type { Packed } from '@/misc/json-schema.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; -import type { User } from '@/models/entities/User.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; import { appendQuery, query } from '@/misc/prelude/url.js'; import { deepClone } from '@/misc/clone.js'; +import { bindThis } from '@/decorators.js'; +import { isMimeImage } from '@/misc/is-mime-image.js'; +import { isNotNull } from '@/misc/is-not-null.js'; import { UtilityService } from '../UtilityService.js'; import { VideoProcessingService } from '../VideoProcessingService.js'; import { UserEntityService } from './UserEntityService.js'; @@ -24,9 +27,6 @@ type PackOptions = { self?: boolean, withUser?: boolean, }; -import { bindThis } from '@/decorators.js'; -import { isMimeImage } from '@/misc/is-mime-image.js'; -import { isNotNull } from '@/misc/is-not-null.js'; @Injectable() export class DriveFileEntityService { @@ -59,7 +59,7 @@ export class DriveFileEntityService { } @bindThis - public getPublicProperties(file: DriveFile): DriveFile['properties'] { + public getPublicProperties(file: MiDriveFile): MiDriveFile['properties'] { if (file.properties.orientation != null) { const properties = deepClone(file.properties); if (file.properties.orientation >= 5) { @@ -84,7 +84,7 @@ export class DriveFileEntityService { } @bindThis - public getThumbnailUrl(file: DriveFile): string | null { + public getThumbnailUrl(file: MiDriveFile): string | null { if (file.type.startsWith('video')) { if (file.thumbnailUrl) return file.thumbnailUrl; @@ -107,7 +107,7 @@ export class DriveFileEntityService { } @bindThis - public getPublicUrl(file: DriveFile, mode?: 'avatar'): string { // static = thumbnail + public getPublicUrl(file: MiDriveFile, mode?: 'avatar'): string { // static = thumbnail // リモートかつメディアプロキシ if (file.uri != null && file.userHost != null && this.config.externalMediaProxyEnabled) { return this.getProxiedUrl(file.uri, mode); @@ -133,7 +133,7 @@ export class DriveFileEntityService { } @bindThis - public async calcDriveUsageOf(user: User['id'] | { id: User['id'] }): Promise { + public async calcDriveUsageOf(user: MiUser['id'] | { id: MiUser['id'] }): Promise { const id = typeof user === 'object' ? user.id : user; const { sum } = await this.driveFilesRepository @@ -184,7 +184,7 @@ export class DriveFileEntityService { @bindThis public async pack( - src: DriveFile['id'] | DriveFile, + src: MiDriveFile['id'] | MiDriveFile, options?: PackOptions, ): Promise> { const opts = Object.assign({ @@ -218,7 +218,7 @@ export class DriveFileEntityService { @bindThis public async packNullable( - src: DriveFile['id'] | DriveFile, + src: MiDriveFile['id'] | MiDriveFile, options?: PackOptions, ): Promise | null> { const opts = Object.assign({ @@ -253,7 +253,7 @@ export class DriveFileEntityService { @bindThis public async packMany( - files: DriveFile[], + files: MiDriveFile[], options?: PackOptions, ): Promise[]> { const items = await Promise.all(files.map(f => this.packNullable(f, options))); @@ -262,7 +262,7 @@ export class DriveFileEntityService { @bindThis public async packManyByIdsMap( - fileIds: DriveFile['id'][], + fileIds: MiDriveFile['id'][], options?: PackOptions, ): Promise['id'], Packed<'DriveFile'> | null>> { if (fileIds.length === 0) return new Map(); @@ -277,7 +277,7 @@ export class DriveFileEntityService { @bindThis public async packManyByIds( - fileIds: DriveFile['id'][], + fileIds: MiDriveFile['id'][], options?: PackOptions, ): Promise[]> { if (fileIds.length === 0) return []; diff --git a/packages/backend/src/core/entities/DriveFolderEntityService.ts b/packages/backend/src/core/entities/DriveFolderEntityService.ts index ab561169656e..55014284bdb6 100644 --- a/packages/backend/src/core/entities/DriveFolderEntityService.ts +++ b/packages/backend/src/core/entities/DriveFolderEntityService.ts @@ -5,11 +5,11 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository, DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFilesRepository, DriveFoldersRepository } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/json-schema.js'; -import type { } from '@/models/entities/Blocking.js'; -import type { DriveFolder } from '@/models/entities/DriveFolder.js'; +import type { } from '@/models/Blocking.js'; +import type { MiDriveFolder } from '@/models/DriveFolder.js'; import { bindThis } from '@/decorators.js'; @Injectable() @@ -25,7 +25,7 @@ export class DriveFolderEntityService { @bindThis public async pack( - src: DriveFolder['id'] | DriveFolder, + src: MiDriveFolder['id'] | MiDriveFolder, options?: { detail: boolean }, diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts index 263117bd3d2e..5b97cfad5ee7 100644 --- a/packages/backend/src/core/entities/EmojiEntityService.ts +++ b/packages/backend/src/core/entities/EmojiEntityService.ts @@ -5,10 +5,10 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/_.js'; import type { Packed } from '@/misc/json-schema.js'; -import type { } from '@/models/entities/Blocking.js'; -import type { Emoji } from '@/models/entities/Emoji.js'; +import type { } from '@/models/Blocking.js'; +import type { MiEmoji } from '@/models/Emoji.js'; import { bindThis } from '@/decorators.js'; @Injectable() @@ -21,7 +21,7 @@ export class EmojiEntityService { @bindThis public async packSimple( - src: Emoji['id'] | Emoji, + src: MiEmoji['id'] | MiEmoji, ): Promise> { const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src }); @@ -45,7 +45,7 @@ export class EmojiEntityService { @bindThis public async packDetailed( - src: Emoji['id'] | Emoji, + src: MiEmoji['id'] | MiEmoji, ): Promise> { const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src }); diff --git a/packages/backend/src/core/entities/FlashEntityService.ts b/packages/backend/src/core/entities/FlashEntityService.ts index 4c95d834813f..4701cddcba6e 100644 --- a/packages/backend/src/core/entities/FlashEntityService.ts +++ b/packages/backend/src/core/entities/FlashEntityService.ts @@ -5,12 +5,12 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { FlashsRepository, FlashLikesRepository } from '@/models/index.js'; +import type { FlashsRepository, FlashLikesRepository } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/json-schema.js'; -import type { } from '@/models/entities/Blocking.js'; -import type { User } from '@/models/entities/User.js'; -import type { Flash } from '@/models/entities/Flash.js'; +import type { } from '@/models/Blocking.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiFlash } from '@/models/Flash.js'; import { bindThis } from '@/decorators.js'; import { UserEntityService } from './UserEntityService.js'; @@ -29,8 +29,8 @@ export class FlashEntityService { @bindThis public async pack( - src: Flash['id'] | Flash, - me?: { id: User['id'] } | null | undefined, + src: MiFlash['id'] | MiFlash, + me?: { id: MiUser['id'] } | null | undefined, ): Promise> { const meId = me ? me.id : null; const flash = typeof src === 'object' ? src : await this.flashsRepository.findOneByOrFail({ id: src }); @@ -51,8 +51,8 @@ export class FlashEntityService { @bindThis public packMany( - flashs: Flash[], - me?: { id: User['id'] } | null | undefined, + flashs: MiFlash[], + me?: { id: MiUser['id'] } | null | undefined, ) { return Promise.all(flashs.map(x => this.pack(x, me))); } diff --git a/packages/backend/src/core/entities/FlashLikeEntityService.ts b/packages/backend/src/core/entities/FlashLikeEntityService.ts index 20a2b44bed4d..2eff86217a65 100644 --- a/packages/backend/src/core/entities/FlashLikeEntityService.ts +++ b/packages/backend/src/core/entities/FlashLikeEntityService.ts @@ -5,10 +5,10 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { FlashLikesRepository } from '@/models/index.js'; -import type { } from '@/models/entities/Blocking.js'; -import type { User } from '@/models/entities/User.js'; -import type { FlashLike } from '@/models/entities/FlashLike.js'; +import type { FlashLikesRepository } from '@/models/_.js'; +import type { } from '@/models/Blocking.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiFlashLike } from '@/models/FlashLike.js'; import { bindThis } from '@/decorators.js'; import { FlashEntityService } from './FlashEntityService.js'; @@ -24,8 +24,8 @@ export class FlashLikeEntityService { @bindThis public async pack( - src: FlashLike['id'] | FlashLike, - me?: { id: User['id'] } | null | undefined, + src: MiFlashLike['id'] | MiFlashLike, + me?: { id: MiUser['id'] } | null | undefined, ) { const like = typeof src === 'object' ? src : await this.flashLikesRepository.findOneByOrFail({ id: src }); @@ -38,7 +38,7 @@ export class FlashLikeEntityService { @bindThis public packMany( likes: any[], - me: { id: User['id'] }, + me: { id: MiUser['id'] }, ) { return Promise.all(likes.map(x => this.pack(x, me))); } diff --git a/packages/backend/src/core/entities/FollowRequestEntityService.ts b/packages/backend/src/core/entities/FollowRequestEntityService.ts index 00ce3ae73e6e..0e0fec9f46ad 100644 --- a/packages/backend/src/core/entities/FollowRequestEntityService.ts +++ b/packages/backend/src/core/entities/FollowRequestEntityService.ts @@ -5,10 +5,10 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { FollowRequestsRepository } from '@/models/index.js'; -import type { } from '@/models/entities/Blocking.js'; -import type { User } from '@/models/entities/User.js'; -import type { FollowRequest } from '@/models/entities/FollowRequest.js'; +import type { FollowRequestsRepository } from '@/models/_.js'; +import type { } from '@/models/Blocking.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiFollowRequest } from '@/models/FollowRequest.js'; import { bindThis } from '@/decorators.js'; import { UserEntityService } from './UserEntityService.js'; @@ -24,8 +24,8 @@ export class FollowRequestEntityService { @bindThis public async pack( - src: FollowRequest['id'] | FollowRequest, - me?: { id: User['id'] } | null | undefined, + src: MiFollowRequest['id'] | MiFollowRequest, + me?: { id: MiUser['id'] } | null | undefined, ) { const request = typeof src === 'object' ? src : await this.followRequestsRepository.findOneByOrFail({ id: src }); diff --git a/packages/backend/src/core/entities/FollowingEntityService.ts b/packages/backend/src/core/entities/FollowingEntityService.ts index 2c04e98c326c..9f6eb51e8c95 100644 --- a/packages/backend/src/core/entities/FollowingEntityService.ts +++ b/packages/backend/src/core/entities/FollowingEntityService.ts @@ -5,34 +5,34 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { FollowingsRepository } from '@/models/index.js'; +import type { FollowingsRepository } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/json-schema.js'; -import type { } from '@/models/entities/Blocking.js'; -import type { User } from '@/models/entities/User.js'; -import type { Following } from '@/models/entities/Following.js'; +import type { } from '@/models/Blocking.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiFollowing } from '@/models/Following.js'; import { bindThis } from '@/decorators.js'; import { UserEntityService } from './UserEntityService.js'; -type LocalFollowerFollowing = Following & { +type LocalFollowerFollowing = MiFollowing & { followerHost: null; followerInbox: null; followerSharedInbox: null; }; -type RemoteFollowerFollowing = Following & { +type RemoteFollowerFollowing = MiFollowing & { followerHost: string; followerInbox: string; followerSharedInbox: string; }; -type LocalFolloweeFollowing = Following & { +type LocalFolloweeFollowing = MiFollowing & { followeeHost: null; followeeInbox: null; followeeSharedInbox: null; }; -type RemoteFolloweeFollowing = Following & { +type RemoteFolloweeFollowing = MiFollowing & { followeeHost: string; followeeInbox: string; followeeSharedInbox: string; @@ -49,29 +49,29 @@ export class FollowingEntityService { } @bindThis - public isLocalFollower(following: Following): following is LocalFollowerFollowing { + public isLocalFollower(following: MiFollowing): following is LocalFollowerFollowing { return following.followerHost == null; } @bindThis - public isRemoteFollower(following: Following): following is RemoteFollowerFollowing { + public isRemoteFollower(following: MiFollowing): following is RemoteFollowerFollowing { return following.followerHost != null; } @bindThis - public isLocalFollowee(following: Following): following is LocalFolloweeFollowing { + public isLocalFollowee(following: MiFollowing): following is LocalFolloweeFollowing { return following.followeeHost == null; } @bindThis - public isRemoteFollowee(following: Following): following is RemoteFolloweeFollowing { + public isRemoteFollowee(following: MiFollowing): following is RemoteFolloweeFollowing { return following.followeeHost != null; } @bindThis public async pack( - src: Following['id'] | Following, - me?: { id: User['id'] } | null | undefined, + src: MiFollowing['id'] | MiFollowing, + me?: { id: MiUser['id'] } | null | undefined, opts?: { populateFollowee?: boolean; populateFollower?: boolean; @@ -98,7 +98,7 @@ export class FollowingEntityService { @bindThis public packMany( followings: any[], - me?: { id: User['id'] } | null | undefined, + me?: { id: MiUser['id'] } | null | undefined, opts?: { populateFollowee?: boolean; populateFollower?: boolean; diff --git a/packages/backend/src/core/entities/GalleryLikeEntityService.ts b/packages/backend/src/core/entities/GalleryLikeEntityService.ts index 7332ca4c501e..e7407018880e 100644 --- a/packages/backend/src/core/entities/GalleryLikeEntityService.ts +++ b/packages/backend/src/core/entities/GalleryLikeEntityService.ts @@ -5,9 +5,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { GalleryLikesRepository } from '@/models/index.js'; -import type { } from '@/models/entities/Blocking.js'; -import type { GalleryLike } from '@/models/entities/GalleryLike.js'; +import type { GalleryLikesRepository } from '@/models/_.js'; +import type { } from '@/models/Blocking.js'; +import type { MiGalleryLike } from '@/models/GalleryLike.js'; import { bindThis } from '@/decorators.js'; import { GalleryPostEntityService } from './GalleryPostEntityService.js'; @@ -23,7 +23,7 @@ export class GalleryLikeEntityService { @bindThis public async pack( - src: GalleryLike['id'] | GalleryLike, + src: MiGalleryLike['id'] | MiGalleryLike, me?: any, ) { const like = typeof src === 'object' ? src : await this.galleryLikesRepository.findOneByOrFail({ id: src }); diff --git a/packages/backend/src/core/entities/GalleryPostEntityService.ts b/packages/backend/src/core/entities/GalleryPostEntityService.ts index 0a0a03824b6c..bbaf70f0fdff 100644 --- a/packages/backend/src/core/entities/GalleryPostEntityService.ts +++ b/packages/backend/src/core/entities/GalleryPostEntityService.ts @@ -5,12 +5,12 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { GalleryLikesRepository, GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryLikesRepository, GalleryPostsRepository } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/json-schema.js'; -import type { } from '@/models/entities/Blocking.js'; -import type { User } from '@/models/entities/User.js'; -import type { GalleryPost } from '@/models/entities/GalleryPost.js'; +import type { } from '@/models/Blocking.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiGalleryPost } from '@/models/GalleryPost.js'; import { bindThis } from '@/decorators.js'; import { UserEntityService } from './UserEntityService.js'; import { DriveFileEntityService } from './DriveFileEntityService.js'; @@ -31,8 +31,8 @@ export class GalleryPostEntityService { @bindThis public async pack( - src: GalleryPost['id'] | GalleryPost, - me?: { id: User['id'] } | null | undefined, + src: MiGalleryPost['id'] | MiGalleryPost, + me?: { id: MiUser['id'] } | null | undefined, ): Promise> { const meId = me ? me.id : null; const post = typeof src === 'object' ? src : await this.galleryPostsRepository.findOneByOrFail({ id: src }); @@ -57,8 +57,8 @@ export class GalleryPostEntityService { @bindThis public packMany( - posts: GalleryPost[], - me?: { id: User['id'] } | null | undefined, + posts: MiGalleryPost[], + me?: { id: MiUser['id'] } | null | undefined, ) { return Promise.all(posts.map(x => this.pack(x, me))); } diff --git a/packages/backend/src/core/entities/HashtagEntityService.ts b/packages/backend/src/core/entities/HashtagEntityService.ts index 7dc92a95e284..006e267b12c1 100644 --- a/packages/backend/src/core/entities/HashtagEntityService.ts +++ b/packages/backend/src/core/entities/HashtagEntityService.ts @@ -5,8 +5,8 @@ import { Injectable } from '@nestjs/common'; import type { Packed } from '@/misc/json-schema.js'; -import type { } from '@/models/entities/Blocking.js'; -import type { Hashtag } from '@/models/entities/Hashtag.js'; +import type { } from '@/models/Blocking.js'; +import type { MiHashtag } from '@/models/Hashtag.js'; import { bindThis } from '@/decorators.js'; @Injectable() @@ -17,7 +17,7 @@ export class HashtagEntityService { @bindThis public async pack( - src: Hashtag, + src: MiHashtag, ): Promise> { return { tag: src.name, @@ -32,7 +32,7 @@ export class HashtagEntityService { @bindThis public packMany( - hashtags: Hashtag[], + hashtags: MiHashtag[], ) { return Promise.all(hashtags.map(x => this.pack(x))); } diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts index 961016e5b605..0e27e9df7f02 100644 --- a/packages/backend/src/core/entities/InstanceEntityService.ts +++ b/packages/backend/src/core/entities/InstanceEntityService.ts @@ -5,8 +5,8 @@ import { Injectable } from '@nestjs/common'; import type { Packed } from '@/misc/json-schema.js'; -import type { } from '@/models/entities/Blocking.js'; -import type { Instance } from '@/models/entities/Instance.js'; +import type { } from '@/models/Blocking.js'; +import type { MiInstance } from '@/models/Instance.js'; import { MetaService } from '@/core/MetaService.js'; import { bindThis } from '@/decorators.js'; import { UtilityService } from '../UtilityService.js'; @@ -22,7 +22,7 @@ export class InstanceEntityService { @bindThis public async pack( - instance: Instance, + instance: MiInstance, ): Promise> { const meta = await this.metaService.fetch(); return { @@ -52,7 +52,7 @@ export class InstanceEntityService { @bindThis public packMany( - instances: Instance[], + instances: MiInstance[], ) { return Promise.all(instances.map(x => this.pack(x))); } diff --git a/packages/backend/src/core/entities/InviteCodeEntityService.ts b/packages/backend/src/core/entities/InviteCodeEntityService.ts index 112572abe47f..914eaafe68c2 100644 --- a/packages/backend/src/core/entities/InviteCodeEntityService.ts +++ b/packages/backend/src/core/entities/InviteCodeEntityService.ts @@ -5,11 +5,11 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { RegistrationTicketsRepository } from '@/models/index.js'; +import type { RegistrationTicketsRepository } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/json-schema.js'; -import type { User } from '@/models/entities/User.js'; -import type { RegistrationTicket } from '@/models/entities/RegistrationTicket.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiRegistrationTicket } from '@/models/RegistrationTicket.js'; import { bindThis } from '@/decorators.js'; import { UserEntityService } from './UserEntityService.js'; @@ -25,8 +25,8 @@ export class InviteCodeEntityService { @bindThis public async pack( - src: RegistrationTicket['id'] | RegistrationTicket, - me?: { id: User['id'] } | null | undefined, + src: MiRegistrationTicket['id'] | MiRegistrationTicket, + me?: { id: MiUser['id'] } | null | undefined, ): Promise> { const target = typeof src === 'object' ? src : await this.registrationTicketsRepository.findOneOrFail({ where: { @@ -50,7 +50,7 @@ export class InviteCodeEntityService { @bindThis public packMany( targets: any[], - me: { id: User['id'] }, + me: { id: MiUser['id'] }, ) { return Promise.all(targets.map(x => this.pack(x, me))); } diff --git a/packages/backend/src/core/entities/ModerationLogEntityService.ts b/packages/backend/src/core/entities/ModerationLogEntityService.ts index 29f1a03b2b16..83b024d83bf0 100644 --- a/packages/backend/src/core/entities/ModerationLogEntityService.ts +++ b/packages/backend/src/core/entities/ModerationLogEntityService.ts @@ -5,10 +5,10 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { ModerationLogsRepository } from '@/models/index.js'; +import type { ModerationLogsRepository } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; -import type { } from '@/models/entities/Blocking.js'; -import type { ModerationLog } from '@/models/entities/ModerationLog.js'; +import type { } from '@/models/Blocking.js'; +import type { MiModerationLog } from '@/models/ModerationLog.js'; import { bindThis } from '@/decorators.js'; import { UserEntityService } from './UserEntityService.js'; @@ -24,7 +24,7 @@ export class ModerationLogEntityService { @bindThis public async pack( - src: ModerationLog['id'] | ModerationLog, + src: MiModerationLog['id'] | MiModerationLog, ) { const log = typeof src === 'object' ? src : await this.moderationLogsRepository.findOneByOrFail({ id: src }); diff --git a/packages/backend/src/core/entities/MutingEntityService.ts b/packages/backend/src/core/entities/MutingEntityService.ts index b4527402872c..e3d5d2e211b0 100644 --- a/packages/backend/src/core/entities/MutingEntityService.ts +++ b/packages/backend/src/core/entities/MutingEntityService.ts @@ -5,12 +5,12 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { MutingsRepository } from '@/models/index.js'; +import type { MutingsRepository } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/json-schema.js'; -import type { } from '@/models/entities/Blocking.js'; -import type { User } from '@/models/entities/User.js'; -import type { Muting } from '@/models/entities/Muting.js'; +import type { } from '@/models/Blocking.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiMuting } from '@/models/Muting.js'; import { bindThis } from '@/decorators.js'; import { UserEntityService } from './UserEntityService.js'; @@ -26,8 +26,8 @@ export class MutingEntityService { @bindThis public async pack( - src: Muting['id'] | Muting, - me?: { id: User['id'] } | null | undefined, + src: MiMuting['id'] | MiMuting, + me?: { id: MiUser['id'] } | null | undefined, ): Promise> { const muting = typeof src === 'object' ? src : await this.mutingsRepository.findOneByOrFail({ id: src }); @@ -45,7 +45,7 @@ export class MutingEntityService { @bindThis public packMany( mutings: any[], - me: { id: User['id'] }, + me: { id: MiUser['id'] }, ) { return Promise.all(mutings.map(x => this.pack(x, me))); } diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 7d7183dc8b75..bf42e98ce0a9 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -11,10 +11,10 @@ import { DI } from '@/di-symbols.js'; import type { Packed } from '@/misc/json-schema.js'; import { nyaize } from '@/misc/nyaize.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; -import type { User } from '@/models/entities/User.js'; -import type { Note } from '@/models/entities/Note.js'; -import type { NoteReaction } from '@/models/entities/NoteReaction.js'; -import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository } from '@/models/index.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiNote } from '@/models/Note.js'; +import type { MiNoteReaction } from '@/models/NoteReaction.js'; +import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { isNotNull } from '@/misc/is-not-null.js'; import type { OnModuleInit } from '@nestjs/common'; @@ -69,7 +69,7 @@ export class NoteEntityService implements OnModuleInit { } @bindThis - private async hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) { + private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null) { // TODO: isVisibleForMe を使うようにしても良さそう(型違うけど) let hide = false; @@ -128,7 +128,7 @@ export class NoteEntityService implements OnModuleInit { } @bindThis - private async populatePoll(note: Note, meId: User['id'] | null) { + private async populatePoll(note: MiNote, meId: MiUser['id'] | null) { const poll = await this.pollsRepository.findOneByOrFail({ noteId: note.id }); const choices = poll.choices.map(c => ({ text: c, @@ -167,8 +167,8 @@ export class NoteEntityService implements OnModuleInit { } @bindThis - private async populateMyReaction(note: Note, meId: User['id'], _hint_?: { - myReactions: Map; + private async populateMyReaction(note: MiNote, meId: MiUser['id'], _hint_?: { + myReactions: Map; }) { if (_hint_?.myReactions) { const reaction = _hint_.myReactions.get(note.id); @@ -198,7 +198,7 @@ export class NoteEntityService implements OnModuleInit { } @bindThis - public async isVisibleForMe(note: Note, meId: User['id'] | null): Promise { + public async isVisibleForMe(note: MiNote, meId: MiUser['id'] | null): Promise { // This code must always be synchronized with the checks in generateVisibilityQuery. // visibility が specified かつ自分が指定されていなかったら非表示 if (note.visibility === 'specified') { @@ -252,7 +252,7 @@ export class NoteEntityService implements OnModuleInit { } @bindThis - public async packAttachedFiles(fileIds: Note['fileIds'], packedFiles: Map | null>): Promise[]> { + public async packAttachedFiles(fileIds: MiNote['fileIds'], packedFiles: Map | null>): Promise[]> { const missingIds = []; for (const id of fileIds) { if (!packedFiles.has(id)) missingIds.push(id); @@ -268,14 +268,14 @@ export class NoteEntityService implements OnModuleInit { @bindThis public async pack( - src: Note['id'] | Note, - me?: { id: User['id'] } | null | undefined, + src: MiNote['id'] | MiNote, + me?: { id: MiUser['id'] } | null | undefined, options?: { detail?: boolean; skipHide?: boolean; _hint_?: { - myReactions: Map; - packedFiles: Map | null>; + myReactions: Map; + packedFiles: Map | null>; }; }, ): Promise> { @@ -340,6 +340,8 @@ export class NoteEntityService implements OnModuleInit { url: note.url ?? undefined, ...(opts.detail ? { + clippedCount: note.clippedCount, + reply: note.replyId ? this.pack(note.reply ?? note.replyId, me, { detail: false, _hint_: options?._hint_, @@ -386,8 +388,8 @@ export class NoteEntityService implements OnModuleInit { @bindThis public async packMany( - notes: Note[], - me?: { id: User['id'] } | null | undefined, + notes: MiNote[], + me?: { id: MiUser['id'] } | null | undefined, options?: { detail?: boolean; skipHide?: boolean; @@ -396,7 +398,7 @@ export class NoteEntityService implements OnModuleInit { if (notes.length === 0) return []; const meId = me ? me.id : null; - const myReactionsMap = new Map(); + const myReactionsMap = new Map(); if (meId) { const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!); // パフォーマンスのためノートが作成されてから1秒以上経っていない場合はリアクションを取得しない @@ -426,7 +428,7 @@ export class NoteEntityService implements OnModuleInit { } @bindThis - public aggregateNoteEmojis(notes: Note[]) { + public aggregateNoteEmojis(notes: MiNote[]) { let emojis: { name: string | null; host: string | null; }[] = []; for (const note of notes) { emojis = emojis.concat(note.emojis diff --git a/packages/backend/src/core/entities/NoteFavoriteEntityService.ts b/packages/backend/src/core/entities/NoteFavoriteEntityService.ts index 1597246dc271..808c8c9f690d 100644 --- a/packages/backend/src/core/entities/NoteFavoriteEntityService.ts +++ b/packages/backend/src/core/entities/NoteFavoriteEntityService.ts @@ -5,10 +5,10 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { NoteFavoritesRepository } from '@/models/index.js'; -import type { } from '@/models/entities/Blocking.js'; -import type { User } from '@/models/entities/User.js'; -import type { NoteFavorite } from '@/models/entities/NoteFavorite.js'; +import type { NoteFavoritesRepository } from '@/models/_.js'; +import type { } from '@/models/Blocking.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiNoteFavorite } from '@/models/NoteFavorite.js'; import { bindThis } from '@/decorators.js'; import { NoteEntityService } from './NoteEntityService.js'; @@ -24,8 +24,8 @@ export class NoteFavoriteEntityService { @bindThis public async pack( - src: NoteFavorite['id'] | NoteFavorite, - me?: { id: User['id'] } | null | undefined, + src: MiNoteFavorite['id'] | MiNoteFavorite, + me?: { id: MiUser['id'] } | null | undefined, ) { const favorite = typeof src === 'object' ? src : await this.noteFavoritesRepository.findOneByOrFail({ id: src }); @@ -40,7 +40,7 @@ export class NoteFavoriteEntityService { @bindThis public packMany( favorites: any[], - me: { id: User['id'] }, + me: { id: MiUser['id'] }, ) { return Promise.all(favorites.map(x => this.pack(x, me))); } diff --git a/packages/backend/src/core/entities/NoteReactionEntityService.ts b/packages/backend/src/core/entities/NoteReactionEntityService.ts index cc20334fdb8b..9701f37fdb16 100644 --- a/packages/backend/src/core/entities/NoteReactionEntityService.ts +++ b/packages/backend/src/core/entities/NoteReactionEntityService.ts @@ -5,13 +5,13 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { NoteReactionsRepository } from '@/models/index.js'; +import type { NoteReactionsRepository } from '@/models/_.js'; import type { Packed } from '@/misc/json-schema.js'; import { bindThis } from '@/decorators.js'; import type { OnModuleInit } from '@nestjs/common'; -import type { } from '@/models/entities/Blocking.js'; -import type { User } from '@/models/entities/User.js'; -import type { NoteReaction } from '@/models/entities/NoteReaction.js'; +import type { } from '@/models/Blocking.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiNoteReaction } from '@/models/NoteReaction.js'; import type { ReactionService } from '../ReactionService.js'; import type { UserEntityService } from './UserEntityService.js'; import type { NoteEntityService } from './NoteEntityService.js'; @@ -43,8 +43,8 @@ export class NoteReactionEntityService implements OnModuleInit { @bindThis public async pack( - src: NoteReaction['id'] | NoteReaction, - me?: { id: User['id'] } | null | undefined, + src: MiNoteReaction['id'] | MiNoteReaction, + me?: { id: MiUser['id'] } | null | undefined, options?: { withNote: boolean; }, diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts index 2da4f271b7d9..3ee7c91f3a2e 100644 --- a/packages/backend/src/core/entities/NotificationEntityService.ts +++ b/packages/backend/src/core/entities/NotificationEntityService.ts @@ -7,10 +7,10 @@ import { Inject, Injectable } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { AccessTokensRepository, FollowRequestsRepository, NotesRepository, User, UsersRepository } from '@/models/index.js'; +import type { AccessTokensRepository, FollowRequestsRepository, NotesRepository, MiUser, UsersRepository } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; -import type { Notification } from '@/models/entities/Notification.js'; -import type { Note } from '@/models/entities/Note.js'; +import type { MiNotification } from '@/models/Notification.js'; +import type { MiNote } from '@/models/Note.js'; import type { Packed } from '@/misc/json-schema.js'; import { bindThis } from '@/decorators.js'; import { isNotNull } from '@/misc/is-not-null.js'; @@ -20,7 +20,7 @@ import type { CustomEmojiService } from '../CustomEmojiService.js'; import type { UserEntityService } from './UserEntityService.js'; import type { NoteEntityService } from './NoteEntityService.js'; -const NOTE_REQUIRED_NOTIFICATION_TYPES = new Set(['mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded'] as (typeof notificationTypes[number])[]); +const NOTE_REQUIRED_NOTIFICATION_TYPES = new Set(['note', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded'] as (typeof notificationTypes[number])[]); @Injectable() export class NotificationEntityService implements OnModuleInit { @@ -57,15 +57,15 @@ export class NotificationEntityService implements OnModuleInit { @bindThis public async pack( - src: Notification, - meId: User['id'], + src: MiNotification, + meId: MiUser['id'], // eslint-disable-next-line @typescript-eslint/ban-types options: { }, hint?: { - packedNotes: Map>; - packedUsers: Map>; + packedNotes: Map>; + packedUsers: Map>; }, ): Promise> { const notification = src; @@ -108,8 +108,8 @@ export class NotificationEntityService implements OnModuleInit { @bindThis public async packMany( - notifications: Notification[], - meId: User['id'], + notifications: MiNotification[], + meId: MiUser['id'], ) { if (notifications.length === 0) return []; diff --git a/packages/backend/src/core/entities/PageEntityService.ts b/packages/backend/src/core/entities/PageEntityService.ts index 8f51daeb9bbe..e3a1e19ddd18 100644 --- a/packages/backend/src/core/entities/PageEntityService.ts +++ b/packages/backend/src/core/entities/PageEntityService.ts @@ -5,13 +5,13 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository, PagesRepository, PageLikesRepository } from '@/models/index.js'; +import type { DriveFilesRepository, PagesRepository, PageLikesRepository } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/json-schema.js'; -import type { } from '@/models/entities/Blocking.js'; -import type { User } from '@/models/entities/User.js'; -import type { Page } from '@/models/entities/Page.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; +import type { } from '@/models/Blocking.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiPage } from '@/models/Page.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; import { bindThis } from '@/decorators.js'; import { UserEntityService } from './UserEntityService.js'; import { DriveFileEntityService } from './DriveFileEntityService.js'; @@ -35,13 +35,13 @@ export class PageEntityService { @bindThis public async pack( - src: Page['id'] | Page, - me?: { id: User['id'] } | null | undefined, + src: MiPage['id'] | MiPage, + me?: { id: MiUser['id'] } | null | undefined, ): Promise> { const meId = me ? me.id : null; const page = typeof src === 'object' ? src : await this.pagesRepository.findOneByOrFail({ id: src }); - const attachedFiles: Promise[] = []; + const attachedFiles: Promise[] = []; const collectFile = (xs: any[]) => { for (const x of xs) { if (x.type === 'image') { @@ -100,7 +100,7 @@ export class PageEntityService { script: page.script, eyeCatchingImageId: page.eyeCatchingImageId, eyeCatchingImage: page.eyeCatchingImageId ? await this.driveFileEntityService.pack(page.eyeCatchingImageId) : null, - attachedFiles: this.driveFileEntityService.packMany((await Promise.all(attachedFiles)).filter((x): x is DriveFile => x != null)), + attachedFiles: this.driveFileEntityService.packMany((await Promise.all(attachedFiles)).filter((x): x is MiDriveFile => x != null)), likedCount: page.likedCount, isLiked: meId ? await this.pageLikesRepository.exist({ where: { pageId: page.id, userId: meId } }) : undefined, }); @@ -108,8 +108,8 @@ export class PageEntityService { @bindThis public packMany( - pages: Page[], - me?: { id: User['id'] } | null | undefined, + pages: MiPage[], + me?: { id: MiUser['id'] } | null | undefined, ) { return Promise.all(pages.map(x => this.pack(x, me))); } diff --git a/packages/backend/src/core/entities/PageLikeEntityService.ts b/packages/backend/src/core/entities/PageLikeEntityService.ts index 1a14d5cac3d6..4dc691ab9313 100644 --- a/packages/backend/src/core/entities/PageLikeEntityService.ts +++ b/packages/backend/src/core/entities/PageLikeEntityService.ts @@ -5,10 +5,10 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { PageLikesRepository } from '@/models/index.js'; -import type { } from '@/models/entities/Blocking.js'; -import type { User } from '@/models/entities/User.js'; -import type { PageLike } from '@/models/entities/PageLike.js'; +import type { PageLikesRepository } from '@/models/_.js'; +import type { } from '@/models/Blocking.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiPageLike } from '@/models/PageLike.js'; import { bindThis } from '@/decorators.js'; import { PageEntityService } from './PageEntityService.js'; @@ -24,8 +24,8 @@ export class PageLikeEntityService { @bindThis public async pack( - src: PageLike['id'] | PageLike, - me?: { id: User['id'] } | null | undefined, + src: MiPageLike['id'] | MiPageLike, + me?: { id: MiUser['id'] } | null | undefined, ) { const like = typeof src === 'object' ? src : await this.pageLikesRepository.findOneByOrFail({ id: src }); @@ -38,7 +38,7 @@ export class PageLikeEntityService { @bindThis public packMany( likes: any[], - me: { id: User['id'] }, + me: { id: MiUser['id'] }, ) { return Promise.all(likes.map(x => this.pack(x, me))); } diff --git a/packages/backend/src/core/entities/RenoteMutingEntityService.ts b/packages/backend/src/core/entities/RenoteMutingEntityService.ts index ca192379fe8f..7111fab08a51 100644 --- a/packages/backend/src/core/entities/RenoteMutingEntityService.ts +++ b/packages/backend/src/core/entities/RenoteMutingEntityService.ts @@ -5,12 +5,12 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { RenoteMutingsRepository } from '@/models/index.js'; +import type { RenoteMutingsRepository } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/json-schema.js'; -import type { } from '@/models/entities/Blocking.js'; -import type { User } from '@/models/entities/User.js'; -import type { RenoteMuting } from '@/models/entities/RenoteMuting.js'; +import type { } from '@/models/Blocking.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiRenoteMuting } from '@/models/RenoteMuting.js'; import { bindThis } from '@/decorators.js'; import { UserEntityService } from './UserEntityService.js'; @@ -26,8 +26,8 @@ export class RenoteMutingEntityService { @bindThis public async pack( - src: RenoteMuting['id'] | RenoteMuting, - me?: { id: User['id'] } | null | undefined, + src: MiRenoteMuting['id'] | MiRenoteMuting, + me?: { id: MiUser['id'] } | null | undefined, ): Promise> { const muting = typeof src === 'object' ? src : await this.renoteMutingsRepository.findOneByOrFail({ id: src }); @@ -44,7 +44,7 @@ export class RenoteMutingEntityService { @bindThis public packMany( mutings: any[], - me: { id: User['id'] }, + me: { id: MiUser['id'] }, ) { return Promise.all(mutings.map(x => this.pack(x, me))); } diff --git a/packages/backend/src/core/entities/RoleEntityService.ts b/packages/backend/src/core/entities/RoleEntityService.ts index 99766105cb81..23e82561d670 100644 --- a/packages/backend/src/core/entities/RoleEntityService.ts +++ b/packages/backend/src/core/entities/RoleEntityService.ts @@ -6,10 +6,10 @@ import { Inject, Injectable } from '@nestjs/common'; import { Brackets } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { RoleAssignmentsRepository, RolesRepository } from '@/models/index.js'; +import type { RoleAssignmentsRepository, RolesRepository } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; -import type { User } from '@/models/entities/User.js'; -import type { Role } from '@/models/entities/Role.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiRole } from '@/models/Role.js'; import { bindThis } from '@/decorators.js'; import { DEFAULT_POLICIES } from '@/core/RoleService.js'; @@ -26,8 +26,8 @@ export class RoleEntityService { @bindThis public async pack( - src: Role['id'] | Role, - me?: { id: User['id'] } | null | undefined, + src: MiRole['id'] | MiRole, + me?: { id: MiUser['id'] } | null | undefined, ) { const role = typeof src === 'object' ? src : await this.rolesRepository.findOneByOrFail({ id: src }); @@ -73,7 +73,7 @@ export class RoleEntityService { @bindThis public packMany( roles: any[], - me: { id: User['id'] }, + me: { id: MiUser['id'] }, ) { return Promise.all(roles.map(x => this.pack(x, me))); } diff --git a/packages/backend/src/core/entities/SigninEntityService.ts b/packages/backend/src/core/entities/SigninEntityService.ts index a84e89dc1b58..8c88e8560a70 100644 --- a/packages/backend/src/core/entities/SigninEntityService.ts +++ b/packages/backend/src/core/entities/SigninEntityService.ts @@ -4,8 +4,8 @@ */ import { Injectable } from '@nestjs/common'; -import type { } from '@/models/entities/Blocking.js'; -import type { Signin } from '@/models/entities/Signin.js'; +import type { } from '@/models/Blocking.js'; +import type { MiSignin } from '@/models/Signin.js'; import { bindThis } from '@/decorators.js'; @Injectable() @@ -16,7 +16,7 @@ export class SigninEntityService { @bindThis public async pack( - src: Signin, + src: MiSignin, ) { return src; } diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 7be547fbb975..3dd64ce625ab 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -13,9 +13,9 @@ import type { Packed } from '@/misc/json-schema.js'; import type { Promiseable } from '@/misc/prelude/await-all.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js'; -import type { LocalUser, PartialLocalUser, PartialRemoteUser, RemoteUser, User } from '@/models/entities/User.js'; -import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/entities/User.js'; -import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, UserNotePiningsRepository, UserProfilesRepository, AnnouncementReadsRepository, AnnouncementsRepository, UserProfile, RenoteMutingsRepository, UserMemoRepository, Announcement } from '@/models/index.js'; +import type { MiLocalUser, MiPartialLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js'; +import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/User.js'; +import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, UserNotePiningsRepository, UserProfilesRepository, AnnouncementReadsRepository, AnnouncementsRepository, MiUserProfile, RenoteMutingsRepository, UserMemoRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; @@ -38,15 +38,15 @@ type IsMeAndIsUserDetailed(user: T): user is (T & { host: null; }); -function isLocalUser(user: User | { host: User['host'] }): boolean { +function isLocalUser(user: MiUser): user is MiLocalUser; +function isLocalUser(user: T): user is (T & { host: null; }); +function isLocalUser(user: MiUser | { host: MiUser['host'] }): boolean { return user.host == null; } -function isRemoteUser(user: User): user is RemoteUser; -function isRemoteUser(user: T): user is (T & { host: string; }); -function isRemoteUser(user: User | { host: User['host'] }): boolean { +function isRemoteUser(user: MiUser): user is MiRemoteUser; +function isRemoteUser(user: T): user is (T & { host: string; }); +function isRemoteUser(user: MiUser | { host: MiUser['host'] }): boolean { return !isLocalUser(user); } @@ -145,16 +145,15 @@ export class UserEntityService implements OnModuleInit { public isRemoteUser = isRemoteUser; @bindThis - public async getRelation(me: User['id'], target: User['id']) { + public async getRelation(me: MiUser['id'], target: MiUser['id']) { + const following = await this.followingsRepository.findOneBy({ + followerId: me, + followeeId: target, + }); return awaitAll({ id: target, - isFollowing: this.followingsRepository.count({ - where: { - followerId: me, - followeeId: target, - }, - take: 1, - }).then(n => n > 0), + following, + isFollowing: following != null, isFollowed: this.followingsRepository.count({ where: { followerId: target, @@ -208,7 +207,7 @@ export class UserEntityService implements OnModuleInit { } @bindThis - public async getHasUnreadAntenna(userId: User['id']): Promise { + public async getHasUnreadAntenna(userId: MiUser['id']): Promise { /* const myAntennas = (await this.antennaService.getAntennas()).filter(a => a.userId === userId); @@ -225,7 +224,7 @@ export class UserEntityService implements OnModuleInit { } @bindThis - public async getHasUnreadNotification(userId: User['id']): Promise { + public async getHasUnreadNotification(userId: MiUser['id']): Promise { const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${userId}`); const latestNotificationIdsRes = await this.redisClient.xrevrange( @@ -239,7 +238,7 @@ export class UserEntityService implements OnModuleInit { } @bindThis - public async getHasPendingReceivedFollowRequest(userId: User['id']): Promise { + public async getHasPendingReceivedFollowRequest(userId: MiUser['id']): Promise { const count = await this.followRequestsRepository.countBy({ followeeId: userId, }); @@ -248,7 +247,7 @@ export class UserEntityService implements OnModuleInit { } @bindThis - public getOnlineStatus(user: User): 'unknown' | 'online' | 'active' | 'offline' { + public getOnlineStatus(user: MiUser): 'unknown' | 'online' | 'active' | 'offline' { if (user.hideOnlineStatus) return 'unknown'; if (user.lastActiveDate == null) return 'unknown'; const elapsed = Date.now() - user.lastActiveDate.getTime(); @@ -260,12 +259,12 @@ export class UserEntityService implements OnModuleInit { } @bindThis - public getIdenticonUrl(user: User): string { + public getIdenticonUrl(user: MiUser): string { return `${this.config.url}/identicon/${user.username.toLowerCase()}@${user.host ?? this.config.host}`; } @bindThis - public getUserUri(user: LocalUser | PartialLocalUser | RemoteUser | PartialRemoteUser): string { + public getUserUri(user: MiLocalUser | MiPartialLocalUser | MiRemoteUser | MiPartialRemoteUser): string { return this.isRemoteUser(user) ? user.uri : this.genLocalUserUri(user.id); } @@ -276,12 +275,12 @@ export class UserEntityService implements OnModuleInit { } public async pack( - src: User['id'] | User, - me?: { id: User['id']; } | null | undefined, + src: MiUser['id'] | MiUser, + me?: { id: MiUser['id']; } | null | undefined, options?: { detail?: D, includeSecrets?: boolean, - userProfile?: UserProfile, + userProfile?: MiUserProfile, }, ): Promise> { const opts = Object.assign({ @@ -311,7 +310,7 @@ export class UserEntityService implements OnModuleInit { const meId = me ? me.id : null; const isMe = meId === user.id; - const iAmModerator = me ? await this.roleService.isModerator(me as User) : false; + const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false; const relation = meId && !isMe && opts.detail ? await this.getRelation(meId, user.id) : null; const pins = opts.detail ? await this.userNotePiningsRepository.createQueryBuilder('pin') @@ -384,6 +383,7 @@ export class UserEntityService implements OnModuleInit { birthday: profile!.birthday, lang: profile!.lang, fields: profile!.fields, + verifiedLinks: profile!.verifiedLinks, followersCount: followersCount ?? 0, followingCount: followingCount ?? 0, notesCount: user.notesCount, @@ -434,6 +434,7 @@ export class UserEntityService implements OnModuleInit { preventAiLearning: profile!.preventAiLearning, isExplorable: user.isExplorable, isDeleted: user.isDeleted, + twoFactorBackupCodesStock: profile?.twoFactorBackupSecret?.length === 5 ? 'full' : (profile?.twoFactorBackupSecret?.length ?? 0) > 0 ? 'partial' : 'none', hideOnlineStatus: user.hideOnlineStatus, hasUnreadSpecifiedNotes: this.noteUnreadsRepository.count({ where: { userId: user.id, isSpecified: true }, @@ -484,6 +485,7 @@ export class UserEntityService implements OnModuleInit { isBlocked: relation.isBlocked, isMuted: relation.isMuted, isRenoteMuted: relation.isRenoteMuted, + notify: relation.following?.notify ?? 'none', } : {}), } as Promiseable> as Promiseable>; @@ -491,8 +493,8 @@ export class UserEntityService implements OnModuleInit { } public packMany( - users: (User['id'] | User)[], - me?: { id: User['id'] } | null | undefined, + users: (MiUser['id'] | MiUser)[], + me?: { id: MiUser['id'] } | null | undefined, options?: { detail?: D, includeSecrets?: boolean, diff --git a/packages/backend/src/core/entities/UserListEntityService.ts b/packages/backend/src/core/entities/UserListEntityService.ts index 9e89969d8823..a7f28851943f 100644 --- a/packages/backend/src/core/entities/UserListEntityService.ts +++ b/packages/backend/src/core/entities/UserListEntityService.ts @@ -5,10 +5,10 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; +import type { UserListJoiningsRepository, UserListsRepository } from '@/models/_.js'; import type { Packed } from '@/misc/json-schema.js'; -import type { } from '@/models/entities/Blocking.js'; -import type { UserList } from '@/models/entities/UserList.js'; +import type { } from '@/models/Blocking.js'; +import type { MiUserList } from '@/models/UserList.js'; import { bindThis } from '@/decorators.js'; @Injectable() @@ -24,7 +24,7 @@ export class UserListEntityService { @bindThis public async pack( - src: UserList['id'] | UserList, + src: MiUserList['id'] | MiUserList, ): Promise> { const userList = typeof src === 'object' ? src : await this.userListsRepository.findOneByOrFail({ id: src }); diff --git a/packages/backend/src/daemons/DaemonModule.ts b/packages/backend/src/daemons/DaemonModule.ts index 7543a2ea3d89..236985076cb3 100644 --- a/packages/backend/src/daemons/DaemonModule.ts +++ b/packages/backend/src/daemons/DaemonModule.ts @@ -6,7 +6,6 @@ import { Module } from '@nestjs/common'; import { CoreModule } from '@/core/CoreModule.js'; import { GlobalModule } from '@/GlobalModule.js'; -import { JanitorService } from './JanitorService.js'; import { QueueStatsService } from './QueueStatsService.js'; import { ServerStatsService } from './ServerStatsService.js'; @@ -16,12 +15,10 @@ import { ServerStatsService } from './ServerStatsService.js'; CoreModule, ], providers: [ - JanitorService, QueueStatsService, ServerStatsService, ], exports: [ - JanitorService, QueueStatsService, ServerStatsService, ], diff --git a/packages/backend/src/daemons/JanitorService.ts b/packages/backend/src/daemons/JanitorService.ts deleted file mode 100644 index 6f5e5a7f3bac..000000000000 --- a/packages/backend/src/daemons/JanitorService.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and other misskey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { Inject, Injectable } from '@nestjs/common'; -import { LessThan } from 'typeorm'; -import { DI } from '@/di-symbols.js'; -import type { AttestationChallengesRepository } from '@/models/index.js'; -import { bindThis } from '@/decorators.js'; -import type { OnApplicationShutdown } from '@nestjs/common'; - -const interval = 30 * 60 * 1000; - -@Injectable() -export class JanitorService implements OnApplicationShutdown { - private intervalId: NodeJS.Timer; - - constructor( - @Inject(DI.attestationChallengesRepository) - private attestationChallengesRepository: AttestationChallengesRepository, - ) { - } - - /** - * Clean up database occasionally - */ - @bindThis - public start(): void { - const tick = async () => { - await this.attestationChallengesRepository.delete({ - createdAt: LessThan(new Date(new Date().getTime() - 5 * 60 * 1000)), - }); - }; - - tick(); - - this.intervalId = setInterval(tick, interval); - } - - @bindThis - public dispose(): void { - clearInterval(this.intervalId); - } - - @bindThis - public onApplicationShutdown(signal?: string | undefined): void { - this.dispose(); - } -} diff --git a/packages/backend/src/daemons/QueueStatsService.ts b/packages/backend/src/daemons/QueueStatsService.ts index dc9482f02298..5edc0f45ab61 100644 --- a/packages/backend/src/daemons/QueueStatsService.ts +++ b/packages/backend/src/daemons/QueueStatsService.ts @@ -19,7 +19,7 @@ const interval = 10000; @Injectable() export class QueueStatsService implements OnApplicationShutdown { - private intervalId: NodeJS.Timer; + private intervalId: NodeJS.Timeout; constructor( @Inject(DI.config) diff --git a/packages/backend/src/daemons/ServerStatsService.ts b/packages/backend/src/daemons/ServerStatsService.ts index 95026099e87a..d29462874066 100644 --- a/packages/backend/src/daemons/ServerStatsService.ts +++ b/packages/backend/src/daemons/ServerStatsService.ts @@ -20,7 +20,7 @@ const round = (num: number) => Math.round(num * 10) / 10; @Injectable() export class ServerStatsService implements OnApplicationShutdown { - private intervalId: NodeJS.Timer | null = null; + private intervalId: NodeJS.Timeout | null = null; constructor( private metaService: MetaService, diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index c911f60566d6..72ec98cebe89 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -26,7 +26,6 @@ export const DI = { userProfilesRepository: Symbol('userProfilesRepository'), userKeypairsRepository: Symbol('userKeypairsRepository'), userPendingsRepository: Symbol('userPendingsRepository'), - attestationChallengesRepository: Symbol('attestationChallengesRepository'), userSecurityKeysRepository: Symbol('userSecurityKeysRepository'), userPublickeysRepository: Symbol('userPublickeysRepository'), userListsRepository: Symbol('userListsRepository'), diff --git a/packages/backend/src/logger.ts b/packages/backend/src/logger.ts index 92ea3d13a1b9..5c10559ec6f9 100644 --- a/packages/backend/src/logger.ts +++ b/packages/backend/src/logger.ts @@ -18,6 +18,7 @@ type Context = { type Level = 'error' | 'success' | 'warning' | 'debug' | 'info'; +// eslint-disable-next-line import/no-default-export export default class Logger { private context: Context; private parentLogger: Logger | null = null; diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts index 046d4d6c2f6c..c235871931f9 100644 --- a/packages/backend/src/misc/cache.ts +++ b/packages/backend/src/misc/cache.ts @@ -193,7 +193,7 @@ function nothingToDo(value: T): V { export class MemoryKVCache { public cache: Map; private lifetime: number; - private gcIntervalHandle: NodeJS.Timer; + private gcIntervalHandle: NodeJS.Timeout; private toMapConverter: (value: T) => V; private fromMapConverter: (cached: V) => T | undefined; diff --git a/packages/backend/src/misc/check-word-mute.ts b/packages/backend/src/misc/check-word-mute.ts index 0a8f75c8fd7a..cef559545100 100644 --- a/packages/backend/src/misc/check-word-mute.ts +++ b/packages/backend/src/misc/check-word-mute.ts @@ -5,17 +5,17 @@ import { AhoCorasick } from 'slacc'; import RE2 from 're2'; -import type { Note } from '@/models/entities/Note.js'; -import type { User } from '@/models/entities/User.js'; +import type { MiNote } from '@/models/Note.js'; +import type { MiUser } from '@/models/User.js'; type NoteLike = { - userId: Note['userId']; - text: Note['text']; - cw?: Note['cw']; + userId: MiNote['userId']; + text: MiNote['text']; + cw?: MiNote['cw']; }; type UserLike = { - id: User['id']; + id: MiUser['id']; }; const acCache = new Map(); diff --git a/packages/backend/src/misc/generate-native-user-token.ts b/packages/backend/src/misc/generate-native-user-token.ts index bfde2bc489d3..094c62512076 100644 --- a/packages/backend/src/misc/generate-native-user-token.ts +++ b/packages/backend/src/misc/generate-native-user-token.ts @@ -5,4 +5,5 @@ import { secureRndstr } from '@/misc/secure-rndstr.js'; +// eslint-disable-next-line import/no-default-export export default () => secureRndstr(16); diff --git a/packages/backend/src/misc/get-reaction-emoji.ts b/packages/backend/src/misc/get-reaction-emoji.ts index eac6630968a7..80ef7ff7bc23 100644 --- a/packages/backend/src/misc/get-reaction-emoji.ts +++ b/packages/backend/src/misc/get-reaction-emoji.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +// eslint-disable-next-line import/no-default-export export default function(reaction: string): string { switch (reaction) { case 'like': return '👍'; diff --git a/packages/backend/src/misc/id/aidx.ts b/packages/backend/src/misc/id/aidx.ts new file mode 100644 index 000000000000..5b031ea4c0ce --- /dev/null +++ b/packages/backend/src/misc/id/aidx.ts @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +// AIDX +// 長さ8の[2000年1月1日からの経過ミリ秒をbase36でエンコードしたもの] + 長さ4の[個体ID] + 長さ4の[カウンタ] +// (c) mei23 +// https://misskey.m544.net/notes/71899acdcc9859ec5708ac24 + +import { customAlphabet } from 'nanoid'; + +export const aidxRegExp = /^[0-9a-z]{16}$/; + +const TIME2000 = 946684800000; +const TIME_LENGTH = 8; +const NODE_LENGTH = 4; +const NOISE_LENGTH = 4; + +const nodeId = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', NODE_LENGTH)(); +let counter = 0; + +function getTime(time: number): string { + time = time - TIME2000; + if (time < 0) time = 0; + + return time.toString(36).padStart(TIME_LENGTH, '0').slice(-TIME_LENGTH); +} + +function getNoise(): string { + return counter.toString(36).padStart(NOISE_LENGTH, '0').slice(-NOISE_LENGTH); +} + +export function genAidx(date: Date): string { + const t = date.getTime(); + if (isNaN(t)) throw new Error('Failed to create AIDX: Invalid Date'); + counter++; + return getTime(t) + nodeId + getNoise(); +} + +export function parseAidx(id: string): { date: Date; } { + const time = parseInt(id.slice(0, TIME_LENGTH), 36) + TIME2000; + return { date: new Date(time) }; +} diff --git a/packages/backend/src/misc/is-native-token.ts b/packages/backend/src/misc/is-native-token.ts index 3046e72a346d..618e60b7d870 100644 --- a/packages/backend/src/misc/is-native-token.ts +++ b/packages/backend/src/misc/is-native-token.ts @@ -3,4 +3,5 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +// eslint-disable-next-line import/no-default-export export default (token: string) => token.length === 16; diff --git a/packages/backend/src/misc/is-quote.ts b/packages/backend/src/misc/is-quote.ts index b8d7e9dabd97..059f6a4b5f5d 100644 --- a/packages/backend/src/misc/is-quote.ts +++ b/packages/backend/src/misc/is-quote.ts @@ -3,8 +3,9 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { Note } from '@/models/entities/Note.js'; +import type { MiNote } from '@/models/Note.js'; -export default function(note: Note): boolean { +// eslint-disable-next-line import/no-default-export +export default function(note: MiNote): boolean { return note.renoteId != null && (note.text != null || note.hasPoll || (note.fileIds != null && note.fileIds.length > 0)); } diff --git a/packages/backend/src/models/entities/AbuseUserReport.ts b/packages/backend/src/models/AbuseUserReport.ts similarity index 72% rename from packages/backend/src/models/entities/AbuseUserReport.ts rename to packages/backend/src/models/AbuseUserReport.ts index 8da06e426614..2551af7cb68b 100644 --- a/packages/backend/src/models/entities/AbuseUserReport.ts +++ b/packages/backend/src/models/AbuseUserReport.ts @@ -4,11 +4,11 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() -export class AbuseUserReport { +@Entity('abuse_user_report') +export class MiAbuseUserReport { @PrimaryColumn(id()) public id: string; @@ -20,35 +20,35 @@ export class AbuseUserReport { @Index() @Column(id()) - public targetUserId: User['id']; + public targetUserId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public targetUser: User | null; + public targetUser: MiUser | null; @Index() @Column(id()) - public reporterId: User['id']; + public reporterId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public reporter: User | null; + public reporter: MiUser | null; @Column({ ...id(), nullable: true, }) - public assigneeId: User['id'] | null; + public assigneeId: MiUser['id'] | null; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'SET NULL', }) @JoinColumn() - public assignee: User | null; + public assignee: MiUser | null; @Index() @Column('boolean', { diff --git a/packages/backend/src/models/entities/AccessToken.ts b/packages/backend/src/models/AccessToken.ts similarity index 79% rename from packages/backend/src/models/entities/AccessToken.ts rename to packages/backend/src/models/AccessToken.ts index b2713bb21f2e..5a6269a7297e 100644 --- a/packages/backend/src/models/entities/AccessToken.ts +++ b/packages/backend/src/models/AccessToken.ts @@ -4,12 +4,12 @@ */ import { Entity, PrimaryColumn, Index, Column, ManyToOne, JoinColumn } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; -import { App } from './App.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiApp } from './App.js'; -@Entity() -export class AccessToken { +@Entity('access_token') +export class MiAccessToken { @PrimaryColumn(id()) public id: string; @@ -44,25 +44,25 @@ export class AccessToken { @Index() @Column(id()) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column({ ...id(), nullable: true, }) - public appId: App['id'] | null; + public appId: MiApp['id'] | null; - @ManyToOne(type => App, { + @ManyToOne(type => MiApp, { onDelete: 'CASCADE', }) @JoinColumn() - public app: App | null; + public app: MiApp | null; @Column('varchar', { length: 128, diff --git a/packages/backend/src/models/entities/Ad.ts b/packages/backend/src/models/Ad.ts similarity index 92% rename from packages/backend/src/models/entities/Ad.ts rename to packages/backend/src/models/Ad.ts index 2d99a207699f..6dfc9cb30e41 100644 --- a/packages/backend/src/models/entities/Ad.ts +++ b/packages/backend/src/models/Ad.ts @@ -4,10 +4,10 @@ */ import { Entity, Index, Column, PrimaryColumn } from 'typeorm'; -import { id } from '../id.js'; +import { id } from './util/id.js'; -@Entity() -export class Ad { +@Entity('ad') +export class MiAd { @PrimaryColumn(id()) public id: string; @@ -64,7 +64,7 @@ export class Ad { default: 0, nullable: false, }) public dayOfWeek: number; - constructor(data: Partial) { + constructor(data: Partial) { if (data == null) return; for (const [k, v] of Object.entries(data)) { diff --git a/packages/backend/src/models/entities/Announcement.ts b/packages/backend/src/models/Announcement.ts similarity index 86% rename from packages/backend/src/models/entities/Announcement.ts rename to packages/backend/src/models/Announcement.ts index 18c26faab043..34b092a8d4d9 100644 --- a/packages/backend/src/models/entities/Announcement.ts +++ b/packages/backend/src/models/Announcement.ts @@ -4,11 +4,11 @@ */ import { Entity, Index, Column, PrimaryColumn, ManyToOne, JoinColumn } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() -export class Announcement { +@Entity('announcement') +export class MiAnnouncement { @PrimaryColumn(id()) public id: string; @@ -77,15 +77,15 @@ export class Announcement { ...id(), nullable: true, }) - public userId: User['id'] | null; + public userId: MiUser['id'] | null; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; - constructor(data: Partial) { + constructor(data: Partial) { if (data == null) return; for (const [k, v] of Object.entries(data)) { diff --git a/packages/backend/src/models/entities/AnnouncementRead.ts b/packages/backend/src/models/AnnouncementRead.ts similarity index 58% rename from packages/backend/src/models/entities/AnnouncementRead.ts rename to packages/backend/src/models/AnnouncementRead.ts index 13c90d4608b5..3d6ec5652c1b 100644 --- a/packages/backend/src/models/entities/AnnouncementRead.ts +++ b/packages/backend/src/models/AnnouncementRead.ts @@ -4,13 +4,13 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; -import { Announcement } from './Announcement.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiAnnouncement } from './Announcement.js'; -@Entity() +@Entity('announcement_read') @Index(['userId', 'announcementId'], { unique: true }) -export class AnnouncementRead { +export class MiAnnouncementRead { @PrimaryColumn(id()) public id: string; @@ -21,21 +21,21 @@ export class AnnouncementRead { @Index() @Column(id()) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Index() @Column(id()) - public announcementId: Announcement['id']; + public announcementId: MiAnnouncement['id']; - @ManyToOne(type => Announcement, { + @ManyToOne(type => MiAnnouncement, { onDelete: 'CASCADE', }) @JoinColumn() - public announcement: Announcement | null; + public announcement: MiAnnouncement | null; } diff --git a/packages/backend/src/models/entities/Antenna.ts b/packages/backend/src/models/Antenna.ts similarity index 80% rename from packages/backend/src/models/entities/Antenna.ts rename to packages/backend/src/models/Antenna.ts index 95e246a0fda4..7c1d80cc46ed 100644 --- a/packages/backend/src/models/entities/Antenna.ts +++ b/packages/backend/src/models/Antenna.ts @@ -4,12 +4,12 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; -import { UserList } from './UserList.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiUserList } from './UserList.js'; -@Entity() -export class Antenna { +@Entity('antenna') +export class MiAntenna { @PrimaryColumn(id()) public id: string; @@ -27,13 +27,13 @@ export class Antenna { ...id(), comment: 'The owner ID.', }) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column('varchar', { length: 128, @@ -48,13 +48,13 @@ export class Antenna { ...id(), nullable: true, }) - public userListId: UserList['id'] | null; + public userListId: MiUserList['id'] | null; - @ManyToOne(type => UserList, { + @ManyToOne(type => MiUserList, { onDelete: 'CASCADE', }) @JoinColumn() - public userList: UserList | null; + public userList: MiUserList | null; @Column('varchar', { length: 1024, array: true, diff --git a/packages/backend/src/models/entities/App.ts b/packages/backend/src/models/App.ts similarity index 84% rename from packages/backend/src/models/entities/App.ts rename to packages/backend/src/models/App.ts index 7ed92a7fc747..c599ef8be0cc 100644 --- a/packages/backend/src/models/entities/App.ts +++ b/packages/backend/src/models/App.ts @@ -4,11 +4,11 @@ */ import { Entity, PrimaryColumn, Column, Index, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() -export class App { +@Entity('app') +export class MiApp { @PrimaryColumn(id()) public id: string; @@ -24,13 +24,13 @@ export class App { nullable: true, comment: 'The owner ID.', }) - public userId: User['id'] | null; + public userId: MiUser['id'] | null; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'SET NULL', nullable: true, }) - public user: User | null; + public user: MiUser | null; @Index() @Column('varchar', { diff --git a/packages/backend/src/models/entities/AuthSession.ts b/packages/backend/src/models/AuthSession.ts similarity index 64% rename from packages/backend/src/models/entities/AuthSession.ts rename to packages/backend/src/models/AuthSession.ts index ab34d3d0c30a..d9de6b69799c 100644 --- a/packages/backend/src/models/entities/AuthSession.ts +++ b/packages/backend/src/models/AuthSession.ts @@ -4,12 +4,12 @@ */ import { Entity, PrimaryColumn, Index, Column, ManyToOne, JoinColumn } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; -import { App } from './App.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiApp } from './App.js'; -@Entity() -export class AuthSession { +@Entity('auth_session') +export class MiAuthSession { @PrimaryColumn(id()) public id: string; @@ -28,21 +28,21 @@ export class AuthSession { ...id(), nullable: true, }) - public userId: User['id'] | null; + public userId: MiUser['id'] | null; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', nullable: true, }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column(id()) - public appId: App['id']; + public appId: MiApp['id']; - @ManyToOne(type => App, { + @ManyToOne(type => MiApp, { onDelete: 'CASCADE', }) @JoinColumn() - public app: App | null; + public app: MiApp | null; } diff --git a/packages/backend/src/models/entities/Blocking.ts b/packages/backend/src/models/Blocking.ts similarity index 68% rename from packages/backend/src/models/entities/Blocking.ts rename to packages/backend/src/models/Blocking.ts index ad0686abaf1a..1e3dd3a644b1 100644 --- a/packages/backend/src/models/entities/Blocking.ts +++ b/packages/backend/src/models/Blocking.ts @@ -4,12 +4,12 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() +@Entity('blocking') @Index(['blockerId', 'blockeeId'], { unique: true }) -export class Blocking { +export class MiBlocking { @PrimaryColumn(id()) public id: string; @@ -24,24 +24,24 @@ export class Blocking { ...id(), comment: 'The blockee user ID.', }) - public blockeeId: User['id']; + public blockeeId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public blockee: User | null; + public blockee: MiUser | null; @Index() @Column({ ...id(), comment: 'The blocker user ID.', }) - public blockerId: User['id']; + public blockerId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public blocker: User | null; + public blocker: MiUser | null; } diff --git a/packages/backend/src/models/entities/Channel.ts b/packages/backend/src/models/Channel.ts similarity index 80% rename from packages/backend/src/models/entities/Channel.ts rename to packages/backend/src/models/Channel.ts index 4df8b5aed5d7..ae3886a65701 100644 --- a/packages/backend/src/models/entities/Channel.ts +++ b/packages/backend/src/models/Channel.ts @@ -4,12 +4,12 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; -import { DriveFile } from './DriveFile.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiDriveFile } from './DriveFile.js'; -@Entity() -export class Channel { +@Entity('channel') +export class MiChannel { @PrimaryColumn(id()) public id: string; @@ -31,13 +31,13 @@ export class Channel { nullable: true, comment: 'The owner ID.', }) - public userId: User['id'] | null; + public userId: MiUser['id'] | null; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'SET NULL', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column('varchar', { length: 128, @@ -56,13 +56,13 @@ export class Channel { nullable: true, comment: 'The ID of banner Channel.', }) - public bannerId: DriveFile['id'] | null; + public bannerId: MiDriveFile['id'] | null; - @ManyToOne(type => DriveFile, { + @ManyToOne(type => MiDriveFile, { onDelete: 'SET NULL', }) @JoinColumn() - public banner: DriveFile | null; + public banner: MiDriveFile | null; @Column('varchar', { array: true, length: 128, default: '{}', diff --git a/packages/backend/src/models/entities/ChannelFavorite.ts b/packages/backend/src/models/ChannelFavorite.ts similarity index 61% rename from packages/backend/src/models/entities/ChannelFavorite.ts rename to packages/backend/src/models/ChannelFavorite.ts index 28a53f64e061..ab74aa5530f9 100644 --- a/packages/backend/src/models/entities/ChannelFavorite.ts +++ b/packages/backend/src/models/ChannelFavorite.ts @@ -4,13 +4,13 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; -import { Channel } from './Channel.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiChannel } from './Channel.js'; -@Entity() +@Entity('channel_favorite') @Index(['userId', 'channelId'], { unique: true }) -export class ChannelFavorite { +export class MiChannelFavorite { @PrimaryColumn(id()) public id: string; @@ -24,23 +24,23 @@ export class ChannelFavorite { @Column({ ...id(), }) - public channelId: Channel['id']; + public channelId: MiChannel['id']; - @ManyToOne(type => Channel, { + @ManyToOne(type => MiChannel, { onDelete: 'CASCADE', }) @JoinColumn() - public channel: Channel | null; + public channel: MiChannel | null; @Index() @Column({ ...id(), }) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; } diff --git a/packages/backend/src/models/entities/ChannelFollowing.ts b/packages/backend/src/models/ChannelFollowing.ts similarity index 63% rename from packages/backend/src/models/entities/ChannelFollowing.ts rename to packages/backend/src/models/ChannelFollowing.ts index c862cf3910dc..c62a95332a7e 100644 --- a/packages/backend/src/models/entities/ChannelFollowing.ts +++ b/packages/backend/src/models/ChannelFollowing.ts @@ -4,13 +4,13 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; -import { Channel } from './Channel.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiChannel } from './Channel.js'; -@Entity() +@Entity('channel_following') @Index(['followerId', 'followeeId'], { unique: true }) -export class ChannelFollowing { +export class MiChannelFollowing { @PrimaryColumn(id()) public id: string; @@ -25,24 +25,24 @@ export class ChannelFollowing { ...id(), comment: 'The followee channel ID.', }) - public followeeId: Channel['id']; + public followeeId: MiChannel['id']; - @ManyToOne(type => Channel, { + @ManyToOne(type => MiChannel, { onDelete: 'CASCADE', }) @JoinColumn() - public followee: Channel | null; + public followee: MiChannel | null; @Index() @Column({ ...id(), comment: 'The follower user ID.', }) - public followerId: User['id']; + public followerId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public follower: User | null; + public follower: MiUser | null; } diff --git a/packages/backend/src/models/entities/Clip.ts b/packages/backend/src/models/Clip.ts similarity index 81% rename from packages/backend/src/models/entities/Clip.ts rename to packages/backend/src/models/Clip.ts index 814e8231ed8c..c60b2964e012 100644 --- a/packages/backend/src/models/entities/Clip.ts +++ b/packages/backend/src/models/Clip.ts @@ -4,11 +4,11 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() -export class Clip { +@Entity('clip') +export class MiClip { @PrimaryColumn(id()) public id: string; @@ -28,13 +28,13 @@ export class Clip { ...id(), comment: 'The owner ID.', }) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column('varchar', { length: 128, diff --git a/packages/backend/src/models/entities/ClipFavorite.ts b/packages/backend/src/models/ClipFavorite.ts similarity index 58% rename from packages/backend/src/models/entities/ClipFavorite.ts rename to packages/backend/src/models/ClipFavorite.ts index e0ec261cb4ed..054764389b80 100644 --- a/packages/backend/src/models/entities/ClipFavorite.ts +++ b/packages/backend/src/models/ClipFavorite.ts @@ -4,13 +4,13 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; -import { Clip } from './Clip.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiClip } from './Clip.js'; -@Entity() +@Entity('clip_favorite') @Index(['userId', 'clipId'], { unique: true }) -export class ClipFavorite { +export class MiClipFavorite { @PrimaryColumn(id()) public id: string; @@ -19,20 +19,20 @@ export class ClipFavorite { @Index() @Column(id()) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column(id()) - public clipId: Clip['id']; + public clipId: MiClip['id']; - @ManyToOne(type => Clip, { + @ManyToOne(type => MiClip, { onDelete: 'CASCADE', }) @JoinColumn() - public clip: Clip | null; + public clip: MiClip | null; } diff --git a/packages/backend/src/models/entities/ClipNote.ts b/packages/backend/src/models/ClipNote.ts similarity index 60% rename from packages/backend/src/models/entities/ClipNote.ts rename to packages/backend/src/models/ClipNote.ts index c4028163fbd7..b7cc5ee39bf2 100644 --- a/packages/backend/src/models/entities/ClipNote.ts +++ b/packages/backend/src/models/ClipNote.ts @@ -4,13 +4,13 @@ */ import { Entity, Index, JoinColumn, Column, ManyToOne, PrimaryColumn } from 'typeorm'; -import { id } from '../id.js'; -import { Note } from './Note.js'; -import { Clip } from './Clip.js'; +import { id } from './util/id.js'; +import { MiNote } from './Note.js'; +import { MiClip } from './Clip.js'; -@Entity() +@Entity('clip_note') @Index(['noteId', 'clipId'], { unique: true }) -export class ClipNote { +export class MiClipNote { @PrimaryColumn(id()) public id: string; @@ -19,24 +19,24 @@ export class ClipNote { ...id(), comment: 'The note ID.', }) - public noteId: Note['id']; + public noteId: MiNote['id']; - @ManyToOne(type => Note, { + @ManyToOne(type => MiNote, { onDelete: 'CASCADE', }) @JoinColumn() - public note: Note | null; + public note: MiNote | null; @Index() @Column({ ...id(), comment: 'The clip ID.', }) - public clipId: Clip['id']; + public clipId: MiClip['id']; - @ManyToOne(type => Clip, { + @ManyToOne(type => MiClip, { onDelete: 'CASCADE', }) @JoinColumn() - public clip: Clip | null; + public clip: MiClip | null; } diff --git a/packages/backend/src/models/entities/DriveFile.ts b/packages/backend/src/models/DriveFile.ts similarity index 90% rename from packages/backend/src/models/entities/DriveFile.ts rename to packages/backend/src/models/DriveFile.ts index 7c70f5503aa7..c12f0e0f0211 100644 --- a/packages/backend/src/models/entities/DriveFile.ts +++ b/packages/backend/src/models/DriveFile.ts @@ -4,13 +4,13 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; -import { DriveFolder } from './DriveFolder.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiDriveFolder } from './DriveFolder.js'; -@Entity() +@Entity('drive_file') @Index(['userId', 'folderId', 'id']) -export class DriveFile { +export class MiDriveFile { @PrimaryColumn(id()) public id: string; @@ -26,13 +26,13 @@ export class DriveFile { nullable: true, comment: 'The owner ID.', }) - public userId: User['id'] | null; + public userId: MiUser['id'] | null; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'SET NULL', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Index() @Column('varchar', { @@ -146,13 +146,13 @@ export class DriveFile { nullable: true, comment: 'The parent folder ID. If null, it means the DriveFile is located in root.', }) - public folderId: DriveFolder['id'] | null; + public folderId: MiDriveFolder['id'] | null; - @ManyToOne(type => DriveFolder, { + @ManyToOne(type => MiDriveFolder, { onDelete: 'SET NULL', }) @JoinColumn() - public folder: DriveFolder | null; + public folder: MiDriveFolder | null; @Index() @Column('boolean', { diff --git a/packages/backend/src/models/entities/DriveFolder.ts b/packages/backend/src/models/DriveFolder.ts similarity index 70% rename from packages/backend/src/models/entities/DriveFolder.ts rename to packages/backend/src/models/DriveFolder.ts index 216279d3c561..3e049136bd26 100644 --- a/packages/backend/src/models/entities/DriveFolder.ts +++ b/packages/backend/src/models/DriveFolder.ts @@ -4,11 +4,11 @@ */ import { JoinColumn, ManyToOne, Entity, PrimaryColumn, Index, Column } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() -export class DriveFolder { +@Entity('drive_folder') +export class MiDriveFolder { @PrimaryColumn(id()) public id: string; @@ -30,13 +30,13 @@ export class DriveFolder { nullable: true, comment: 'The owner ID.', }) - public userId: User['id'] | null; + public userId: MiUser['id'] | null; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Index() @Column({ @@ -44,11 +44,11 @@ export class DriveFolder { nullable: true, comment: 'The parent folder ID. If null, it means the DriveFolder is located in root.', }) - public parentId: DriveFolder['id'] | null; + public parentId: MiDriveFolder['id'] | null; - @ManyToOne(type => DriveFolder, { + @ManyToOne(type => MiDriveFolder, { onDelete: 'SET NULL', }) @JoinColumn() - public parent: DriveFolder | null; + public parent: MiDriveFolder | null; } diff --git a/packages/backend/src/models/entities/Emoji.ts b/packages/backend/src/models/Emoji.ts similarity index 95% rename from packages/backend/src/models/entities/Emoji.ts rename to packages/backend/src/models/Emoji.ts index 8c42045998d4..563ac1d9d306 100644 --- a/packages/backend/src/models/entities/Emoji.ts +++ b/packages/backend/src/models/Emoji.ts @@ -4,11 +4,11 @@ */ import { PrimaryColumn, Entity, Index, Column } from 'typeorm'; -import { id } from '../id.js'; +import { id } from './util/id.js'; -@Entity() +@Entity('emoji') @Index(['name', 'host'], { unique: true }) -export class Emoji { +export class MiEmoji { @PrimaryColumn(id()) public id: string; diff --git a/packages/backend/src/models/entities/Flash.ts b/packages/backend/src/models/Flash.ts similarity index 71% rename from packages/backend/src/models/entities/Flash.ts rename to packages/backend/src/models/Flash.ts index 7443671838ad..185063029d88 100644 --- a/packages/backend/src/models/entities/Flash.ts +++ b/packages/backend/src/models/Flash.ts @@ -4,11 +4,11 @@ */ import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() -export class Flash { +@Entity('flash') +export class MiFlash { @PrimaryColumn(id()) public id: string; @@ -39,13 +39,13 @@ export class Flash { ...id(), comment: 'The ID of author.', }) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column('varchar', { length: 65536, @@ -61,4 +61,13 @@ export class Flash { default: 0, }) public likedCount: number; + + /** + * public ... 公開 + * private ... プロフィールには表示しない + */ + @Column('varchar', { + length: 512, default: 'public', + }) + public visibility: 'public' | 'private'; } diff --git a/packages/backend/src/models/entities/FlashLike.ts b/packages/backend/src/models/FlashLike.ts similarity index 58% rename from packages/backend/src/models/entities/FlashLike.ts rename to packages/backend/src/models/FlashLike.ts index f9890742d49b..7c66010ae62f 100644 --- a/packages/backend/src/models/entities/FlashLike.ts +++ b/packages/backend/src/models/FlashLike.ts @@ -4,13 +4,13 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; -import { Flash } from './Flash.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiFlash } from './Flash.js'; -@Entity() +@Entity('flash_like') @Index(['userId', 'flashId'], { unique: true }) -export class FlashLike { +export class MiFlashLike { @PrimaryColumn(id()) public id: string; @@ -19,20 +19,20 @@ export class FlashLike { @Index() @Column(id()) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column(id()) - public flashId: Flash['id']; + public flashId: MiFlash['id']; - @ManyToOne(type => Flash, { + @ManyToOne(type => MiFlash, { onDelete: 'CASCADE', }) @JoinColumn() - public flash: Flash | null; + public flash: MiFlash | null; } diff --git a/packages/backend/src/models/entities/FollowRequest.ts b/packages/backend/src/models/FollowRequest.ts similarity index 83% rename from packages/backend/src/models/entities/FollowRequest.ts rename to packages/backend/src/models/FollowRequest.ts index 1dadddbdfde4..769b9a6cb594 100644 --- a/packages/backend/src/models/entities/FollowRequest.ts +++ b/packages/backend/src/models/FollowRequest.ts @@ -4,12 +4,12 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() +@Entity('follow_request') @Index(['followerId', 'followeeId'], { unique: true }) -export class FollowRequest { +export class MiFollowRequest { @PrimaryColumn(id()) public id: string; @@ -23,26 +23,26 @@ export class FollowRequest { ...id(), comment: 'The followee user ID.', }) - public followeeId: User['id']; + public followeeId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public followee: User | null; + public followee: MiUser | null; @Index() @Column({ ...id(), comment: 'The follower user ID.', }) - public followerId: User['id']; + public followerId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public follower: User | null; + public follower: MiUser | null; @Column('varchar', { length: 128, nullable: true, diff --git a/packages/backend/src/models/entities/Following.ts b/packages/backend/src/models/Following.ts similarity index 78% rename from packages/backend/src/models/entities/Following.ts rename to packages/backend/src/models/Following.ts index 5230e40592b1..8c9f965fadd4 100644 --- a/packages/backend/src/models/entities/Following.ts +++ b/packages/backend/src/models/Following.ts @@ -4,12 +4,12 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() +@Entity('following') @Index(['followerId', 'followeeId'], { unique: true }) -export class Following { +export class MiFollowing { @PrimaryColumn(id()) public id: string; @@ -24,26 +24,33 @@ export class Following { ...id(), comment: 'The followee user ID.', }) - public followeeId: User['id']; + public followeeId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public followee: User | null; + public followee: MiUser | null; @Index() @Column({ ...id(), comment: 'The follower user ID.', }) - public followerId: User['id']; + public followerId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public follower: User | null; + public follower: MiUser | null; + + @Index() + @Column('varchar', { + length: 32, + nullable: true, + }) + public notify: 'normal' | null; //#region Denormalized fields @Index() diff --git a/packages/backend/src/models/entities/GalleryLike.ts b/packages/backend/src/models/GalleryLike.ts similarity index 56% rename from packages/backend/src/models/entities/GalleryLike.ts rename to packages/backend/src/models/GalleryLike.ts index 9db4de596dbf..b5f71764aa74 100644 --- a/packages/backend/src/models/entities/GalleryLike.ts +++ b/packages/backend/src/models/GalleryLike.ts @@ -4,13 +4,13 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; -import { GalleryPost } from './GalleryPost.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiGalleryPost } from './GalleryPost.js'; -@Entity() +@Entity('gallery_like') @Index(['userId', 'postId'], { unique: true }) -export class GalleryLike { +export class MiGalleryLike { @PrimaryColumn(id()) public id: string; @@ -19,20 +19,20 @@ export class GalleryLike { @Index() @Column(id()) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column(id()) - public postId: GalleryPost['id']; + public postId: MiGalleryPost['id']; - @ManyToOne(type => GalleryPost, { + @ManyToOne(type => MiGalleryPost, { onDelete: 'CASCADE', }) @JoinColumn() - public post: GalleryPost | null; + public post: MiGalleryPost | null; } diff --git a/packages/backend/src/models/entities/GalleryPost.ts b/packages/backend/src/models/GalleryPost.ts similarity index 78% rename from packages/backend/src/models/entities/GalleryPost.ts rename to packages/backend/src/models/GalleryPost.ts index 455cb4b618d6..4c6063f32bfa 100644 --- a/packages/backend/src/models/entities/GalleryPost.ts +++ b/packages/backend/src/models/GalleryPost.ts @@ -4,12 +4,12 @@ */ import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; -import type { DriveFile } from './DriveFile.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import type { MiDriveFile } from './DriveFile.js'; -@Entity() -export class GalleryPost { +@Entity('gallery_post') +export class MiGalleryPost { @PrimaryColumn(id()) public id: string; @@ -40,20 +40,20 @@ export class GalleryPost { ...id(), comment: 'The ID of author.', }) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Index() @Column({ ...id(), array: true, default: '{}', }) - public fileIds: DriveFile['id'][]; + public fileIds: MiDriveFile['id'][]; @Index() @Column('boolean', { @@ -74,7 +74,7 @@ export class GalleryPost { }) public tags: string[]; - constructor(data: Partial) { + constructor(data: Partial) { if (data == null) return; for (const [k, v] of Object.entries(data)) { diff --git a/packages/backend/src/models/entities/Hashtag.ts b/packages/backend/src/models/Hashtag.ts similarity index 74% rename from packages/backend/src/models/entities/Hashtag.ts rename to packages/backend/src/models/Hashtag.ts index 39e78d58f006..149377475241 100644 --- a/packages/backend/src/models/entities/Hashtag.ts +++ b/packages/backend/src/models/Hashtag.ts @@ -4,11 +4,11 @@ */ import { Entity, PrimaryColumn, Index, Column } from 'typeorm'; -import { id } from '../id.js'; -import type { User } from './User.js'; +import { id } from './util/id.js'; +import type { MiUser } from './User.js'; -@Entity() -export class Hashtag { +@Entity('hashtag') +export class MiHashtag { @PrimaryColumn(id()) public id: string; @@ -22,7 +22,7 @@ export class Hashtag { ...id(), array: true, }) - public mentionedUserIds: User['id'][]; + public mentionedUserIds: MiUser['id'][]; @Index() @Column('integer', { @@ -34,7 +34,7 @@ export class Hashtag { ...id(), array: true, }) - public mentionedLocalUserIds: User['id'][]; + public mentionedLocalUserIds: MiUser['id'][]; @Index() @Column('integer', { @@ -46,7 +46,7 @@ export class Hashtag { ...id(), array: true, }) - public mentionedRemoteUserIds: User['id'][]; + public mentionedRemoteUserIds: MiUser['id'][]; @Index() @Column('integer', { @@ -58,7 +58,7 @@ export class Hashtag { ...id(), array: true, }) - public attachedUserIds: User['id'][]; + public attachedUserIds: MiUser['id'][]; @Index() @Column('integer', { @@ -70,7 +70,7 @@ export class Hashtag { ...id(), array: true, }) - public attachedLocalUserIds: User['id'][]; + public attachedLocalUserIds: MiUser['id'][]; @Index() @Column('integer', { @@ -82,7 +82,7 @@ export class Hashtag { ...id(), array: true, }) - public attachedRemoteUserIds: User['id'][]; + public attachedRemoteUserIds: MiUser['id'][]; @Index() @Column('integer', { diff --git a/packages/backend/src/models/entities/Instance.ts b/packages/backend/src/models/Instance.ts similarity index 97% rename from packages/backend/src/models/entities/Instance.ts rename to packages/backend/src/models/Instance.ts index 67f8dfe5deda..b225d918d641 100644 --- a/packages/backend/src/models/entities/Instance.ts +++ b/packages/backend/src/models/Instance.ts @@ -4,10 +4,10 @@ */ import { Entity, PrimaryColumn, Index, Column } from 'typeorm'; -import { id } from '../id.js'; +import { id } from './util/id.js'; -@Entity() -export class Instance { +@Entity('instance') +export class MiInstance { @PrimaryColumn(id()) public id: string; diff --git a/packages/backend/src/models/entities/Meta.ts b/packages/backend/src/models/Meta.ts similarity index 94% rename from packages/backend/src/models/entities/Meta.ts rename to packages/backend/src/models/Meta.ts index a81defae0f60..a9e116341f50 100644 --- a/packages/backend/src/models/entities/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -4,11 +4,11 @@ */ import { Entity, Column, PrimaryColumn, ManyToOne, JoinColumn } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() -export class Meta { +@Entity('meta') +export class MiMeta { @PrimaryColumn({ type: 'varchar', length: 32, @@ -107,6 +107,18 @@ export class Meta { }) public iconUrl: string | null; + @Column('varchar', { + length: 1024, + nullable: true, + }) + public app192IconUrl: string | null; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public app512IconUrl: string | null; + @Column('varchar', { length: 1024, nullable: true, @@ -139,13 +151,13 @@ export class Meta { ...id(), nullable: true, }) - public proxyAccountId: User['id'] | null; + public proxyAccountId: MiUser['id'] | null; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'SET NULL', }) @JoinColumn() - public proxyAccount: User | null; + public proxyAccount: MiUser | null; @Column('boolean', { default: false, @@ -444,6 +456,12 @@ export class Meta { }) public serverRules: string[]; + @Column('varchar', { + length: 8192, + default: '{}', + }) + public manifestJsonOverride: string; + @Column('varchar', { length: 1024, array: true, default: '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }', }) diff --git a/packages/backend/src/models/entities/ModerationLog.ts b/packages/backend/src/models/ModerationLog.ts similarity index 71% rename from packages/backend/src/models/entities/ModerationLog.ts rename to packages/backend/src/models/ModerationLog.ts index caf0ecfaef91..a12b6ab614f3 100644 --- a/packages/backend/src/models/entities/ModerationLog.ts +++ b/packages/backend/src/models/ModerationLog.ts @@ -4,11 +4,11 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() -export class ModerationLog { +@Entity('moderation_log') +export class MiModerationLog { @PrimaryColumn(id()) public id: string; @@ -19,13 +19,13 @@ export class ModerationLog { @Index() @Column(id()) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column('varchar', { length: 128, diff --git a/packages/backend/src/models/entities/MutedNote.ts b/packages/backend/src/models/MutedNote.ts similarity index 64% rename from packages/backend/src/models/entities/MutedNote.ts rename to packages/backend/src/models/MutedNote.ts index 3495bd2ebb70..89a678a2a778 100644 --- a/packages/backend/src/models/entities/MutedNote.ts +++ b/packages/backend/src/models/MutedNote.ts @@ -4,14 +4,14 @@ */ import { Entity, Index, JoinColumn, Column, ManyToOne, PrimaryColumn } from 'typeorm'; -import { id } from '../id.js'; -import { mutedNoteReasons } from '../../types.js'; -import { Note } from './Note.js'; -import { User } from './User.js'; +import { mutedNoteReasons } from '@/types.js'; +import { id } from './util/id.js'; +import { MiNote } from './Note.js'; +import { MiUser } from './User.js'; -@Entity() +@Entity('muted_note') @Index(['noteId', 'userId'], { unique: true }) -export class MutedNote { +export class MiMutedNote { @PrimaryColumn(id()) public id: string; @@ -20,26 +20,26 @@ export class MutedNote { ...id(), comment: 'The note ID.', }) - public noteId: Note['id']; + public noteId: MiNote['id']; - @ManyToOne(type => Note, { + @ManyToOne(type => MiNote, { onDelete: 'CASCADE', }) @JoinColumn() - public note: Note | null; + public note: MiNote | null; @Index() @Column({ ...id(), comment: 'The user ID.', }) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; /** * ミュートされた理由。 diff --git a/packages/backend/src/models/entities/Muting.ts b/packages/backend/src/models/Muting.ts similarity index 71% rename from packages/backend/src/models/entities/Muting.ts rename to packages/backend/src/models/Muting.ts index c55004122dc2..2f06ca8e5e25 100644 --- a/packages/backend/src/models/entities/Muting.ts +++ b/packages/backend/src/models/Muting.ts @@ -4,12 +4,12 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() +@Entity('muting') @Index(['muterId', 'muteeId'], { unique: true }) -export class Muting { +export class MiMuting { @PrimaryColumn(id()) public id: string; @@ -30,24 +30,24 @@ export class Muting { ...id(), comment: 'The mutee user ID.', }) - public muteeId: User['id']; + public muteeId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public mutee: User | null; + public mutee: MiUser | null; @Index() @Column({ ...id(), comment: 'The muter user ID.', }) - public muterId: User['id']; + public muterId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public muter: User | null; + public muter: MiUser | null; } diff --git a/packages/backend/src/models/entities/Note.ts b/packages/backend/src/models/Note.ts similarity index 81% rename from packages/backend/src/models/entities/Note.ts rename to packages/backend/src/models/Note.ts index c9b69087a6ef..ed86d4549e63 100644 --- a/packages/backend/src/models/entities/Note.ts +++ b/packages/backend/src/models/Note.ts @@ -4,17 +4,17 @@ */ import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { noteVisibilities } from '../../types.js'; -import { User } from './User.js'; -import { Channel } from './Channel.js'; -import type { DriveFile } from './DriveFile.js'; +import { noteVisibilities } from '@/types.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiChannel } from './Channel.js'; +import type { MiDriveFile } from './DriveFile.js'; -@Entity() +@Entity('note') @Index('IDX_NOTE_TAGS', { synchronize: false }) @Index('IDX_NOTE_MENTIONS', { synchronize: false }) @Index('IDX_NOTE_VISIBLE_USER_IDS', { synchronize: false }) -export class Note { +export class MiNote { @PrimaryColumn(id()) public id: string; @@ -30,13 +30,13 @@ export class Note { nullable: true, comment: 'The ID of reply target.', }) - public replyId: Note['id'] | null; + public replyId: MiNote['id'] | null; - @ManyToOne(type => Note, { + @ManyToOne(type => MiNote, { onDelete: 'CASCADE', }) @JoinColumn() - public reply: Note | null; + public reply: MiNote | null; @Index() @Column({ @@ -44,13 +44,13 @@ export class Note { nullable: true, comment: 'The ID of renote target.', }) - public renoteId: Note['id'] | null; + public renoteId: MiNote['id'] | null; - @ManyToOne(type => Note, { + @ManyToOne(type => MiNote, { onDelete: 'CASCADE', }) @JoinColumn() - public renote: Note | null; + public renote: MiNote | null; @Index() @Column('varchar', { @@ -79,13 +79,13 @@ export class Note { ...id(), comment: 'The ID of author.', }) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column('boolean', { default: false, @@ -107,6 +107,11 @@ export class Note { }) public repliesCount: number; + @Column('smallint', { + default: 0, + }) + public clippedCount: number; + @Column('jsonb', { default: {}, }) @@ -144,7 +149,7 @@ export class Note { ...id(), array: true, default: '{}', }) - public fileIds: DriveFile['id'][]; + public fileIds: MiDriveFile['id'][]; @Index() @Column('varchar', { @@ -157,14 +162,14 @@ export class Note { ...id(), array: true, default: '{}', }) - public visibleUserIds: User['id'][]; + public visibleUserIds: MiUser['id'][]; @Index() @Column({ ...id(), array: true, default: '{}', }) - public mentions: User['id'][]; + public mentions: MiUser['id'][]; @Column('text', { default: '[]', @@ -193,13 +198,13 @@ export class Note { nullable: true, comment: 'The ID of source channel.', }) - public channelId: Channel['id'] | null; + public channelId: MiChannel['id'] | null; - @ManyToOne(type => Channel, { + @ManyToOne(type => MiChannel, { onDelete: 'CASCADE', }) @JoinColumn() - public channel: Channel | null; + public channel: MiChannel | null; //#region Denormalized fields @Index() @@ -214,7 +219,7 @@ export class Note { nullable: true, comment: '[Denormalized]', }) - public replyUserId: User['id'] | null; + public replyUserId: MiUser['id'] | null; @Column('varchar', { length: 128, nullable: true, @@ -227,7 +232,7 @@ export class Note { nullable: true, comment: '[Denormalized]', }) - public renoteUserId: User['id'] | null; + public renoteUserId: MiUser['id'] | null; @Column('varchar', { length: 128, nullable: true, @@ -236,7 +241,7 @@ export class Note { public renoteUserHost: string | null; //#endregion - constructor(data: Partial) { + constructor(data: Partial) { if (data == null) return; for (const [k, v] of Object.entries(data)) { diff --git a/packages/backend/src/models/entities/NoteFavorite.ts b/packages/backend/src/models/NoteFavorite.ts similarity index 61% rename from packages/backend/src/models/entities/NoteFavorite.ts rename to packages/backend/src/models/NoteFavorite.ts index 30b213067d58..1171684bcf06 100644 --- a/packages/backend/src/models/entities/NoteFavorite.ts +++ b/packages/backend/src/models/NoteFavorite.ts @@ -4,13 +4,13 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { Note } from './Note.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiNote } from './Note.js'; +import { MiUser } from './User.js'; -@Entity() +@Entity('note_favorite') @Index(['userId', 'noteId'], { unique: true }) -export class NoteFavorite { +export class MiNoteFavorite { @PrimaryColumn(id()) public id: string; @@ -21,20 +21,20 @@ export class NoteFavorite { @Index() @Column(id()) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column(id()) - public noteId: Note['id']; + public noteId: MiNote['id']; - @ManyToOne(type => Note, { + @ManyToOne(type => MiNote, { onDelete: 'CASCADE', }) @JoinColumn() - public note: Note | null; + public note: MiNote | null; } diff --git a/packages/backend/src/models/entities/NoteReaction.ts b/packages/backend/src/models/NoteReaction.ts similarity index 69% rename from packages/backend/src/models/entities/NoteReaction.ts rename to packages/backend/src/models/NoteReaction.ts index 7c76e0759946..7c08d31c6d78 100644 --- a/packages/backend/src/models/entities/NoteReaction.ts +++ b/packages/backend/src/models/NoteReaction.ts @@ -4,13 +4,13 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; -import { Note } from './Note.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiNote } from './Note.js'; -@Entity() +@Entity('note_reaction') @Index(['userId', 'noteId'], { unique: true }) -export class NoteReaction { +export class MiNoteReaction { @PrimaryColumn(id()) public id: string; @@ -22,23 +22,23 @@ export class NoteReaction { @Index() @Column(id()) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user?: User | null; + public user?: MiUser | null; @Index() @Column(id()) - public noteId: Note['id']; + public noteId: MiNote['id']; - @ManyToOne(type => Note, { + @ManyToOne(type => MiNote, { onDelete: 'CASCADE', }) @JoinColumn() - public note?: Note | null; + public note?: MiNote | null; // TODO: 対象noteのuserIdを非正規化したい(「受け取ったリアクション一覧」のようなものを(JOIN無しで)実装したいため) diff --git a/packages/backend/src/models/entities/NoteThreadMuting.ts b/packages/backend/src/models/NoteThreadMuting.ts similarity index 70% rename from packages/backend/src/models/entities/NoteThreadMuting.ts rename to packages/backend/src/models/NoteThreadMuting.ts index 5d891beaf24b..2d120e4c25e3 100644 --- a/packages/backend/src/models/entities/NoteThreadMuting.ts +++ b/packages/backend/src/models/NoteThreadMuting.ts @@ -4,12 +4,12 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() +@Entity('note_thread_muting') @Index(['userId', 'threadId'], { unique: true }) -export class NoteThreadMuting { +export class MiNoteThreadMuting { @PrimaryColumn(id()) public id: string; @@ -21,13 +21,13 @@ export class NoteThreadMuting { @Column({ ...id(), }) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Index() @Column('varchar', { diff --git a/packages/backend/src/models/entities/NoteUnread.ts b/packages/backend/src/models/NoteUnread.ts similarity index 64% rename from packages/backend/src/models/entities/NoteUnread.ts rename to packages/backend/src/models/NoteUnread.ts index 91ff52f64c67..d86a47455387 100644 --- a/packages/backend/src/models/entities/NoteUnread.ts +++ b/packages/backend/src/models/NoteUnread.ts @@ -4,36 +4,36 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; -import { Note } from './Note.js'; -import type { Channel } from './Channel.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiNote } from './Note.js'; +import type { MiChannel } from './Channel.js'; -@Entity() +@Entity('note_unread') @Index(['userId', 'noteId'], { unique: true }) -export class NoteUnread { +export class MiNoteUnread { @PrimaryColumn(id()) public id: string; @Index() @Column(id()) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Index() @Column(id()) - public noteId: Note['id']; + public noteId: MiNote['id']; - @ManyToOne(type => Note, { + @ManyToOne(type => MiNote, { onDelete: 'CASCADE', }) @JoinColumn() - public note: Note | null; + public note: MiNote | null; /** * メンションか否か @@ -55,7 +55,7 @@ export class NoteUnread { ...id(), comment: '[Denormalized]', }) - public noteUserId: User['id']; + public noteUserId: MiUser['id']; @Index() @Column({ @@ -63,6 +63,6 @@ export class NoteUnread { nullable: true, comment: '[Denormalized]', }) - public noteChannelId: Channel['id'] | null; + public noteChannelId: MiChannel['id'] | null; //#endregion } diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts new file mode 100644 index 000000000000..c0a9df2e23b2 --- /dev/null +++ b/packages/backend/src/models/Notification.ts @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { notificationTypes } from '@/types.js'; +import { MiUser } from './User.js'; +import { MiNote } from './Note.js'; +import { MiFollowRequest } from './FollowRequest.js'; +import { MiAccessToken } from './AccessToken.js'; + +export type MiNotification = { + id: string; + + // RedisのためDateではなくstring + createdAt: string; + + /** + * 通知の送信者(initiator) + */ + notifierId: MiUser['id'] | null; + + /** + * 通知の種類。 + */ + type: typeof notificationTypes[number]; + + noteId: MiNote['id'] | null; + + followRequestId: MiFollowRequest['id'] | null; + + reaction: string | null; + + choice: number | null; + + achievement: string | null; + + /** + * アプリ通知のbody + */ + customBody: string | null; + + /** + * アプリ通知のheader + * (省略時はアプリ名で表示されることを期待) + */ + customHeader: string | null; + + /** + * アプリ通知のicon(URL) + * (省略時はアプリアイコンで表示されることを期待) + */ + customIcon: string | null; + + /** + * アプリ通知のアプリ(のトークン) + */ + appAccessTokenId: MiAccessToken['id'] | null; +} diff --git a/packages/backend/src/models/entities/Page.ts b/packages/backend/src/models/Page.ts similarity index 80% rename from packages/backend/src/models/entities/Page.ts rename to packages/backend/src/models/Page.ts index 089e9c133b9d..3cb986f4eea0 100644 --- a/packages/backend/src/models/entities/Page.ts +++ b/packages/backend/src/models/Page.ts @@ -4,13 +4,13 @@ */ import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; -import { DriveFile } from './DriveFile.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiDriveFile } from './DriveFile.js'; -@Entity() +@Entity('page') @Index(['userId', 'name'], { unique: true }) -export class Page { +export class MiPage { @PrimaryColumn(id()) public id: string; @@ -60,25 +60,25 @@ export class Page { ...id(), comment: 'The ID of author.', }) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column({ ...id(), nullable: true, }) - public eyeCatchingImageId: DriveFile['id'] | null; + public eyeCatchingImageId: MiDriveFile['id'] | null; - @ManyToOne(type => DriveFile, { + @ManyToOne(type => MiDriveFile, { onDelete: 'CASCADE', }) @JoinColumn() - public eyeCatchingImage: DriveFile | null; + public eyeCatchingImage: MiDriveFile | null; @Column('jsonb', { default: [], @@ -109,14 +109,14 @@ export class Page { ...id(), array: true, default: '{}', }) - public visibleUserIds: User['id'][]; + public visibleUserIds: MiUser['id'][]; @Column('integer', { default: 0, }) public likedCount: number; - constructor(data: Partial) { + constructor(data: Partial) { if (data == null) return; for (const [k, v] of Object.entries(data)) { diff --git a/packages/backend/src/models/entities/PageLike.ts b/packages/backend/src/models/PageLike.ts similarity index 59% rename from packages/backend/src/models/entities/PageLike.ts rename to packages/backend/src/models/PageLike.ts index 494c878b52c4..92adf9bcc2cf 100644 --- a/packages/backend/src/models/entities/PageLike.ts +++ b/packages/backend/src/models/PageLike.ts @@ -4,13 +4,13 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; -import { Page } from './Page.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiPage } from './Page.js'; -@Entity() +@Entity('page_like') @Index(['userId', 'pageId'], { unique: true }) -export class PageLike { +export class MiPageLike { @PrimaryColumn(id()) public id: string; @@ -19,20 +19,20 @@ export class PageLike { @Index() @Column(id()) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column(id()) - public pageId: Page['id']; + public pageId: MiPage['id']; - @ManyToOne(type => Page, { + @ManyToOne(type => MiPage, { onDelete: 'CASCADE', }) @JoinColumn() - public page: Page | null; + public page: MiPage | null; } diff --git a/packages/backend/src/models/entities/PasswordResetRequest.ts b/packages/backend/src/models/PasswordResetRequest.ts similarity index 67% rename from packages/backend/src/models/entities/PasswordResetRequest.ts rename to packages/backend/src/models/PasswordResetRequest.ts index 4804c703bc5c..79f2e984b821 100644 --- a/packages/backend/src/models/entities/PasswordResetRequest.ts +++ b/packages/backend/src/models/PasswordResetRequest.ts @@ -4,11 +4,11 @@ */ import { PrimaryColumn, Entity, Index, Column, ManyToOne, JoinColumn } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() -export class PasswordResetRequest { +@Entity('password_reset_request') +export class MiPasswordResetRequest { @PrimaryColumn(id()) public id: string; @@ -25,11 +25,11 @@ export class PasswordResetRequest { @Column({ ...id(), }) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; } diff --git a/packages/backend/src/models/entities/Poll.ts b/packages/backend/src/models/Poll.ts similarity index 76% rename from packages/backend/src/models/entities/Poll.ts rename to packages/backend/src/models/Poll.ts index 1e86fb67a07c..5ce0b9a2fc42 100644 --- a/packages/backend/src/models/entities/Poll.ts +++ b/packages/backend/src/models/Poll.ts @@ -4,21 +4,21 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'typeorm'; -import { id } from '../id.js'; -import { noteVisibilities } from '../../types.js'; -import { Note } from './Note.js'; -import type { User } from './User.js'; +import { noteVisibilities } from '@/types.js'; +import { id } from './util/id.js'; +import { MiNote } from './Note.js'; +import type { MiUser } from './User.js'; -@Entity() -export class Poll { +@Entity('poll') +export class MiPoll { @PrimaryColumn(id()) - public noteId: Note['id']; + public noteId: MiNote['id']; - @OneToOne(type => Note, { + @OneToOne(type => MiNote, { onDelete: 'CASCADE', }) @JoinColumn() - public note: Note | null; + public note: MiNote | null; @Column('timestamp with time zone', { nullable: true, @@ -50,7 +50,7 @@ export class Poll { ...id(), comment: '[Denormalized]', }) - public userId: User['id']; + public userId: MiUser['id']; @Index() @Column('varchar', { @@ -60,7 +60,7 @@ export class Poll { public userHost: string | null; //#endregion - constructor(data: Partial) { + constructor(data: Partial) { if (data == null) return; for (const [k, v] of Object.entries(data)) { diff --git a/packages/backend/src/models/entities/PollVote.ts b/packages/backend/src/models/PollVote.ts similarity index 64% rename from packages/backend/src/models/entities/PollVote.ts rename to packages/backend/src/models/PollVote.ts index d6eea74a822b..37cd55fc18fa 100644 --- a/packages/backend/src/models/entities/PollVote.ts +++ b/packages/backend/src/models/PollVote.ts @@ -4,13 +4,13 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; -import { Note } from './Note.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiNote } from './Note.js'; -@Entity() +@Entity('poll_vote') @Index(['userId', 'noteId', 'choice'], { unique: true }) -export class PollVote { +export class MiPollVote { @PrimaryColumn(id()) public id: string; @@ -22,23 +22,23 @@ export class PollVote { @Index() @Column(id()) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Index() @Column(id()) - public noteId: Note['id']; + public noteId: MiNote['id']; - @ManyToOne(type => Note, { + @ManyToOne(type => MiNote, { onDelete: 'CASCADE', }) @JoinColumn() - public note: Note | null; + public note: MiNote | null; @Column('integer') public choice: number; diff --git a/packages/backend/src/models/entities/PromoNote.ts b/packages/backend/src/models/PromoNote.ts similarity index 61% rename from packages/backend/src/models/entities/PromoNote.ts rename to packages/backend/src/models/PromoNote.ts index be123ca46a20..f4425fe88b42 100644 --- a/packages/backend/src/models/entities/PromoNote.ts +++ b/packages/backend/src/models/PromoNote.ts @@ -4,20 +4,20 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'typeorm'; -import { id } from '../id.js'; -import { Note } from './Note.js'; -import type { User } from './User.js'; +import { id } from './util/id.js'; +import { MiNote } from './Note.js'; +import type { MiUser } from './User.js'; -@Entity() -export class PromoNote { +@Entity('promo_note') +export class MiPromoNote { @PrimaryColumn(id()) - public noteId: Note['id']; + public noteId: MiNote['id']; - @OneToOne(type => Note, { + @OneToOne(type => MiNote, { onDelete: 'CASCADE', }) @JoinColumn() - public note: Note | null; + public note: MiNote | null; @Column('timestamp with time zone') public expiresAt: Date; @@ -28,6 +28,6 @@ export class PromoNote { ...id(), comment: '[Denormalized]', }) - public userId: User['id']; + public userId: MiUser['id']; //#endregion } diff --git a/packages/backend/src/models/entities/PromoRead.ts b/packages/backend/src/models/PromoRead.ts similarity index 61% rename from packages/backend/src/models/entities/PromoRead.ts rename to packages/backend/src/models/PromoRead.ts index bddac7908cf6..09ebfc8346c9 100644 --- a/packages/backend/src/models/entities/PromoRead.ts +++ b/packages/backend/src/models/PromoRead.ts @@ -4,13 +4,13 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { Note } from './Note.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiNote } from './Note.js'; +import { MiUser } from './User.js'; -@Entity() +@Entity('promo_read') @Index(['userId', 'noteId'], { unique: true }) -export class PromoRead { +export class MiPromoRead { @PrimaryColumn(id()) public id: string; @@ -21,20 +21,20 @@ export class PromoRead { @Index() @Column(id()) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column(id()) - public noteId: Note['id']; + public noteId: MiNote['id']; - @ManyToOne(type => Note, { + @ManyToOne(type => MiNote, { onDelete: 'CASCADE', }) @JoinColumn() - public note: Note | null; + public note: MiNote | null; } diff --git a/packages/backend/src/models/entities/RegistrationTicket.ts b/packages/backend/src/models/RegistrationTicket.ts similarity index 72% rename from packages/backend/src/models/entities/RegistrationTicket.ts rename to packages/backend/src/models/RegistrationTicket.ts index 6f43121f3608..d94f4659165f 100644 --- a/packages/backend/src/models/entities/RegistrationTicket.ts +++ b/packages/backend/src/models/RegistrationTicket.ts @@ -4,11 +4,11 @@ */ import { PrimaryColumn, Entity, Index, Column, ManyToOne, JoinColumn, OneToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() -export class RegistrationTicket { +@Entity('registration_ticket') +export class MiRegistrationTicket { @PrimaryColumn(id()) public id: string; @@ -26,31 +26,31 @@ export class RegistrationTicket { @Column('timestamp with time zone') public createdAt: Date; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public createdBy: User | null; + public createdBy: MiUser | null; @Index() @Column({ ...id(), nullable: true, }) - public createdById: User['id'] | null; + public createdById: MiUser['id'] | null; - @OneToOne(type => User, { + @OneToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public usedBy: User | null; + public usedBy: MiUser | null; @Index() @Column({ ...id(), nullable: true, }) - public usedById: User['id'] | null; + public usedById: MiUser['id'] | null; @Column('timestamp with time zone', { nullable: true, diff --git a/packages/backend/src/models/entities/RegistryItem.ts b/packages/backend/src/models/RegistryItem.ts similarity index 85% rename from packages/backend/src/models/entities/RegistryItem.ts rename to packages/backend/src/models/RegistryItem.ts index 76b9b01ed3a0..fdce57c46752 100644 --- a/packages/backend/src/models/entities/RegistryItem.ts +++ b/packages/backend/src/models/RegistryItem.ts @@ -4,12 +4,12 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; // TODO: 同じdomain、同じscope、同じkeyのレコードは二つ以上存在しないように制約付けたい -@Entity() -export class RegistryItem { +@Entity('registry_item') +export class MiRegistryItem { @PrimaryColumn(id()) public id: string; @@ -28,13 +28,13 @@ export class RegistryItem { ...id(), comment: 'The owner ID.', }) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column('varchar', { length: 1024, diff --git a/packages/backend/src/models/entities/Relay.ts b/packages/backend/src/models/Relay.ts similarity index 85% rename from packages/backend/src/models/entities/Relay.ts rename to packages/backend/src/models/Relay.ts index 6be8d7561132..293fccecfc9f 100644 --- a/packages/backend/src/models/entities/Relay.ts +++ b/packages/backend/src/models/Relay.ts @@ -4,10 +4,10 @@ */ import { PrimaryColumn, Entity, Index, Column } from 'typeorm'; -import { id } from '../id.js'; +import { id } from './util/id.js'; -@Entity() -export class Relay { +@Entity('relay') +export class MiRelay { @PrimaryColumn(id()) public id: string; diff --git a/packages/backend/src/models/entities/RenoteMuting.ts b/packages/backend/src/models/RenoteMuting.ts similarity index 67% rename from packages/backend/src/models/entities/RenoteMuting.ts rename to packages/backend/src/models/RenoteMuting.ts index a1f488b73d75..d2a36249dc41 100644 --- a/packages/backend/src/models/entities/RenoteMuting.ts +++ b/packages/backend/src/models/RenoteMuting.ts @@ -4,12 +4,12 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() +@Entity('renote_muting') @Index(['muterId', 'muteeId'], { unique: true }) -export class RenoteMuting { +export class MiRenoteMuting { @PrimaryColumn(id()) public id: string; @@ -24,24 +24,24 @@ export class RenoteMuting { ...id(), comment: 'The mutee user ID.', }) - public muteeId: User['id']; + public muteeId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public mutee: User | null; + public mutee: MiUser | null; @Index() @Column({ ...id(), comment: 'The muter user ID.', }) - public muterId: User['id']; + public muterId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public muter: User | null; + public muter: MiUser | null; } diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index 1f8cb2a2a3a2..766e7ce21c70 100644 --- a/packages/backend/src/models/RepositoryModule.ts +++ b/packages/backend/src/models/RepositoryModule.ts @@ -5,403 +5,397 @@ import { Module } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite, UserMemo, UserListFavorite } from './index.js'; +import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMutedNote, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListJoining, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook } from './_.js'; import type { DataSource } from 'typeorm'; import type { Provider } from '@nestjs/common'; const $usersRepository: Provider = { provide: DI.usersRepository, - useFactory: (db: DataSource) => db.getRepository(User), + useFactory: (db: DataSource) => db.getRepository(MiUser), inject: [DI.db], }; const $notesRepository: Provider = { provide: DI.notesRepository, - useFactory: (db: DataSource) => db.getRepository(Note), + useFactory: (db: DataSource) => db.getRepository(MiNote), inject: [DI.db], }; const $announcementsRepository: Provider = { provide: DI.announcementsRepository, - useFactory: (db: DataSource) => db.getRepository(Announcement), + useFactory: (db: DataSource) => db.getRepository(MiAnnouncement), inject: [DI.db], }; const $announcementReadsRepository: Provider = { provide: DI.announcementReadsRepository, - useFactory: (db: DataSource) => db.getRepository(AnnouncementRead), + useFactory: (db: DataSource) => db.getRepository(MiAnnouncementRead), inject: [DI.db], }; const $appsRepository: Provider = { provide: DI.appsRepository, - useFactory: (db: DataSource) => db.getRepository(App), + useFactory: (db: DataSource) => db.getRepository(MiApp), inject: [DI.db], }; const $noteFavoritesRepository: Provider = { provide: DI.noteFavoritesRepository, - useFactory: (db: DataSource) => db.getRepository(NoteFavorite), + useFactory: (db: DataSource) => db.getRepository(MiNoteFavorite), inject: [DI.db], }; const $noteThreadMutingsRepository: Provider = { provide: DI.noteThreadMutingsRepository, - useFactory: (db: DataSource) => db.getRepository(NoteThreadMuting), + useFactory: (db: DataSource) => db.getRepository(MiNoteThreadMuting), inject: [DI.db], }; const $noteReactionsRepository: Provider = { provide: DI.noteReactionsRepository, - useFactory: (db: DataSource) => db.getRepository(NoteReaction), + useFactory: (db: DataSource) => db.getRepository(MiNoteReaction), inject: [DI.db], }; const $noteUnreadsRepository: Provider = { provide: DI.noteUnreadsRepository, - useFactory: (db: DataSource) => db.getRepository(NoteUnread), + useFactory: (db: DataSource) => db.getRepository(MiNoteUnread), inject: [DI.db], }; const $pollsRepository: Provider = { provide: DI.pollsRepository, - useFactory: (db: DataSource) => db.getRepository(Poll), + useFactory: (db: DataSource) => db.getRepository(MiPoll), inject: [DI.db], }; const $pollVotesRepository: Provider = { provide: DI.pollVotesRepository, - useFactory: (db: DataSource) => db.getRepository(PollVote), + useFactory: (db: DataSource) => db.getRepository(MiPollVote), inject: [DI.db], }; const $userProfilesRepository: Provider = { provide: DI.userProfilesRepository, - useFactory: (db: DataSource) => db.getRepository(UserProfile), + useFactory: (db: DataSource) => db.getRepository(MiUserProfile), inject: [DI.db], }; const $userKeypairsRepository: Provider = { provide: DI.userKeypairsRepository, - useFactory: (db: DataSource) => db.getRepository(UserKeypair), + useFactory: (db: DataSource) => db.getRepository(MiUserKeypair), inject: [DI.db], }; const $userPendingsRepository: Provider = { provide: DI.userPendingsRepository, - useFactory: (db: DataSource) => db.getRepository(UserPending), - inject: [DI.db], -}; - -const $attestationChallengesRepository: Provider = { - provide: DI.attestationChallengesRepository, - useFactory: (db: DataSource) => db.getRepository(AttestationChallenge), + useFactory: (db: DataSource) => db.getRepository(MiUserPending), inject: [DI.db], }; const $userSecurityKeysRepository: Provider = { provide: DI.userSecurityKeysRepository, - useFactory: (db: DataSource) => db.getRepository(UserSecurityKey), + useFactory: (db: DataSource) => db.getRepository(MiUserSecurityKey), inject: [DI.db], }; const $userPublickeysRepository: Provider = { provide: DI.userPublickeysRepository, - useFactory: (db: DataSource) => db.getRepository(UserPublickey), + useFactory: (db: DataSource) => db.getRepository(MiUserPublickey), inject: [DI.db], }; const $userListsRepository: Provider = { provide: DI.userListsRepository, - useFactory: (db: DataSource) => db.getRepository(UserList), + useFactory: (db: DataSource) => db.getRepository(MiUserList), inject: [DI.db], }; const $userListFavoritesRepository: Provider = { provide: DI.userListFavoritesRepository, - useFactory: (db: DataSource) => db.getRepository(UserListFavorite), + useFactory: (db: DataSource) => db.getRepository(MiUserListFavorite), inject: [DI.db], }; const $userListJoiningsRepository: Provider = { provide: DI.userListJoiningsRepository, - useFactory: (db: DataSource) => db.getRepository(UserListJoining), + useFactory: (db: DataSource) => db.getRepository(MiUserListJoining), inject: [DI.db], }; const $userNotePiningsRepository: Provider = { provide: DI.userNotePiningsRepository, - useFactory: (db: DataSource) => db.getRepository(UserNotePining), + useFactory: (db: DataSource) => db.getRepository(MiUserNotePining), inject: [DI.db], }; const $userIpsRepository: Provider = { provide: DI.userIpsRepository, - useFactory: (db: DataSource) => db.getRepository(UserIp), + useFactory: (db: DataSource) => db.getRepository(MiUserIp), inject: [DI.db], }; const $usedUsernamesRepository: Provider = { provide: DI.usedUsernamesRepository, - useFactory: (db: DataSource) => db.getRepository(UsedUsername), + useFactory: (db: DataSource) => db.getRepository(MiUsedUsername), inject: [DI.db], }; const $followingsRepository: Provider = { provide: DI.followingsRepository, - useFactory: (db: DataSource) => db.getRepository(Following), + useFactory: (db: DataSource) => db.getRepository(MiFollowing), inject: [DI.db], }; const $followRequestsRepository: Provider = { provide: DI.followRequestsRepository, - useFactory: (db: DataSource) => db.getRepository(FollowRequest), + useFactory: (db: DataSource) => db.getRepository(MiFollowRequest), inject: [DI.db], }; const $instancesRepository: Provider = { provide: DI.instancesRepository, - useFactory: (db: DataSource) => db.getRepository(Instance), + useFactory: (db: DataSource) => db.getRepository(MiInstance), inject: [DI.db], }; const $emojisRepository: Provider = { provide: DI.emojisRepository, - useFactory: (db: DataSource) => db.getRepository(Emoji), + useFactory: (db: DataSource) => db.getRepository(MiEmoji), inject: [DI.db], }; const $driveFilesRepository: Provider = { provide: DI.driveFilesRepository, - useFactory: (db: DataSource) => db.getRepository(DriveFile), + useFactory: (db: DataSource) => db.getRepository(MiDriveFile), inject: [DI.db], }; const $driveFoldersRepository: Provider = { provide: DI.driveFoldersRepository, - useFactory: (db: DataSource) => db.getRepository(DriveFolder), + useFactory: (db: DataSource) => db.getRepository(MiDriveFolder), inject: [DI.db], }; const $metasRepository: Provider = { provide: DI.metasRepository, - useFactory: (db: DataSource) => db.getRepository(Meta), + useFactory: (db: DataSource) => db.getRepository(MiMeta), inject: [DI.db], }; const $mutingsRepository: Provider = { provide: DI.mutingsRepository, - useFactory: (db: DataSource) => db.getRepository(Muting), + useFactory: (db: DataSource) => db.getRepository(MiMuting), inject: [DI.db], }; const $renoteMutingsRepository: Provider = { provide: DI.renoteMutingsRepository, - useFactory: (db: DataSource) => db.getRepository(RenoteMuting), + useFactory: (db: DataSource) => db.getRepository(MiRenoteMuting), inject: [DI.db], }; const $blockingsRepository: Provider = { provide: DI.blockingsRepository, - useFactory: (db: DataSource) => db.getRepository(Blocking), + useFactory: (db: DataSource) => db.getRepository(MiBlocking), inject: [DI.db], }; const $swSubscriptionsRepository: Provider = { provide: DI.swSubscriptionsRepository, - useFactory: (db: DataSource) => db.getRepository(SwSubscription), + useFactory: (db: DataSource) => db.getRepository(MiSwSubscription), inject: [DI.db], }; const $hashtagsRepository: Provider = { provide: DI.hashtagsRepository, - useFactory: (db: DataSource) => db.getRepository(Hashtag), + useFactory: (db: DataSource) => db.getRepository(MiHashtag), inject: [DI.db], }; const $abuseUserReportsRepository: Provider = { provide: DI.abuseUserReportsRepository, - useFactory: (db: DataSource) => db.getRepository(AbuseUserReport), + useFactory: (db: DataSource) => db.getRepository(MiAbuseUserReport), inject: [DI.db], }; const $registrationTicketsRepository: Provider = { provide: DI.registrationTicketsRepository, - useFactory: (db: DataSource) => db.getRepository(RegistrationTicket), + useFactory: (db: DataSource) => db.getRepository(MiRegistrationTicket), inject: [DI.db], }; const $authSessionsRepository: Provider = { provide: DI.authSessionsRepository, - useFactory: (db: DataSource) => db.getRepository(AuthSession), + useFactory: (db: DataSource) => db.getRepository(MiAuthSession), inject: [DI.db], }; const $accessTokensRepository: Provider = { provide: DI.accessTokensRepository, - useFactory: (db: DataSource) => db.getRepository(AccessToken), + useFactory: (db: DataSource) => db.getRepository(MiAccessToken), inject: [DI.db], }; const $signinsRepository: Provider = { provide: DI.signinsRepository, - useFactory: (db: DataSource) => db.getRepository(Signin), + useFactory: (db: DataSource) => db.getRepository(MiSignin), inject: [DI.db], }; const $pagesRepository: Provider = { provide: DI.pagesRepository, - useFactory: (db: DataSource) => db.getRepository(Page), + useFactory: (db: DataSource) => db.getRepository(MiPage), inject: [DI.db], }; const $pageLikesRepository: Provider = { provide: DI.pageLikesRepository, - useFactory: (db: DataSource) => db.getRepository(PageLike), + useFactory: (db: DataSource) => db.getRepository(MiPageLike), inject: [DI.db], }; const $galleryPostsRepository: Provider = { provide: DI.galleryPostsRepository, - useFactory: (db: DataSource) => db.getRepository(GalleryPost), + useFactory: (db: DataSource) => db.getRepository(MiGalleryPost), inject: [DI.db], }; const $galleryLikesRepository: Provider = { provide: DI.galleryLikesRepository, - useFactory: (db: DataSource) => db.getRepository(GalleryLike), + useFactory: (db: DataSource) => db.getRepository(MiGalleryLike), inject: [DI.db], }; const $moderationLogsRepository: Provider = { provide: DI.moderationLogsRepository, - useFactory: (db: DataSource) => db.getRepository(ModerationLog), + useFactory: (db: DataSource) => db.getRepository(MiModerationLog), inject: [DI.db], }; const $clipsRepository: Provider = { provide: DI.clipsRepository, - useFactory: (db: DataSource) => db.getRepository(Clip), + useFactory: (db: DataSource) => db.getRepository(MiClip), inject: [DI.db], }; const $clipNotesRepository: Provider = { provide: DI.clipNotesRepository, - useFactory: (db: DataSource) => db.getRepository(ClipNote), + useFactory: (db: DataSource) => db.getRepository(MiClipNote), inject: [DI.db], }; const $clipFavoritesRepository: Provider = { provide: DI.clipFavoritesRepository, - useFactory: (db: DataSource) => db.getRepository(ClipFavorite), + useFactory: (db: DataSource) => db.getRepository(MiClipFavorite), inject: [DI.db], }; const $antennasRepository: Provider = { provide: DI.antennasRepository, - useFactory: (db: DataSource) => db.getRepository(Antenna), + useFactory: (db: DataSource) => db.getRepository(MiAntenna), inject: [DI.db], }; const $promoNotesRepository: Provider = { provide: DI.promoNotesRepository, - useFactory: (db: DataSource) => db.getRepository(PromoNote), + useFactory: (db: DataSource) => db.getRepository(MiPromoNote), inject: [DI.db], }; const $promoReadsRepository: Provider = { provide: DI.promoReadsRepository, - useFactory: (db: DataSource) => db.getRepository(PromoRead), + useFactory: (db: DataSource) => db.getRepository(MiPromoRead), inject: [DI.db], }; const $relaysRepository: Provider = { provide: DI.relaysRepository, - useFactory: (db: DataSource) => db.getRepository(Relay), + useFactory: (db: DataSource) => db.getRepository(MiRelay), inject: [DI.db], }; const $mutedNotesRepository: Provider = { provide: DI.mutedNotesRepository, - useFactory: (db: DataSource) => db.getRepository(MutedNote), + useFactory: (db: DataSource) => db.getRepository(MiMutedNote), inject: [DI.db], }; const $channelsRepository: Provider = { provide: DI.channelsRepository, - useFactory: (db: DataSource) => db.getRepository(Channel), + useFactory: (db: DataSource) => db.getRepository(MiChannel), inject: [DI.db], }; const $channelFollowingsRepository: Provider = { provide: DI.channelFollowingsRepository, - useFactory: (db: DataSource) => db.getRepository(ChannelFollowing), + useFactory: (db: DataSource) => db.getRepository(MiChannelFollowing), inject: [DI.db], }; const $channelFavoritesRepository: Provider = { provide: DI.channelFavoritesRepository, - useFactory: (db: DataSource) => db.getRepository(ChannelFavorite), + useFactory: (db: DataSource) => db.getRepository(MiChannelFavorite), inject: [DI.db], }; const $registryItemsRepository: Provider = { provide: DI.registryItemsRepository, - useFactory: (db: DataSource) => db.getRepository(RegistryItem), + useFactory: (db: DataSource) => db.getRepository(MiRegistryItem), inject: [DI.db], }; const $webhooksRepository: Provider = { provide: DI.webhooksRepository, - useFactory: (db: DataSource) => db.getRepository(Webhook), + useFactory: (db: DataSource) => db.getRepository(MiWebhook), inject: [DI.db], }; const $adsRepository: Provider = { provide: DI.adsRepository, - useFactory: (db: DataSource) => db.getRepository(Ad), + useFactory: (db: DataSource) => db.getRepository(MiAd), inject: [DI.db], }; const $passwordResetRequestsRepository: Provider = { provide: DI.passwordResetRequestsRepository, - useFactory: (db: DataSource) => db.getRepository(PasswordResetRequest), + useFactory: (db: DataSource) => db.getRepository(MiPasswordResetRequest), inject: [DI.db], }; const $retentionAggregationsRepository: Provider = { provide: DI.retentionAggregationsRepository, - useFactory: (db: DataSource) => db.getRepository(RetentionAggregation), + useFactory: (db: DataSource) => db.getRepository(MiRetentionAggregation), inject: [DI.db], }; const $flashsRepository: Provider = { provide: DI.flashsRepository, - useFactory: (db: DataSource) => db.getRepository(Flash), + useFactory: (db: DataSource) => db.getRepository(MiFlash), inject: [DI.db], }; const $flashLikesRepository: Provider = { provide: DI.flashLikesRepository, - useFactory: (db: DataSource) => db.getRepository(FlashLike), + useFactory: (db: DataSource) => db.getRepository(MiFlashLike), inject: [DI.db], }; const $rolesRepository: Provider = { provide: DI.rolesRepository, - useFactory: (db: DataSource) => db.getRepository(Role), + useFactory: (db: DataSource) => db.getRepository(MiRole), inject: [DI.db], }; const $roleAssignmentsRepository: Provider = { provide: DI.roleAssignmentsRepository, - useFactory: (db: DataSource) => db.getRepository(RoleAssignment), + useFactory: (db: DataSource) => db.getRepository(MiRoleAssignment), inject: [DI.db], }; const $userMemosRepository: Provider = { provide: DI.userMemosRepository, - useFactory: (db: DataSource) => db.getRepository(UserMemo), + useFactory: (db: DataSource) => db.getRepository(MiUserMemo), inject: [DI.db], }; @@ -423,7 +417,6 @@ const $userMemosRepository: Provider = { $userProfilesRepository, $userKeypairsRepository, $userPendingsRepository, - $attestationChallengesRepository, $userSecurityKeysRepository, $userPublickeysRepository, $userListsRepository, @@ -491,7 +484,6 @@ const $userMemosRepository: Provider = { $userProfilesRepository, $userKeypairsRepository, $userPendingsRepository, - $attestationChallengesRepository, $userSecurityKeysRepository, $userPublickeysRepository, $userListsRepository, diff --git a/packages/backend/src/models/entities/RetentionAggregation.ts b/packages/backend/src/models/RetentionAggregation.ts similarity index 80% rename from packages/backend/src/models/entities/RetentionAggregation.ts rename to packages/backend/src/models/RetentionAggregation.ts index 7eef8cb8293a..9da401597ccb 100644 --- a/packages/backend/src/models/entities/RetentionAggregation.ts +++ b/packages/backend/src/models/RetentionAggregation.ts @@ -4,11 +4,11 @@ */ import { Entity, PrimaryColumn, Index, Column } from 'typeorm'; -import { id } from '../id.js'; -import type { User } from './User.js'; +import { id } from './util/id.js'; +import type { MiUser } from './User.js'; -@Entity() -export class RetentionAggregation { +@Entity('retention_aggregation') +export class MiRetentionAggregation { @PrimaryColumn(id()) public id: string; @@ -33,7 +33,7 @@ export class RetentionAggregation { ...id(), array: true, }) - public userIds: User['id'][]; + public userIds: MiUser['id'][]; @Column('integer', { }) diff --git a/packages/backend/src/models/entities/Role.ts b/packages/backend/src/models/Role.ts similarity index 97% rename from packages/backend/src/models/entities/Role.ts rename to packages/backend/src/models/Role.ts index 3a89115d9ace..df7541db3d4a 100644 --- a/packages/backend/src/models/entities/Role.ts +++ b/packages/backend/src/models/Role.ts @@ -4,7 +4,7 @@ */ import { Entity, Column, PrimaryColumn } from 'typeorm'; -import { id } from '../id.js'; +import { id } from './util/id.js'; type CondFormulaValueAnd = { type: 'and'; @@ -84,8 +84,8 @@ export type RoleCondFormulaValue = CondFormulaValueNotesLessThanOrEq | CondFormulaValueNotesMoreThanOrEq; -@Entity() -export class Role { +@Entity('role') +export class MiRole { @PrimaryColumn(id()) public id: string; diff --git a/packages/backend/src/models/entities/RoleAssignment.ts b/packages/backend/src/models/RoleAssignment.ts similarity index 68% rename from packages/backend/src/models/entities/RoleAssignment.ts rename to packages/backend/src/models/RoleAssignment.ts index 7499a4b66020..4e5322c60bdc 100644 --- a/packages/backend/src/models/entities/RoleAssignment.ts +++ b/packages/backend/src/models/RoleAssignment.ts @@ -4,13 +4,13 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { Role } from './Role.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiRole } from './Role.js'; +import { MiUser } from './User.js'; -@Entity() +@Entity('role_assignment') @Index(['userId', 'roleId'], { unique: true }) -export class RoleAssignment { +export class MiRoleAssignment { @PrimaryColumn(id()) public id: string; @@ -24,26 +24,26 @@ export class RoleAssignment { ...id(), comment: 'The user ID.', }) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Index() @Column({ ...id(), comment: 'The role ID.', }) - public roleId: Role['id']; + public roleId: MiRole['id']; - @ManyToOne(type => Role, { + @ManyToOne(type => MiRole, { onDelete: 'CASCADE', }) @JoinColumn() - public role: Role | null; + public role: MiRole | null; @Index() @Column('timestamp with time zone', { diff --git a/packages/backend/src/models/entities/Signin.ts b/packages/backend/src/models/Signin.ts similarity index 74% rename from packages/backend/src/models/entities/Signin.ts rename to packages/backend/src/models/Signin.ts index 72c9e67e2991..a8b1a45c5306 100644 --- a/packages/backend/src/models/entities/Signin.ts +++ b/packages/backend/src/models/Signin.ts @@ -4,11 +4,11 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() -export class Signin { +@Entity('signin') +export class MiSignin { @PrimaryColumn(id()) public id: string; @@ -19,13 +19,13 @@ export class Signin { @Index() @Column(id()) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column('varchar', { length: 128, diff --git a/packages/backend/src/models/entities/SwSubscription.ts b/packages/backend/src/models/SwSubscription.ts similarity index 75% rename from packages/backend/src/models/entities/SwSubscription.ts rename to packages/backend/src/models/SwSubscription.ts index e279edfe73c0..be1e4e3687da 100644 --- a/packages/backend/src/models/entities/SwSubscription.ts +++ b/packages/backend/src/models/SwSubscription.ts @@ -4,11 +4,11 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() -export class SwSubscription { +@Entity('sw_subscription') +export class MiSwSubscription { @PrimaryColumn(id()) public id: string; @@ -17,13 +17,13 @@ export class SwSubscription { @Index() @Column(id()) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column('varchar', { length: 512, diff --git a/packages/backend/src/models/entities/UsedUsername.ts b/packages/backend/src/models/UsedUsername.ts similarity index 80% rename from packages/backend/src/models/entities/UsedUsername.ts rename to packages/backend/src/models/UsedUsername.ts index 3056543f24c7..c75bf424c108 100644 --- a/packages/backend/src/models/entities/UsedUsername.ts +++ b/packages/backend/src/models/UsedUsername.ts @@ -5,8 +5,8 @@ import { PrimaryColumn, Entity, Column } from 'typeorm'; -@Entity() -export class UsedUsername { +@Entity('used_username') +export class MiUsedUsername { @PrimaryColumn('varchar', { length: 128, }) @@ -15,7 +15,7 @@ export class UsedUsername { @Column('timestamp with time zone') public createdAt: Date; - constructor(data: Partial) { + constructor(data: Partial) { if (data == null) return; for (const [k, v] of Object.entries(data)) { diff --git a/packages/backend/src/models/entities/User.ts b/packages/backend/src/models/User.ts similarity index 90% rename from packages/backend/src/models/entities/User.ts rename to packages/backend/src/models/User.ts index 2282e776482f..b040d302ce7c 100644 --- a/packages/backend/src/models/entities/User.ts +++ b/packages/backend/src/models/User.ts @@ -4,12 +4,12 @@ */ import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm'; -import { id } from '../id.js'; -import { DriveFile } from './DriveFile.js'; +import { id } from './util/id.js'; +import { MiDriveFile } from './DriveFile.js'; -@Entity() +@Entity('user') @Index(['usernameLower', 'host'], { unique: true }) -export class User { +export class MiUser { @PrimaryColumn(id()) public id: string; @@ -103,26 +103,26 @@ export class User { nullable: true, comment: 'The ID of avatar DriveFile.', }) - public avatarId: DriveFile['id'] | null; + public avatarId: MiDriveFile['id'] | null; - @OneToOne(type => DriveFile, { + @OneToOne(type => MiDriveFile, { onDelete: 'SET NULL', }) @JoinColumn() - public avatar: DriveFile | null; + public avatar: MiDriveFile | null; @Column({ ...id(), nullable: true, comment: 'The ID of banner DriveFile.', }) - public bannerId: DriveFile['id'] | null; + public bannerId: MiDriveFile['id'] | null; - @OneToOne(type => DriveFile, { + @OneToOne(type => MiDriveFile, { onDelete: 'SET NULL', }) @JoinColumn() - public banner: DriveFile | null; + public banner: MiDriveFile | null; @Column('varchar', { length: 512, nullable: true, @@ -244,7 +244,7 @@ export class User { }) public token: string | null; - constructor(data: Partial) { + constructor(data: Partial) { if (data == null) return; for (const [k, v] of Object.entries(data)) { @@ -253,24 +253,24 @@ export class User { } } -export type LocalUser = User & { +export type MiLocalUser = MiUser & { host: null; uri: null; } -export type PartialLocalUser = Partial & { - id: User['id']; +export type MiPartialLocalUser = Partial & { + id: MiUser['id']; host: null; uri: null; } -export type RemoteUser = User & { +export type MiRemoteUser = MiUser & { host: string; uri: string; } -export type PartialRemoteUser = Partial & { - id: User['id']; +export type MiPartialRemoteUser = Partial & { + id: MiUser['id']; host: string; uri: string; } diff --git a/packages/backend/src/models/entities/UserIp.ts b/packages/backend/src/models/UserIp.ts similarity index 74% rename from packages/backend/src/models/entities/UserIp.ts rename to packages/backend/src/models/UserIp.ts index 15e5f1bb6f03..60a7bc8b0193 100644 --- a/packages/backend/src/models/entities/UserIp.ts +++ b/packages/backend/src/models/UserIp.ts @@ -4,12 +4,12 @@ */ import { Entity, Index, Column, PrimaryGeneratedColumn } from 'typeorm'; -import { id } from '../id.js'; -import type { User } from './User.js'; +import { id } from './util/id.js'; +import type { MiUser } from './User.js'; -@Entity() +@Entity('user_ip') @Index(['userId', 'ip'], { unique: true }) -export class UserIp { +export class MiUserIp { @PrimaryGeneratedColumn() public id: string; @@ -19,7 +19,7 @@ export class UserIp { @Index() @Column(id()) - public userId: User['id']; + public userId: MiUser['id']; @Column('varchar', { length: 128, diff --git a/packages/backend/src/models/entities/UserKeypair.ts b/packages/backend/src/models/UserKeypair.ts similarity index 66% rename from packages/backend/src/models/entities/UserKeypair.ts rename to packages/backend/src/models/UserKeypair.ts index 6c08388fa332..a316dbaeb4b9 100644 --- a/packages/backend/src/models/entities/UserKeypair.ts +++ b/packages/backend/src/models/UserKeypair.ts @@ -4,19 +4,19 @@ */ import { PrimaryColumn, Entity, JoinColumn, Column, OneToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() -export class UserKeypair { +@Entity('user_keypair') +export class MiUserKeypair { @PrimaryColumn(id()) - public userId: User['id']; + public userId: MiUser['id']; - @OneToOne(type => User, { + @OneToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column('varchar', { length: 4096, @@ -28,7 +28,7 @@ export class UserKeypair { }) public privateKey: string; - constructor(data: Partial) { + constructor(data: Partial) { if (data == null) return; for (const [k, v] of Object.entries(data)) { diff --git a/packages/backend/src/models/entities/UserList.ts b/packages/backend/src/models/UserList.ts similarity index 76% rename from packages/backend/src/models/entities/UserList.ts rename to packages/backend/src/models/UserList.ts index 9870bbaec567..9af85af97e82 100644 --- a/packages/backend/src/models/entities/UserList.ts +++ b/packages/backend/src/models/UserList.ts @@ -4,11 +4,11 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() -export class UserList { +@Entity('user_list') +export class MiUserList { @PrimaryColumn(id()) public id: string; @@ -22,7 +22,7 @@ export class UserList { ...id(), comment: 'The owner ID.', }) - public userId: User['id']; + public userId: MiUser['id']; @Index() @Column('boolean', { @@ -30,11 +30,11 @@ export class UserList { }) public isPublic: boolean; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column('varchar', { length: 128, diff --git a/packages/backend/src/models/entities/UserListFavorite.ts b/packages/backend/src/models/UserListFavorite.ts similarity index 56% rename from packages/backend/src/models/entities/UserListFavorite.ts rename to packages/backend/src/models/UserListFavorite.ts index d8994f3b6bf7..d0b054b932d8 100644 --- a/packages/backend/src/models/entities/UserListFavorite.ts +++ b/packages/backend/src/models/UserListFavorite.ts @@ -4,13 +4,13 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; -import { UserList } from './UserList.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiUserList } from './UserList.js'; -@Entity() +@Entity('user_list_favorite') @Index(['userId', 'userListId'], { unique: true }) -export class UserListFavorite { +export class MiUserListFavorite { @PrimaryColumn(id()) public id: string; @@ -19,20 +19,20 @@ export class UserListFavorite { @Index() @Column(id()) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column(id()) - public userListId: UserList['id']; + public userListId: MiUserList['id']; - @ManyToOne(type => UserList, { + @ManyToOne(type => MiUserList, { onDelete: 'CASCADE', }) @JoinColumn() - public userList: UserList | null; + public userList: MiUserList | null; } diff --git a/packages/backend/src/models/entities/UserListJoining.ts b/packages/backend/src/models/UserListJoining.ts similarity index 62% rename from packages/backend/src/models/entities/UserListJoining.ts rename to packages/backend/src/models/UserListJoining.ts index 90288921046d..4918f2f70001 100644 --- a/packages/backend/src/models/entities/UserListJoining.ts +++ b/packages/backend/src/models/UserListJoining.ts @@ -4,13 +4,13 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; -import { UserList } from './UserList.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiUserList } from './UserList.js'; -@Entity() +@Entity('user_list_joining') @Index(['userId', 'userListId'], { unique: true }) -export class UserListJoining { +export class MiUserListJoining { @PrimaryColumn(id()) public id: string; @@ -24,24 +24,24 @@ export class UserListJoining { ...id(), comment: 'The user ID.', }) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Index() @Column({ ...id(), comment: 'The list ID.', }) - public userListId: UserList['id']; + public userListId: MiUserList['id']; - @ManyToOne(type => UserList, { + @ManyToOne(type => MiUserList, { onDelete: 'CASCADE', }) @JoinColumn() - public userList: UserList | null; + public userList: MiUserList | null; } diff --git a/packages/backend/src/models/entities/UserMemo.ts b/packages/backend/src/models/UserMemo.ts similarity index 66% rename from packages/backend/src/models/entities/UserMemo.ts rename to packages/backend/src/models/UserMemo.ts index f62d8908094f..ab5e812c4498 100644 --- a/packages/backend/src/models/entities/UserMemo.ts +++ b/packages/backend/src/models/UserMemo.ts @@ -4,12 +4,12 @@ */ import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() +@Entity('user_memo') @Index(['userId', 'targetUserId'], { unique: true }) -export class UserMemo { +export class MiUserMemo { @PrimaryColumn(id()) public id: string; @@ -18,26 +18,26 @@ export class UserMemo { ...id(), comment: 'The ID of author.', }) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Index() @Column({ ...id(), comment: 'The ID of target user.', }) - public targetUserId: User['id']; + public targetUserId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public targetUser: User | null; + public targetUser: MiUser | null; @Column('varchar', { length: 2048, diff --git a/packages/backend/src/models/entities/UserNotePining.ts b/packages/backend/src/models/UserNotePining.ts similarity index 61% rename from packages/backend/src/models/entities/UserNotePining.ts rename to packages/backend/src/models/UserNotePining.ts index a6362ca6b802..1d50a5068eff 100644 --- a/packages/backend/src/models/entities/UserNotePining.ts +++ b/packages/backend/src/models/UserNotePining.ts @@ -4,13 +4,13 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { Note } from './Note.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiNote } from './Note.js'; +import { MiUser } from './User.js'; -@Entity() +@Entity('user_note_pining') @Index(['userId', 'noteId'], { unique: true }) -export class UserNotePining { +export class MiUserNotePining { @PrimaryColumn(id()) public id: string; @@ -21,20 +21,20 @@ export class UserNotePining { @Index() @Column(id()) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column(id()) - public noteId: Note['id']; + public noteId: MiNote['id']; - @ManyToOne(type => Note, { + @ManyToOne(type => MiNote, { onDelete: 'CASCADE', }) @JoinColumn() - public note: Note | null; + public note: MiNote | null; } diff --git a/packages/backend/src/models/entities/UserPending.ts b/packages/backend/src/models/UserPending.ts similarity index 86% rename from packages/backend/src/models/entities/UserPending.ts rename to packages/backend/src/models/UserPending.ts index cbcec2967128..b15ededa143e 100644 --- a/packages/backend/src/models/entities/UserPending.ts +++ b/packages/backend/src/models/UserPending.ts @@ -4,10 +4,10 @@ */ import { PrimaryColumn, Entity, Index, Column } from 'typeorm'; -import { id } from '../id.js'; +import { id } from './util/id.js'; -@Entity() -export class UserPending { +@Entity('user_pending') +export class MiUserPending { @PrimaryColumn(id()) public id: string; diff --git a/packages/backend/src/models/entities/UserProfile.ts b/packages/backend/src/models/UserProfile.ts similarity index 89% rename from packages/backend/src/models/entities/UserProfile.ts rename to packages/backend/src/models/UserProfile.ts index 73535c366571..e4405c9da7f2 100644 --- a/packages/backend/src/models/entities/UserProfile.ts +++ b/packages/backend/src/models/UserProfile.ts @@ -5,22 +5,22 @@ import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm'; import { obsoleteNotificationTypes, ffVisibility, notificationTypes } from '@/types.js'; -import { id } from '../id.js'; -import { User } from './User.js'; -import { Page } from './Page.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; +import { MiPage } from './Page.js'; // TODO: このテーブルで管理している情報すべてレジストリで管理するようにしても良いかも // ただ、「emailVerified が true なユーザーを find する」のようなクエリは書けなくなるからウーン -@Entity() -export class UserProfile { +@Entity('user_profile') +export class MiUserProfile { @PrimaryColumn(id()) - public userId: User['id']; + public userId: MiUser['id']; - @OneToOne(type => User, { + @OneToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column('varchar', { length: 128, nullable: true, @@ -48,6 +48,12 @@ export class UserProfile { value: string; }[]; + @Column('varchar', { + array: true, + default: '{}', + }) + public verifiedLinks: string[]; + @Column('varchar', { length: 32, nullable: true, }) @@ -101,6 +107,11 @@ export class UserProfile { }) public twoFactorSecret: string | null; + @Column('varchar', { + nullable: true, array: true, + }) + public twoFactorBackupSecret: string[] | null; + @Column('boolean', { default: false, }) @@ -186,13 +197,13 @@ export class UserProfile { ...id(), nullable: true, }) - public pinnedPageId: Page['id'] | null; + public pinnedPageId: MiPage['id'] | null; - @OneToOne(type => Page, { + @OneToOne(type => MiPage, { onDelete: 'SET NULL', }) @JoinColumn() - public pinnedPage: Page | null; + public pinnedPage: MiPage | null; @Index() @Column('boolean', { @@ -244,7 +255,7 @@ export class UserProfile { public userHost: string | null; //#endregion - constructor(data: Partial) { + constructor(data: Partial) { if (data == null) return; for (const [k, v] of Object.entries(data)) { diff --git a/packages/backend/src/models/entities/UserPublickey.ts b/packages/backend/src/models/UserPublickey.ts similarity index 67% rename from packages/backend/src/models/entities/UserPublickey.ts rename to packages/backend/src/models/UserPublickey.ts index fdc1e1411fcd..33de73c636c2 100644 --- a/packages/backend/src/models/entities/UserPublickey.ts +++ b/packages/backend/src/models/UserPublickey.ts @@ -4,19 +4,19 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; -@Entity() -export class UserPublickey { +@Entity('user_publickey') +export class MiUserPublickey { @PrimaryColumn(id()) - public userId: User['id']; + public userId: MiUser['id']; - @OneToOne(type => User, { + @OneToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Index({ unique: true }) @Column('varchar', { @@ -29,7 +29,7 @@ export class UserPublickey { }) public keyPem: string; - constructor(data: Partial) { + constructor(data: Partial) { if (data == null) return; for (const [k, v] of Object.entries(data)) { diff --git a/packages/backend/src/models/UserSecurityKey.ts b/packages/backend/src/models/UserSecurityKey.ts new file mode 100644 index 000000000000..02c29bfbb59f --- /dev/null +++ b/packages/backend/src/models/UserSecurityKey.ts @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { PrimaryColumn, Entity, JoinColumn, Column, ManyToOne, Index } from 'typeorm'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; + +@Entity('user_security_key') +export class MiUserSecurityKey { + @PrimaryColumn('varchar', { + comment: 'Variable-length id given to navigator.credentials.get()', + }) + public id: string; + + @Index() + @Column(id()) + public userId: MiUser['id']; + + @ManyToOne(type => MiUser, { + onDelete: 'CASCADE', + }) + @JoinColumn() + public user: MiUser | null; + + @Column('varchar', { + comment: 'User-defined name for this key', + length: 30, + }) + public name: string; + + @Index() + @Column('varchar', { + comment: 'The public key of the UserSecurityKey, hex-encoded.', + }) + public publicKey: string; + + @Column('bigint', { + comment: 'The number of times the UserSecurityKey was validated.', + default: 0, + }) + public counter: number; + + @Column('timestamp with time zone', { + comment: 'Timestamp of the last time the UserSecurityKey was used.', + default: () => 'now()', + }) + public lastUsed: Date; + + @Column('varchar', { + comment: 'The type of Backup Eligibility in authenticator data', + length: 32, nullable: true, + }) + public credentialDeviceType: string | null; + + @Column('boolean', { + comment: 'Whether or not the credential has been backed up', + nullable: true, + }) + public credentialBackedUp: boolean | null; + + @Column('varchar', { + comment: 'The type of the credential returned by the browser', + length: 32, array: true, nullable: true, + }) + public transports: string[] | null; + + constructor(data: Partial) { + if (data == null) return; + + for (const [k, v] of Object.entries(data)) { + (this as any)[k] = v; + } + } +} diff --git a/packages/backend/src/models/entities/Webhook.ts b/packages/backend/src/models/Webhook.ts similarity index 86% rename from packages/backend/src/models/entities/Webhook.ts rename to packages/backend/src/models/Webhook.ts index 408770f2fcc2..5b009c18a6ac 100644 --- a/packages/backend/src/models/entities/Webhook.ts +++ b/packages/backend/src/models/Webhook.ts @@ -4,13 +4,13 @@ */ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; export const webhookEventTypes = ['mention', 'unfollow', 'follow', 'followed', 'note', 'reply', 'renote', 'reaction'] as const; -@Entity() -export class Webhook { +@Entity('webhook') +export class MiWebhook { @PrimaryColumn(id()) public id: string; @@ -24,13 +24,13 @@ export class Webhook { ...id(), comment: 'The owner ID.', }) - public userId: User['id']; + public userId: MiUser['id']; - @ManyToOne(type => User, { + @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) @JoinColumn() - public user: User | null; + public user: MiUser | null; @Column('varchar', { length: 128, diff --git a/packages/backend/src/models/_.ts b/packages/backend/src/models/_.ts new file mode 100644 index 000000000000..6be7bd0df6ba --- /dev/null +++ b/packages/backend/src/models/_.ts @@ -0,0 +1,205 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { MiAbuseUserReport } from '@/models/AbuseUserReport.js'; +import { MiAccessToken } from '@/models/AccessToken.js'; +import { MiAd } from '@/models/Ad.js'; +import { MiAnnouncement } from '@/models/Announcement.js'; +import { MiAnnouncementRead } from '@/models/AnnouncementRead.js'; +import { MiAntenna } from '@/models/Antenna.js'; +import { MiApp } from '@/models/App.js'; +import { MiAuthSession } from '@/models/AuthSession.js'; +import { MiBlocking } from '@/models/Blocking.js'; +import { MiChannelFollowing } from '@/models/ChannelFollowing.js'; +import { MiChannelFavorite } from '@/models/ChannelFavorite.js'; +import { MiClip } from '@/models/Clip.js'; +import { MiClipNote } from '@/models/ClipNote.js'; +import { MiClipFavorite } from '@/models/ClipFavorite.js'; +import { MiDriveFile } from '@/models/DriveFile.js'; +import { MiDriveFolder } from '@/models/DriveFolder.js'; +import { MiEmoji } from '@/models/Emoji.js'; +import { MiFollowing } from '@/models/Following.js'; +import { MiFollowRequest } from '@/models/FollowRequest.js'; +import { MiGalleryLike } from '@/models/GalleryLike.js'; +import { MiGalleryPost } from '@/models/GalleryPost.js'; +import { MiHashtag } from '@/models/Hashtag.js'; +import { MiInstance } from '@/models/Instance.js'; +import { MiMeta } from '@/models/Meta.js'; +import { MiModerationLog } from '@/models/ModerationLog.js'; +import { MiMutedNote } from '@/models/MutedNote.js'; +import { MiMuting } from '@/models/Muting.js'; +import { MiRenoteMuting } from '@/models/RenoteMuting.js'; +import { MiNote } from '@/models/Note.js'; +import { MiNoteFavorite } from '@/models/NoteFavorite.js'; +import { MiNoteReaction } from '@/models/NoteReaction.js'; +import { MiNoteThreadMuting } from '@/models/NoteThreadMuting.js'; +import { MiNoteUnread } from '@/models/NoteUnread.js'; +import { MiPage } from '@/models/Page.js'; +import { MiPageLike } from '@/models/PageLike.js'; +import { MiPasswordResetRequest } from '@/models/PasswordResetRequest.js'; +import { MiPoll } from '@/models/Poll.js'; +import { MiPollVote } from '@/models/PollVote.js'; +import { MiPromoNote } from '@/models/PromoNote.js'; +import { MiPromoRead } from '@/models/PromoRead.js'; +import { MiRegistrationTicket } from '@/models/RegistrationTicket.js'; +import { MiRegistryItem } from '@/models/RegistryItem.js'; +import { MiRelay } from '@/models/Relay.js'; +import { MiSignin } from '@/models/Signin.js'; +import { MiSwSubscription } from '@/models/SwSubscription.js'; +import { MiUsedUsername } from '@/models/UsedUsername.js'; +import { MiUser } from '@/models/User.js'; +import { MiUserIp } from '@/models/UserIp.js'; +import { MiUserKeypair } from '@/models/UserKeypair.js'; +import { MiUserList } from '@/models/UserList.js'; +import { MiUserListJoining } from '@/models/UserListJoining.js'; +import { MiUserNotePining } from '@/models/UserNotePining.js'; +import { MiUserPending } from '@/models/UserPending.js'; +import { MiUserProfile } from '@/models/UserProfile.js'; +import { MiUserPublickey } from '@/models/UserPublickey.js'; +import { MiUserSecurityKey } from '@/models/UserSecurityKey.js'; +import { MiUserMemo } from '@/models/UserMemo.js'; +import { MiWebhook } from '@/models/Webhook.js'; +import { MiChannel } from '@/models/Channel.js'; +import { MiRetentionAggregation } from '@/models/RetentionAggregation.js'; +import { MiRole } from '@/models/Role.js'; +import { MiRoleAssignment } from '@/models/RoleAssignment.js'; +import { MiFlash } from '@/models/Flash.js'; +import { MiFlashLike } from '@/models/FlashLike.js'; +import { MiUserListFavorite } from '@/models/UserListFavorite.js'; +import type { Repository } from 'typeorm'; + +export { + MiAbuseUserReport, + MiAccessToken, + MiAd, + MiAnnouncement, + MiAnnouncementRead, + MiAntenna, + MiApp, + MiAuthSession, + MiBlocking, + MiChannelFollowing, + MiChannelFavorite, + MiClip, + MiClipNote, + MiClipFavorite, + MiDriveFile, + MiDriveFolder, + MiEmoji, + MiFollowing, + MiFollowRequest, + MiGalleryLike, + MiGalleryPost, + MiHashtag, + MiInstance, + MiMeta, + MiModerationLog, + MiMutedNote, + MiMuting, + MiRenoteMuting, + MiNote, + MiNoteFavorite, + MiNoteReaction, + MiNoteThreadMuting, + MiNoteUnread, + MiPage, + MiPageLike, + MiPasswordResetRequest, + MiPoll, + MiPollVote, + MiPromoNote, + MiPromoRead, + MiRegistrationTicket, + MiRegistryItem, + MiRelay, + MiSignin, + MiSwSubscription, + MiUsedUsername, + MiUser, + MiUserIp, + MiUserKeypair, + MiUserList, + MiUserListFavorite, + MiUserListJoining, + MiUserNotePining, + MiUserPending, + MiUserProfile, + MiUserPublickey, + MiUserSecurityKey, + MiWebhook, + MiChannel, + MiRetentionAggregation, + MiRole, + MiRoleAssignment, + MiFlash, + MiFlashLike, + MiUserMemo, +}; + +export type AbuseUserReportsRepository = Repository; +export type AccessTokensRepository = Repository; +export type AdsRepository = Repository; +export type AnnouncementsRepository = Repository; +export type AnnouncementReadsRepository = Repository; +export type AntennasRepository = Repository; +export type AppsRepository = Repository; +export type AuthSessionsRepository = Repository; +export type BlockingsRepository = Repository; +export type ChannelFollowingsRepository = Repository; +export type ChannelFavoritesRepository = Repository; +export type ClipsRepository = Repository; +export type ClipNotesRepository = Repository; +export type ClipFavoritesRepository = Repository; +export type DriveFilesRepository = Repository; +export type DriveFoldersRepository = Repository; +export type EmojisRepository = Repository; +export type FollowingsRepository = Repository; +export type FollowRequestsRepository = Repository; +export type GalleryLikesRepository = Repository; +export type GalleryPostsRepository = Repository; +export type HashtagsRepository = Repository; +export type InstancesRepository = Repository; +export type MetasRepository = Repository; +export type ModerationLogsRepository = Repository; +export type MutedNotesRepository = Repository; +export type MutingsRepository = Repository; +export type RenoteMutingsRepository = Repository; +export type NotesRepository = Repository; +export type NoteFavoritesRepository = Repository; +export type NoteReactionsRepository = Repository; +export type NoteThreadMutingsRepository = Repository; +export type NoteUnreadsRepository = Repository; +export type PagesRepository = Repository; +export type PageLikesRepository = Repository; +export type PasswordResetRequestsRepository = Repository; +export type PollsRepository = Repository; +export type PollVotesRepository = Repository; +export type PromoNotesRepository = Repository; +export type PromoReadsRepository = Repository; +export type RegistrationTicketsRepository = Repository; +export type RegistryItemsRepository = Repository; +export type RelaysRepository = Repository; +export type SigninsRepository = Repository; +export type SwSubscriptionsRepository = Repository; +export type UsedUsernamesRepository = Repository; +export type UsersRepository = Repository; +export type UserIpsRepository = Repository; +export type UserKeypairsRepository = Repository; +export type UserListsRepository = Repository; +export type UserListFavoritesRepository = Repository; +export type UserListJoiningsRepository = Repository; +export type UserNotePiningsRepository = Repository; +export type UserPendingsRepository = Repository; +export type UserProfilesRepository = Repository; +export type UserPublickeysRepository = Repository; +export type UserSecurityKeysRepository = Repository; +export type WebhooksRepository = Repository; +export type ChannelsRepository = Repository; +export type RetentionAggregationsRepository = Repository; +export type RolesRepository = Repository; +export type RoleAssignmentsRepository = Repository; +export type FlashsRepository = Repository; +export type FlashLikesRepository = Repository; +export type UserMemoRepository = Repository; diff --git a/packages/backend/src/models/entities/AttestationChallenge.ts b/packages/backend/src/models/entities/AttestationChallenge.ts deleted file mode 100644 index 9a653c027152..000000000000 --- a/packages/backend/src/models/entities/AttestationChallenge.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and other misskey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { PrimaryColumn, Entity, JoinColumn, Column, ManyToOne, Index } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; - -@Entity() -export class AttestationChallenge { - @PrimaryColumn(id()) - public id: string; - - @Index() - @PrimaryColumn(id()) - public userId: User['id']; - - @ManyToOne(type => User, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public user: User | null; - - @Index() - @Column('varchar', { - length: 64, - comment: 'Hex-encoded sha256 hash of the challenge.', - }) - public challenge: string; - - @Column('timestamp with time zone', { - comment: 'The date challenge was created for expiry purposes.', - }) - public createdAt: Date; - - @Column('boolean', { - comment: - 'Indicates that the challenge is only for registration purposes if true to prevent the challenge for being used as authentication.', - default: false, - }) - public registrationChallenge: boolean; - - constructor(data: Partial) { - if (data == null) return; - - for (const [k, v] of Object.entries(data)) { - (this as any)[k] = v; - } - } -} diff --git a/packages/backend/src/models/entities/Notification.ts b/packages/backend/src/models/entities/Notification.ts deleted file mode 100644 index 0c9d10238377..000000000000 --- a/packages/backend/src/models/entities/Notification.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and other misskey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { notificationTypes } from '@/types.js'; -import { User } from './User.js'; -import { Note } from './Note.js'; -import { FollowRequest } from './FollowRequest.js'; -import { AccessToken } from './AccessToken.js'; - -export type Notification = { - id: string; - - // RedisのためDateではなくstring - createdAt: string; - - /** - * 通知の送信者(initiator) - */ - notifierId: User['id'] | null; - - /** - * 通知の種類。 - * follow - フォローされた - * mention - 投稿で自分が言及された - * reply - 投稿に返信された - * renote - 投稿がRenoteされた - * quote - 投稿が引用Renoteされた - * reaction - 投稿にリアクションされた - * pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した - * receiveFollowRequest - フォローリクエストされた - * followRequestAccepted - 自分の送ったフォローリクエストが承認された - * achievementEarned - 実績を獲得 - * app - アプリ通知 - */ - type: typeof notificationTypes[number]; - - noteId: Note['id'] | null; - - followRequestId: FollowRequest['id'] | null; - - reaction: string | null; - - choice: number | null; - - achievement: string | null; - - /** - * アプリ通知のbody - */ - customBody: string | null; - - /** - * アプリ通知のheader - * (省略時はアプリ名で表示されることを期待) - */ - customHeader: string | null; - - /** - * アプリ通知のicon(URL) - * (省略時はアプリアイコンで表示されることを期待) - */ - customIcon: string | null; - - /** - * アプリ通知のアプリ(のトークン) - */ - appAccessTokenId: AccessToken['id'] | null; -} diff --git a/packages/backend/src/models/entities/UserSecurityKey.ts b/packages/backend/src/models/entities/UserSecurityKey.ts deleted file mode 100644 index 368d03c16845..000000000000 --- a/packages/backend/src/models/entities/UserSecurityKey.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and other misskey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { PrimaryColumn, Entity, JoinColumn, Column, ManyToOne, Index } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; - -@Entity() -export class UserSecurityKey { - @PrimaryColumn('varchar', { - comment: 'Variable-length id given to navigator.credentials.get()', - }) - public id: string; - - @Index() - @Column(id()) - public userId: User['id']; - - @ManyToOne(type => User, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public user: User | null; - - @Index() - @Column('varchar', { - comment: - 'Variable-length public key used to verify attestations (hex-encoded).', - }) - public publicKey: string; - - @Column('timestamp with time zone', { - comment: - 'The date of the last time the UserSecurityKey was successfully validated.', - }) - public lastUsed: Date; - - @Column('varchar', { - comment: 'User-defined name for this key', - length: 30, - }) - public name: string; - - constructor(data: Partial) { - if (data == null) return; - - for (const [k, v] of Object.entries(data)) { - (this as any)[k] = v; - } - } -} diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts deleted file mode 100644 index f289303e0025..000000000000 --- a/packages/backend/src/models/index.ts +++ /dev/null @@ -1,208 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and other misskey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { AbuseUserReport } from '@/models/entities/AbuseUserReport.js'; -import { AccessToken } from '@/models/entities/AccessToken.js'; -import { Ad } from '@/models/entities/Ad.js'; -import { Announcement } from '@/models/entities/Announcement.js'; -import { AnnouncementRead } from '@/models/entities/AnnouncementRead.js'; -import { Antenna } from '@/models/entities/Antenna.js'; -import { App } from '@/models/entities/App.js'; -import { AttestationChallenge } from '@/models/entities/AttestationChallenge.js'; -import { AuthSession } from '@/models/entities/AuthSession.js'; -import { Blocking } from '@/models/entities/Blocking.js'; -import { ChannelFollowing } from '@/models/entities/ChannelFollowing.js'; -import { ChannelFavorite } from '@/models/entities/ChannelFavorite.js'; -import { Clip } from '@/models/entities/Clip.js'; -import { ClipNote } from '@/models/entities/ClipNote.js'; -import { ClipFavorite } from '@/models/entities/ClipFavorite.js'; -import { DriveFile } from '@/models/entities/DriveFile.js'; -import { DriveFolder } from '@/models/entities/DriveFolder.js'; -import { Emoji } from '@/models/entities/Emoji.js'; -import { Following } from '@/models/entities/Following.js'; -import { FollowRequest } from '@/models/entities/FollowRequest.js'; -import { GalleryLike } from '@/models/entities/GalleryLike.js'; -import { GalleryPost } from '@/models/entities/GalleryPost.js'; -import { Hashtag } from '@/models/entities/Hashtag.js'; -import { Instance } from '@/models/entities/Instance.js'; -import { Meta } from '@/models/entities/Meta.js'; -import { ModerationLog } from '@/models/entities/ModerationLog.js'; -import { MutedNote } from '@/models/entities/MutedNote.js'; -import { Muting } from '@/models/entities/Muting.js'; -import { RenoteMuting } from '@/models/entities/RenoteMuting.js'; -import { Note } from '@/models/entities/Note.js'; -import { NoteFavorite } from '@/models/entities/NoteFavorite.js'; -import { NoteReaction } from '@/models/entities/NoteReaction.js'; -import { NoteThreadMuting } from '@/models/entities/NoteThreadMuting.js'; -import { NoteUnread } from '@/models/entities/NoteUnread.js'; -import { Page } from '@/models/entities/Page.js'; -import { PageLike } from '@/models/entities/PageLike.js'; -import { PasswordResetRequest } from '@/models/entities/PasswordResetRequest.js'; -import { Poll } from '@/models/entities/Poll.js'; -import { PollVote } from '@/models/entities/PollVote.js'; -import { PromoNote } from '@/models/entities/PromoNote.js'; -import { PromoRead } from '@/models/entities/PromoRead.js'; -import { RegistrationTicket } from '@/models/entities/RegistrationTicket.js'; -import { RegistryItem } from '@/models/entities/RegistryItem.js'; -import { Relay } from '@/models/entities/Relay.js'; -import { Signin } from '@/models/entities/Signin.js'; -import { SwSubscription } from '@/models/entities/SwSubscription.js'; -import { UsedUsername } from '@/models/entities/UsedUsername.js'; -import { User } from '@/models/entities/User.js'; -import { UserIp } from '@/models/entities/UserIp.js'; -import { UserKeypair } from '@/models/entities/UserKeypair.js'; -import { UserList } from '@/models/entities/UserList.js'; -import { UserListJoining } from '@/models/entities/UserListJoining.js'; -import { UserNotePining } from '@/models/entities/UserNotePining.js'; -import { UserPending } from '@/models/entities/UserPending.js'; -import { UserProfile } from '@/models/entities/UserProfile.js'; -import { UserPublickey } from '@/models/entities/UserPublickey.js'; -import { UserSecurityKey } from '@/models/entities/UserSecurityKey.js'; -import { UserMemo } from '@/models/entities/UserMemo.js'; -import { Webhook } from '@/models/entities/Webhook.js'; -import { Channel } from '@/models/entities/Channel.js'; -import { RetentionAggregation } from '@/models/entities/RetentionAggregation.js'; -import { Role } from '@/models/entities/Role.js'; -import { RoleAssignment } from '@/models/entities/RoleAssignment.js'; -import { Flash } from '@/models/entities/Flash.js'; -import { FlashLike } from '@/models/entities/FlashLike.js'; -import { UserListFavorite } from './entities/UserListFavorite.js'; -import type { Repository } from 'typeorm'; - -export { - AbuseUserReport, - AccessToken, - Ad, - Announcement, - AnnouncementRead, - Antenna, - App, - AttestationChallenge, - AuthSession, - Blocking, - ChannelFollowing, - ChannelFavorite, - Clip, - ClipNote, - ClipFavorite, - DriveFile, - DriveFolder, - Emoji, - Following, - FollowRequest, - GalleryLike, - GalleryPost, - Hashtag, - Instance, - Meta, - ModerationLog, - MutedNote, - Muting, - RenoteMuting, - Note, - NoteFavorite, - NoteReaction, - NoteThreadMuting, - NoteUnread, - Page, - PageLike, - PasswordResetRequest, - Poll, - PollVote, - PromoNote, - PromoRead, - RegistrationTicket, - RegistryItem, - Relay, - Signin, - SwSubscription, - UsedUsername, - User, - UserIp, - UserKeypair, - UserList, - UserListFavorite, - UserListJoining, - UserNotePining, - UserPending, - UserProfile, - UserPublickey, - UserSecurityKey, - Webhook, - Channel, - RetentionAggregation, - Role, - RoleAssignment, - Flash, - FlashLike, - UserMemo, -}; - -export type AbuseUserReportsRepository = Repository; -export type AccessTokensRepository = Repository; -export type AdsRepository = Repository; -export type AnnouncementsRepository = Repository; -export type AnnouncementReadsRepository = Repository; -export type AntennasRepository = Repository; -export type AppsRepository = Repository; -export type AttestationChallengesRepository = Repository; -export type AuthSessionsRepository = Repository; -export type BlockingsRepository = Repository; -export type ChannelFollowingsRepository = Repository; -export type ChannelFavoritesRepository = Repository; -export type ClipsRepository = Repository; -export type ClipNotesRepository = Repository; -export type ClipFavoritesRepository = Repository; -export type DriveFilesRepository = Repository; -export type DriveFoldersRepository = Repository; -export type EmojisRepository = Repository; -export type FollowingsRepository = Repository; -export type FollowRequestsRepository = Repository; -export type GalleryLikesRepository = Repository; -export type GalleryPostsRepository = Repository; -export type HashtagsRepository = Repository; -export type InstancesRepository = Repository; -export type MetasRepository = Repository; -export type ModerationLogsRepository = Repository; -export type MutedNotesRepository = Repository; -export type MutingsRepository = Repository; -export type RenoteMutingsRepository = Repository; -export type NotesRepository = Repository; -export type NoteFavoritesRepository = Repository; -export type NoteReactionsRepository = Repository; -export type NoteThreadMutingsRepository = Repository; -export type NoteUnreadsRepository = Repository; -export type PagesRepository = Repository; -export type PageLikesRepository = Repository; -export type PasswordResetRequestsRepository = Repository; -export type PollsRepository = Repository; -export type PollVotesRepository = Repository; -export type PromoNotesRepository = Repository; -export type PromoReadsRepository = Repository; -export type RegistrationTicketsRepository = Repository; -export type RegistryItemsRepository = Repository; -export type RelaysRepository = Repository; -export type SigninsRepository = Repository; -export type SwSubscriptionsRepository = Repository; -export type UsedUsernamesRepository = Repository; -export type UsersRepository = Repository; -export type UserIpsRepository = Repository; -export type UserKeypairsRepository = Repository; -export type UserListsRepository = Repository; -export type UserListFavoritesRepository = Repository; -export type UserListJoiningsRepository = Repository; -export type UserNotePiningsRepository = Repository; -export type UserPendingsRepository = Repository; -export type UserProfilesRepository = Repository; -export type UserPublickeysRepository = Repository; -export type UserSecurityKeysRepository = Repository; -export type WebhooksRepository = Repository; -export type ChannelsRepository = Repository; -export type RetentionAggregationsRepository = Repository; -export type RolesRepository = Repository; -export type RoleAssignmentsRepository = Repository; -export type FlashsRepository = Repository; -export type FlashLikesRepository = Repository; -export type UserMemoRepository = Repository; diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 0c205654eb63..f15b225a303c 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -169,6 +169,15 @@ export const packedUserDetailedNotMeOnlySchema = { }, }, }, + verifiedLinks: { + type: 'array', + nullable: false, optional: false, + items: { + type: 'string', + nullable: false, optional: false, + format: 'url', + }, + }, followersCount: { type: 'number', nullable: false, optional: false, @@ -264,6 +273,10 @@ export const packedUserDetailedNotMeOnlySchema = { type: 'string', nullable: false, optional: true, }, + notify: { + type: 'string', + nullable: false, optional: true, + }, //#endregion }, } as const; @@ -321,6 +334,11 @@ export const packedMeDetailedOnlySchema = { type: 'boolean', nullable: false, optional: false, }, + twoFactorBackupCodesStock: { + type: 'string', + enum: ['full', 'partial', 'none'], + nullable: false, optional: false, + }, hideOnlineStatus: { type: 'boolean', nullable: false, optional: false, diff --git a/packages/backend/src/models/id.ts b/packages/backend/src/models/util/id.ts similarity index 100% rename from packages/backend/src/models/id.ts rename to packages/backend/src/models/util/id.ts diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index 57262f31f0aa..10126eab2bbd 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -11,72 +11,71 @@ import { DataSource, Logger } from 'typeorm'; import * as highlight from 'cli-highlight'; import { entities as charts } from '@/core/chart/entities.js'; -import { AbuseUserReport } from '@/models/entities/AbuseUserReport.js'; -import { AccessToken } from '@/models/entities/AccessToken.js'; -import { Ad } from '@/models/entities/Ad.js'; -import { Announcement } from '@/models/entities/Announcement.js'; -import { AnnouncementRead } from '@/models/entities/AnnouncementRead.js'; -import { Antenna } from '@/models/entities/Antenna.js'; -import { App } from '@/models/entities/App.js'; -import { AttestationChallenge } from '@/models/entities/AttestationChallenge.js'; -import { AuthSession } from '@/models/entities/AuthSession.js'; -import { Blocking } from '@/models/entities/Blocking.js'; -import { ChannelFollowing } from '@/models/entities/ChannelFollowing.js'; -import { ChannelFavorite } from '@/models/entities/ChannelFavorite.js'; -import { Clip } from '@/models/entities/Clip.js'; -import { ClipNote } from '@/models/entities/ClipNote.js'; -import { ClipFavorite } from '@/models/entities/ClipFavorite.js'; -import { DriveFile } from '@/models/entities/DriveFile.js'; -import { DriveFolder } from '@/models/entities/DriveFolder.js'; -import { Emoji } from '@/models/entities/Emoji.js'; -import { Following } from '@/models/entities/Following.js'; -import { FollowRequest } from '@/models/entities/FollowRequest.js'; -import { GalleryLike } from '@/models/entities/GalleryLike.js'; -import { GalleryPost } from '@/models/entities/GalleryPost.js'; -import { Hashtag } from '@/models/entities/Hashtag.js'; -import { Instance } from '@/models/entities/Instance.js'; -import { Meta } from '@/models/entities/Meta.js'; -import { ModerationLog } from '@/models/entities/ModerationLog.js'; -import { MutedNote } from '@/models/entities/MutedNote.js'; -import { Muting } from '@/models/entities/Muting.js'; -import { RenoteMuting } from '@/models/entities/RenoteMuting.js'; -import { Note } from '@/models/entities/Note.js'; -import { NoteFavorite } from '@/models/entities/NoteFavorite.js'; -import { NoteReaction } from '@/models/entities/NoteReaction.js'; -import { NoteThreadMuting } from '@/models/entities/NoteThreadMuting.js'; -import { NoteUnread } from '@/models/entities/NoteUnread.js'; -import { Page } from '@/models/entities/Page.js'; -import { PageLike } from '@/models/entities/PageLike.js'; -import { PasswordResetRequest } from '@/models/entities/PasswordResetRequest.js'; -import { Poll } from '@/models/entities/Poll.js'; -import { PollVote } from '@/models/entities/PollVote.js'; -import { PromoNote } from '@/models/entities/PromoNote.js'; -import { PromoRead } from '@/models/entities/PromoRead.js'; -import { RegistrationTicket } from '@/models/entities/RegistrationTicket.js'; -import { RegistryItem } from '@/models/entities/RegistryItem.js'; -import { Relay } from '@/models/entities/Relay.js'; -import { Signin } from '@/models/entities/Signin.js'; -import { SwSubscription } from '@/models/entities/SwSubscription.js'; -import { UsedUsername } from '@/models/entities/UsedUsername.js'; -import { User } from '@/models/entities/User.js'; -import { UserIp } from '@/models/entities/UserIp.js'; -import { UserKeypair } from '@/models/entities/UserKeypair.js'; -import { UserList } from '@/models/entities/UserList.js'; -import { UserListFavorite } from '@/models/entities/UserListFavorite.js'; -import { UserListJoining } from '@/models/entities/UserListJoining.js'; -import { UserNotePining } from '@/models/entities/UserNotePining.js'; -import { UserPending } from '@/models/entities/UserPending.js'; -import { UserProfile } from '@/models/entities/UserProfile.js'; -import { UserPublickey } from '@/models/entities/UserPublickey.js'; -import { UserSecurityKey } from '@/models/entities/UserSecurityKey.js'; -import { Webhook } from '@/models/entities/Webhook.js'; -import { Channel } from '@/models/entities/Channel.js'; -import { RetentionAggregation } from '@/models/entities/RetentionAggregation.js'; -import { Role } from '@/models/entities/Role.js'; -import { RoleAssignment } from '@/models/entities/RoleAssignment.js'; -import { Flash } from '@/models/entities/Flash.js'; -import { FlashLike } from '@/models/entities/FlashLike.js'; -import { UserMemo } from '@/models/entities/UserMemo.js'; +import { MiAbuseUserReport } from '@/models/AbuseUserReport.js'; +import { MiAccessToken } from '@/models/AccessToken.js'; +import { MiAd } from '@/models/Ad.js'; +import { MiAnnouncement } from '@/models/Announcement.js'; +import { MiAnnouncementRead } from '@/models/AnnouncementRead.js'; +import { MiAntenna } from '@/models/Antenna.js'; +import { MiApp } from '@/models/App.js'; +import { MiAuthSession } from '@/models/AuthSession.js'; +import { MiBlocking } from '@/models/Blocking.js'; +import { MiChannelFollowing } from '@/models/ChannelFollowing.js'; +import { MiChannelFavorite } from '@/models/ChannelFavorite.js'; +import { MiClip } from '@/models/Clip.js'; +import { MiClipNote } from '@/models/ClipNote.js'; +import { MiClipFavorite } from '@/models/ClipFavorite.js'; +import { MiDriveFile } from '@/models/DriveFile.js'; +import { MiDriveFolder } from '@/models/DriveFolder.js'; +import { MiEmoji } from '@/models/Emoji.js'; +import { MiFollowing } from '@/models/Following.js'; +import { MiFollowRequest } from '@/models/FollowRequest.js'; +import { MiGalleryLike } from '@/models/GalleryLike.js'; +import { MiGalleryPost } from '@/models/GalleryPost.js'; +import { MiHashtag } from '@/models/Hashtag.js'; +import { MiInstance } from '@/models/Instance.js'; +import { MiMeta } from '@/models/Meta.js'; +import { MiModerationLog } from '@/models/ModerationLog.js'; +import { MiMutedNote } from '@/models/MutedNote.js'; +import { MiMuting } from '@/models/Muting.js'; +import { MiRenoteMuting } from '@/models/RenoteMuting.js'; +import { MiNote } from '@/models/Note.js'; +import { MiNoteFavorite } from '@/models/NoteFavorite.js'; +import { MiNoteReaction } from '@/models/NoteReaction.js'; +import { MiNoteThreadMuting } from '@/models/NoteThreadMuting.js'; +import { MiNoteUnread } from '@/models/NoteUnread.js'; +import { MiPage } from '@/models/Page.js'; +import { MiPageLike } from '@/models/PageLike.js'; +import { MiPasswordResetRequest } from '@/models/PasswordResetRequest.js'; +import { MiPoll } from '@/models/Poll.js'; +import { MiPollVote } from '@/models/PollVote.js'; +import { MiPromoNote } from '@/models/PromoNote.js'; +import { MiPromoRead } from '@/models/PromoRead.js'; +import { MiRegistrationTicket } from '@/models/RegistrationTicket.js'; +import { MiRegistryItem } from '@/models/RegistryItem.js'; +import { MiRelay } from '@/models/Relay.js'; +import { MiSignin } from '@/models/Signin.js'; +import { MiSwSubscription } from '@/models/SwSubscription.js'; +import { MiUsedUsername } from '@/models/UsedUsername.js'; +import { MiUser } from '@/models/User.js'; +import { MiUserIp } from '@/models/UserIp.js'; +import { MiUserKeypair } from '@/models/UserKeypair.js'; +import { MiUserList } from '@/models/UserList.js'; +import { MiUserListFavorite } from '@/models/UserListFavorite.js'; +import { MiUserListJoining } from '@/models/UserListJoining.js'; +import { MiUserNotePining } from '@/models/UserNotePining.js'; +import { MiUserPending } from '@/models/UserPending.js'; +import { MiUserProfile } from '@/models/UserProfile.js'; +import { MiUserPublickey } from '@/models/UserPublickey.js'; +import { MiUserSecurityKey } from '@/models/UserSecurityKey.js'; +import { MiWebhook } from '@/models/Webhook.js'; +import { MiChannel } from '@/models/Channel.js'; +import { MiRetentionAggregation } from '@/models/RetentionAggregation.js'; +import { MiRole } from '@/models/Role.js'; +import { MiRoleAssignment } from '@/models/RoleAssignment.js'; +import { MiFlash } from '@/models/Flash.js'; +import { MiFlashLike } from '@/models/FlashLike.js'; +import { MiUserMemo } from '@/models/UserMemo.js'; import { Config } from '@/config.js'; import MisskeyLogger from '@/logger.js'; @@ -126,72 +125,71 @@ class MyCustomLogger implements Logger { } export const entities = [ - Announcement, - AnnouncementRead, - Meta, - Instance, - App, - AuthSession, - AccessToken, - User, - UserProfile, - UserKeypair, - UserPublickey, - UserList, - UserListFavorite, - UserListJoining, - UserNotePining, - UserSecurityKey, - UsedUsername, - AttestationChallenge, - Following, - FollowRequest, - Muting, - RenoteMuting, - Blocking, - Note, - NoteFavorite, - NoteReaction, - NoteThreadMuting, - NoteUnread, - Page, - PageLike, - GalleryPost, - GalleryLike, - DriveFile, - DriveFolder, - Poll, - PollVote, - Emoji, - Hashtag, - SwSubscription, - AbuseUserReport, - RegistrationTicket, - Signin, - ModerationLog, - Clip, - ClipNote, - ClipFavorite, - Antenna, - PromoNote, - PromoRead, - Relay, - MutedNote, - Channel, - ChannelFollowing, - ChannelFavorite, - RegistryItem, - Ad, - PasswordResetRequest, - UserPending, - Webhook, - UserIp, - RetentionAggregation, - Role, - RoleAssignment, - Flash, - FlashLike, - UserMemo, + MiAnnouncement, + MiAnnouncementRead, + MiMeta, + MiInstance, + MiApp, + MiAuthSession, + MiAccessToken, + MiUser, + MiUserProfile, + MiUserKeypair, + MiUserPublickey, + MiUserList, + MiUserListFavorite, + MiUserListJoining, + MiUserNotePining, + MiUserSecurityKey, + MiUsedUsername, + MiFollowing, + MiFollowRequest, + MiMuting, + MiRenoteMuting, + MiBlocking, + MiNote, + MiNoteFavorite, + MiNoteReaction, + MiNoteThreadMuting, + MiNoteUnread, + MiPage, + MiPageLike, + MiGalleryPost, + MiGalleryLike, + MiDriveFile, + MiDriveFolder, + MiPoll, + MiPollVote, + MiEmoji, + MiHashtag, + MiSwSubscription, + MiAbuseUserReport, + MiRegistrationTicket, + MiSignin, + MiModerationLog, + MiClip, + MiClipNote, + MiClipFavorite, + MiAntenna, + MiPromoNote, + MiPromoRead, + MiRelay, + MiMutedNote, + MiChannel, + MiChannelFollowing, + MiChannelFavorite, + MiRegistryItem, + MiAd, + MiPasswordResetRequest, + MiUserPending, + MiWebhook, + MiUserIp, + MiRetentionAggregation, + MiRole, + MiRoleAssignment, + MiFlash, + MiFlashLike, + MiUserMemo, ...charts, ]; diff --git a/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts b/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts index 757bfa3079de..5aac3f19e8da 100644 --- a/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts +++ b/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts @@ -8,7 +8,7 @@ import { IsNull, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; -import type { RetentionAggregationsRepository, UsersRepository } from '@/models/index.js'; +import type { RetentionAggregationsRepository, UsersRepository } from '@/models/_.js'; import { deepClone } from '@/misc/clone.js'; import { IdService } from '@/core/IdService.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; diff --git a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts index d87f4537c843..9b07389dc369 100644 --- a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts +++ b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { MutingsRepository } from '@/models/index.js'; +import type { MutingsRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import { UserMutingService } from '@/core/UserMutingService.js'; diff --git a/packages/backend/src/queue/processors/CleanProcessorService.ts b/packages/backend/src/queue/processors/CleanProcessorService.ts index 03a33e76acc3..f0453f705400 100644 --- a/packages/backend/src/queue/processors/CleanProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanProcessorService.ts @@ -6,10 +6,11 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, LessThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { AntennasRepository, MutedNotesRepository, RoleAssignmentsRepository, UserIpsRepository } from '@/models/index.js'; +import type { AntennasRepository, MutedNotesRepository, RoleAssignmentsRepository, UserIpsRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; +import type { Config } from '@/config.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -18,6 +19,9 @@ export class CleanProcessorService { private logger: Logger; constructor( + @Inject(DI.config) + private config: Config, + @Inject(DI.userIpsRepository) private userIpsRepository: UserIpsRepository, @@ -54,12 +58,14 @@ export class CleanProcessorService { reason: 'word', }); - // 7日以上使われてないアンテナを停止 - this.antennasRepository.update({ - lastUsedAt: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 7))), - }, { - isActive: false, - }); + // 使われてないアンテナを停止 + if (this.config.deactivateAntennaThreshold > 0) { + this.antennasRepository.update({ + lastUsedAt: LessThan(new Date(Date.now() - this.config.deactivateAntennaThreshold)), + }, { + isActive: false, + }); + } const expiredRoleAssignments = await this.roleAssignmentsRepository.createQueryBuilder('assign') .where('assign.expiresAt IS NOT NULL') diff --git a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts index a1aadb4c0b5d..b62cc8a8fdfe 100644 --- a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan, Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { DriveFile, DriveFilesRepository } from '@/models/index.js'; +import type { MiDriveFile, DriveFilesRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { bindThis } from '@/decorators.js'; @@ -32,7 +32,7 @@ export class CleanRemoteFilesProcessorService { this.logger.info('Deleting cached remote files...'); let deletedCount = 0; - let cursor: DriveFile['id'] | null = null; + let cursor: MiDriveFile['id'] | null = null; while (true) { const files = await this.driveFilesRepository.find({ diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index 07cbff2bac79..39967165d43a 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -6,11 +6,11 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { DriveFilesRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; -import type { Note } from '@/models/entities/Note.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; +import type { MiNote } from '@/models/Note.js'; import { EmailService } from '@/core/EmailService.js'; import { bindThis } from '@/decorators.js'; import { SearchService } from '@/core/SearchService.js'; @@ -53,7 +53,7 @@ export class DeleteAccountProcessorService { } { // Delete notes - let cursor: Note['id'] | null = null; + let cursor: MiNote['id'] | null = null; while (true) { const notes = await this.notesRepository.find({ @@ -65,7 +65,7 @@ export class DeleteAccountProcessorService { order: { id: 1, }, - }) as Note[]; + }) as MiNote[]; if (notes.length === 0) { break; @@ -84,7 +84,7 @@ export class DeleteAccountProcessorService { } { // Delete files - let cursor: DriveFile['id'] | null = null; + let cursor: MiDriveFile['id'] | null = null; while (true) { const files = await this.driveFilesRepository.find({ @@ -96,7 +96,7 @@ export class DeleteAccountProcessorService { order: { id: 1, }, - }) as DriveFile[]; + }) as MiDriveFile[]; if (files.length === 0) { break; diff --git a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts index e56c63d3f46b..6d0a45bcc072 100644 --- a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository, DriveFilesRepository, DriveFile } from '@/models/index.js'; +import type { UsersRepository, DriveFilesRepository, MiDriveFile } from '@/models/_.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { bindThis } from '@/decorators.js'; @@ -41,7 +41,7 @@ export class DeleteDriveFilesProcessorService { } let deletedCount = 0; - let cursor: DriveFile['id'] | null = null; + let cursor: MiDriveFile['id'] | null = null; while (true) { const files = await this.driveFilesRepository.find({ diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts index a9ac2a404065..4a1d9f28b4fd 100644 --- a/packages/backend/src/queue/processors/DeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts @@ -6,14 +6,14 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Bull from 'bullmq'; import { DI } from '@/di-symbols.js'; -import type { InstancesRepository } from '@/models/index.js'; +import type { InstancesRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { MetaService } from '@/core/MetaService.js'; import { ApRequestService } from '@/core/activitypub/ApRequestService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; import { MemorySingleCache } from '@/misc/cache.js'; -import type { Instance } from '@/models/entities/Instance.js'; +import type { MiInstance } from '@/models/Instance.js'; import InstanceChart from '@/core/chart/charts/instance.js'; import ApRequestChart from '@/core/chart/charts/ap-request.js'; import FederationChart from '@/core/chart/charts/federation.js'; @@ -26,7 +26,7 @@ import type { DeliverJobData } from '../types.js'; @Injectable() export class DeliverProcessorService { private logger: Logger; - private suspendedHostsCache: MemorySingleCache; + private suspendedHostsCache: MemorySingleCache; private latest: string | null; constructor( @@ -44,7 +44,7 @@ export class DeliverProcessorService { private queueLoggerService: QueueLoggerService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('deliver'); - this.suspendedHostsCache = new MemorySingleCache(1000 * 60 * 60); + this.suspendedHostsCache = new MemorySingleCache(1000 * 60 * 60); } @bindThis diff --git a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts index d855ee570754..4a480844368f 100644 --- a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts +++ b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { PollVotesRepository, NotesRepository } from '@/models/index.js'; +import type { PollVotesRepository, NotesRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts index 30c6fb016ec3..f941fb6e858c 100644 --- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts @@ -8,7 +8,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { format as DateFormat } from 'date-fns'; import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { AntennasRepository, UsersRepository, UserListJoiningsRepository, User } from '@/models/index.js'; +import type { AntennasRepository, UsersRepository, UserListJoiningsRepository, MiUser } from '@/models/_.js'; import Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { bindThis } from '@/decorators.js'; @@ -63,7 +63,7 @@ export class ExportAntennasProcessorService { const antennas = await this.antennsRepository.findBy({ userId: job.data.user.id }); write('['); for (const [index, antenna] of antennas.entries()) { - let users: User[] | undefined; + let users: MiUser[] | undefined; if (antenna.userListId !== null) { const joinings = await this.userListJoiningsRepository.findBy({ userListId: antenna.userListId }); users = await this.usersRepository.findBy({ diff --git a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts index 97ab81bca10f..0a37e3ca1e8f 100644 --- a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts @@ -8,7 +8,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository, BlockingsRepository, Blocking } from '@/models/index.js'; +import type { UsersRepository, BlockingsRepository, MiBlocking } from '@/models/_.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; @@ -54,7 +54,7 @@ export class ExportBlockingProcessorService { const stream = fs.createWriteStream(path, { flags: 'a' }); let exportedCount = 0; - let cursor: Blocking['id'] | null = null; + let cursor: MiBlocking['id'] | null = null; while (true) { const blockings = await this.blockingsRepository.find({ diff --git a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts index 688a0a1ccd2b..d5387fe42e75 100644 --- a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts @@ -10,7 +10,7 @@ import { format as dateFormat } from 'date-fns'; import mime from 'mime-types'; import archiver from 'archiver'; import { DI } from '@/di-symbols.js'; -import type { EmojisRepository, UsersRepository } from '@/models/index.js'; +import type { EmojisRepository, UsersRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; diff --git a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts index f76b9d02ad43..7248c7a649d7 100644 --- a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts @@ -8,12 +8,12 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import type { NoteFavorite, NoteFavoritesRepository, PollsRepository, User, UsersRepository } from '@/models/index.js'; +import type { MiNoteFavorite, NoteFavoritesRepository, PollsRepository, MiUser, UsersRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; -import type { Poll } from '@/models/entities/Poll.js'; -import type { Note } from '@/models/entities/Note.js'; +import type { MiPoll } from '@/models/Poll.js'; +import type { MiNote } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -72,7 +72,7 @@ export class ExportFavoritesProcessorService { await write('['); let exportedFavoritesCount = 0; - let cursor: NoteFavorite['id'] | null = null; + let cursor: MiNoteFavorite['id'] | null = null; while (true) { const favorites = await this.noteFavoritesRepository.find({ @@ -85,7 +85,7 @@ export class ExportFavoritesProcessorService { id: 1, }, relations: ['note', 'note.user'], - }) as (NoteFavorite & { note: Note & { user: User } })[]; + }) as (MiNoteFavorite & { note: MiNote & { user: MiUser } })[]; if (favorites.length === 0) { job.updateProgress(100); @@ -95,7 +95,7 @@ export class ExportFavoritesProcessorService { cursor = favorites.at(-1)?.id ?? null; for (const favorite of favorites) { - let poll: Poll | undefined; + let poll: MiPoll | undefined; if (favorite.note.hasPoll) { poll = await this.pollsRepository.findOneByOrFail({ noteId: favorite.note.id }); } @@ -127,7 +127,7 @@ export class ExportFavoritesProcessorService { } } -function serialize(favorite: NoteFavorite & { note: Note & { user: User } }, poll: Poll | null = null): Record { +function serialize(favorite: MiNoteFavorite & { note: MiNote & { user: MiUser } }, poll: MiPoll | null = null): Record { return { id: favorite.id, createdAt: favorite.createdAt, diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts index efb363518369..c9739eb1cbfa 100644 --- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts @@ -8,11 +8,11 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, MoreThan, Not } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository, FollowingsRepository, MutingsRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository, MutingsRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; -import type { Following } from '@/models/entities/Following.js'; +import type { MiFollowing } from '@/models/Following.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; @@ -57,7 +57,7 @@ export class ExportFollowingProcessorService { try { const stream = fs.createWriteStream(path, { flags: 'a' }); - let cursor: Following['id'] | null = null; + let cursor: MiFollowing['id'] | null = null; const mutings = job.data.excludeMuting ? await this.mutingsRepository.findBy({ muterId: user.id, @@ -74,7 +74,7 @@ export class ExportFollowingProcessorService { order: { id: 1, }, - }) as Following[]; + }) as MiFollowing[]; if (followings.length === 0) { break; diff --git a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts index 2e60529d1c34..c8425c1f2d09 100644 --- a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts @@ -8,7 +8,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import type { MutingsRepository, UsersRepository, Muting } from '@/models/index.js'; +import type { MutingsRepository, UsersRepository, MiMuting } from '@/models/_.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; @@ -54,7 +54,7 @@ export class ExportMutingProcessorService { const stream = fs.createWriteStream(path, { flags: 'a' }); let exportedCount = 0; - let cursor: Muting['id'] | null = null; + let cursor: MiMuting['id'] | null = null; while (true) { const mutes = await this.mutingsRepository.find({ diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts index 5a7964c1a7a4..e0bc80e1902a 100644 --- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts @@ -8,12 +8,12 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import type { NotesRepository, PollsRepository, UsersRepository } from '@/models/index.js'; +import type { NotesRepository, PollsRepository, UsersRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; -import type { Poll } from '@/models/entities/Poll.js'; -import type { Note } from '@/models/entities/Note.js'; +import type { MiPoll } from '@/models/Poll.js'; +import type { MiNote } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { Packed } from '@/misc/json-schema.js'; @@ -76,7 +76,7 @@ export class ExportNotesProcessorService { await write('['); let exportedNotesCount = 0; - let cursor: Note['id'] | null = null; + let cursor: MiNote['id'] | null = null; while (true) { const notes = await this.notesRepository.find({ @@ -88,7 +88,7 @@ export class ExportNotesProcessorService { order: { id: 1, }, - }) as Note[]; + }) as MiNote[]; if (notes.length === 0) { job.updateProgress(100); @@ -98,7 +98,7 @@ export class ExportNotesProcessorService { cursor = notes.at(-1)?.id ?? null; for (const note of notes) { - let poll: Poll | undefined; + let poll: MiPoll | undefined; if (note.hasPoll) { poll = await this.pollsRepository.findOneByOrFail({ noteId: note.id }); } @@ -131,7 +131,7 @@ export class ExportNotesProcessorService { } } -function serialize(note: Note, poll: Poll | null = null, files: Packed<'DriveFile'>[]): Record { +function serialize(note: MiNote, poll: MiPoll | null = null, files: Packed<'DriveFile'>[]): Record { return { id: note.id, text: note.text, diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts index ba3e18b563c2..7baaa7081a86 100644 --- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts @@ -8,7 +8,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import type { UserListJoiningsRepository, UserListsRepository, UsersRepository } from '@/models/index.js'; +import type { UserListJoiningsRepository, UserListsRepository, UsersRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; diff --git a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts index 9ff9a8a52851..7c95bccaff94 100644 --- a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts @@ -8,7 +8,7 @@ import _Ajv from 'ajv'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import Logger from '@/logger.js'; -import type { AntennasRepository } from '@/models/index.js'; +import type { AntennasRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; diff --git a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts index 1d4c3d907e42..64520b770b36 100644 --- a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository, DriveFilesRepository } from '@/models/index.js'; +import type { UsersRepository, DriveFilesRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts index 54e92d3129b1..a52af54a3966 100644 --- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts @@ -7,7 +7,7 @@ import * as fs from 'node:fs'; import { Inject, Injectable } from '@nestjs/common'; import { ZipReader } from 'slacc'; import { DI } from '@/di-symbols.js'; -import type { EmojisRepository, DriveFilesRepository } from '@/models/index.js'; +import type { EmojisRepository, DriveFilesRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { createTempDir } from '@/misc/create-temp.js'; diff --git a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts index 585311982fa1..2b5e41a12de9 100644 --- a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository, DriveFilesRepository } from '@/models/index.js'; +import type { UsersRepository, DriveFilesRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; diff --git a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts index 174046c5611d..9db4e5d8e0e9 100644 --- a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository, DriveFilesRepository } from '@/models/index.js'; +import type { UsersRepository, DriveFilesRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts index a434b4da95d2..54ca1a86df25 100644 --- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository, DriveFilesRepository, UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; +import type { UsersRepository, DriveFilesRepository, UserListJoiningsRepository, UserListsRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 50a1dd73c04b..99e823f9fa55 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -15,8 +15,8 @@ import InstanceChart from '@/core/chart/charts/instance.js'; import ApRequestChart from '@/core/chart/charts/ap-request.js'; import FederationChart from '@/core/chart/charts/federation.js'; import { getApId } from '@/core/activitypub/type.js'; -import type { RemoteUser } from '@/models/entities/User.js'; -import type { UserPublickey } from '@/models/entities/UserPublickey.js'; +import type { MiRemoteUser } from '@/models/User.js'; +import type { MiUserPublickey } from '@/models/UserPublickey.js'; import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js'; import { StatusError } from '@/misc/status-error.js'; import { UtilityService } from '@/core/UtilityService.js'; @@ -74,8 +74,8 @@ export class InboxProcessorService { // HTTP-Signature keyIdを元にDBから取得 let authUser: { - user: RemoteUser; - key: UserPublickey | null; + user: MiRemoteUser; + key: MiUserPublickey | null; } | null = await this.apDbResolverService.getAuthUserFromKeyId(signature.keyId); // keyIdでわからなければ、activity.actorを元にDBから取得 || activity.actorを元にリモートから取得 diff --git a/packages/backend/src/queue/processors/RelationshipProcessorService.ts b/packages/backend/src/queue/processors/RelationshipProcessorService.ts index 5a06938290af..5b2d2ef3139f 100644 --- a/packages/backend/src/queue/processors/RelationshipProcessorService.ts +++ b/packages/backend/src/queue/processors/RelationshipProcessorService.ts @@ -10,9 +10,9 @@ import { UserBlockingService } from '@/core/UserBlockingService.js'; import { bindThis } from '@/decorators.js'; import type Logger from '@/logger.js'; -import type { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; -import { LocalUser, RemoteUser } from '@/models/entities/User.js'; +import { MiLocalUser, MiRemoteUser } from '@/models/User.js'; import { RelationshipJobData } from '../types.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -45,7 +45,7 @@ export class RelationshipProcessorService { const [follower, followee] = await Promise.all([ this.usersRepository.findOneByOrFail({ id: job.data.from.id }), this.usersRepository.findOneByOrFail({ id: job.data.to.id }), - ]) as [LocalUser | RemoteUser, LocalUser | RemoteUser]; + ]) as [MiLocalUser | MiRemoteUser, MiLocalUser | MiRemoteUser]; await this.userFollowingService.unfollow(follower, followee, job.data.silent); return 'ok'; } diff --git a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts index 169df77924ce..a41f5565c8ed 100644 --- a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Bull from 'bullmq'; import { DI } from '@/di-symbols.js'; -import type { WebhooksRepository } from '@/models/index.js'; +import type { WebhooksRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; @@ -47,6 +47,7 @@ export class WebhookDeliverProcessorService { 'Content-Type': 'application/json', }, body: JSON.stringify({ + server: this.config.url, hookId: job.data.webhookId, userId: job.data.userId, eventId: job.data.eventId, diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts index f8eafaf1c160..c9122f5ca2ef 100644 --- a/packages/backend/src/queue/types.ts +++ b/packages/backend/src/queue/types.ts @@ -4,10 +4,10 @@ */ import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; -import type { Note } from '@/models/entities/Note.js'; -import type { User } from '@/models/entities/User.js'; -import type { Webhook } from '@/models/entities/Webhook.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; +import type { MiNote } from '@/models/Note.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiWebhook } from '@/models/Webhook.js'; import type { IActivity } from '@/core/activitypub/type.js'; import type httpSignature from '@peertube/http-signature'; @@ -78,7 +78,7 @@ export type DbUserDeleteJobData = { export type DbUserImportJobData = { user: ThinUser; - fileId: DriveFile['id']; + fileId: MiDriveFile['id']; }; export type DBAntennaImportJobData = { @@ -98,14 +98,14 @@ export type ObjectStorageFileJobData = { }; export type EndedPollNotificationJobData = { - noteId: Note['id']; + noteId: MiNote['id']; }; export type WebhookDeliverJobData = { type: string; content: unknown; - webhookId: Webhook['id']; - userId: User['id']; + webhookId: MiWebhook['id']; + userId: MiUser['id']; to: string; secret: string; createdAt: number; @@ -113,5 +113,5 @@ export type WebhookDeliverJobData = { }; export type ThinUser = { - id: User['id']; + id: MiUser['id']; }; diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index c9ce1dcaee6a..2428fa279233 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -11,16 +11,16 @@ import { Brackets, In, IsNull, LessThan, Not } from 'typeorm'; import accepts from 'accepts'; import vary from 'vary'; import { DI } from '@/di-symbols.js'; -import type { FollowingsRepository, NotesRepository, EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository, FollowRequestsRepository } from '@/models/index.js'; +import type { FollowingsRepository, NotesRepository, EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository, FollowRequestsRepository } from '@/models/_.js'; import * as url from '@/misc/prelude/url.js'; import type { Config } from '@/config.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { QueueService } from '@/core/QueueService.js'; -import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js'; +import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js'; import { UserKeypairService } from '@/core/UserKeypairService.js'; -import type { Following } from '@/models/entities/Following.js'; +import type { MiFollowing } from '@/models/Following.js'; import { countIf } from '@/misc/prelude/array.js'; -import type { Note } from '@/models/entities/Note.js'; +import type { MiNote } from '@/models/Note.js'; import { QueryService } from '@/core/QueryService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -87,7 +87,7 @@ export class ActivityPubServerService { * @param note Note */ @bindThis - private async packActivity(note: Note): Promise { + private async packActivity(note: MiNote): Promise { if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) { const renote = await this.notesRepository.findOneByOrFail({ id: note.renoteId }); return this.apRendererService.renderAnnounce(renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`, note); @@ -158,7 +158,7 @@ export class ActivityPubServerService { if (page) { const query = { followeeId: user.id, - } as FindOptionsWhere; + } as FindOptionsWhere; // カーソルが指定されている場合 if (cursor) { @@ -250,7 +250,7 @@ export class ActivityPubServerService { if (page) { const query = { followerId: user.id, - } as FindOptionsWhere; + } as FindOptionsWhere; // カーソルが指定されている場合 if (cursor) { @@ -424,7 +424,7 @@ export class ActivityPubServerService { } @bindThis - private async userInfo(request: FastifyRequest, reply: FastifyReply, user: User | null) { + private async userInfo(request: FastifyRequest, reply: FastifyReply, user: MiUser | null) { if (user == null) { reply.code(404); return; @@ -432,7 +432,7 @@ export class ActivityPubServerService { reply.header('Cache-Control', 'public, max-age=180'); this.setResponseType(request, reply); - return (this.apRendererService.addContext(await this.apRendererService.renderPerson(user as LocalUser))); + return (this.apRendererService.addContext(await this.apRendererService.renderPerson(user as MiLocalUser))); } @bindThis @@ -648,7 +648,7 @@ export class ActivityPubServerService { id: request.params.followee, host: Not(IsNull()), }), - ]) as [LocalUser | RemoteUser | null, LocalUser | RemoteUser | null]; + ]) as [MiLocalUser | MiRemoteUser | null, MiLocalUser | MiRemoteUser | null]; if (follower == null || followee == null) { reply.code(404); @@ -683,7 +683,7 @@ export class ActivityPubServerService { id: followRequest.followeeId, host: Not(IsNull()), }), - ]) as [LocalUser | RemoteUser | null, LocalUser | RemoteUser | null]; + ]) as [MiLocalUser | MiRemoteUser | null, MiLocalUser | MiRemoteUser | null]; if (follower == null || followee == null) { reply.code(404); diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index c52ce4568e9d..11721263d30f 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -11,7 +11,7 @@ import rename from 'rename'; import sharp from 'sharp'; import { sharpBmp } from 'sharp-read-bmp'; import type { Config } from '@/config.js'; -import type { DriveFile, DriveFilesRepository } from '@/models/index.js'; +import type { MiDriveFile, DriveFilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { createTemp } from '@/misc/create-temp.js'; import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; @@ -372,8 +372,8 @@ export class FileServerService { @bindThis private async getStreamAndTypeFromUrl(url: string): Promise< - { state: 'remote'; fileRole?: 'thumbnail' | 'webpublic' | 'original'; file?: DriveFile; mime: string; ext: string | null; path: string; cleanup: () => void; filename: string; } - | { state: 'stored_internal'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: DriveFile; filename: string; mime: string; ext: string | null; path: string; } + { state: 'remote'; fileRole?: 'thumbnail' | 'webpublic' | 'original'; file?: MiDriveFile; mime: string; ext: string | null; path: string; cleanup: () => void; filename: string; } + | { state: 'stored_internal'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: MiDriveFile; filename: string; mime: string; ext: string | null; path: string; } | '404' | '204' > { @@ -411,8 +411,8 @@ export class FileServerService { @bindThis private async getFileFromKey(key: string): Promise< - { state: 'remote'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: DriveFile; filename: string; url: string; mime: string; ext: string | null; path: string; cleanup: () => void; } - | { state: 'stored_internal'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: DriveFile; filename: string; mime: string; ext: string | null; path: string; } + { state: 'remote'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: MiDriveFile; filename: string; url: string; mime: string; ext: string | null; path: string; cleanup: () => void; } + | { state: 'stored_internal'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: MiDriveFile; filename: string; mime: string; ext: string | null; path: string; } | '404' | '204' > { diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts index 95cc697768ea..79f130dabe4a 100644 --- a/packages/backend/src/server/NodeinfoServerService.ts +++ b/packages/backend/src/server/NodeinfoServerService.ts @@ -18,6 +18,7 @@ import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; const nodeinfo2_1path = '/nodeinfo/2.1'; const nodeinfo2_0path = '/nodeinfo/2.0'; +const nodeinfo_homepage = 'https://misskey-hub.net'; @Injectable() export class NodeinfoServerService { @@ -35,10 +36,10 @@ export class NodeinfoServerService { @bindThis public getLinks() { - return [/* (awaiting release) { - rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1', - href: config.url + nodeinfo2_1path - }, */{ + return [{ + rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1', + href: this.config.url + nodeinfo2_1path + }, { rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0', href: this.config.url + nodeinfo2_0path, }]; @@ -46,7 +47,7 @@ export class NodeinfoServerService { @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { - const nodeinfo2 = async () => { + const nodeinfo2 = async (version: number) => { const now = Date.now(); const notesChart = await this.notesChart.getChart('hour', 1, null); @@ -73,10 +74,12 @@ export class NodeinfoServerService { const basePolicies = { ...DEFAULT_POLICIES, ...meta.policies }; - return { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const document: any = { software: { name: 'misskey', version: this.config.version, + homepage: nodeinfo_homepage, repository: meta.repositoryUrl, }, protocols: ['activitypub'], @@ -114,23 +117,36 @@ export class NodeinfoServerService { themeColor: meta.themeColor ?? '#86b300', }, }; + if (version >= 21) { + document.software.repository = meta.repositoryUrl; + document.software.homepage = meta.repositoryUrl; + } + return document; }; const cache = new MemorySingleCache>>(1000 * 60 * 10); fastify.get(nodeinfo2_1path, async (request, reply) => { - const base = await cache.fetch(() => nodeinfo2()); + const base = await cache.fetch(() => nodeinfo2(21)); - reply.header('Cache-Control', 'public, max-age=600'); + reply + .type( + 'application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.1#"', + ) + .header('Cache-Control', 'public, max-age=600'); return { version: '2.1', ...base }; }); fastify.get(nodeinfo2_0path, async (request, reply) => { - const base = await cache.fetch(() => nodeinfo2()); + const base = await cache.fetch(() => nodeinfo2(20)); delete (base as any).software.repository; - reply.header('Cache-Control', 'public, max-age=600'); + reply + .type( + 'application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.0#"', + ) + .header('Cache-Control', 'public, max-age=600'); return { version: '2.0', ...base }; }); diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index b40eeb31b174..0e4a5ece3ec9 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -12,7 +12,7 @@ import fastifyStatic from '@fastify/static'; import { IsNull } from 'typeorm'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import type { Config } from '@/config.js'; -import type { EmojisRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { EmojisRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; diff --git a/packages/backend/src/server/WellKnownServerService.ts b/packages/backend/src/server/WellKnownServerService.ts index 27139a0d6971..8fc3c96de65c 100644 --- a/packages/backend/src/server/WellKnownServerService.ts +++ b/packages/backend/src/server/WellKnownServerService.ts @@ -8,10 +8,10 @@ import { IsNull } from 'typeorm'; import vary from 'vary'; import fastifyAccepts from '@fastify/accepts'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; import { escapeAttribute, escapeValue } from '@/misc/prelude/xml.js'; -import type { User } from '@/models/entities/User.js'; +import type { MiUser } from '@/models/User.js'; import * as Acct from '@/misc/acct.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; @@ -73,7 +73,7 @@ export class WellKnownServerService { }); fastify.get('/.well-known/host-meta.json', async (request, reply) => { - reply.header('Content-Type', jrd); + reply.header('Content-Type', 'application/json'); return { links: [{ rel: 'lrdd', @@ -93,13 +93,13 @@ fastify.get('/.well-known/change-password', async (request, reply) => { */ fastify.get<{ Querystring: { resource: string } }>(webFingerPath, async (request, reply) => { - const fromId = (id: User['id']): FindOptionsWhere => ({ + const fromId = (id: MiUser['id']): FindOptionsWhere => ({ id, host: IsNull(), isSuspended: false, }); - const generateQuery = (resource: string): FindOptionsWhere | number => + const generateQuery = (resource: string): FindOptionsWhere | number => resource.startsWith(`${this.config.url.toLowerCase()}/users/`) ? fromId(resource.split('/').pop()!) : fromAcct(Acct.parse( @@ -107,7 +107,7 @@ fastify.get('/.well-known/change-password', async (request, reply) => { resource.startsWith('acct:') ? resource.slice('acct:'.length) : resource)); - const fromAcct = (acct: Acct.Acct): FindOptionsWhere | number => + const fromAcct = (acct: Acct.Acct): FindOptionsWhere | number => !acct.host || acct.host === this.config.host.toLowerCase() ? { usernameLower: acct.username, host: IsNull(), diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index ce8d3e6d9c26..e84d8145de56 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -9,10 +9,10 @@ import * as stream from 'node:stream/promises'; import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; import { getIpHash } from '@/misc/get-ip-hash.js'; -import type { LocalUser, User } from '@/models/entities/User.js'; -import type { AccessToken } from '@/models/entities/AccessToken.js'; +import type { MiLocalUser, MiUser } from '@/models/User.js'; +import type { MiAccessToken } from '@/models/AccessToken.js'; import type Logger from '@/logger.js'; -import type { UserIpsRepository } from '@/models/index.js'; +import type { UserIpsRepository } from '@/models/_.js'; import { MetaService } from '@/core/MetaService.js'; import { createTemp } from '@/misc/create-temp.js'; import { bindThis } from '@/decorators.js'; @@ -34,8 +34,8 @@ const accessDenied = { @Injectable() export class ApiCallService implements OnApplicationShutdown { private logger: Logger; - private userIpHistories: Map>; - private userIpHistoriesClearIntervalId: NodeJS.Timer; + private userIpHistories: Map>; + private userIpHistoriesClearIntervalId: NodeJS.Timeout; constructor( @Inject(DI.userIpsRepository) @@ -48,7 +48,7 @@ export class ApiCallService implements OnApplicationShutdown { private apiLoggerService: ApiLoggerService, ) { this.logger = this.apiLoggerService.logger; - this.userIpHistories = new Map>(); + this.userIpHistories = new Map>(); this.userIpHistoriesClearIntervalId = setInterval(() => { this.userIpHistories.clear(); @@ -196,7 +196,7 @@ export class ApiCallService implements OnApplicationShutdown { } @bindThis - private async logIp(request: FastifyRequest, user: LocalUser) { + private async logIp(request: FastifyRequest, user: MiLocalUser) { const meta = await this.metaService.fetch(); if (!meta.enableIpLogging) return; const ip = request.ip; @@ -222,8 +222,8 @@ export class ApiCallService implements OnApplicationShutdown { @bindThis private async call( ep: IEndpoint & { exec: any }, - user: LocalUser | null | undefined, - token: AccessToken | null | undefined, + user: MiLocalUser | null | undefined, + token: MiAccessToken | null | undefined, data: any, file: { name: string; diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index 96b6a821d509..1758c03acab8 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -9,7 +9,7 @@ import multipart from '@fastify/multipart'; import fastifyCookie from '@fastify/cookie'; import { ModuleRef } from '@nestjs/core'; import type { Config } from '@/config.js'; -import type { InstancesRepository, AccessTokensRepository } from '@/models/index.js'; +import type { InstancesRepository, AccessTokensRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts index 6c05377f965c..f07568819406 100644 --- a/packages/backend/src/server/api/AuthenticateService.ts +++ b/packages/backend/src/server/api/AuthenticateService.ts @@ -5,11 +5,11 @@ import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { AccessTokensRepository, AppsRepository, UsersRepository } from '@/models/index.js'; -import type { LocalUser } from '@/models/entities/User.js'; -import type { AccessToken } from '@/models/entities/AccessToken.js'; +import type { AccessTokensRepository, AppsRepository, UsersRepository } from '@/models/_.js'; +import type { MiLocalUser } from '@/models/User.js'; +import type { MiAccessToken } from '@/models/AccessToken.js'; import { MemoryKVCache } from '@/misc/cache.js'; -import type { App } from '@/models/entities/App.js'; +import type { MiApp } from '@/models/App.js'; import { CacheService } from '@/core/CacheService.js'; import isNativeToken from '@/misc/is-native-token.js'; import { bindThis } from '@/decorators.js'; @@ -23,7 +23,7 @@ export class AuthenticationError extends Error { @Injectable() export class AuthenticateService implements OnApplicationShutdown { - private appCache: MemoryKVCache; + private appCache: MemoryKVCache; constructor( @Inject(DI.usersRepository) @@ -37,18 +37,18 @@ export class AuthenticateService implements OnApplicationShutdown { private cacheService: CacheService, ) { - this.appCache = new MemoryKVCache(Infinity); + this.appCache = new MemoryKVCache(Infinity); } @bindThis - public async authenticate(token: string | null | undefined): Promise<[LocalUser | null, AccessToken | null]> { + public async authenticate(token: string | null | undefined): Promise<[MiLocalUser | null, MiAccessToken | null]> { if (token == null) { return [null, null]; } if (isNativeToken(token)) { const user = await this.cacheService.localUserByNativeTokenCache.fetch(token, - () => this.usersRepository.findOneBy({ token }) as Promise); + () => this.usersRepository.findOneBy({ token }) as Promise); if (user == null) { throw new AuthenticationError('user not found'); @@ -75,7 +75,7 @@ export class AuthenticateService implements OnApplicationShutdown { const user = await this.cacheService.localUserByIdCache.fetch(accessToken.userId, () => this.usersRepository.findOneBy({ id: accessToken.userId, - }) as Promise); + }) as Promise); if (accessToken.appId) { const app = await this.appCache.fetch(accessToken.appId, @@ -84,7 +84,7 @@ export class AuthenticateService implements OnApplicationShutdown { return [user, { id: accessToken.id, permission: app.permission, - } as AccessToken]; + } as MiAccessToken]; } else { return [user, accessToken]; } diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 3be652468956..41a11bfb1913 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -160,6 +160,7 @@ 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_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'; @@ -283,6 +284,7 @@ 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_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'; @@ -336,6 +338,7 @@ import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite import * as ep___users_lists_create_from_public from './endpoints/users/lists/create-from-public.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'; @@ -505,6 +508,7 @@ const $federation_users: Provider = { provide: 'ep:federation/users', useClass: 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_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 }; @@ -628,6 +632,7 @@ const $notes_unrenote: Provider = { provide: 'ep:notes/unrenote', useClass: ep__ 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_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 }; @@ -681,6 +686,7 @@ const $users_lists_unfavorite: Provider = { provide: 'ep:users/lists/unfavorite' const $users_lists_create_from_public: Provider = { provide: 'ep:users/lists/create-from-public', useClass: ep___users_lists_create_from_public.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 }; @@ -854,6 +860,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $federation_stats, $following_create, $following_delete, + $following_update, $following_invalidate, $following_requests_accept, $following_requests_cancel, @@ -977,6 +984,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $notes_userListTimeline, $notifications_create, $notifications_markAllAsRead, + $notifications_testNotification, $pagePush, $pages_create, $pages_delete, @@ -1030,6 +1038,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $users_lists_create_from_public, $users_notes, $users_pages, + $users_flashs, $users_reactions, $users_recommendation, $users_relation, @@ -1197,6 +1206,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $federation_stats, $following_create, $following_delete, + $following_update, $following_invalidate, $following_requests_accept, $following_requests_cancel, @@ -1371,6 +1381,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $users_lists_create_from_public, $users_notes, $users_pages, + $users_flashs, $users_reactions, $users_recommendation, $users_relation, diff --git a/packages/backend/src/server/api/GetterService.ts b/packages/backend/src/server/api/GetterService.ts index c16f2f504fb3..e2b98c34e71a 100644 --- a/packages/backend/src/server/api/GetterService.ts +++ b/packages/backend/src/server/api/GetterService.ts @@ -5,10 +5,10 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { NotesRepository, UsersRepository } from '@/models/index.js'; +import type { NotesRepository, UsersRepository } from '@/models/_.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; -import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js'; -import type { Note } from '@/models/entities/Note.js'; +import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js'; +import type { MiNote } from '@/models/Note.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; @@ -29,7 +29,7 @@ export class GetterService { * Get note for API processing */ @bindThis - public async getNote(noteId: Note['id']) { + public async getNote(noteId: MiNote['id']) { const note = await this.notesRepository.findOneBy({ id: noteId }); if (note == null) { @@ -43,21 +43,21 @@ export class GetterService { * Get user for API processing */ @bindThis - public async getUser(userId: User['id']) { + public async getUser(userId: MiUser['id']) { const user = await this.usersRepository.findOneBy({ id: userId }); if (user == null) { throw new IdentifiableError('15348ddd-432d-49c2-8a5a-8069753becff', 'No such user.'); } - return user as LocalUser | RemoteUser; + return user as MiLocalUser | MiRemoteUser; } /** * Get remote user for API processing */ @bindThis - public async getRemoteUser(userId: User['id']) { + public async getRemoteUser(userId: MiUser['id']) { const user = await this.getUser(userId); if (!this.userEntityService.isRemoteUser(user)) { @@ -71,7 +71,7 @@ export class GetterService { * Get local user for API processing */ @bindThis - public async getLocalUser(userId: User['id']) { + public async getLocalUser(userId: MiUser['id']) { const user = await this.getUser(userId); if (!this.userEntityService.isLocalUser(user)) { diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index 95ab4d51d780..150f3f24d42c 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -3,22 +3,27 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { randomBytes } from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; import * as OTPAuth from 'otpauth'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { UserSecurityKeysRepository, SigninsRepository, UserProfilesRepository, AttestationChallengesRepository, UsersRepository } from '@/models/index.js'; +import type { + SigninsRepository, + UserProfilesRepository, + UsersRepository, +} from '@/models/_.js'; import type { Config } from '@/config.js'; import { getIpHash } from '@/misc/get-ip-hash.js'; -import type { LocalUser } from '@/models/entities/User.js'; +import type { MiLocalUser } from '@/models/User.js'; import { IdService } from '@/core/IdService.js'; -import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js'; import { bindThis } from '@/decorators.js'; +import { WebAuthnService } from '@/core/WebAuthnService.js'; +import { UserAuthService } from '@/core/UserAuthService.js'; import { RateLimiterService } from './RateLimiterService.js'; import { SigninService } from './SigninService.js'; -import type { FastifyRequest, FastifyReply } from 'fastify'; +import type { AuthenticationResponseJSON } from '@simplewebauthn/typescript-types'; +import type { FastifyReply, FastifyRequest } from 'fastify'; @Injectable() export class SigninApiService { @@ -29,22 +34,17 @@ export class SigninApiService { @Inject(DI.usersRepository) private usersRepository: UsersRepository, - @Inject(DI.userSecurityKeysRepository) - private userSecurityKeysRepository: UserSecurityKeysRepository, - @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, - @Inject(DI.attestationChallengesRepository) - private attestationChallengesRepository: AttestationChallengesRepository, - @Inject(DI.signinsRepository) private signinsRepository: SigninsRepository, private idService: IdService, private rateLimiterService: RateLimiterService, private signinService: SigninService, - private twoFactorAuthenticationService: TwoFactorAuthenticationService, + private userAuthService: UserAuthService, + private webAuthnService: WebAuthnService, ) { } @@ -55,11 +55,7 @@ export class SigninApiService { username: string; password: string; token?: string; - signature?: string; - authenticatorData?: string; - clientDataJSON?: string; - credentialId?: string; - challengeId?: string; + credential?: AuthenticationResponseJSON; }; }>, reply: FastifyReply, @@ -110,7 +106,7 @@ export class SigninApiService { const user = await this.usersRepository.findOneBy({ usernameLower: username.toLowerCase(), host: IsNull(), - }) as LocalUser; + }) as MiLocalUser; if (user == null) { return error(404, { @@ -130,7 +126,7 @@ export class SigninApiService { const same = await bcrypt.compare(password, profile.password!); const fail = async (status?: number, failure?: { id: string }) => { - // Append signin history + // Append signin history await this.signinsRepository.insert({ id: this.idService.genId(), createdAt: new Date(), @@ -160,78 +156,25 @@ export class SigninApiService { }); } - const delta = OTPAuth.TOTP.validate({ - secret: OTPAuth.Secret.fromBase32(profile.twoFactorSecret!), - digits: 6, - token, - window: 1, - }); - - if (delta === null) { + try { + await this.userAuthService.twoFactorAuthenticate(profile, token); + } catch (e) { return await fail(403, { id: 'cdf1235b-ac71-46d4-a3a6-84ccce48df6f', }); - } else { - return this.signinService.signin(request, reply, user); } - } else if (body.credentialId && body.clientDataJSON && body.authenticatorData && body.signature) { + + return this.signinService.signin(request, reply, user); + } else if (body.credential) { if (!same && !profile.usePasswordLessLogin) { return await fail(403, { id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', }); } - const clientDataJSON = Buffer.from(body.clientDataJSON, 'hex'); - const clientData = JSON.parse(clientDataJSON.toString('utf-8')); - const challenge = await this.attestationChallengesRepository.findOneBy({ - userId: user.id, - id: body.challengeId, - registrationChallenge: false, - challenge: this.twoFactorAuthenticationService.hash(clientData.challenge).toString('hex'), - }); - - if (!challenge) { - return await fail(403, { - id: '2715a88a-2125-4013-932f-aa6fe72792da', - }); - } - - await this.attestationChallengesRepository.delete({ - userId: user.id, - id: body.challengeId, - }); - - if (new Date().getTime() - challenge.createdAt.getTime() >= 5 * 60 * 1000) { - return await fail(403, { - id: '2715a88a-2125-4013-932f-aa6fe72792da', - }); - } - - const securityKey = await this.userSecurityKeysRepository.findOneBy({ - id: Buffer.from( - body.credentialId - .replace(/-/g, '+') - .replace(/_/g, '/'), - 'base64', - ).toString('hex'), - }); + const authorized = await this.webAuthnService.verifyAuthentication(user.id, body.credential); - if (!securityKey) { - return await fail(403, { - id: '66269679-aeaf-4474-862b-eb761197e046', - }); - } - - const isValid = this.twoFactorAuthenticationService.verifySignin({ - publicKey: Buffer.from(securityKey.publicKey, 'hex'), - authenticatorData: Buffer.from(body.authenticatorData, 'hex'), - clientDataJSON, - clientData, - signature: Buffer.from(body.signature, 'hex'), - challenge: challenge.challenge, - }); - - if (isValid) { + if (authorized) { return this.signinService.signin(request, reply, user); } else { return await fail(403, { @@ -245,42 +188,11 @@ export class SigninApiService { }); } - const keys = await this.userSecurityKeysRepository.findBy({ - userId: user.id, - }); - - if (keys.length === 0) { - return await fail(403, { - id: 'f27fd449-9af4-4841-9249-1f989b9fa4a4', - }); - } - - // 32 byte challenge - const challenge = randomBytes(32).toString('base64') - .replace(/=/g, '') - .replace(/\+/g, '-') - .replace(/\//g, '_'); - - const challengeId = this.idService.genId(); - - await this.attestationChallengesRepository.insert({ - userId: user.id, - id: challengeId, - challenge: this.twoFactorAuthenticationService.hash(Buffer.from(challenge, 'utf-8')).toString('hex'), - createdAt: new Date(), - registrationChallenge: false, - }); + const authRequest = await this.webAuthnService.initiateAuthentication(user.id); reply.code(200); - return { - challenge, - challengeId, - securityKeys: keys.map(key => ({ - id: key.id, - })), - }; + return authRequest; } - // never get here + // never get here } } - diff --git a/packages/backend/src/server/api/SigninService.ts b/packages/backend/src/server/api/SigninService.ts index e23cf430155d..cebba8c8ee20 100644 --- a/packages/backend/src/server/api/SigninService.ts +++ b/packages/backend/src/server/api/SigninService.ts @@ -5,9 +5,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { SigninsRepository } from '@/models/index.js'; +import type { SigninsRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; -import type { LocalUser } from '@/models/entities/User.js'; +import type { MiLocalUser } from '@/models/User.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { SigninEntityService } from '@/core/entities/SigninEntityService.js'; import { bindThis } from '@/decorators.js'; @@ -26,7 +26,7 @@ export class SigninService { } @bindThis - public signin(request: FastifyRequest, reply: FastifyReply, user: LocalUser) { + public signin(request: FastifyRequest, reply: FastifyReply, user: MiLocalUser) { setImmediate(async () => { // Append signin history const record = await this.signinsRepository.insert({ diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index 920bbb92c53f..431df581b58e 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository, RegistrationTicket } from '@/models/index.js'; +import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository, MiRegistrationTicket } from '@/models/_.js'; import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; import { CaptchaService } from '@/core/CaptchaService.js'; @@ -15,7 +15,7 @@ import { IdService } from '@/core/IdService.js'; import { SignupService } from '@/core/SignupService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { EmailService } from '@/core/EmailService.js'; -import { LocalUser } from '@/models/entities/User.js'; +import { MiLocalUser } from '@/models/User.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { bindThis } from '@/decorators.js'; import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js'; @@ -114,7 +114,7 @@ export class SignupApiService { } } - let ticket: RegistrationTicket | null = null; + let ticket: MiRegistrationTicket | null = null; if (instance.disableRegistration) { if (invitationCode == null || typeof invitationCode !== 'string') { @@ -251,7 +251,7 @@ export class SignupApiService { }); } - return this.signinService.signin(request, reply, account as LocalUser); + return this.signinService.signin(request, reply, account as MiLocalUser); } catch (err) { throw new FastifyReplyError(400, typeof err === 'string' ? err : (err as Error).toString()); } diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index d3ff9e4ad2b4..9acaa688c508 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -8,14 +8,14 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; import * as WebSocket from 'ws'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository, AccessToken } from '@/models/index.js'; +import type { UsersRepository, MiAccessToken } from '@/models/_.js'; import { NoteReadService } from '@/core/NoteReadService.js'; import { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; import { CacheService } from '@/core/CacheService.js'; -import { LocalUser } from '@/models/entities/User.js'; +import { MiLocalUser } from '@/models/User.js'; import { AuthenticateService, AuthenticationError } from './AuthenticateService.js'; -import MainStreamConnection from './stream/index.js'; +import MainStreamConnection from './stream/Connection.js'; import { ChannelsService } from './stream/ChannelsService.js'; import type * as http from 'node:http'; @@ -55,8 +55,8 @@ export class StreamingApiServerService { const q = new URL(request.url, `http://${request.headers.host}`).searchParams; - let user: LocalUser | null = null; - let app: AccessToken | null = null; + let user: MiLocalUser | null = null; + let app: MiAccessToken | null = null; // https://datatracker.ietf.org/doc/html/rfc6750.html#section-2.1 // Note that the standard WHATWG WebSocket API does not support setting any headers, @@ -112,8 +112,8 @@ export class StreamingApiServerService { this.#wss.on('connection', async (connection: WebSocket.WebSocket, request: http.IncomingMessage, ctx: { stream: MainStreamConnection, - user: LocalUser | null; - app: AccessToken | null + user: MiLocalUser | null; + app: MiAccessToken | null }) => { const { stream, user, app } = ctx; diff --git a/packages/backend/src/server/api/endpoint-base.ts b/packages/backend/src/server/api/endpoint-base.ts index 867fa1af24c3..d5279faa1c32 100644 --- a/packages/backend/src/server/api/endpoint-base.ts +++ b/packages/backend/src/server/api/endpoint-base.ts @@ -6,8 +6,8 @@ import * as fs from 'node:fs'; import _Ajv from 'ajv'; import type { Schema, SchemaType } from '@/misc/json-schema.js'; -import type { LocalUser } from '@/models/entities/User.js'; -import type { AccessToken } from '@/models/entities/AccessToken.js'; +import type { MiLocalUser } from '@/models/User.js'; +import type { MiAccessToken } from '@/models/AccessToken.js'; import { ApiError } from './error.js'; import type { IEndpointMeta } from './endpoints.js'; @@ -28,16 +28,16 @@ type File = { // TODO: paramsの型をT['params']のスキーマ定義から推論する type Executor = - (params: SchemaType, user: T['requireCredential'] extends true ? LocalUser : LocalUser | null, token: AccessToken | null, file?: File, cleanup?: () => any, ip?: string | null, headers?: Record | null) => + (params: SchemaType, user: T['requireCredential'] extends true ? MiLocalUser : MiLocalUser | null, token: MiAccessToken | null, file?: File, cleanup?: () => any, ip?: string | null, headers?: Record | null) => Promise>>; export abstract class Endpoint { - public exec: (params: any, user: T['requireCredential'] extends true ? LocalUser : LocalUser | null, token: AccessToken | null, file?: File, ip?: string | null, headers?: Record | null) => Promise; + public exec: (params: any, user: T['requireCredential'] extends true ? MiLocalUser : MiLocalUser | null, token: MiAccessToken | null, file?: File, ip?: string | null, headers?: Record | null) => Promise; constructor(meta: T, paramDef: Ps, cb: Executor) { const validate = ajv.compile(paramDef); - this.exec = (params: any, user: T['requireCredential'] extends true ? LocalUser : LocalUser | null, token: AccessToken | null, file?: File, ip?: string | null, headers?: Record | null) => { + this.exec = (params: any, user: T['requireCredential'] extends true ? MiLocalUser : MiLocalUser | null, token: MiAccessToken | null, file?: File, ip?: string | null, headers?: Record | null) => { let cleanup: undefined | (() => void) = undefined; if (meta.requireFile) { diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index ee2616554783..879e327fe1bb 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -160,6 +160,7 @@ 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_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'; @@ -283,6 +284,7 @@ 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_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'; @@ -336,6 +338,7 @@ import * as ep___users_lists_create_from_public from './endpoints/users/lists/cr import * as ep___users_lists_update from './endpoints/users/lists/update.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'; @@ -503,6 +506,7 @@ const eps = [ ['federation/stats', ep___federation_stats], ['following/create', ep___following_create], ['following/delete', ep___following_delete], + ['following/update', ep___following_update], ['following/invalidate', ep___following_invalidate], ['following/requests/accept', ep___following_requests_accept], ['following/requests/cancel', ep___following_requests_cancel], @@ -626,6 +630,7 @@ const eps = [ ['notes/user-list-timeline', ep___notes_userListTimeline], ['notifications/create', ep___notifications_create], ['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], @@ -679,6 +684,7 @@ const eps = [ ['users/lists/create-from-public', ep___users_lists_create_from_public], ['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], @@ -805,4 +811,5 @@ const endpoints: IEndpoint[] = (eps as [string, any]).map(([name, ep]) => { }; }); +// eslint-disable-next-line import/no-default-export export default endpoints; diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index 6bf7818de602..be4fc82f0c53 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AbuseUserReportsRepository } from '@/models/index.js'; +import type { AbuseUserReportsRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; import { AbuseUserReportEntityService } from '@/core/entities/AbuseUserReportEntityService.js'; @@ -92,9 +92,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.abuseUserReportsRepository) private abuseUserReportsRepository: AbuseUserReportsRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index b9b5f82f2a9b..070e88f6f341 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -6,10 +6,10 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/_.js'; import { SignupService } from '@/core/SignupService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { localUsernameSchema, passwordSchema } from '@/models/entities/User.js'; +import { localUsernameSchema, passwordSchema } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; export const meta = { @@ -37,9 +37,8 @@ export const paramDef = { required: ['username', 'password'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts index 08e6b11d111d..60e928ccbe27 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/_.js'; import { QueueService } from '@/core/QueueService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; import { DI } from '@/di-symbols.js'; @@ -26,9 +26,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/ad/create.ts b/packages/backend/src/server/api/endpoints/admin/ad/create.ts index b0d6eae19248..a13d08fd3a2a 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/create.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AdsRepository } from '@/models/index.js'; +import type { AdsRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; @@ -32,9 +32,8 @@ export const paramDef = { required: ['url', 'memo', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'imageUrl', 'dayOfWeek'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.adsRepository) private adsRepository: AdsRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts index bebc07e0d6a0..d3c53d4f6759 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AdsRepository } from '@/models/index.js'; +import type { AdsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -32,9 +32,8 @@ export const paramDef = { required: ['id'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.adsRepository) private adsRepository: AdsRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/ad/list.ts b/packages/backend/src/server/api/endpoints/admin/ad/list.ts index b33d921c5fd9..adff3ed0ae64 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/list.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AdsRepository } from '@/models/index.js'; +import type { AdsRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; @@ -26,9 +26,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.adsRepository) private adsRepository: AdsRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/ad/update.ts b/packages/backend/src/server/api/endpoints/admin/ad/update.ts index ada17cf04d14..5b77f67e10e2 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/update.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AdsRepository } from '@/models/index.js'; +import type { AdsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -41,9 +41,8 @@ export const paramDef = { required: ['id', 'memo', 'url', 'imageUrl', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'dayOfWeek'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.adsRepository) private adsRepository: AdsRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts index 6c5520c2efa9..c2f69bb15900 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts @@ -64,9 +64,8 @@ export const paramDef = { required: ['title', 'text', 'imageUrl'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private announcementService: AnnouncementService, ) { diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts index 6066a3ceafcf..80eb6d7a8026 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AnnouncementsRepository } from '@/models/index.js'; +import type { AnnouncementsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -32,9 +32,8 @@ export const paramDef = { required: ['id'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.announcementsRepository) private announcementsRepository: AnnouncementsRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index 4da3f457f992..c82e702eefce 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -4,8 +4,8 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { AnnouncementsRepository, AnnouncementReadsRepository } from '@/models/index.js'; -import type { Announcement } from '@/models/entities/Announcement.js'; +import type { AnnouncementsRepository, AnnouncementReadsRepository } from '@/models/_.js'; +import type { MiAnnouncement } from '@/models/Announcement.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; @@ -71,9 +71,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.announcementsRepository) private announcementsRepository: AnnouncementsRepository, @@ -93,7 +92,7 @@ export default class extends Endpoint { const announcements = await query.limit(ps.limit).getMany(); - const reads = new Map(); + const reads = new Map(); for (const announcement of announcements) { reads.set(announcement, await this.announcementReadsRepository.countBy({ diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts index 7efc7c040215..782928048bf5 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AnnouncementsRepository } from '@/models/index.js'; +import type { AnnouncementsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -40,9 +40,8 @@ export const paramDef = { required: ['id'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.announcementsRepository) private announcementsRepository: AnnouncementsRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/delete-account.ts b/packages/backend/src/server/api/endpoints/admin/delete-account.ts index 32c9174073a3..9ef09b172e61 100644 --- a/packages/backend/src/server/api/endpoints/admin/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/admin/delete-account.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DeleteAccountService } from '@/core/DeleteAccountService.js'; import { DI } from '@/di-symbols.js'; @@ -27,9 +27,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts index c88b5aad8e8c..e47ecd81cf9e 100644 --- a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/_.js'; import { DriveService } from '@/core/DriveService.js'; import { DI } from '@/di-symbols.js'; @@ -24,9 +24,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts b/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts index 3f55467de093..8af44029c50c 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts @@ -20,9 +20,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private queueService: QueueService, ) { diff --git a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts index 188de3f292c7..75d689966f03 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts @@ -6,7 +6,7 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/_.js'; import { DriveService } from '@/core/DriveService.js'; import { DI } from '@/di-symbols.js'; @@ -23,9 +23,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/drive/files.ts b/packages/backend/src/server/api/endpoints/admin/drive/files.ts index 2191a456696c..ac8a70e3dab9 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/files.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; @@ -46,9 +46,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts index 9cd058304903..7fb5342f8d6f 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { DriveFilesRepository, UsersRepository } from '@/models/index.js'; +import type { DriveFilesRepository, UsersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; @@ -153,9 +153,8 @@ export const paramDef = { ], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts index c664e4b52578..66ee4cab3b2e 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts @@ -27,9 +27,8 @@ export const paramDef = { required: ['ids', 'aliases'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private customEmojiService: CustomEmojiService, ) { 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 07a9d7edbabb..7bd920c31248 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; @@ -52,9 +52,8 @@ export const paramDef = { // TODO: ロジックをサービスに切り出す -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, 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 70228bffb9d6..c5f986ff0217 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts @@ -5,9 +5,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; import { DI } from '@/di-symbols.js'; import { DriveService } from '@/core/DriveService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; @@ -51,9 +51,8 @@ export const paramDef = { // TODO: ロジックをサービスに切り出す -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, @@ -70,7 +69,7 @@ export default class extends Endpoint { throw new ApiError(meta.errors.noSuchEmoji); } - let driveFile: DriveFile; + let driveFile: MiDriveFile; try { // Create file diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts index 8fc5c4e995da..4221913049ca 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts @@ -24,9 +24,8 @@ export const paramDef = { required: ['ids'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private customEmojiService: CustomEmojiService, ) { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts index e17ebecbcb09..f020e2218273 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts @@ -30,9 +30,8 @@ export const paramDef = { required: ['id'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private customEmojiService: CustomEmojiService, ) { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts index 8d10578de003..208616c0ac35 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts @@ -21,9 +21,8 @@ export const paramDef = { required: ['fileId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private queueService: QueueService, ) { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts index 912229c1ccb5..855ab8cd244d 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; @@ -77,9 +77,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts index 7e0300e40d5c..ab16d86a3ddd 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -5,8 +5,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { EmojisRepository } from '@/models/index.js'; -import type { Emoji } from '@/models/entities/Emoji.js'; +import type { EmojisRepository } from '@/models/_.js'; +import type { MiEmoji } from '@/models/Emoji.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; @@ -71,9 +71,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, @@ -85,7 +84,7 @@ export default class extends Endpoint { const q = this.queryService.makePaginationQuery(this.emojisRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId) .andWhere('emoji.host IS NULL'); - let emojis: Emoji[]; + let emojis: MiEmoji[]; if (ps.query) { //q.andWhere('emoji.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts index db6e656d6110..a5dd6d5e3a97 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts @@ -27,9 +27,8 @@ export const paramDef = { required: ['ids', 'aliases'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private customEmojiService: CustomEmojiService, ) { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts index 34f8e022ebbe..515053f57b2b 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts @@ -27,9 +27,8 @@ export const paramDef = { required: ['ids', 'aliases'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private customEmojiService: CustomEmojiService, ) { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts index 03b4db452f9c..8e834ad1dde6 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts @@ -29,9 +29,8 @@ export const paramDef = { required: ['ids'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private customEmojiService: CustomEmojiService, ) { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts index 991850e21fb6..2dc9595a7ed1 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts @@ -29,9 +29,8 @@ export const paramDef = { required: ['ids'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private customEmojiService: CustomEmojiService, ) { 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 b08555598a10..f01be9e27a06 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -59,9 +59,8 @@ export const paramDef = { required: ['id', 'name', 'aliases'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts index 93a22a7dea92..b63f01bec305 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/_.js'; import { DriveService } from '@/core/DriveService.js'; import { DI } from '@/di-symbols.js'; @@ -24,9 +24,8 @@ export const paramDef = { required: ['host'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts index 3bc2646370f7..6dbfe3c4f59b 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { InstancesRepository } from '@/models/index.js'; +import type { InstancesRepository } from '@/models/_.js'; import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { DI } from '@/di-symbols.js'; @@ -25,9 +25,8 @@ export const paramDef = { required: ['host'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.instancesRepository) private instancesRepository: InstancesRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts index ac60e3bcc9d8..36ea390e45c7 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { FollowingsRepository, UsersRepository } from '@/models/index.js'; +import type { FollowingsRepository, UsersRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { QueueService } from '@/core/QueueService.js'; @@ -24,9 +24,8 @@ export const paramDef = { required: ['host'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts index 3eb1fc90a95e..fbb91837f287 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { InstancesRepository } from '@/models/index.js'; +import type { InstancesRepository } from '@/models/_.js'; import { UtilityService } from '@/core/UtilityService.js'; import { DI } from '@/di-symbols.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; @@ -26,9 +26,8 @@ export const paramDef = { required: ['host', 'isSuspended'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.instancesRepository) private instancesRepository: InstancesRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts index 11174958fa33..4bd9e7de7f94 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts @@ -21,9 +21,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.db) private db: DataSource, diff --git a/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts index a5e1d002c007..f953b889a3bd 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts @@ -32,9 +32,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.db) private db: DataSource, diff --git a/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts b/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts index 9e4d18e10f29..cf94c998fab0 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserIpsRepository } from '@/models/index.js'; +import type { UserIpsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; @@ -23,9 +23,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.userIpsRepository) private userIpsRepository: UserIpsRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/invite/create.ts b/packages/backend/src/server/api/endpoints/admin/invite/create.ts index 5bdca711426f..7112e06bdcd1 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite/create.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RegistrationTicketsRepository } from '@/models/index.js'; +import type { RegistrationTicketsRepository } from '@/models/_.js'; import { InviteCodeEntityService } from '@/core/entities/InviteCodeEntityService.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; @@ -52,9 +52,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.registrationTicketsRepository) private registrationTicketsRepository: RegistrationTicketsRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/invite/list.ts b/packages/backend/src/server/api/endpoints/admin/invite/list.ts index 6d2874fd1f1f..a20a51121a11 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite/list.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RegistrationTicketsRepository } from '@/models/index.js'; +import type { RegistrationTicketsRepository } from '@/models/_.js'; import { InviteCodeEntityService } from '@/core/entities/InviteCodeEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -36,9 +36,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.registrationTicketsRepository) private registrationTicketsRepository: RegistrationTicketsRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 93423a743dc5..fe9c134d841a 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -85,6 +85,14 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + app192IconUrl: { + type: 'string', + optional: false, nullable: true, + }, + app512IconUrl: { + type: 'string', + optional: false, nullable: true, + }, enableEmail: { type: 'boolean', optional: false, nullable: false, @@ -278,6 +286,10 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + manifestJsonOverride: { + type: 'string', + optional: true, nullable: false, + }, policies: { type: 'object', optional: false, nullable: false, @@ -293,9 +305,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.config) private config: Config, @@ -332,6 +343,8 @@ export default class extends Endpoint { notFoundImageUrl: instance.notFoundImageUrl, infoImageUrl: instance.infoImageUrl, iconUrl: instance.iconUrl, + app192IconUrl: instance.app192IconUrl, + app512IconUrl: instance.app512IconUrl, backgroundImageUrl: instance.backgroundImageUrl, logoImageUrl: instance.logoImageUrl, defaultLightTheme: instance.defaultLightTheme, @@ -384,6 +397,7 @@ export default class extends Endpoint { enableServerMachineStats: instance.enableServerMachineStats, enableIdenticonGeneration: instance.enableIdenticonGeneration, policies: { ...DEFAULT_POLICIES, ...instance.policies }, + manifestJsonOverride: instance.manifestJsonOverride, }; }); } diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts index ca7cecb08bb7..4061e1b5df1d 100644 --- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { PromoNotesRepository } from '@/models/index.js'; +import type { PromoNotesRepository } from '@/models/_.js'; import { GetterService } from '@/server/api/GetterService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -40,9 +40,8 @@ export const paramDef = { required: ['noteId', 'expiresAt'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.promoNotesRepository) private promoNotesRepository: PromoNotesRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/queue/clear.ts b/packages/backend/src/server/api/endpoints/admin/queue/clear.ts index d2e6fd6e2025..b61c5800349e 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/clear.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/clear.ts @@ -21,9 +21,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private moderationLogService: ModerationLogService, private queueService: QueueService, diff --git a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts index 771fca6ca092..1515ae4c745d 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts @@ -44,9 +44,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject('queue:deliver') public deliverQueue: DeliverQueue, ) { diff --git a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts index 8e00aade3a7a..febe0d07c641 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts @@ -44,9 +44,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject('queue:inbox') public inboxQueue: InboxQueue, ) { diff --git a/packages/backend/src/server/api/endpoints/admin/queue/promote.ts b/packages/backend/src/server/api/endpoints/admin/queue/promote.ts index c4dea776f4cd..8d16cddd00c4 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/promote.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/promote.ts @@ -23,9 +23,8 @@ export const paramDef = { required: ['type'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private moderationLogService: ModerationLogService, private queueService: QueueService, diff --git a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts index 86ae094fc4d8..901195e9a504 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts @@ -43,9 +43,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject('queue:system') public systemQueue: SystemQueue, @Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue, diff --git a/packages/backend/src/server/api/endpoints/admin/relays/add.ts b/packages/backend/src/server/api/endpoints/admin/relays/add.ts index e420a42ab345..b675db2b8970 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/add.ts @@ -59,9 +59,8 @@ export const paramDef = { required: ['inbox'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private relayService: RelayService, ) { diff --git a/packages/backend/src/server/api/endpoints/admin/relays/list.ts b/packages/backend/src/server/api/endpoints/admin/relays/list.ts index 7be4cfad8328..0633c57ed5c1 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/list.ts @@ -51,9 +51,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private relayService: RelayService, ) { diff --git a/packages/backend/src/server/api/endpoints/admin/relays/remove.ts b/packages/backend/src/server/api/endpoints/admin/relays/remove.ts index 6454463f008f..661b4243c47b 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/remove.ts @@ -22,9 +22,8 @@ export const paramDef = { required: ['inbox'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private relayService: RelayService, ) { diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts index 6e4ba959a2b0..0dd4fb4126f5 100644 --- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, UserProfilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; @@ -38,9 +38,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts index d37d46daa86d..8667640a6707 100644 --- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts +++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UsersRepository, AbuseUserReportsRepository } from '@/models/index.js'; +import type { UsersRepository, AbuseUserReportsRepository } from '@/models/_.js'; import { InstanceActorService } from '@/core/InstanceActorService.js'; import { QueueService } from '@/core/QueueService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; @@ -29,9 +29,8 @@ export const paramDef = { // TODO: ロジックをサービスに切り出す -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/roles/assign.ts b/packages/backend/src/server/api/endpoints/admin/roles/assign.ts index 78ab13a773ea..9a005982d413 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/assign.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/assign.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RolesRepository, UsersRepository } from '@/models/index.js'; +import type { RolesRepository, UsersRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '@/server/api/error.js'; import { RoleService } from '@/core/RoleService.js'; @@ -53,9 +53,8 @@ export const paramDef = { ], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/roles/create.ts b/packages/backend/src/server/api/endpoints/admin/roles/create.ts index d4c3e4d78961..f567b0d38769 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/create.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RolesRepository } from '@/models/index.js'; +import type { RolesRepository } from '@/models/_.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { IdService } from '@/core/IdService.js'; @@ -55,9 +55,8 @@ export const paramDef = { ], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.rolesRepository) private rolesRepository: RolesRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/roles/delete.ts b/packages/backend/src/server/api/endpoints/admin/roles/delete.ts index b57cd0d5c830..6e012f6428b4 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/delete.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RolesRepository } from '@/models/index.js'; +import type { RolesRepository } from '@/models/_.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '@/server/api/error.js'; @@ -35,9 +35,8 @@ export const paramDef = { ], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.rolesRepository) private rolesRepository: RolesRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/roles/list.ts b/packages/backend/src/server/api/endpoints/admin/roles/list.ts index 363cac7f05cc..3ed4b324dcf3 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/list.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RolesRepository } from '@/models/index.js'; +import type { RolesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { RoleEntityService } from '@/core/entities/RoleEntityService.js'; @@ -24,9 +24,8 @@ export const paramDef = { ], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.rolesRepository) private rolesRepository: RolesRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/roles/show.ts b/packages/backend/src/server/api/endpoints/admin/roles/show.ts index 189e40dd6f91..5f0accab6fee 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/show.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/show.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RolesRepository } from '@/models/index.js'; +import type { RolesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '@/server/api/error.js'; import { RoleEntityService } from '@/core/entities/RoleEntityService.js'; @@ -35,9 +35,8 @@ export const paramDef = { ], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.rolesRepository) private rolesRepository: RolesRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/roles/unassign.ts b/packages/backend/src/server/api/endpoints/admin/roles/unassign.ts index 1b3c49571bc1..0a79296c05e9 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/unassign.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/unassign.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RolesRepository, UsersRepository } from '@/models/index.js'; +import type { RolesRepository, UsersRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '@/server/api/error.js'; import { RoleService } from '@/core/RoleService.js'; @@ -55,9 +55,8 @@ export const paramDef = { ], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/roles/update-default-policies.ts b/packages/backend/src/server/api/endpoints/admin/roles/update-default-policies.ts index 85ddecf83945..b4e7e29e9091 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/update-default-policies.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/update-default-policies.ts @@ -27,9 +27,8 @@ export const paramDef = { ], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private metaService: MetaService, private globalEventService: GlobalEventService, diff --git a/packages/backend/src/server/api/endpoints/admin/roles/update.ts b/packages/backend/src/server/api/endpoints/admin/roles/update.ts index f336a3f8e93f..65fdb4b4b45a 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/update.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RolesRepository } from '@/models/index.js'; +import type { RolesRepository } from '@/models/_.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '@/server/api/error.js'; @@ -64,9 +64,8 @@ export const paramDef = { ], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.rolesRepository) private rolesRepository: RolesRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/roles/users.ts b/packages/backend/src/server/api/endpoints/admin/roles/users.ts index 89f58d036e01..b1772be7777e 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/users.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/users.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Brackets } from 'typeorm'; -import type { RoleAssignmentsRepository, RolesRepository } from '@/models/index.js'; +import type { RoleAssignmentsRepository, RolesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; @@ -38,9 +38,8 @@ export const paramDef = { required: ['roleId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.rolesRepository) private rolesRepository: RolesRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/send-email.ts b/packages/backend/src/server/api/endpoints/admin/send-email.ts index 48e52ca75f06..b9f2c6a6f1e7 100644 --- a/packages/backend/src/server/api/endpoints/admin/send-email.ts +++ b/packages/backend/src/server/api/endpoints/admin/send-email.ts @@ -24,9 +24,8 @@ export const paramDef = { required: ['to', 'subject', 'text'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private emailService: EmailService, ) { diff --git a/packages/backend/src/server/api/endpoints/admin/server-info.ts b/packages/backend/src/server/api/endpoints/admin/server-info.ts index c3446da3c00b..3169373b0e80 100644 --- a/packages/backend/src/server/api/endpoints/admin/server-info.ts +++ b/packages/backend/src/server/api/endpoints/admin/server-info.ts @@ -100,9 +100,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.db) private db: DataSource, diff --git a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts index 16e424890fdb..d5f97ab14926 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ModerationLogsRepository } from '@/models/index.js'; +import type { ModerationLogsRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; import { ModerationLogEntityService } from '@/core/entities/ModerationLogEntityService.js'; @@ -66,9 +66,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.moderationLogsRepository) private moderationLogsRepository: ModerationLogsRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index 1e8c38c96bd4..e065b99e936c 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository, SigninsRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, SigninsRepository, UserProfilesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; @@ -30,9 +30,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts index cc07e77f4922..e89e1a1490ac 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -47,9 +47,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts index 718700abbd9e..bcf12fa4e8ad 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -6,8 +6,8 @@ import { IsNull, Not } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UsersRepository, FollowingsRepository } from '@/models/index.js'; -import type { User } from '@/models/entities/User.js'; +import type { UsersRepository, FollowingsRepository } from '@/models/_.js'; +import type { MiUser } from '@/models/User.js'; import type { RelationshipJobData } from '@/queue/types.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; @@ -31,9 +31,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -73,7 +72,7 @@ export default class extends Endpoint { } @bindThis - private async unFollowAll(follower: User) { + private async unFollowAll(follower: MiUser) { const followings = await this.followingsRepository.find({ where: { followerId: follower.id, diff --git a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts index 984e0ee07489..59e89e15bd41 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/_.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; import { DI } from '@/di-symbols.js'; @@ -25,9 +25,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 6ecfe7b7162a..614e0a95d7a8 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -4,7 +4,7 @@ */ import { Injectable } from '@nestjs/common'; -import type { Meta } from '@/models/entities/Meta.js'; +import type { MiMeta } from '@/models/Meta.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { MetaService } from '@/core/MetaService.js'; @@ -39,6 +39,8 @@ export const paramDef = { infoImageUrl: { type: 'string', nullable: true }, notFoundImageUrl: { type: 'string', nullable: true }, iconUrl: { type: 'string', nullable: true }, + app192IconUrl: { type: 'string', nullable: true }, + app512IconUrl: { type: 'string', nullable: true }, backgroundImageUrl: { type: 'string', nullable: true }, logoImageUrl: { type: 'string', nullable: true }, name: { type: 'string', nullable: true }, @@ -104,19 +106,19 @@ export const paramDef = { enableIdenticonGeneration: { type: 'boolean' }, serverRules: { type: 'array', items: { type: 'string' } }, preservedUsernames: { type: 'array', items: { type: 'string' } }, + manifestJsonOverride: { type: 'string' }, }, required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private metaService: MetaService, private moderationLogService: ModerationLogService, ) { super(meta, paramDef, async (ps, me) => { - const set = {} as Partial; + const set = {} as Partial; if (typeof ps.disableRegistration === 'boolean') { set.disableRegistration = ps.disableRegistration; @@ -154,6 +156,14 @@ export default class extends Endpoint { set.iconUrl = ps.iconUrl; } + if (ps.app192IconUrl !== undefined) { + set.app192IconUrl = ps.app192IconUrl; + } + + if (ps.app512IconUrl !== undefined) { + set.app512IconUrl = ps.app512IconUrl; + } + if (ps.serverErrorImageUrl !== undefined) { set.serverErrorImageUrl = ps.serverErrorImageUrl; } @@ -422,6 +432,10 @@ export default class extends Endpoint { set.preservedUsernames = ps.preservedUsernames; } + if (ps.manifestJsonOverride !== undefined) { + set.manifestJsonOverride = ps.manifestJsonOverride; + } + await this.metaService.update(set); this.moderationLogService.insertModerationLog(me, 'updateMeta'); }); diff --git a/packages/backend/src/server/api/endpoints/admin/update-user-note.ts b/packages/backend/src/server/api/endpoints/admin/update-user-note.ts index 2cfd77263596..c86a43977e6c 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-user-note.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-user-note.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { UserProfilesRepository, UsersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; @@ -24,9 +24,8 @@ export const paramDef = { required: ['userId', 'text'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/announcements.ts b/packages/backend/src/server/api/endpoints/announcements.ts index 070e6f0d77b1..498afe344828 100644 --- a/packages/backend/src/server/api/endpoints/announcements.ts +++ b/packages/backend/src/server/api/endpoints/announcements.ts @@ -9,7 +9,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { AnnouncementService } from '@/core/AnnouncementService.js'; import { DI } from '@/di-symbols.js'; -import type { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models/index.js'; +import type { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models/_.js'; export const meta = { tags: ['meta'], @@ -38,9 +38,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.announcementsRepository) private announcementsRepository: AnnouncementsRepository, diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index d9656c5b4c6b..54361fd0c519 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; -import type { UserListsRepository, AntennasRepository } from '@/models/index.js'; +import type { UserListsRepository, AntennasRepository } from '@/models/_.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -70,9 +70,8 @@ export const paramDef = { required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile', 'notify'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.antennasRepository) private antennasRepository: AntennasRepository, @@ -86,8 +85,8 @@ export default class extends Endpoint { private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps, me) => { - if ((ps.keywords.length === 0) || ps.keywords[0].every(x => x === '')) { - throw new Error('invalid param'); + if (ps.keywords.flat().every(x => x === '') && ps.excludeKeywords.flat().every(x => x === '')) { + throw new Error('either keywords or excludeKeywords is required.'); } const currentAntennasCount = await this.antennasRepository.countBy({ diff --git a/packages/backend/src/server/api/endpoints/antennas/delete.ts b/packages/backend/src/server/api/endpoints/antennas/delete.ts index 59ccd724c0fa..e6240aec65e6 100644 --- a/packages/backend/src/server/api/endpoints/antennas/delete.ts +++ b/packages/backend/src/server/api/endpoints/antennas/delete.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AntennasRepository } from '@/models/index.js'; +import type { AntennasRepository } from '@/models/_.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -34,9 +34,8 @@ export const paramDef = { required: ['antennaId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.antennasRepository) private antennasRepository: AntennasRepository, diff --git a/packages/backend/src/server/api/endpoints/antennas/list.ts b/packages/backend/src/server/api/endpoints/antennas/list.ts index db5d65589d5b..3a9f969d241a 100644 --- a/packages/backend/src/server/api/endpoints/antennas/list.ts +++ b/packages/backend/src/server/api/endpoints/antennas/list.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AntennasRepository } from '@/models/index.js'; +import type { AntennasRepository } from '@/models/_.js'; import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -33,9 +33,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.antennasRepository) private antennasRepository: AntennasRepository, diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index 145134dcf712..eaae7bff62f1 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { NotesRepository, AntennasRepository } from '@/models/index.js'; +import type { NotesRepository, AntennasRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteReadService } from '@/core/NoteReadService.js'; import { DI } from '@/di-symbols.js'; @@ -53,9 +53,8 @@ export const paramDef = { required: ['antennaId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.redis) private redisClient: Redis.Redis, diff --git a/packages/backend/src/server/api/endpoints/antennas/show.ts b/packages/backend/src/server/api/endpoints/antennas/show.ts index 48012a11ecd2..77c9b31763ae 100644 --- a/packages/backend/src/server/api/endpoints/antennas/show.ts +++ b/packages/backend/src/server/api/endpoints/antennas/show.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AntennasRepository } from '@/models/index.js'; +import type { AntennasRepository } from '@/models/_.js'; import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -40,9 +40,8 @@ export const paramDef = { required: ['antennaId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.antennasRepository) private antennasRepository: AntennasRepository, diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index 310712ec9191..62c3db883faf 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AntennasRepository, UserListsRepository } from '@/models/index.js'; +import type { AntennasRepository, UserListsRepository } from '@/models/_.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -69,9 +69,8 @@ export const paramDef = { required: ['antennaId', 'name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile', 'notify'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.antennasRepository) private antennasRepository: AntennasRepository, @@ -83,6 +82,9 @@ export default class extends Endpoint { private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps, me) => { + if (ps.keywords.flat().every(x => x === '') && ps.excludeKeywords.flat().every(x => x === '')) { + throw new Error('either keywords or excludeKeywords is required.'); + } // Fetch the antenna const antenna = await this.antennasRepository.findOneBy({ id: ps.antennaId, diff --git a/packages/backend/src/server/api/endpoints/ap/get.ts b/packages/backend/src/server/api/endpoints/ap/get.ts index b19a4d7eb30e..a4a7fd20373f 100644 --- a/packages/backend/src/server/api/endpoints/ap/get.ts +++ b/packages/backend/src/server/api/endpoints/ap/get.ts @@ -35,9 +35,8 @@ export const paramDef = { required: ['uri'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private apResolverService: ApResolverService, ) { diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index e10c44cb8a15..f442fbdd2f46 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -6,8 +6,8 @@ import { Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { Note } from '@/models/entities/Note.js'; -import type { LocalUser, User } from '@/models/entities/User.js'; +import type { MiNote } from '@/models/Note.js'; +import type { MiLocalUser, MiUser } from '@/models/User.js'; import { isActor, isPost, getApId } from '@/core/activitypub/type.js'; import type { SchemaType } from '@/misc/json-schema.js'; import { ApResolverService } from '@/core/activitypub/ApResolverService.js'; @@ -84,9 +84,8 @@ export const paramDef = { required: ['uri'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private utilityService: UtilityService, private userEntityService: UserEntityService, @@ -111,7 +110,7 @@ export default class extends Endpoint { * URIからUserかNoteを解決する */ @bindThis - private async fetchAny(uri: string, me: LocalUser | null | undefined): Promise | null> { + private async fetchAny(uri: string, me: MiLocalUser | null | undefined): Promise | null> { // ブロックしてたら中断 const fetchedMeta = await this.metaService.fetch(); if (this.utilityService.isBlockedHost(fetchedMeta.blockedHosts, this.utilityService.extractDbHost(uri))) return null; @@ -144,7 +143,7 @@ export default class extends Endpoint { } @bindThis - private async mergePack(me: LocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise | null> { + private async mergePack(me: MiLocalUser | null | undefined, user: MiUser | null | undefined, note: MiNote | null | undefined): Promise | null> { if (user != null) { return { type: 'User', diff --git a/packages/backend/src/server/api/endpoints/app/create.ts b/packages/backend/src/server/api/endpoints/app/create.ts index 5e7486eaec86..cb00221506a3 100644 --- a/packages/backend/src/server/api/endpoints/app/create.ts +++ b/packages/backend/src/server/api/endpoints/app/create.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AppsRepository } from '@/models/index.js'; +import type { AppsRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { unique } from '@/misc/prelude/array.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; @@ -37,9 +37,8 @@ export const paramDef = { required: ['name', 'description', 'permission'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.appsRepository) private appsRepository: AppsRepository, diff --git a/packages/backend/src/server/api/endpoints/app/show.ts b/packages/backend/src/server/api/endpoints/app/show.ts index 8878dc641233..cb968a1c650b 100644 --- a/packages/backend/src/server/api/endpoints/app/show.ts +++ b/packages/backend/src/server/api/endpoints/app/show.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AppsRepository } from '@/models/index.js'; +import type { AppsRepository } from '@/models/_.js'; import { AppEntityService } from '@/core/entities/AppEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -36,9 +36,8 @@ export const paramDef = { required: ['appId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.appsRepository) private appsRepository: AppsRepository, diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts index 1070384ba303..1b1893fd9480 100644 --- a/packages/backend/src/server/api/endpoints/auth/accept.ts +++ b/packages/backend/src/server/api/endpoints/auth/accept.ts @@ -6,7 +6,7 @@ import * as crypto from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AuthSessionsRepository, AppsRepository, AccessTokensRepository } from '@/models/index.js'; +import type { AuthSessionsRepository, AppsRepository, AccessTokensRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; import { DI } from '@/di-symbols.js'; @@ -36,9 +36,8 @@ export const paramDef = { required: ['token'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.appsRepository) private appsRepository: AppsRepository, diff --git a/packages/backend/src/server/api/endpoints/auth/session/generate.ts b/packages/backend/src/server/api/endpoints/auth/session/generate.ts index 60eebecf3621..8b6a2c213dd4 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/generate.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/generate.ts @@ -6,7 +6,7 @@ import { randomUUID } from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AppsRepository, AuthSessionsRepository } from '@/models/index.js'; +import type { AppsRepository, AuthSessionsRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; @@ -50,9 +50,8 @@ export const paramDef = { required: ['appSecret'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.config) private config: Config, diff --git a/packages/backend/src/server/api/endpoints/auth/session/show.ts b/packages/backend/src/server/api/endpoints/auth/session/show.ts index 173f56722562..0f5da0f252ee 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/show.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/show.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AuthSessionsRepository } from '@/models/index.js'; +import type { AuthSessionsRepository } from '@/models/_.js'; import { AuthSessionEntityService } from '@/core/entities/AuthSessionEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -53,9 +53,8 @@ export const paramDef = { required: ['token'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.authSessionsRepository) private authSessionsRepository: AuthSessionsRepository, diff --git a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts index 29d82f9a5f26..ffddda090b0c 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AppsRepository, AccessTokensRepository, AuthSessionsRepository } from '@/models/index.js'; +import type { AppsRepository, AccessTokensRepository, AuthSessionsRepository } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -62,9 +62,8 @@ export const paramDef = { required: ['appSecret', 'token'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.appsRepository) private appsRepository: AppsRepository, diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index f7a3a6b1ba35..3c7d7ac8cde3 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -6,7 +6,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UsersRepository, BlockingsRepository } from '@/models/index.js'; +import type { UsersRepository, BlockingsRepository } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; import { DI } from '@/di-symbols.js'; @@ -60,9 +60,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts index 8ef7dd9c64a8..0ce334d5591b 100644 --- a/packages/backend/src/server/api/endpoints/blocking/delete.ts +++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts @@ -6,7 +6,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UsersRepository, BlockingsRepository } from '@/models/index.js'; +import type { UsersRepository, BlockingsRepository } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; import { DI } from '@/di-symbols.js'; @@ -60,9 +60,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/blocking/list.ts b/packages/backend/src/server/api/endpoints/blocking/list.ts index 3d3e786cdb49..58d24540d1aa 100644 --- a/packages/backend/src/server/api/endpoints/blocking/list.ts +++ b/packages/backend/src/server/api/endpoints/blocking/list.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { BlockingsRepository } from '@/models/index.js'; +import type { BlockingsRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { BlockingEntityService } from '@/core/entities/BlockingEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -38,9 +38,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.blockingsRepository) private blockingsRepository: BlockingsRepository, diff --git a/packages/backend/src/server/api/endpoints/channels/create.ts b/packages/backend/src/server/api/endpoints/channels/create.ts index 8f6451bca89e..70a08e42cb90 100644 --- a/packages/backend/src/server/api/endpoints/channels/create.ts +++ b/packages/backend/src/server/api/endpoints/channels/create.ts @@ -6,8 +6,8 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ChannelsRepository, DriveFilesRepository } from '@/models/index.js'; -import type { Channel } from '@/models/entities/Channel.js'; +import type { ChannelsRepository, DriveFilesRepository } from '@/models/_.js'; +import type { MiChannel } from '@/models/Channel.js'; import { IdService } from '@/core/IdService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -54,9 +54,8 @@ export const paramDef = { required: ['name'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, @@ -89,7 +88,7 @@ export default class extends Endpoint { bannerId: banner ? banner.id : null, isSensitive: ps.isSensitive ?? false, ...(ps.color !== undefined ? { color: ps.color } : {}), - } as Channel).then(x => this.channelsRepository.findOneByOrFail(x.identifiers[0])); + } as MiChannel).then(x => this.channelsRepository.findOneByOrFail(x.identifiers[0])); return await this.channelEntityService.pack(channel, me); }); diff --git a/packages/backend/src/server/api/endpoints/channels/favorite.ts b/packages/backend/src/server/api/endpoints/channels/favorite.ts index 65c4777f4293..bc53b1b2d6d9 100644 --- a/packages/backend/src/server/api/endpoints/channels/favorite.ts +++ b/packages/backend/src/server/api/endpoints/channels/favorite.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ChannelFavoritesRepository, ChannelsRepository } from '@/models/index.js'; +import type { ChannelFavoritesRepository, ChannelsRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -36,9 +36,8 @@ export const paramDef = { required: ['channelId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.channelsRepository) private channelsRepository: ChannelsRepository, diff --git a/packages/backend/src/server/api/endpoints/channels/featured.ts b/packages/backend/src/server/api/endpoints/channels/featured.ts index 3e26c1fdbb60..412ea1bb1636 100644 --- a/packages/backend/src/server/api/endpoints/channels/featured.ts +++ b/packages/backend/src/server/api/endpoints/channels/featured.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ChannelsRepository } from '@/models/index.js'; +import type { ChannelsRepository } from '@/models/_.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -31,9 +31,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.channelsRepository) private channelsRepository: ChannelsRepository, diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts index b18bbe49d30e..055ad48d441b 100644 --- a/packages/backend/src/server/api/endpoints/channels/follow.ts +++ b/packages/backend/src/server/api/endpoints/channels/follow.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ChannelFollowingsRepository, ChannelsRepository } from '@/models/index.js'; +import type { ChannelFollowingsRepository, ChannelsRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -36,9 +36,8 @@ export const paramDef = { required: ['channelId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.channelsRepository) private channelsRepository: ChannelsRepository, diff --git a/packages/backend/src/server/api/endpoints/channels/followed.ts b/packages/backend/src/server/api/endpoints/channels/followed.ts index 6e4a13c1dbd3..6514f1ea3cd6 100644 --- a/packages/backend/src/server/api/endpoints/channels/followed.ts +++ b/packages/backend/src/server/api/endpoints/channels/followed.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ChannelFollowingsRepository } from '@/models/index.js'; +import type { ChannelFollowingsRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -38,9 +38,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.channelFollowingsRepository) private channelFollowingsRepository: ChannelFollowingsRepository, diff --git a/packages/backend/src/server/api/endpoints/channels/my-favorites.ts b/packages/backend/src/server/api/endpoints/channels/my-favorites.ts index e4999a395fe5..057a438ac912 100644 --- a/packages/backend/src/server/api/endpoints/channels/my-favorites.ts +++ b/packages/backend/src/server/api/endpoints/channels/my-favorites.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ChannelFavoritesRepository } from '@/models/index.js'; +import type { ChannelFavoritesRepository } from '@/models/_.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -34,9 +34,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.channelFavoritesRepository) private channelFavoritesRepository: ChannelFavoritesRepository, diff --git a/packages/backend/src/server/api/endpoints/channels/owned.ts b/packages/backend/src/server/api/endpoints/channels/owned.ts index 1b85b4154378..b1dd6935371f 100644 --- a/packages/backend/src/server/api/endpoints/channels/owned.ts +++ b/packages/backend/src/server/api/endpoints/channels/owned.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ChannelsRepository } from '@/models/index.js'; +import type { ChannelsRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -38,9 +38,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.channelsRepository) private channelsRepository: ChannelsRepository, diff --git a/packages/backend/src/server/api/endpoints/channels/search.ts b/packages/backend/src/server/api/endpoints/channels/search.ts index b66245c48ec4..65df45706b60 100644 --- a/packages/backend/src/server/api/endpoints/channels/search.ts +++ b/packages/backend/src/server/api/endpoints/channels/search.ts @@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Brackets } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; -import type { ChannelsRepository } from '@/models/index.js'; +import type { ChannelsRepository } from '@/models/_.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { DI } from '@/di-symbols.js'; import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; @@ -40,9 +40,8 @@ export const paramDef = { required: ['query'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.channelsRepository) private channelsRepository: ChannelsRepository, diff --git a/packages/backend/src/server/api/endpoints/channels/show.ts b/packages/backend/src/server/api/endpoints/channels/show.ts index 1a3d95f7fd83..3eaa83c7e82a 100644 --- a/packages/backend/src/server/api/endpoints/channels/show.ts +++ b/packages/backend/src/server/api/endpoints/channels/show.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ChannelsRepository } from '@/models/index.js'; +import type { ChannelsRepository } from '@/models/_.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -38,9 +38,8 @@ export const paramDef = { required: ['channelId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.channelsRepository) private channelsRepository: ChannelsRepository, diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index 2307b07fc52a..026b6495372b 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ChannelsRepository, Note, NotesRepository } from '@/models/index.js'; +import type { ChannelsRepository, MiNote, NotesRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; @@ -51,9 +51,8 @@ export const paramDef = { required: ['channelId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.redis) private redisClient: Redis.Redis, @@ -78,7 +77,7 @@ export default class extends Endpoint { throw new ApiError(meta.errors.noSuchChannel); } - let timeline: Note[] = []; + let timeline: MiNote[] = []; const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1 let noteIdsRes: [string, string[]][] = []; diff --git a/packages/backend/src/server/api/endpoints/channels/unfavorite.ts b/packages/backend/src/server/api/endpoints/channels/unfavorite.ts index 47da73e2d64a..e700fad90fc6 100644 --- a/packages/backend/src/server/api/endpoints/channels/unfavorite.ts +++ b/packages/backend/src/server/api/endpoints/channels/unfavorite.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ChannelFavoritesRepository, ChannelsRepository } from '@/models/index.js'; +import type { ChannelFavoritesRepository, ChannelsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -35,9 +35,8 @@ export const paramDef = { required: ['channelId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.channelsRepository) private channelsRepository: ChannelsRepository, diff --git a/packages/backend/src/server/api/endpoints/channels/unfollow.ts b/packages/backend/src/server/api/endpoints/channels/unfollow.ts index a3b4e4ed40b8..6a27858619ef 100644 --- a/packages/backend/src/server/api/endpoints/channels/unfollow.ts +++ b/packages/backend/src/server/api/endpoints/channels/unfollow.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ChannelFollowingsRepository, ChannelsRepository } from '@/models/index.js'; +import type { ChannelFollowingsRepository, ChannelsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -35,9 +35,8 @@ export const paramDef = { required: ['channelId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.channelsRepository) private channelsRepository: ChannelsRepository, diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts index 528e5cb38c8c..ab69f62a7b50 100644 --- a/packages/backend/src/server/api/endpoints/channels/update.ts +++ b/packages/backend/src/server/api/endpoints/channels/update.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DriveFilesRepository, ChannelsRepository } from '@/models/index.js'; +import type { DriveFilesRepository, ChannelsRepository } from '@/models/_.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; @@ -65,9 +65,8 @@ export const paramDef = { required: ['channelId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.channelsRepository) private channelsRepository: ChannelsRepository, diff --git a/packages/backend/src/server/api/endpoints/charts/active-users.ts b/packages/backend/src/server/api/endpoints/charts/active-users.ts index a7dd9c23f31a..e768923ce10c 100644 --- a/packages/backend/src/server/api/endpoints/charts/active-users.ts +++ b/packages/backend/src/server/api/endpoints/charts/active-users.ts @@ -28,9 +28,8 @@ export const paramDef = { required: ['span'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private activeUsersChart: ActiveUsersChart, ) { diff --git a/packages/backend/src/server/api/endpoints/charts/ap-request.ts b/packages/backend/src/server/api/endpoints/charts/ap-request.ts index afa84e5a3d46..f518ae41caaa 100644 --- a/packages/backend/src/server/api/endpoints/charts/ap-request.ts +++ b/packages/backend/src/server/api/endpoints/charts/ap-request.ts @@ -28,9 +28,8 @@ export const paramDef = { required: ['span'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private apRequestChart: ApRequestChart, ) { diff --git a/packages/backend/src/server/api/endpoints/charts/drive.ts b/packages/backend/src/server/api/endpoints/charts/drive.ts index 0f2f8193e575..94afab113e15 100644 --- a/packages/backend/src/server/api/endpoints/charts/drive.ts +++ b/packages/backend/src/server/api/endpoints/charts/drive.ts @@ -28,9 +28,8 @@ export const paramDef = { required: ['span'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private driveChart: DriveChart, ) { diff --git a/packages/backend/src/server/api/endpoints/charts/federation.ts b/packages/backend/src/server/api/endpoints/charts/federation.ts index 5a56e34123b6..bc33930ca425 100644 --- a/packages/backend/src/server/api/endpoints/charts/federation.ts +++ b/packages/backend/src/server/api/endpoints/charts/federation.ts @@ -28,9 +28,8 @@ export const paramDef = { required: ['span'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private federationChart: FederationChart, ) { diff --git a/packages/backend/src/server/api/endpoints/charts/instance.ts b/packages/backend/src/server/api/endpoints/charts/instance.ts index 7c9b265c7e2b..a432845b3854 100644 --- a/packages/backend/src/server/api/endpoints/charts/instance.ts +++ b/packages/backend/src/server/api/endpoints/charts/instance.ts @@ -29,9 +29,8 @@ export const paramDef = { required: ['span', 'host'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private instanceChart: InstanceChart, ) { diff --git a/packages/backend/src/server/api/endpoints/charts/notes.ts b/packages/backend/src/server/api/endpoints/charts/notes.ts index 5b07418ccfd9..e1e9d0631148 100644 --- a/packages/backend/src/server/api/endpoints/charts/notes.ts +++ b/packages/backend/src/server/api/endpoints/charts/notes.ts @@ -28,9 +28,8 @@ export const paramDef = { required: ['span'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private notesChart: NotesChart, ) { diff --git a/packages/backend/src/server/api/endpoints/charts/user/drive.ts b/packages/backend/src/server/api/endpoints/charts/user/drive.ts index 6a410bec3c82..b4a58c98724e 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/drive.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/drive.ts @@ -29,9 +29,8 @@ export const paramDef = { required: ['span', 'userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private perUserDriveChart: PerUserDriveChart, ) { diff --git a/packages/backend/src/server/api/endpoints/charts/user/following.ts b/packages/backend/src/server/api/endpoints/charts/user/following.ts index ecb9fb922e8f..c609c5a7fe01 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/following.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/following.ts @@ -29,9 +29,8 @@ export const paramDef = { required: ['span', 'userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private perUserFollowingChart: PerUserFollowingChart, ) { diff --git a/packages/backend/src/server/api/endpoints/charts/user/notes.ts b/packages/backend/src/server/api/endpoints/charts/user/notes.ts index 42610b29e095..ad6a342fb799 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/notes.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/notes.ts @@ -29,9 +29,8 @@ export const paramDef = { required: ['span', 'userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private perUserNotesChart: PerUserNotesChart, ) { diff --git a/packages/backend/src/server/api/endpoints/charts/user/pv.ts b/packages/backend/src/server/api/endpoints/charts/user/pv.ts index 2ab77a6b4e7a..635a403d1269 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/pv.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/pv.ts @@ -29,9 +29,8 @@ export const paramDef = { required: ['span', 'userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private perUserPvChart: PerUserPvChart, ) { diff --git a/packages/backend/src/server/api/endpoints/charts/user/reactions.ts b/packages/backend/src/server/api/endpoints/charts/user/reactions.ts index 39f1d6e28cbf..92bc7028adea 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/reactions.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/reactions.ts @@ -29,9 +29,8 @@ export const paramDef = { required: ['span', 'userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private perUserReactionsChart: PerUserReactionsChart, ) { diff --git a/packages/backend/src/server/api/endpoints/charts/users.ts b/packages/backend/src/server/api/endpoints/charts/users.ts index 24ba51ee8b76..3be3721e3aec 100644 --- a/packages/backend/src/server/api/endpoints/charts/users.ts +++ b/packages/backend/src/server/api/endpoints/charts/users.ts @@ -28,9 +28,8 @@ export const paramDef = { required: ['span'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private usersChart: UsersChart, ) { diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts index 004f672ce30d..ac5814242841 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -6,11 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { IdService } from '@/core/IdService.js'; -import { DI } from '@/di-symbols.js'; -import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; -import { GetterService } from '@/server/api/GetterService.js'; -import { RoleService } from '@/core/RoleService.js'; +import { ClipService } from '@/core/ClipService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -63,62 +59,27 @@ export const paramDef = { required: ['clipId', 'noteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.clipsRepository) - private clipsRepository: ClipsRepository, - - @Inject(DI.clipNotesRepository) - private clipNotesRepository: ClipNotesRepository, - - private idService: IdService, - private roleService: RoleService, - private getterService: GetterService, + private clipService: ClipService, ) { super(meta, paramDef, async (ps, me) => { - const clip = await this.clipsRepository.findOneBy({ - id: ps.clipId, - userId: me.id, - }); - - if (clip == null) { - throw new ApiError(meta.errors.noSuchClip); + try { + await this.clipService.addNote(me, ps.clipId, ps.noteId); + } catch (e) { + if (e instanceof ClipService.NoSuchClipError) { + throw new ApiError(meta.errors.noSuchClip); + } else if (e instanceof ClipService.NoSuchNoteError) { + throw new ApiError(meta.errors.noSuchNote); + } else if (e instanceof ClipService.AlreadyAddedError) { + throw new ApiError(meta.errors.alreadyClipped); + } else if (e instanceof ClipService.TooManyClipNotesError) { + throw new ApiError(meta.errors.tooManyClipNotes); + } else { + throw e; + } } - - const note = await this.getterService.getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - - const exist = await this.clipNotesRepository.exist({ - where: { - noteId: note.id, - clipId: clip.id, - }, - }); - - if (exist) { - throw new ApiError(meta.errors.alreadyClipped); - } - - const currentCount = await this.clipNotesRepository.countBy({ - clipId: clip.id, - }); - if (currentCount > (await this.roleService.getUserPolicies(me.id)).noteEachClipsLimit) { - throw new ApiError(meta.errors.tooManyClipNotes); - } - - await this.clipNotesRepository.insert({ - id: this.idService.genId(), - noteId: note.id, - clipId: clip.id, - }); - - await this.clipsRepository.update(clip.id, { - lastClippedAt: new Date(), - }); }); } } diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts index 6a0425d9c6b5..36c9acd04add 100644 --- a/packages/backend/src/server/api/endpoints/clips/create.ts +++ b/packages/backend/src/server/api/endpoints/clips/create.ts @@ -5,12 +5,10 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { IdService } from '@/core/IdService.js'; -import type { ClipsRepository } from '@/models/index.js'; +import type { MiClip } from '@/models/_.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; -import { DI } from '@/di-symbols.js'; -import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '@/server/api/error.js'; +import { ClipService } from '@/core/ClipService.js'; export const meta = { tags: ['clips'], @@ -46,34 +44,22 @@ export const paramDef = { required: ['name'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.clipsRepository) - private clipsRepository: ClipsRepository, - private clipEntityService: ClipEntityService, - private roleService: RoleService, - private idService: IdService, + private clipService: ClipService, ) { super(meta, paramDef, async (ps, me) => { - const currentCount = await this.clipsRepository.countBy({ - userId: me.id, - }); - if (currentCount > (await this.roleService.getUserPolicies(me.id)).clipLimit) { - throw new ApiError(meta.errors.tooManyClips); + let clip: MiClip; + try { + clip = await this.clipService.create(me, ps.name, ps.isPublic, ps.description ?? null); + } catch (e) { + if (e instanceof ClipService.TooManyClipsError) { + throw new ApiError(meta.errors.tooManyClips); + } + throw e; } - - const clip = await this.clipsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), - userId: me.id, - name: ps.name, - isPublic: ps.isPublic, - description: ps.description, - }).then(x => this.clipsRepository.findOneByOrFail(x.identifiers[0])); - return await this.clipEntityService.pack(clip, me); }); } diff --git a/packages/backend/src/server/api/endpoints/clips/delete.ts b/packages/backend/src/server/api/endpoints/clips/delete.ts index 3f3976d84a1d..239945e8a4a0 100644 --- a/packages/backend/src/server/api/endpoints/clips/delete.ts +++ b/packages/backend/src/server/api/endpoints/clips/delete.ts @@ -5,8 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ClipsRepository } from '@/models/index.js'; -import { DI } from '@/di-symbols.js'; +import { ClipService } from '@/core/ClipService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -33,24 +32,20 @@ export const paramDef = { required: ['clipId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.clipsRepository) - private clipsRepository: ClipsRepository, + private clipService: ClipService, ) { super(meta, paramDef, async (ps, me) => { - const clip = await this.clipsRepository.findOneBy({ - id: ps.clipId, - userId: me.id, - }); - - if (clip == null) { - throw new ApiError(meta.errors.noSuchClip); + try { + await this.clipService.delete(me, ps.clipId); + } catch (e) { + if (e instanceof ClipService.NoSuchClipError) { + throw new ApiError(meta.errors.noSuchClip); + } + throw e; } - - await this.clipsRepository.delete(clip.id); }); } } diff --git a/packages/backend/src/server/api/endpoints/clips/favorite.ts b/packages/backend/src/server/api/endpoints/clips/favorite.ts index 47b042b88b3f..5179777445d1 100644 --- a/packages/backend/src/server/api/endpoints/clips/favorite.ts +++ b/packages/backend/src/server/api/endpoints/clips/favorite.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { ClipsRepository, ClipFavoritesRepository } from '@/models/index.js'; +import type { ClipsRepository, ClipFavoritesRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; @@ -42,9 +42,8 @@ export const paramDef = { required: ['clipId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.clipsRepository) private clipsRepository: ClipsRepository, diff --git a/packages/backend/src/server/api/endpoints/clips/list.ts b/packages/backend/src/server/api/endpoints/clips/list.ts index 65289d33e677..c124762e3302 100644 --- a/packages/backend/src/server/api/endpoints/clips/list.ts +++ b/packages/backend/src/server/api/endpoints/clips/list.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ClipsRepository } from '@/models/index.js'; +import type { ClipsRepository } from '@/models/_.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -33,9 +33,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.clipsRepository) private clipsRepository: ClipsRepository, diff --git a/packages/backend/src/server/api/endpoints/clips/my-favorites.ts b/packages/backend/src/server/api/endpoints/clips/my-favorites.ts index b5efa56b6a02..c58c16e25f66 100644 --- a/packages/backend/src/server/api/endpoints/clips/my-favorites.ts +++ b/packages/backend/src/server/api/endpoints/clips/my-favorites.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ClipFavoritesRepository } from '@/models/index.js'; +import type { ClipFavoritesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; @@ -34,9 +34,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.clipFavoritesRepository) private clipFavoritesRepository: ClipFavoritesRepository, diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts index 0a35691ec082..1427d8d0a755 100644 --- a/packages/backend/src/server/api/endpoints/clips/notes.ts +++ b/packages/backend/src/server/api/endpoints/clips/notes.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { NotesRepository, ClipsRepository, ClipNotesRepository } from '@/models/index.js'; +import type { NotesRepository, ClipsRepository, ClipNotesRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -48,9 +48,8 @@ export const paramDef = { required: ['clipId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.clipsRepository) private clipsRepository: ClipsRepository, diff --git a/packages/backend/src/server/api/endpoints/clips/remove-note.ts b/packages/backend/src/server/api/endpoints/clips/remove-note.ts index 399a2e4777c2..72b76e194b79 100644 --- a/packages/backend/src/server/api/endpoints/clips/remove-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/remove-note.ts @@ -5,9 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; -import { DI } from '@/di-symbols.js'; -import { GetterService } from '@/server/api/GetterService.js'; +import { ClipService } from '@/core/ClipService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -43,37 +41,22 @@ export const paramDef = { required: ['clipId', 'noteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.clipsRepository) - private clipsRepository: ClipsRepository, - - @Inject(DI.clipNotesRepository) - private clipNotesRepository: ClipNotesRepository, - - private getterService: GetterService, + private clipService: ClipService, ) { super(meta, paramDef, async (ps, me) => { - const clip = await this.clipsRepository.findOneBy({ - id: ps.clipId, - userId: me.id, - }); - - if (clip == null) { - throw new ApiError(meta.errors.noSuchClip); + try { + await this.clipService.removeNote(me, ps.clipId, ps.noteId); + } catch (e) { + if (e instanceof ClipService.NoSuchClipError) { + throw new ApiError(meta.errors.noSuchClip); + } else if (e instanceof ClipService.NoSuchNoteError) { + throw new ApiError(meta.errors.noSuchNote); + } + throw e; } - - const note = await this.getterService.getNote(ps.noteId).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw err; - }); - - await this.clipNotesRepository.delete({ - noteId: note.id, - clipId: clip.id, - }); }); } } diff --git a/packages/backend/src/server/api/endpoints/clips/show.ts b/packages/backend/src/server/api/endpoints/clips/show.ts index 0e2485dec6bc..03b1e09dfb9e 100644 --- a/packages/backend/src/server/api/endpoints/clips/show.ts +++ b/packages/backend/src/server/api/endpoints/clips/show.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ClipsRepository } from '@/models/index.js'; +import type { ClipsRepository } from '@/models/_.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -40,9 +40,8 @@ export const paramDef = { required: ['clipId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.clipsRepository) private clipsRepository: ClipsRepository, diff --git a/packages/backend/src/server/api/endpoints/clips/unfavorite.ts b/packages/backend/src/server/api/endpoints/clips/unfavorite.ts index 24d216608ea1..8836c8a87ae1 100644 --- a/packages/backend/src/server/api/endpoints/clips/unfavorite.ts +++ b/packages/backend/src/server/api/endpoints/clips/unfavorite.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { ClipsRepository, ClipFavoritesRepository } from '@/models/index.js'; +import type { ClipsRepository, ClipFavoritesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -41,9 +41,8 @@ export const paramDef = { required: ['clipId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.clipsRepository) private clipsRepository: ClipsRepository, diff --git a/packages/backend/src/server/api/endpoints/clips/update.ts b/packages/backend/src/server/api/endpoints/clips/update.ts index 26f79d4e324e..60ba5627f017 100644 --- a/packages/backend/src/server/api/endpoints/clips/update.ts +++ b/packages/backend/src/server/api/endpoints/clips/update.ts @@ -5,9 +5,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ClipsRepository } from '@/models/index.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; -import { DI } from '@/di-symbols.js'; +import { ClipService } from '@/core/ClipService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -45,33 +44,24 @@ export const paramDef = { required: ['clipId', 'name'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.clipsRepository) - private clipsRepository: ClipsRepository, + private clipService: ClipService, private clipEntityService: ClipEntityService, ) { super(meta, paramDef, async (ps, me) => { - // Fetch the clip - const clip = await this.clipsRepository.findOneBy({ - id: ps.clipId, - userId: me.id, - }); - - if (clip == null) { - throw new ApiError(meta.errors.noSuchClip); + try { + await this.clipService.update(me, ps.clipId, ps.name, ps.isPublic, ps.description); + } catch (e) { + if (e instanceof ClipService.NoSuchClipError) { + throw new ApiError(meta.errors.noSuchClip); + } + throw e; } - await this.clipsRepository.update(clip.id, { - name: ps.name, - description: ps.description, - isPublic: ps.isPublic, - }); - - return await this.clipEntityService.pack(clip.id, me); + return await this.clipEntityService.pack(ps.clipId, me); }); } } diff --git a/packages/backend/src/server/api/endpoints/drive.ts b/packages/backend/src/server/api/endpoints/drive.ts index 8b4a9e5935af..71d3ca5f146d 100644 --- a/packages/backend/src/server/api/endpoints/drive.ts +++ b/packages/backend/src/server/api/endpoints/drive.ts @@ -38,9 +38,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private metaService: MetaService, private driveFileEntityService: DriveFileEntityService, diff --git a/packages/backend/src/server/api/endpoints/drive/files.ts b/packages/backend/src/server/api/endpoints/drive/files.ts index 96c51a16ad34..6f3a62977f56 100644 --- a/packages/backend/src/server/api/endpoints/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/drive/files.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -41,9 +41,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts index 23929bcfa782..779231a856f1 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { NotesRepository, DriveFilesRepository } from '@/models/index.js'; +import type { NotesRepository, DriveFilesRepository } from '@/models/_.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -46,9 +46,8 @@ export const paramDef = { required: ['fileId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts index f780cf90dafd..85e6312b6abe 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; export const meta = { @@ -31,9 +31,8 @@ export const paramDef = { required: ['md5'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts index 7bc72c2ee663..15ff7a05b80c 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -70,9 +70,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private driveFileEntityService: DriveFileEntityService, private metaService: MetaService, diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts index c04ce8313ea5..d7fdc81cdb6e 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/_.js'; import { DriveService } from '@/core/DriveService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; @@ -44,9 +44,8 @@ export const paramDef = { required: ['fileId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, diff --git a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts index 3217bd4421c5..7b784f253e9a 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -37,9 +37,8 @@ export const paramDef = { required: ['md5'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, diff --git a/packages/backend/src/server/api/endpoints/drive/files/find.ts b/packages/backend/src/server/api/endpoints/drive/files/find.ts index ac09d82ee70d..0ceb31e58dce 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/_.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -39,9 +39,8 @@ export const paramDef = { required: ['name'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts index d9c0d766cbd4..474c7f02d30c 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts @@ -4,8 +4,8 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; +import type { DriveFilesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -54,9 +54,8 @@ export const paramDef = { ], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, @@ -65,7 +64,7 @@ export default class extends Endpoint { private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { - let file: DriveFile | null = null; + let file: MiDriveFile | null = null; if (ps.fileId) { file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts index c6fe65420956..d26ed6347427 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { DriveFilesRepository, DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFilesRepository, DriveFoldersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; @@ -71,9 +71,8 @@ export const paramDef = { required: ['fileId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts index ee91ee1e1b3c..b12a213c9d72 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts @@ -40,9 +40,8 @@ export const paramDef = { required: ['url'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private driveFileEntityService: DriveFileEntityService, private driveService: DriveService, diff --git a/packages/backend/src/server/api/endpoints/drive/folders.ts b/packages/backend/src/server/api/endpoints/drive/folders.ts index a1408fd213a6..3a0926659144 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFoldersRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -39,9 +39,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFoldersRepository) private driveFoldersRepository: DriveFoldersRepository, diff --git a/packages/backend/src/server/api/endpoints/drive/folders/create.ts b/packages/backend/src/server/api/endpoints/drive/folders/create.ts index 6272fa683e8a..bc3a9bbe2181 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/create.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFoldersRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; @@ -49,9 +49,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFoldersRepository) private driveFoldersRepository: DriveFoldersRepository, diff --git a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts index 50db0c208566..46a00ca3dce8 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DriveFoldersRepository, DriveFilesRepository } from '@/models/index.js'; +import type { DriveFoldersRepository, DriveFilesRepository } from '@/models/_.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -40,9 +40,8 @@ export const paramDef = { required: ['folderId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, diff --git a/packages/backend/src/server/api/endpoints/drive/folders/find.ts b/packages/backend/src/server/api/endpoints/drive/folders/find.ts index b8af55c10395..2f5cdcc648e6 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/find.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFoldersRepository } from '@/models/_.js'; import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -37,9 +37,8 @@ export const paramDef = { required: ['name'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFoldersRepository) private driveFoldersRepository: DriveFoldersRepository, diff --git a/packages/backend/src/server/api/endpoints/drive/folders/show.ts b/packages/backend/src/server/api/endpoints/drive/folders/show.ts index 041ed371a8e9..dd44fc46c947 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/show.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFoldersRepository } from '@/models/_.js'; import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -40,9 +40,8 @@ export const paramDef = { required: ['folderId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFoldersRepository) private driveFoldersRepository: DriveFoldersRepository, diff --git a/packages/backend/src/server/api/endpoints/drive/folders/update.ts b/packages/backend/src/server/api/endpoints/drive/folders/update.ts index c6f115b3585a..f8683132b293 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/update.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DriveFoldersRepository } from '@/models/index.js'; +import type { DriveFoldersRepository } from '@/models/_.js'; import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; @@ -55,9 +55,8 @@ export const paramDef = { required: ['folderId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFoldersRepository) private driveFoldersRepository: DriveFoldersRepository, diff --git a/packages/backend/src/server/api/endpoints/drive/stream.ts b/packages/backend/src/server/api/endpoints/drive/stream.ts index 1311939e167a..27e1656f8253 100644 --- a/packages/backend/src/server/api/endpoints/drive/stream.ts +++ b/packages/backend/src/server/api/endpoints/drive/stream.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -39,9 +39,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, diff --git a/packages/backend/src/server/api/endpoints/email-address/available.ts b/packages/backend/src/server/api/endpoints/email-address/available.ts index 55495fa3c93c..787009f13c7d 100644 --- a/packages/backend/src/server/api/endpoints/email-address/available.ts +++ b/packages/backend/src/server/api/endpoints/email-address/available.ts @@ -36,9 +36,8 @@ export const paramDef = { required: ['emailAddress'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private emailService: EmailService, ) { diff --git a/packages/backend/src/server/api/endpoints/emoji.ts b/packages/backend/src/server/api/endpoints/emoji.ts index 0ab9e9b004e4..ead8c9979e92 100644 --- a/packages/backend/src/server/api/endpoints/emoji.ts +++ b/packages/backend/src/server/api/endpoints/emoji.ts @@ -5,7 +5,7 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -34,9 +34,8 @@ export const paramDef = { required: ['name'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, diff --git a/packages/backend/src/server/api/endpoints/emojis.ts b/packages/backend/src/server/api/endpoints/emojis.ts index 7cd3eba51817..2adf0a21b324 100644 --- a/packages/backend/src/server/api/endpoints/emojis.ts +++ b/packages/backend/src/server/api/endpoints/emojis.ts @@ -5,7 +5,7 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -41,9 +41,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, diff --git a/packages/backend/src/server/api/endpoints/endpoint.ts b/packages/backend/src/server/api/endpoints/endpoint.ts index b0272671e01e..cecaded20a20 100644 --- a/packages/backend/src/server/api/endpoints/endpoint.ts +++ b/packages/backend/src/server/api/endpoints/endpoint.ts @@ -21,9 +21,8 @@ export const paramDef = { required: ['endpoint'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( ) { super(meta, paramDef, async (ps) => { diff --git a/packages/backend/src/server/api/endpoints/endpoints.ts b/packages/backend/src/server/api/endpoints/endpoints.ts index 826fadf68417..86def04acac0 100644 --- a/packages/backend/src/server/api/endpoints/endpoints.ts +++ b/packages/backend/src/server/api/endpoints/endpoints.ts @@ -34,9 +34,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( ) { super(meta, paramDef, async () => { diff --git a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts index 77b402c59ea5..7380c593e388 100644 --- a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts +++ b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts @@ -23,9 +23,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private queueService: QueueService, ) { diff --git a/packages/backend/src/server/api/endpoints/federation/followers.ts b/packages/backend/src/server/api/endpoints/federation/followers.ts index 9b18f9deb48f..a92cf6a9d8a5 100644 --- a/packages/backend/src/server/api/endpoints/federation/followers.ts +++ b/packages/backend/src/server/api/endpoints/federation/followers.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { FollowingsRepository } from '@/models/index.js'; +import type { FollowingsRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -37,9 +37,8 @@ export const paramDef = { required: ['host'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, diff --git a/packages/backend/src/server/api/endpoints/federation/following.ts b/packages/backend/src/server/api/endpoints/federation/following.ts index 6e526996bea7..d72ceeeea2f4 100644 --- a/packages/backend/src/server/api/endpoints/federation/following.ts +++ b/packages/backend/src/server/api/endpoints/federation/following.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { FollowingsRepository } from '@/models/index.js'; +import type { FollowingsRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -37,9 +37,8 @@ export const paramDef = { required: ['host'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index 96063403b2a3..be73e5dbb829 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { InstancesRepository } from '@/models/index.js'; +import type { InstancesRepository } from '@/models/_.js'; import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js'; import { MetaService } from '@/core/MetaService.js'; import { DI } from '@/di-symbols.js'; @@ -46,9 +46,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.instancesRepository) private instancesRepository: InstancesRepository, diff --git a/packages/backend/src/server/api/endpoints/federation/show-instance.ts b/packages/backend/src/server/api/endpoints/federation/show-instance.ts index d76303ed45cf..71eec11235d6 100644 --- a/packages/backend/src/server/api/endpoints/federation/show-instance.ts +++ b/packages/backend/src/server/api/endpoints/federation/show-instance.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { InstancesRepository } from '@/models/index.js'; +import type { InstancesRepository } from '@/models/_.js'; import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { DI } from '@/di-symbols.js'; @@ -33,9 +33,8 @@ export const paramDef = { required: ['host'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.instancesRepository) private instancesRepository: InstancesRepository, diff --git a/packages/backend/src/server/api/endpoints/federation/stats.ts b/packages/backend/src/server/api/endpoints/federation/stats.ts index 048a7ff4e3ed..e3ffea7b7e0c 100644 --- a/packages/backend/src/server/api/endpoints/federation/stats.ts +++ b/packages/backend/src/server/api/endpoints/federation/stats.ts @@ -5,7 +5,7 @@ import { IsNull, MoreThan, Not } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { FollowingsRepository, InstancesRepository } from '@/models/index.js'; +import type { FollowingsRepository, InstancesRepository } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js'; @@ -28,9 +28,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.instancesRepository) private instancesRepository: InstancesRepository, diff --git a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts index 90ef86cf4b2b..c0aa882088d0 100644 --- a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts +++ b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts @@ -22,9 +22,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private getterService: GetterService, private apPersonService: ApPersonService, diff --git a/packages/backend/src/server/api/endpoints/federation/users.ts b/packages/backend/src/server/api/endpoints/federation/users.ts index f284b2175cda..d97171865a64 100644 --- a/packages/backend/src/server/api/endpoints/federation/users.ts +++ b/packages/backend/src/server/api/endpoints/federation/users.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -37,9 +37,8 @@ export const paramDef = { required: ['host'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/fetch-rss.ts b/packages/backend/src/server/api/endpoints/fetch-rss.ts index 0080f3a2953a..37859d833019 100644 --- a/packages/backend/src/server/api/endpoints/fetch-rss.ts +++ b/packages/backend/src/server/api/endpoints/fetch-rss.ts @@ -26,9 +26,8 @@ export const paramDef = { required: ['url'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private httpRequestService: HttpRequestService, ) { diff --git a/packages/backend/src/server/api/endpoints/flash/create.ts b/packages/backend/src/server/api/endpoints/flash/create.ts index 8a68b90e4421..5d4954d74371 100644 --- a/packages/backend/src/server/api/endpoints/flash/create.ts +++ b/packages/backend/src/server/api/endpoints/flash/create.ts @@ -5,7 +5,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import type { FlashsRepository } from '@/models/index.js'; +import type { FlashsRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; @@ -42,9 +42,8 @@ export const paramDef = { required: ['title', 'summary', 'script', 'permissions'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.flashsRepository) private flashsRepository: FlashsRepository, diff --git a/packages/backend/src/server/api/endpoints/flash/delete.ts b/packages/backend/src/server/api/endpoints/flash/delete.ts index b71754000a62..e5448c816acc 100644 --- a/packages/backend/src/server/api/endpoints/flash/delete.ts +++ b/packages/backend/src/server/api/endpoints/flash/delete.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { FlashsRepository } from '@/models/index.js'; +import type { FlashsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -39,9 +39,8 @@ export const paramDef = { required: ['flashId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.flashsRepository) private flashsRepository: FlashsRepository, diff --git a/packages/backend/src/server/api/endpoints/flash/featured.ts b/packages/backend/src/server/api/endpoints/flash/featured.ts index a8f81fb380ff..1fa5612ac422 100644 --- a/packages/backend/src/server/api/endpoints/flash/featured.ts +++ b/packages/backend/src/server/api/endpoints/flash/featured.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { FlashsRepository } from '@/models/index.js'; +import type { FlashsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { FlashEntityService } from '@/core/entities/FlashEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -31,9 +31,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.flashsRepository) private flashsRepository: FlashsRepository, diff --git a/packages/backend/src/server/api/endpoints/flash/like.ts b/packages/backend/src/server/api/endpoints/flash/like.ts index c1d29b779b08..69b92560879c 100644 --- a/packages/backend/src/server/api/endpoints/flash/like.ts +++ b/packages/backend/src/server/api/endpoints/flash/like.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { FlashsRepository, FlashLikesRepository } from '@/models/index.js'; +import type { FlashsRepository, FlashLikesRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; @@ -48,9 +48,8 @@ export const paramDef = { required: ['flashId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.flashsRepository) private flashsRepository: FlashsRepository, diff --git a/packages/backend/src/server/api/endpoints/flash/my-likes.ts b/packages/backend/src/server/api/endpoints/flash/my-likes.ts index ed1ee0ffde03..e328bdbee574 100644 --- a/packages/backend/src/server/api/endpoints/flash/my-likes.ts +++ b/packages/backend/src/server/api/endpoints/flash/my-likes.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { FlashLikesRepository } from '@/models/index.js'; +import type { FlashLikesRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { FlashLikeEntityService } from '@/core/entities/FlashLikeEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -48,9 +48,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.flashLikesRepository) private flashLikesRepository: FlashLikesRepository, diff --git a/packages/backend/src/server/api/endpoints/flash/my.ts b/packages/backend/src/server/api/endpoints/flash/my.ts index 2417b448aa3d..442d8dcd758f 100644 --- a/packages/backend/src/server/api/endpoints/flash/my.ts +++ b/packages/backend/src/server/api/endpoints/flash/my.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { FlashsRepository } from '@/models/index.js'; +import type { FlashsRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { FlashEntityService } from '@/core/entities/FlashEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -38,9 +38,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.flashsRepository) private flashsRepository: FlashsRepository, diff --git a/packages/backend/src/server/api/endpoints/flash/show.ts b/packages/backend/src/server/api/endpoints/flash/show.ts index 0e6a561df79b..c41a27c925b9 100644 --- a/packages/backend/src/server/api/endpoints/flash/show.ts +++ b/packages/backend/src/server/api/endpoints/flash/show.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { FlashsRepository } from '@/models/index.js'; +import type { FlashsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { FlashEntityService } from '@/core/entities/FlashEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -38,9 +38,8 @@ export const paramDef = { required: ['flashId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.flashsRepository) private flashsRepository: FlashsRepository, diff --git a/packages/backend/src/server/api/endpoints/flash/unlike.ts b/packages/backend/src/server/api/endpoints/flash/unlike.ts index e65cefa9bdbd..2bf8ba85f768 100644 --- a/packages/backend/src/server/api/endpoints/flash/unlike.ts +++ b/packages/backend/src/server/api/endpoints/flash/unlike.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { FlashsRepository, FlashLikesRepository } from '@/models/index.js'; +import type { FlashsRepository, FlashLikesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -41,9 +41,8 @@ export const paramDef = { required: ['flashId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.flashsRepository) private flashsRepository: FlashsRepository, diff --git a/packages/backend/src/server/api/endpoints/flash/update.ts b/packages/backend/src/server/api/endpoints/flash/update.ts index 4b3c02580741..faac00e540f1 100644 --- a/packages/backend/src/server/api/endpoints/flash/update.ts +++ b/packages/backend/src/server/api/endpoints/flash/update.ts @@ -5,7 +5,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import type { FlashsRepository } from '@/models/index.js'; +import type { FlashsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -49,13 +49,13 @@ export const paramDef = { permissions: { type: 'array', items: { type: 'string', } }, + visibility: { type: 'string', enum: ['public', 'private'] }, }, required: ['flashId', 'title', 'summary', 'script', 'permissions'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.flashsRepository) private flashsRepository: FlashsRepository, diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index 6269eb2a8f8e..4bc5b92791d9 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -6,7 +6,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { FollowingsRepository } from '@/models/index.js'; +import type { FollowingsRepository } from '@/models/_.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; @@ -19,7 +19,7 @@ export const meta = { limit: { duration: ms('1hour'), - max: 50, + max: 100, }, requireCredential: true, @@ -75,9 +75,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index 1d7e47a9ab7f..f44692ba6d7e 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -6,7 +6,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { FollowingsRepository } from '@/models/index.js'; +import type { FollowingsRepository } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; @@ -60,9 +60,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts index 1f4be3d51817..53ef925b2fbe 100644 --- a/packages/backend/src/server/api/endpoints/following/invalidate.ts +++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts @@ -6,7 +6,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { FollowingsRepository } from '@/models/index.js'; +import type { FollowingsRepository } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { DI } from '@/di-symbols.js'; @@ -29,7 +29,7 @@ export const meta = { noSuchUser: { message: 'No such user.', code: 'NO_SUCH_USER', - id: '5b12c78d-2b28-4dca-99d2-f56139b42ff8', + id: 'b77e6ae6-a3e5-40da-9cc8-c240115479cc', }, followerIsYourself: { @@ -41,7 +41,7 @@ export const meta = { notFollowing: { message: 'The other use is not following you.', code: 'NOT_FOLLOWING', - id: '5dbf82f5-c92b-40b1-87d1-6c8c0741fd09', + id: '918faac3-074f-41ae-9c43-ed5d2946770d', }, }, @@ -60,9 +60,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, diff --git a/packages/backend/src/server/api/endpoints/following/requests/accept.ts b/packages/backend/src/server/api/endpoints/following/requests/accept.ts index 324dcec12ad2..91fe92220055 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/accept.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/accept.ts @@ -38,9 +38,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private getterService: GetterService, private userFollowingService: UserFollowingService, diff --git a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts index 54c4a239df85..d9d5c7041b10 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts @@ -47,9 +47,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private userEntityService: UserEntityService, private getterService: GetterService, diff --git a/packages/backend/src/server/api/endpoints/following/requests/list.ts b/packages/backend/src/server/api/endpoints/following/requests/list.ts index 26c068c64621..c4faa88f65e4 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/list.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/list.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; -import type { FollowRequestsRepository } from '@/models/index.js'; +import type { FollowRequestsRepository } from '@/models/_.js'; import { FollowRequestEntityService } from '@/core/entities/FollowRequestEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -54,9 +54,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.followRequestsRepository) private followRequestsRepository: FollowRequestsRepository, diff --git a/packages/backend/src/server/api/endpoints/following/requests/reject.ts b/packages/backend/src/server/api/endpoints/following/requests/reject.ts index a57cbb1848de..35f047bcefa4 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/reject.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/reject.ts @@ -33,9 +33,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private getterService: GetterService, private userFollowingService: UserFollowingService, diff --git a/packages/backend/src/server/api/endpoints/following/update.ts b/packages/backend/src/server/api/endpoints/following/update.ts new file mode 100644 index 000000000000..25f393e51745 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/following/update.ts @@ -0,0 +1,107 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import ms from 'ms'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { FollowingsRepository } from '@/models/_.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { DI } from '@/di-symbols.js'; +import { GetterService } from '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; + +export const meta = { + tags: ['following', 'users'], + + limit: { + duration: ms('1hour'), + max: 100, + }, + + requireCredential: true, + + kind: 'write:following', + + errors: { + noSuchUser: { + message: 'No such user.', + code: 'NO_SUCH_USER', + id: '14318698-f67e-492a-99da-5353a5ac52be', + }, + + followeeIsYourself: { + message: 'Followee is yourself.', + code: 'FOLLOWEE_IS_YOURSELF', + id: '4c4cbaf9-962a-463b-8418-a5e365dbf2eb', + }, + + notFollowing: { + message: 'You are not following that user.', + code: 'NOT_FOLLOWING', + id: 'b8dc75cf-1cb5-46c9-b14b-5f1ffbd782c9', + }, + }, + + res: { + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + userId: { type: 'string', format: 'misskey:id' }, + notify: { type: 'string', enum: ['normal', 'none'] }, + }, + required: ['userId', 'notify'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private userEntityService: UserEntityService, + private getterService: GetterService, + private userFollowingService: UserFollowingService, + ) { + super(meta, paramDef, async (ps, me) => { + const follower = me; + + // Check if the follower is yourself + if (me.id === ps.userId) { + throw new ApiError(meta.errors.followeeIsYourself); + } + + // Get followee + const followee = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // Check not following + const exist = await this.followingsRepository.findOneBy({ + followerId: follower.id, + followeeId: followee.id, + }); + + if (exist == null) { + throw new ApiError(meta.errors.notFollowing); + } + + await this.followingsRepository.update({ + id: exist.id, + }, { + notify: ps.notify === 'none' ? null : ps.notify, + }); + + return await this.userEntityService.pack(follower.id, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/gallery/featured.ts b/packages/backend/src/server/api/endpoints/gallery/featured.ts index 80cad0e5b0a1..cbab3a83a410 100644 --- a/packages/backend/src/server/api/endpoints/gallery/featured.ts +++ b/packages/backend/src/server/api/endpoints/gallery/featured.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/_.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -31,9 +31,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.galleryPostsRepository) private galleryPostsRepository: GalleryPostsRepository, diff --git a/packages/backend/src/server/api/endpoints/gallery/popular.ts b/packages/backend/src/server/api/endpoints/gallery/popular.ts index ef1d54f82c98..c5d06f67dd14 100644 --- a/packages/backend/src/server/api/endpoints/gallery/popular.ts +++ b/packages/backend/src/server/api/endpoints/gallery/popular.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/_.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -31,9 +31,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.galleryPostsRepository) private galleryPostsRepository: GalleryPostsRepository, diff --git a/packages/backend/src/server/api/endpoints/gallery/posts.ts b/packages/backend/src/server/api/endpoints/gallery/posts.ts index 42f35f418ad5..3ca5f4989ae7 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -34,9 +34,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.galleryPostsRepository) private galleryPostsRepository: GalleryPostsRepository, diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts index e9c8f24bc59c..b6748b2e1915 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -6,9 +6,9 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DriveFilesRepository, GalleryPostsRepository } from '@/models/index.js'; -import { GalleryPost } from '@/models/entities/GalleryPost.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; +import type { DriveFilesRepository, GalleryPostsRepository } from '@/models/_.js'; +import { MiGalleryPost } from '@/models/GalleryPost.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; import { IdService } from '@/core/IdService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -51,9 +51,8 @@ export const paramDef = { required: ['title', 'fileIds'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.galleryPostsRepository) private galleryPostsRepository: GalleryPostsRepository, @@ -70,13 +69,13 @@ export default class extends Endpoint { id: fileId, userId: me.id, }), - ))).filter((file): file is DriveFile => file != null); + ))).filter((file): file is MiDriveFile => file != null); if (files.length === 0) { throw new Error(); } - const post = await this.galleryPostsRepository.insert(new GalleryPost({ + const post = await this.galleryPostsRepository.insert(new MiGalleryPost({ id: this.idService.genId(), createdAt: new Date(), updatedAt: new Date(), diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts index 81ed24df15ad..deef2912bb1b 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -33,9 +33,8 @@ export const paramDef = { required: ['postId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.galleryPostsRepository) private galleryPostsRepository: GalleryPostsRepository, diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts index 2d9928f5690c..f74ebeab97d6 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { GalleryLikesRepository, GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryLikesRepository, GalleryPostsRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -48,9 +48,8 @@ export const paramDef = { required: ['postId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.galleryPostsRepository) private galleryPostsRepository: GalleryPostsRepository, diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts index a76e52bd89d6..b3eda1be5218 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/_.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -38,9 +38,8 @@ export const paramDef = { required: ['postId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.galleryPostsRepository) private galleryPostsRepository: GalleryPostsRepository, diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts index 40c1ff6c9eb5..8a787ac17baf 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { GalleryPostsRepository, GalleryLikesRepository } from '@/models/index.js'; +import type { GalleryPostsRepository, GalleryLikesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -41,9 +41,8 @@ export const paramDef = { required: ['postId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.galleryPostsRepository) private galleryPostsRepository: GalleryPostsRepository, diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts index 81508e9b11c3..caaa87870c39 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts @@ -6,8 +6,8 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DriveFilesRepository, GalleryPostsRepository } from '@/models/index.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; +import type { DriveFilesRepository, GalleryPostsRepository } from '@/models/_.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -50,9 +50,8 @@ export const paramDef = { required: ['postId', 'title', 'fileIds'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.galleryPostsRepository) private galleryPostsRepository: GalleryPostsRepository, @@ -68,7 +67,7 @@ export default class extends Endpoint { id: fileId, userId: me.id, }), - ))).filter((file): file is DriveFile => file != null); + ))).filter((file): file is MiDriveFile => file != null); if (files.length === 0) { throw new Error(); diff --git a/packages/backend/src/server/api/endpoints/get-online-users-count.ts b/packages/backend/src/server/api/endpoints/get-online-users-count.ts index 3c265185a9bf..8a61168f2599 100644 --- a/packages/backend/src/server/api/endpoints/get-online-users-count.ts +++ b/packages/backend/src/server/api/endpoints/get-online-users-count.ts @@ -6,7 +6,7 @@ import { MoreThan } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import { USER_ONLINE_THRESHOLD } from '@/const.js'; -import type { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; @@ -24,9 +24,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/hashtags/list.ts b/packages/backend/src/server/api/endpoints/hashtags/list.ts index 71400f4a2397..21d863107d8f 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/list.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/list.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { HashtagsRepository } from '@/models/index.js'; +import type { HashtagsRepository } from '@/models/_.js'; import { HashtagEntityService } from '@/core/entities/HashtagEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -37,9 +37,8 @@ export const paramDef = { required: ['sort'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.hashtagsRepository) private hashtagsRepository: HashtagsRepository, diff --git a/packages/backend/src/server/api/endpoints/hashtags/search.ts b/packages/backend/src/server/api/endpoints/hashtags/search.ts index e9508afd326c..acfef16b1113 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/search.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/search.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { HashtagsRepository } from '@/models/index.js'; +import type { HashtagsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; @@ -34,9 +34,8 @@ export const paramDef = { required: ['query'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.hashtagsRepository) private hashtagsRepository: HashtagsRepository, diff --git a/packages/backend/src/server/api/endpoints/hashtags/show.ts b/packages/backend/src/server/api/endpoints/hashtags/show.ts index 64a556a3c7a3..3ba16fdc85cd 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/show.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/show.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { HashtagsRepository } from '@/models/index.js'; +import type { HashtagsRepository } from '@/models/_.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { HashtagEntityService } from '@/core/entities/HashtagEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -39,9 +39,8 @@ export const paramDef = { required: ['tag'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.hashtagsRepository) private hashtagsRepository: HashtagsRepository, diff --git a/packages/backend/src/server/api/endpoints/hashtags/trend.ts b/packages/backend/src/server/api/endpoints/hashtags/trend.ts index 949787b964b0..75d4fe3819b3 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/trend.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/trend.ts @@ -6,8 +6,8 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { NotesRepository } from '@/models/index.js'; -import type { Note } from '@/models/entities/Note.js'; +import type { NotesRepository } from '@/models/_.js'; +import type { MiNote } from '@/models/Note.js'; import { safeForSql } from '@/misc/safe-for-sql.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { MetaService } from '@/core/MetaService.js'; @@ -68,9 +68,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -101,7 +100,7 @@ export default class extends Endpoint { const tags: { name: string; - users: Note['userId'][]; + users: MiNote['userId'][]; }[] = []; for (const note of tagNotes) { diff --git a/packages/backend/src/server/api/endpoints/hashtags/users.ts b/packages/backend/src/server/api/endpoints/hashtags/users.ts index 6a8c69351d92..1cef76d3d239 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/users.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/users.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/_.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -38,9 +38,8 @@ export const paramDef = { required: ['tag', 'sort'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/i.ts b/packages/backend/src/server/api/endpoints/i.ts index 5046c78f7d8b..c0530bf392f4 100644 --- a/packages/backend/src/server/api/endpoints/i.ts +++ b/packages/backend/src/server/api/endpoints/i.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserProfilesRepository } from '@/models/index.js'; +import type { UserProfilesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -37,9 +37,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, diff --git a/packages/backend/src/server/api/endpoints/i/2fa/done.ts b/packages/backend/src/server/api/endpoints/i/2fa/done.ts index d7ba21c25986..9f8e2894b81c 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts @@ -7,7 +7,7 @@ import * as OTPAuth from 'otpauth'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import type { UserProfilesRepository } from '@/models/index.js'; +import type { UserProfilesRepository } from '@/models/_.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; @@ -25,9 +25,8 @@ export const paramDef = { required: ['token'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, @@ -48,15 +47,18 @@ export default class extends Endpoint { secret: OTPAuth.Secret.fromBase32(profile.twoFactorTempSecret), digits: 6, token, - window: 1, + window: 5, }); if (delta === null) { throw new Error('not verified'); } + const backupCodes = Array.from({ length: 5 }, () => new OTPAuth.Secret().base32); + await this.userProfilesRepository.update(me.id, { twoFactorSecret: profile.twoFactorTempSecret, + twoFactorBackupSecret: backupCodes, twoFactorEnabled: true, }); @@ -65,6 +67,10 @@ export default class extends Endpoint { detail: true, includeSecrets: true, })); + + return { + backupCodes: backupCodes, + }; }); } } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index fd6e70a9dea1..6d530aba3b5f 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -3,156 +3,100 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { promisify } from 'node:util'; import bcrypt from 'bcryptjs'; -import cbor from 'cbor'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; -import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js'; -import type { AttestationChallengesRepository, UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js'; - -const cborDecodeFirst = promisify(cbor.decodeFirst) as any; +import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/_.js'; +import { WebAuthnService } from '@/core/WebAuthnService.js'; +import { ApiError } from '@/server/api/error.js'; +import { UserAuthService } from '@/core/UserAuthService.js'; export const meta = { requireCredential: true, secure: true, + + errors: { + incorrectPassword: { + message: 'Incorrect password.', + code: 'INCORRECT_PASSWORD', + id: '0d7ec6d2-e652-443e-a7bf-9ee9a0cd77b0', + }, + + twoFactorNotEnabled: { + message: '2fa not enabled.', + code: 'TWO_FACTOR_NOT_ENABLED', + id: '798d6847-b1ed-4f9c-b1f9-163c42655995', + }, + }, } as const; export const paramDef = { type: 'object', properties: { - clientDataJSON: { type: 'string' }, - attestationObject: { type: 'string' }, password: { type: 'string' }, - challengeId: { type: 'string' }, + token: { type: 'string', nullable: true }, name: { type: 'string', minLength: 1, maxLength: 30 }, + credential: { type: 'object' }, }, - required: ['clientDataJSON', 'attestationObject', 'password', 'challengeId', 'name'], + required: ['password', 'name', 'credential'], } as const; // eslint-disable-next-line import/no-default-export @Injectable() export default class extends Endpoint { constructor( - @Inject(DI.config) - private config: Config, - @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, @Inject(DI.userSecurityKeysRepository) private userSecurityKeysRepository: UserSecurityKeysRepository, - @Inject(DI.attestationChallengesRepository) - private attestationChallengesRepository: AttestationChallengesRepository, - + private webAuthnService: WebAuthnService, + private userAuthService: UserAuthService, private userEntityService: UserEntityService, private globalEventService: GlobalEventService, - private twoFactorAuthenticationService: TwoFactorAuthenticationService, ) { super(meta, paramDef, async (ps, me) => { - const rpIdHashReal = this.twoFactorAuthenticationService.hash(Buffer.from(this.config.hostname, 'utf-8')); - + const token = ps.token; const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); - - if (!same) { - throw new Error('incorrect password'); - } - - if (!profile.twoFactorEnabled) { - throw new Error('2fa not enabled'); - } - - const clientData = JSON.parse(ps.clientDataJSON); - - if (clientData.type !== 'webauthn.create') { - throw new Error('not a creation attestation'); - } - if (clientData.origin !== this.config.scheme + '://' + this.config.host) { - throw new Error('origin mismatch'); - } - - const clientDataJSONHash = this.twoFactorAuthenticationService.hash(Buffer.from(ps.clientDataJSON, 'utf-8')); - - const attestation = await cborDecodeFirst(ps.attestationObject); + if (profile.twoFactorEnabled) { + if (token == null) { + throw new Error('authentication failed'); + } - const rpIdHash = attestation.authData.slice(0, 32); - if (!rpIdHashReal.equals(rpIdHash)) { - throw new Error('rpIdHash mismatch'); + try { + await this.userAuthService.twoFactorAuthenticate(profile, token); + } catch (e) { + throw new Error('authentication failed'); + } } - const flags = attestation.authData[32]; - - // eslint:disable-next-line:no-bitwise - if (!(flags & 1)) { - throw new Error('user not present'); - } - - const authData = Buffer.from(attestation.authData); - const credentialIdLength = authData.readUInt16BE(53); - const credentialId = authData.slice(55, 55 + credentialIdLength); - const publicKeyData = authData.slice(55 + credentialIdLength); - const publicKey: Map = await cborDecodeFirst(publicKeyData); - if (publicKey.get(3) !== -7) { - throw new Error('alg mismatch'); + const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? ''); + if (!passwordMatched) { + throw new ApiError(meta.errors.incorrectPassword); } - const procedures = this.twoFactorAuthenticationService.getProcedures(); - - if (!(procedures as any)[attestation.fmt]) { - throw new Error(`unsupported fmt: ${attestation.fmt}. Supported ones: ${Object.keys(procedures)}`); - } - - const verificationData = (procedures as any)[attestation.fmt].verify({ - attStmt: attestation.attStmt, - authenticatorData: authData, - clientDataHash: clientDataJSONHash, - credentialId, - publicKey, - rpIdHash, - }); - if (!verificationData.valid) throw new Error('signature invalid'); - - const attestationChallenge = await this.attestationChallengesRepository.findOneBy({ - userId: me.id, - id: ps.challengeId, - registrationChallenge: true, - challenge: this.twoFactorAuthenticationService.hash(clientData.challenge).toString('hex'), - }); - - if (!attestationChallenge) { - throw new Error('non-existent challenge'); - } - - await this.attestationChallengesRepository.delete({ - userId: me.id, - id: ps.challengeId, - }); - - // Expired challenge (> 5min old) - if ( - new Date().getTime() - attestationChallenge.createdAt.getTime() >= - 5 * 60 * 1000 - ) { - throw new Error('expired challenge'); + if (!profile.twoFactorEnabled) { + throw new ApiError(meta.errors.twoFactorNotEnabled); } - const credentialIdString = credentialId.toString('hex'); + const keyInfo = await this.webAuthnService.verifyRegistration(me.id, ps.credential); + const credentialId = Buffer.from(keyInfo.credentialID).toString('base64url'); await this.userSecurityKeysRepository.insert({ + id: credentialId, userId: me.id, - id: credentialIdString, - lastUsed: new Date(), name: ps.name, - publicKey: verificationData.publicKey.toString('hex'), + publicKey: Buffer.from(keyInfo.credentialPublicKey).toString('base64url'), + counter: keyInfo.counter, + credentialDeviceType: keyInfo.credentialDeviceType, + credentialBackedUp: keyInfo.credentialBackedUp, + transports: keyInfo.transports, }); // Publish meUpdated event @@ -162,7 +106,7 @@ export default class extends Endpoint { })); return { - id: credentialIdString, + id: credentialId, name: ps.name, }; }); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts index 4973b49c22ec..2ed701014dc3 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js'; +import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/_.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -33,9 +33,8 @@ export const paramDef = { required: ['value'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts index e27a5a822366..c39005f2ddd6 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts @@ -3,28 +3,46 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { promisify } from 'node:util'; -import * as crypto from 'node:crypto'; import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UserProfilesRepository, AttestationChallengesRepository } from '@/models/index.js'; -import { IdService } from '@/core/IdService.js'; -import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js'; +import type { UserProfilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; - -const randomBytes = promisify(crypto.randomBytes); +import { WebAuthnService } from '@/core/WebAuthnService.js'; +import { ApiError } from '@/server/api/error.js'; +import { UserAuthService } from '@/core/UserAuthService.js'; export const meta = { requireCredential: true, secure: true, + + errors: { + userNotFound: { + message: 'User not found.', + code: 'USER_NOT_FOUND', + id: '652f899f-66d4-490e-993e-6606c8ec04c3', + }, + + incorrectPassword: { + message: 'Incorrect password.', + code: 'INCORRECT_PASSWORD', + id: '38769596-efe2-4faf-9bec-abbb3f2cd9ba', + }, + + twoFactorNotEnabled: { + message: '2fa not enabled.', + code: 'TWO_FACTOR_NOT_ENABLED', + id: 'bf32b864-449b-47b8-974e-f9a5468546f1', + }, + }, } as const; export const paramDef = { type: 'object', properties: { password: { type: 'string' }, + token: { type: 'string', nullable: true }, }, required: ['password'], } as const; @@ -36,47 +54,48 @@ export default class extends Endpoint { @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, - @Inject(DI.attestationChallengesRepository) - private attestationChallengesRepository: AttestationChallengesRepository, - - private idService: IdService, - private twoFactorAuthenticationService: TwoFactorAuthenticationService, + private webAuthnService: WebAuthnService, + private userAuthService: UserAuthService, ) { super(meta, paramDef, async (ps, me) => { - const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); - - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); + const token = ps.token; + const profile = await this.userProfilesRepository.findOne({ + where: { + userId: me.id, + }, + relations: ['user'], + }); - if (!same) { - throw new Error('incorrect password'); + if (profile == null) { + throw new ApiError(meta.errors.userNotFound); } - if (!profile.twoFactorEnabled) { - throw new Error('2fa not enabled'); - } + if (profile.twoFactorEnabled) { + if (token == null) { + throw new Error('authentication failed'); + } - // 32 byte challenge - const entropy = await randomBytes(32); - const challenge = entropy.toString('base64') - .replace(/=/g, '') - .replace(/\+/g, '-') - .replace(/\//g, '_'); + try { + await this.userAuthService.twoFactorAuthenticate(profile, token); + } catch (e) { + throw new Error('authentication failed'); + } + } - const challengeId = this.idService.genId(); + const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? ''); + if (!passwordMatched) { + throw new ApiError(meta.errors.incorrectPassword); + } - await this.attestationChallengesRepository.insert({ - userId: me.id, - id: challengeId, - challenge: this.twoFactorAuthenticationService.hash(Buffer.from(challenge, 'utf-8')).toString('hex'), - createdAt: new Date(), - registrationChallenge: true, - }); + if (!profile.twoFactorEnabled) { + throw new ApiError(meta.errors.twoFactorNotEnabled); + } - return { - challengeId, - challenge, - }; + return await this.webAuthnService.initiateRegistration( + me.id, + profile.user?.username ?? me.id, + profile.user?.name ?? undefined, + ); }); } } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index 0e57f07e5e8b..b358c812eef4 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -7,43 +7,66 @@ import bcrypt from 'bcryptjs'; import * as OTPAuth from 'otpauth'; import * as QRCode from 'qrcode'; import { Inject, Injectable } from '@nestjs/common'; -import type { UserProfilesRepository } from '@/models/index.js'; +import type { UserProfilesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; +import { ApiError } from '@/server/api/error.js'; +import { UserAuthService } from '@/core/UserAuthService.js'; export const meta = { requireCredential: true, secure: true, + + errors: { + incorrectPassword: { + message: 'Incorrect password.', + code: 'INCORRECT_PASSWORD', + id: '78d6c839-20c9-4c66-b90a-fc0542168b48', + }, + }, } as const; export const paramDef = { type: 'object', properties: { password: { type: 'string' }, + token: { type: 'string', nullable: true }, }, required: ['password'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.config) private config: Config, @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, + + private userAuthService: UserAuthService, ) { super(meta, paramDef, async (ps, me) => { + const token = ps.token; const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); + if (profile.twoFactorEnabled) { + if (token == null) { + throw new Error('authentication failed'); + } + + try { + await this.userAuthService.twoFactorAuthenticate(profile, token); + } catch (e) { + throw new Error('authentication failed'); + } + } - if (!same) { - throw new Error('incorrect password'); + const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? ''); + if (!passwordMatched) { + throw new ApiError(meta.errors.incorrectPassword); } // Generate user's secret key diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts index 6402f3440f00..da8ac98556ab 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -6,29 +6,39 @@ import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js'; +import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; +import { ApiError } from '@/server/api/error.js'; +import { UserAuthService } from '@/core/UserAuthService.js'; export const meta = { requireCredential: true, secure: true, + + errors: { + incorrectPassword: { + message: 'Incorrect password.', + code: 'INCORRECT_PASSWORD', + id: '141c598d-a825-44c8-9173-cfb9d92be493', + }, + }, } as const; export const paramDef = { type: 'object', properties: { password: { type: 'string' }, + token: { type: 'string', nullable: true }, credentialId: { type: 'string' }, }, required: ['password', 'credentialId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.userSecurityKeysRepository) private userSecurityKeysRepository: UserSecurityKeysRepository, @@ -37,16 +47,28 @@ export default class extends Endpoint { private userProfilesRepository: UserProfilesRepository, private userEntityService: UserEntityService, + private userAuthService: UserAuthService, private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps, me) => { + const token = ps.token; const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); + if (profile.twoFactorEnabled) { + if (token == null) { + throw new Error('authentication failed'); + } + + try { + await this.userAuthService.twoFactorAuthenticate(profile, token); + } catch (e) { + throw new Error('authentication failed'); + } + } - if (!same) { - throw new Error('incorrect password'); + const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? ''); + if (!passwordMatched) { + throw new ApiError(meta.errors.incorrectPassword); } // Make sure we only delete the user's own creds diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts index c0e9ff5ec061..338f12c5cdc5 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -7,46 +7,69 @@ import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import type { UserProfilesRepository } from '@/models/index.js'; +import type { UserProfilesRepository } from '@/models/_.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; +import { ApiError } from '@/server/api/error.js'; +import { UserAuthService } from '@/core/UserAuthService.js'; export const meta = { requireCredential: true, secure: true, + + errors: { + incorrectPassword: { + message: 'Incorrect password.', + code: 'INCORRECT_PASSWORD', + id: '7add0395-9901-4098-82f9-4f67af65f775', + }, + }, } as const; export const paramDef = { type: 'object', properties: { password: { type: 'string' }, + token: { type: 'string', nullable: true }, }, required: ['password'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, private userEntityService: UserEntityService, + private userAuthService: UserAuthService, private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps, me) => { + const token = ps.token; const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); + if (profile.twoFactorEnabled) { + if (token == null) { + throw new Error('authentication failed'); + } + + try { + await this.userAuthService.twoFactorAuthenticate(profile, token); + } catch (e) { + throw new Error('authentication failed'); + } + } - if (!same) { - throw new Error('incorrect password'); + const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? ''); + if (!passwordMatched) { + throw new ApiError(meta.errors.incorrectPassword); } await this.userProfilesRepository.update(me.id, { twoFactorSecret: null, + twoFactorBackupSecret: null, twoFactorEnabled: false, usePasswordLessLogin: false, }); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts index 535644f0077f..1a140c1d05c2 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts @@ -6,7 +6,7 @@ import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UserSecurityKeysRepository } from '@/models/index.js'; +import type { UserSecurityKeysRepository } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; @@ -25,7 +25,7 @@ export const meta = { }, accessDenied: { - message: 'You do not have edit privilege of the channel.', + message: 'You do not have edit privilege of this key.', code: 'ACCESS_DENIED', id: '1fb7cb09-d46a-4fff-b8df-057708cce513', }, @@ -41,9 +41,8 @@ export const paramDef = { required: ['name', 'credentialId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.userSecurityKeysRepository) private userSecurityKeysRepository: UserSecurityKeysRepository, diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts index 24e80390c23c..daa3e536a48b 100644 --- a/packages/backend/src/server/api/endpoints/i/apps.ts +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AccessTokensRepository } from '@/models/index.js'; +import type { AccessTokensRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; export const meta = { @@ -22,9 +22,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.accessTokensRepository) private accessTokensRepository: AccessTokensRepository, diff --git a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts index 1da5479c380f..32061c2aa4ef 100644 --- a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts +++ b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, Not } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AccessTokensRepository } from '@/models/index.js'; +import type { AccessTokensRepository } from '@/models/_.js'; import { AppEntityService } from '@/core/entities/AppEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -26,9 +26,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.accessTokensRepository) private accessTokensRepository: AccessTokensRepository, diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts index 757a57caedd1..a3c37ffdb7df 100644 --- a/packages/backend/src/server/api/endpoints/i/change-password.ts +++ b/packages/backend/src/server/api/endpoints/i/change-password.ts @@ -6,8 +6,9 @@ import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UserProfilesRepository } from '@/models/index.js'; +import type { UserProfilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; +import { UserAuthService } from '@/core/UserAuthService.js'; export const meta = { requireCredential: true, @@ -20,24 +21,38 @@ export const paramDef = { properties: { currentPassword: { type: 'string' }, newPassword: { type: 'string', minLength: 1 }, + token: { type: 'string', nullable: true }, }, required: ['currentPassword', 'newPassword'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, + + private userAuthService: UserAuthService, ) { super(meta, paramDef, async (ps, me) => { + const token = ps.token; const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); - // Compare password - const same = await bcrypt.compare(ps.currentPassword, profile.password!); + if (profile.twoFactorEnabled) { + if (token == null) { + throw new Error('authentication failed'); + } + + try { + await this.userAuthService.twoFactorAuthenticate(profile, token); + } catch (e) { + throw new Error('authentication failed'); + } + } + + const passwordMatched = await bcrypt.compare(ps.currentPassword, profile.password!); - if (!same) { + if (!passwordMatched) { throw new Error('incorrect password'); } diff --git a/packages/backend/src/server/api/endpoints/i/claim-achievement.ts b/packages/backend/src/server/api/endpoints/i/claim-achievement.ts index a93358de8e11..acafb755c67d 100644 --- a/packages/backend/src/server/api/endpoints/i/claim-achievement.ts +++ b/packages/backend/src/server/api/endpoints/i/claim-achievement.ts @@ -20,9 +20,8 @@ export const paramDef = { required: ['name'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private achievementService: AchievementService, ) { diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts index 721eb01c5147..fbac845fda21 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -5,10 +5,11 @@ import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, UserProfilesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DeleteAccountService } from '@/core/DeleteAccountService.js'; import { DI } from '@/di-symbols.js'; +import { UserAuthService } from '@/core/UserAuthService.js'; export const meta = { requireCredential: true, @@ -20,13 +21,13 @@ export const paramDef = { type: 'object', properties: { password: { type: 'string' }, + token: { type: 'string', nullable: true }, }, required: ['password'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -34,19 +35,32 @@ export default class extends Endpoint { @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, + private userAuthService: UserAuthService, private deleteAccountService: DeleteAccountService, ) { super(meta, paramDef, async (ps, me) => { + const token = ps.token; const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); + + if (profile.twoFactorEnabled) { + if (token == null) { + throw new Error('authentication failed'); + } + + try { + await this.userAuthService.twoFactorAuthenticate(profile, token); + } catch (e) { + throw new Error('authentication failed'); + } + } + const userDetailed = await this.usersRepository.findOneByOrFail({ id: me.id }); if (userDetailed.isDeleted) { return; } - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); - - if (!same) { + const passwordMatched = await bcrypt.compare(ps.password, profile.password!); + if (!passwordMatched) { throw new Error('incorrect password'); } diff --git a/packages/backend/src/server/api/endpoints/i/export-blocking.ts b/packages/backend/src/server/api/endpoints/i/export-blocking.ts index 8e6bfd4cdc49..8068a3b305c1 100644 --- a/packages/backend/src/server/api/endpoints/i/export-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/export-blocking.ts @@ -23,9 +23,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private queueService: QueueService, ) { diff --git a/packages/backend/src/server/api/endpoints/i/export-favorites.ts b/packages/backend/src/server/api/endpoints/i/export-favorites.ts index 5c9f0953d3a5..c22905bc67d3 100644 --- a/packages/backend/src/server/api/endpoints/i/export-favorites.ts +++ b/packages/backend/src/server/api/endpoints/i/export-favorites.ts @@ -23,9 +23,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private queueService: QueueService, ) { diff --git a/packages/backend/src/server/api/endpoints/i/export-following.ts b/packages/backend/src/server/api/endpoints/i/export-following.ts index e3c33a942941..880833ab76a0 100644 --- a/packages/backend/src/server/api/endpoints/i/export-following.ts +++ b/packages/backend/src/server/api/endpoints/i/export-following.ts @@ -26,9 +26,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private queueService: QueueService, ) { diff --git a/packages/backend/src/server/api/endpoints/i/export-mute.ts b/packages/backend/src/server/api/endpoints/i/export-mute.ts index 5234d7d3400d..8eb70a387a3f 100644 --- a/packages/backend/src/server/api/endpoints/i/export-mute.ts +++ b/packages/backend/src/server/api/endpoints/i/export-mute.ts @@ -23,9 +23,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private queueService: QueueService, ) { diff --git a/packages/backend/src/server/api/endpoints/i/export-notes.ts b/packages/backend/src/server/api/endpoints/i/export-notes.ts index cca4a58e4ac7..791f6377902d 100644 --- a/packages/backend/src/server/api/endpoints/i/export-notes.ts +++ b/packages/backend/src/server/api/endpoints/i/export-notes.ts @@ -23,9 +23,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private queueService: QueueService, ) { diff --git a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts index 74b2c7c2c65f..f387f6d0160d 100644 --- a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts @@ -23,9 +23,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private queueService: QueueService, ) { diff --git a/packages/backend/src/server/api/endpoints/i/favorites.ts b/packages/backend/src/server/api/endpoints/i/favorites.ts index d5b19597951a..d6f13c535a25 100644 --- a/packages/backend/src/server/api/endpoints/i/favorites.ts +++ b/packages/backend/src/server/api/endpoints/i/favorites.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { NoteFavoritesRepository } from '@/models/index.js'; +import type { NoteFavoritesRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteFavoriteEntityService } from '@/core/entities/NoteFavoriteEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -38,9 +38,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.noteFavoritesRepository) private noteFavoritesRepository: NoteFavoritesRepository, diff --git a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts index 5017d0695af5..7e37adc4ac8a 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { GalleryLikesRepository } from '@/models/index.js'; +import type { GalleryLikesRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { GalleryLikeEntityService } from '@/core/entities/GalleryLikeEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -49,9 +49,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.galleryLikesRepository) private galleryLikesRepository: GalleryLikesRepository, diff --git a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts index 4e846831bd4e..148d38aa54b5 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -38,9 +38,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.galleryPostsRepository) private galleryPostsRepository: GalleryPostsRepository, diff --git a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts index 402293336076..d62bfbb3edb3 100644 --- a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts +++ b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { MutedNotesRepository } from '@/models/index.js'; +import type { MutedNotesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; export const meta = { @@ -33,9 +33,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.mutedNotesRepository) private mutedNotesRepository: MutedNotesRepository, diff --git a/packages/backend/src/server/api/endpoints/i/import-antennas.ts b/packages/backend/src/server/api/endpoints/i/import-antennas.ts index 864ed9bfb5ac..c71f1ffc0d25 100644 --- a/packages/backend/src/server/api/endpoints/i/import-antennas.ts +++ b/packages/backend/src/server/api/endpoints/i/import-antennas.ts @@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; -import type { AntennasRepository, DriveFilesRepository, UsersRepository, Antenna as _Antenna } from '@/models/index.js'; +import type { AntennasRepository, DriveFilesRepository, UsersRepository, MiAntenna as _Antenna } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; import { DownloadService } from '@/core/DownloadService.js'; diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts index 4ccac716ea53..2a1bdb15ec34 100644 --- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts @@ -8,7 +8,7 @@ import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; import { AccountMoveService } from '@/core/AccountMoveService.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -57,9 +57,8 @@ export const paramDef = { required: ['fileId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts index 309f58ec0ca4..2fdd957c15ee 100644 --- a/packages/backend/src/server/api/endpoints/i/import-following.ts +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -8,7 +8,7 @@ import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; import { AccountMoveService } from '@/core/AccountMoveService.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -56,9 +56,8 @@ export const paramDef = { required: ['fileId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts index df78101c1f40..aec4c6433544 100644 --- a/packages/backend/src/server/api/endpoints/i/import-muting.ts +++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts @@ -8,7 +8,7 @@ import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; import { AccountMoveService } from '@/core/AccountMoveService.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -57,9 +57,8 @@ export const paramDef = { required: ['fileId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts index cd967bf2c072..d077ac674c22 100644 --- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts @@ -8,7 +8,7 @@ import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; import { AccountMoveService } from '@/core/AccountMoveService.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -56,9 +56,8 @@ export const paramDef = { required: ['fileId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, diff --git a/packages/backend/src/server/api/endpoints/i/move.ts b/packages/backend/src/server/api/endpoints/i/move.ts index 49c8c66f39ed..b38b9f8b5c64 100644 --- a/packages/backend/src/server/api/endpoints/i/move.ts +++ b/packages/backend/src/server/api/endpoints/i/move.ts @@ -9,7 +9,7 @@ import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { ApiError } from '@/server/api/error.js'; -import { LocalUser, RemoteUser } from '@/models/entities/User.js'; +import { MiLocalUser, MiRemoteUser } from '@/models/User.js'; import { AccountMoveService } from '@/core/AccountMoveService.js'; import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; @@ -74,9 +74,8 @@ export const paramDef = { required: ['moveToAccount'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private remoteUserResolveService: RemoteUserResolveService, private apiLoggerService: ApiLoggerService, @@ -100,7 +99,7 @@ export default class extends Endpoint { this.apiLoggerService.logger.warn(`failed to resolve remote user: ${e}`); throw new ApiError(meta.errors.noSuchUser); }); - const destination = await this.getterService.getUser(moveTo.id) as LocalUser | RemoteUser; + const destination = await this.getterService.getUser(moveTo.id) as MiLocalUser | MiRemoteUser; const newUri = this.userEntityService.getUserUri(destination); // update local db diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts index eefed1765c31..91dd72e805c1 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications.ts @@ -6,7 +6,7 @@ import { Brackets, In } from 'typeorm'; import * as Redis from 'ioredis'; import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/_.js'; import { obsoleteNotificationTypes, notificationTypes } from '@/types.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteReadService } from '@/core/NoteReadService.js'; @@ -14,7 +14,7 @@ import { NotificationEntityService } from '@/core/entities/NotificationEntitySer import { NotificationService } from '@/core/NotificationService.js'; import { DI } from '@/di-symbols.js'; import { IdService } from '@/core/IdService.js'; -import { Notification } from '@/models/entities/Notification.js'; +import { MiNotification } from '@/models/Notification.js'; export const meta = { tags: ['account', 'notifications'], @@ -57,9 +57,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.redis) private redisClient: Redis.Redis, @@ -96,7 +95,7 @@ export default class extends Endpoint { return []; } - let notifications = notificationsRes.map(x => JSON.parse(x[1][1])).filter(x => x.id !== ps.untilId && x !== ps.sinceId) as Notification[]; + let notifications = notificationsRes.map(x => JSON.parse(x[1][1])).filter(x => x.id !== ps.untilId && x !== ps.sinceId) as MiNotification[]; if (includeTypes && includeTypes.length > 0) { notifications = notifications.filter(notification => includeTypes.includes(notification.type)); diff --git a/packages/backend/src/server/api/endpoints/i/page-likes.ts b/packages/backend/src/server/api/endpoints/i/page-likes.ts index 7510348260ed..6bf7e6aa9b93 100644 --- a/packages/backend/src/server/api/endpoints/i/page-likes.ts +++ b/packages/backend/src/server/api/endpoints/i/page-likes.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { PageLikesRepository } from '@/models/index.js'; +import type { PageLikesRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { PageLikeEntityService } from '@/core/entities/PageLikeEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -48,9 +48,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.pageLikesRepository) private pageLikesRepository: PageLikesRepository, diff --git a/packages/backend/src/server/api/endpoints/i/pages.ts b/packages/backend/src/server/api/endpoints/i/pages.ts index 489956b562df..b8082c018f77 100644 --- a/packages/backend/src/server/api/endpoints/i/pages.ts +++ b/packages/backend/src/server/api/endpoints/i/pages.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { PagesRepository } from '@/models/index.js'; +import type { PagesRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -38,9 +38,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.pagesRepository) private pagesRepository: PagesRepository, diff --git a/packages/backend/src/server/api/endpoints/i/pin.ts b/packages/backend/src/server/api/endpoints/i/pin.ts index feed91518392..6db45eee0b7c 100644 --- a/packages/backend/src/server/api/endpoints/i/pin.ts +++ b/packages/backend/src/server/api/endpoints/i/pin.ts @@ -52,9 +52,8 @@ export const paramDef = { required: ['noteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private userEntityService: UserEntityService, private notePiningService: NotePiningService, diff --git a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts index d7ca50ebf6f3..e43ab7c15eed 100644 --- a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts +++ b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { NoteUnreadsRepository } from '@/models/index.js'; +import type { NoteUnreadsRepository } from '@/models/_.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; @@ -23,9 +23,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.noteUnreadsRepository) private noteUnreadsRepository: NoteUnreadsRepository, diff --git a/packages/backend/src/server/api/endpoints/i/read-announcement.ts b/packages/backend/src/server/api/endpoints/i/read-announcement.ts index 412532939c46..ba7859d0d416 100644 --- a/packages/backend/src/server/api/endpoints/i/read-announcement.ts +++ b/packages/backend/src/server/api/endpoints/i/read-announcement.ts @@ -26,9 +26,8 @@ export const paramDef = { required: ['announcementId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private announcementService: AnnouncementService, ) { diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts index 190ce6e6ff94..b70dcfbaceaa 100644 --- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts +++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts @@ -6,7 +6,7 @@ import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UsersRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, UserProfilesRepository } from '@/models/_.js'; import generateUserToken from '@/misc/generate-native-user-token.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; @@ -25,9 +25,8 @@ export const paramDef = { required: ['password'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts index 293b67ae6727..211e6637dce3 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; export const meta = { @@ -24,9 +24,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.registryItemsRepository) private registryItemsRepository: RegistryItemsRepository, diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts index 77cc7bab7c31..9c6f2d67817d 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -34,9 +34,8 @@ export const paramDef = { required: ['key'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.registryItemsRepository) private registryItemsRepository: RegistryItemsRepository, diff --git a/packages/backend/src/server/api/endpoints/i/registry/get.ts b/packages/backend/src/server/api/endpoints/i/registry/get.ts index 9b9d943e63b6..729e729b8cb7 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -34,9 +34,8 @@ export const paramDef = { required: ['key'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.registryItemsRepository) private registryItemsRepository: RegistryItemsRepository, diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts index 39b34de10862..ffd2860fde28 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; export const meta = { @@ -24,9 +24,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.registryItemsRepository) private registryItemsRepository: RegistryItemsRepository, diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys.ts b/packages/backend/src/server/api/endpoints/i/registry/keys.ts index 2becd1040aeb..7239bb66e1d0 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/keys.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/keys.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; export const meta = { @@ -24,9 +24,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.registryItemsRepository) private registryItemsRepository: RegistryItemsRepository, diff --git a/packages/backend/src/server/api/endpoints/i/registry/remove.ts b/packages/backend/src/server/api/endpoints/i/registry/remove.ts index 633d47eb2176..ae687fefe903 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/remove.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/remove.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -34,9 +34,8 @@ export const paramDef = { required: ['key'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.registryItemsRepository) private registryItemsRepository: RegistryItemsRepository, diff --git a/packages/backend/src/server/api/endpoints/i/registry/scopes.ts b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts index 7cf95926fbad..7637cdcf7314 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/scopes.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; export const meta = { @@ -20,9 +20,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.registryItemsRepository) private registryItemsRepository: RegistryItemsRepository, diff --git a/packages/backend/src/server/api/endpoints/i/registry/set.ts b/packages/backend/src/server/api/endpoints/i/registry/set.ts index ac51ab17f683..c074b152df36 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/set.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/set.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RegistryItemsRepository } from '@/models/index.js'; +import type { RegistryItemsRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; @@ -28,9 +28,8 @@ export const paramDef = { required: ['key', 'value'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.registryItemsRepository) private registryItemsRepository: RegistryItemsRepository, diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts index 76f83019b1b1..8e2f271005a6 100644 --- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts +++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AccessTokensRepository } from '@/models/index.js'; +import type { AccessTokensRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; export const meta = { @@ -22,9 +22,8 @@ export const paramDef = { required: ['tokenId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.accessTokensRepository) private accessTokensRepository: AccessTokensRepository, diff --git a/packages/backend/src/server/api/endpoints/i/signin-history.ts b/packages/backend/src/server/api/endpoints/i/signin-history.ts index 55dca98ad8fd..139bede7bc02 100644 --- a/packages/backend/src/server/api/endpoints/i/signin-history.ts +++ b/packages/backend/src/server/api/endpoints/i/signin-history.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { SigninsRepository } from '@/models/index.js'; +import type { SigninsRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { SigninEntityService } from '@/core/entities/SigninEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -26,9 +26,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.signinsRepository) private signinsRepository: SigninsRepository, diff --git a/packages/backend/src/server/api/endpoints/i/unpin.ts b/packages/backend/src/server/api/endpoints/i/unpin.ts index 8f833a0f53f3..b59c0e954f5a 100644 --- a/packages/backend/src/server/api/endpoints/i/unpin.ts +++ b/packages/backend/src/server/api/endpoints/i/unpin.ts @@ -39,9 +39,8 @@ export const paramDef = { required: ['noteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private userEntityService: UserEntityService, private notePiningService: NotePiningService, diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index 674d41c56fa9..a36b3a732bc4 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -7,13 +7,14 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import bcrypt from 'bcryptjs'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UserProfilesRepository } from '@/models/index.js'; +import type { UserProfilesRepository } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { EmailService } from '@/core/EmailService.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js'; +import { UserAuthService } from '@/core/UserAuthService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -46,13 +47,13 @@ export const paramDef = { properties: { password: { type: 'string' }, email: { type: 'string', nullable: true }, + token: { type: 'string', nullable: true }, }, required: ['password'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.config) private config: Config, @@ -62,15 +63,27 @@ export default class extends Endpoint { private userEntityService: UserEntityService, private emailService: EmailService, + private userAuthService: UserAuthService, private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps, me) => { + const token = ps.token; const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); + if (profile.twoFactorEnabled) { + if (token == null) { + throw new Error('authentication failed'); + } + + try { + await this.userAuthService.twoFactorAuthenticate(profile, token); + } catch (e) { + throw new Error('authentication failed'); + } + } - if (!same) { + const passwordMatched = await bcrypt.compare(ps.password, profile.password!); + if (!passwordMatched) { throw new ApiError(meta.errors.incorrectPassword); } diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 2691106bd1b4..b11e09195700 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -6,13 +6,15 @@ import RE2 from 're2'; import * as mfm from 'mfm-js'; import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; +import { JSDOM } from 'jsdom'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; import { extractHashtags } from '@/misc/extract-hashtags.js'; import * as Acct from '@/misc/acct.js'; -import type { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/index.js'; -import type { User } from '@/models/entities/User.js'; -import { birthdaySchema, descriptionSchema, locationSchema, nameSchema } from '@/models/entities/User.js'; -import type { UserProfile } from '@/models/entities/UserProfile.js'; +import type { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/_.js'; +import type { MiLocalUser, MiUser } from '@/models/User.js'; +import { birthdaySchema, descriptionSchema, locationSchema, nameSchema } from '@/models/User.js'; +import type { MiUserProfile } from '@/models/UserProfile.js'; import { notificationTypes } from '@/types.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { langmap } from '@/misc/langmap.js'; @@ -27,6 +29,9 @@ import { RoleService } from '@/core/RoleService.js'; import { CacheService } from '@/core/CacheService.js'; import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; +import type { Config } from '@/config.js'; +import { safeForSql } from '@/misc/safe-for-sql.js'; import { ApiLoggerService } from '../../ApiLoggerService.js'; import { ApiError } from '../../error.js'; @@ -37,6 +42,11 @@ export const meta = { kind: 'write:account', + limit: { + duration: ms('1hour'), + max: 10, + }, + errors: { noSuchAvatar: { message: 'No such avatar file.', @@ -170,10 +180,12 @@ export const paramDef = { }, } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.config) + private config: Config, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -196,13 +208,14 @@ export default class extends Endpoint { private hashtagService: HashtagService, private roleService: RoleService, private cacheService: CacheService, + private httpRequestService: HttpRequestService, ) { super(meta, paramDef, async (ps, _user, token) => { - const user = await this.usersRepository.findOneByOrFail({ id: _user.id }); + const user = await this.usersRepository.findOneByOrFail({ id: _user.id }) as MiLocalUser; const isSecure = token == null; - const updates = {} as Partial; - const profileUpdates = {} as Partial; + const updates = {} as Partial; + const profileUpdates = {} as Partial; const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); @@ -297,9 +310,9 @@ export default class extends Endpoint { if (ps.fields) { profileUpdates.fields = ps.fields - .filter(x => typeof x.name === 'string' && x.name !== '' && typeof x.value === 'string' && x.value !== '') + .filter(x => typeof x.name === 'string' && x.name.trim() !== '' && typeof x.value === 'string' && x.value.trim() !== '') .map(x => { - return { name: x.name, value: x.value }; + return { name: x.name.trim(), value: x.value.trim() }; }); } @@ -365,7 +378,11 @@ export default class extends Endpoint { if (Object.keys(updates).includes('alsoKnownAs')) { this.cacheService.uriPersonCache.set(this.userEntityService.genLocalUserUri(user.id), { ...user, ...updates }); } - if (Object.keys(profileUpdates).length > 0) await this.userProfilesRepository.update(user.id, profileUpdates); + + await this.userProfilesRepository.update(user.id, { + ...profileUpdates, + verifiedLinks: [], + }); const iObj = await this.userEntityService.pack(user.id, user, { detail: true, @@ -387,7 +404,34 @@ export default class extends Endpoint { // フォロワーにUpdateを配信 this.accountUpdateService.publishToFollowers(user.id); + const urls = updatedProfile.fields.filter(x => x.value.startsWith('https://')); + for (const url of urls) { + this.verifyLink(url.value, user); + } + return iObj; }); } + + private async verifyLink(url: string, user: MiLocalUser) { + if (!safeForSql(url)) return; + + const html = await this.httpRequestService.getHtml(url); + + const { window } = new JSDOM(html); + const doc = window.document; + + const myLink = `${this.config.url}/@${user.username}`; + + const includesMyLink = Array.from(doc.getElementsByTagName('a')).some(a => a.href === myLink); + + if (includesMyLink) { + await this.userProfilesRepository.createQueryBuilder('profile').update() + .where('userId = :userId', { userId: user.id }) + .set({ + verifiedLinks: () => `array_append("verifiedLinks", '${url}')`, // ここでSQLインジェクションされそうなのでとりあえず safeForSql で弾いている + }) + .execute(); + } + } } diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts index 38a52c5cfdaa..48eaeff40619 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts @@ -6,8 +6,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; -import type { WebhooksRepository } from '@/models/index.js'; -import { webhookEventTypes } from '@/models/entities/Webhook.js'; +import type { WebhooksRepository } from '@/models/_.js'; +import { webhookEventTypes } from '@/models/Webhook.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; @@ -34,19 +34,18 @@ export const paramDef = { properties: { name: { type: 'string', minLength: 1, maxLength: 100 }, url: { type: 'string', minLength: 1, maxLength: 1024 }, - secret: { type: 'string', minLength: 1, maxLength: 1024 }, + secret: { type: 'string', maxLength: 1024, default: '' }, on: { type: 'array', items: { type: 'string', enum: webhookEventTypes, } }, }, - required: ['name', 'url', 'secret', 'on'], + required: ['name', 'url', 'on'], } as const; // TODO: ロジックをサービスに切り出す -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.webhooksRepository) private webhooksRepository: WebhooksRepository, diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts index 9d674ec02015..db7d0db13cf4 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { WebhooksRepository } from '@/models/index.js'; +import type { WebhooksRepository } from '@/models/_.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -36,9 +36,8 @@ export const paramDef = { // TODO: ロジックをサービスに切り出す -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.webhooksRepository) private webhooksRepository: WebhooksRepository, diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts index 7cf03bf25f42..aa8921fe24f9 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { WebhooksRepository } from '@/models/index.js'; +import type { WebhooksRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; export const meta = { @@ -22,9 +22,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.webhooksRepository) private webhooksRepository: WebhooksRepository, diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts index fbeaa38f8619..f1294bb5c870 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { WebhooksRepository } from '@/models/index.js'; +import type { WebhooksRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -33,9 +33,8 @@ export const paramDef = { required: ['webhookId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.webhooksRepository) private webhooksRepository: WebhooksRepository, diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts index d6b84c50b3db..b3e000524d0e 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts @@ -5,8 +5,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { WebhooksRepository } from '@/models/index.js'; -import { webhookEventTypes } from '@/models/entities/Webhook.js'; +import type { WebhooksRepository } from '@/models/_.js'; +import { webhookEventTypes } from '@/models/Webhook.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -34,20 +34,19 @@ export const paramDef = { webhookId: { type: 'string', format: 'misskey:id' }, name: { type: 'string', minLength: 1, maxLength: 100 }, url: { type: 'string', minLength: 1, maxLength: 1024 }, - secret: { type: 'string', minLength: 1, maxLength: 1024 }, + secret: { type: 'string', maxLength: 1024, default: '' }, on: { type: 'array', items: { type: 'string', enum: webhookEventTypes, } }, active: { type: 'boolean' }, }, - required: ['webhookId', 'name', 'url', 'secret', 'on', 'active'], + required: ['webhookId', 'name', 'url', 'on', 'active'], } as const; // TODO: ロジックをサービスに切り出す -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.webhooksRepository) private webhooksRepository: WebhooksRepository, diff --git a/packages/backend/src/server/api/endpoints/invite/create.ts b/packages/backend/src/server/api/endpoints/invite/create.ts index eca3989ffcdb..7361ab616ca7 100644 --- a/packages/backend/src/server/api/endpoints/invite/create.ts +++ b/packages/backend/src/server/api/endpoints/invite/create.ts @@ -6,7 +6,7 @@ import { MoreThan } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RegistrationTicketsRepository } from '@/models/index.js'; +import type { RegistrationTicketsRepository } from '@/models/_.js'; import { InviteCodeEntityService } from '@/core/entities/InviteCodeEntityService.js'; import { IdService } from '@/core/IdService.js'; import { RoleService } from '@/core/RoleService.js'; @@ -47,9 +47,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.registrationTicketsRepository) private registrationTicketsRepository: RegistrationTicketsRepository, diff --git a/packages/backend/src/server/api/endpoints/invite/delete.ts b/packages/backend/src/server/api/endpoints/invite/delete.ts index 901efd52375c..3b57775739a8 100644 --- a/packages/backend/src/server/api/endpoints/invite/delete.ts +++ b/packages/backend/src/server/api/endpoints/invite/delete.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RegistrationTicketsRepository } from '@/models/index.js'; +import type { RegistrationTicketsRepository } from '@/models/_.js'; import { RoleService } from '@/core/RoleService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -45,9 +45,8 @@ export const paramDef = { required: ['inviteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.registrationTicketsRepository) private registrationTicketsRepository: RegistrationTicketsRepository, diff --git a/packages/backend/src/server/api/endpoints/invite/limit.ts b/packages/backend/src/server/api/endpoints/invite/limit.ts index 87552ff5d36d..43b94e4f0605 100644 --- a/packages/backend/src/server/api/endpoints/invite/limit.ts +++ b/packages/backend/src/server/api/endpoints/invite/limit.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RegistrationTicketsRepository } from '@/models/index.js'; +import type { RegistrationTicketsRepository } from '@/models/_.js'; import { RoleService } from '@/core/RoleService.js'; import { DI } from '@/di-symbols.js'; @@ -34,9 +34,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.registrationTicketsRepository) private registrationTicketsRepository: RegistrationTicketsRepository, diff --git a/packages/backend/src/server/api/endpoints/invite/list.ts b/packages/backend/src/server/api/endpoints/invite/list.ts index f4fa82bc8303..06139b6806b8 100644 --- a/packages/backend/src/server/api/endpoints/invite/list.ts +++ b/packages/backend/src/server/api/endpoints/invite/list.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RegistrationTicketsRepository } from '@/models/index.js'; +import type { RegistrationTicketsRepository } from '@/models/_.js'; import { InviteCodeEntityService } from '@/core/entities/InviteCodeEntityService.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; @@ -37,9 +37,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.registrationTicketsRepository) private registrationTicketsRepository: RegistrationTicketsRepository, diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index f6f485534618..c0cbfa3f4863 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -6,7 +6,7 @@ import { IsNull, LessThanOrEqual, MoreThan, Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import JSON5 from 'json5'; -import type { AdsRepository, UsersRepository } from '@/models/index.js'; +import type { AdsRepository, UsersRepository } from '@/models/_.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -253,9 +253,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.config) private config: Config, diff --git a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts index a194b22fbff9..e40656cb6dde 100644 --- a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts +++ b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AccessTokensRepository } from '@/models/index.js'; +import type { AccessTokensRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; import { DI } from '@/di-symbols.js'; @@ -43,9 +43,8 @@ export const paramDef = { required: ['session', 'permission'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.accessTokensRepository) private accessTokensRepository: AccessTokensRepository, diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index 1786d550ba1a..12d4a682a2d4 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { MutingsRepository } from '@/models/index.js'; +import type { MutingsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { UserMutingService } from '@/core/UserMutingService.js'; @@ -59,9 +59,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.mutingsRepository) private mutingsRepository: MutingsRepository, diff --git a/packages/backend/src/server/api/endpoints/mute/delete.ts b/packages/backend/src/server/api/endpoints/mute/delete.ts index 13e00183157e..a3fd2dd82f56 100644 --- a/packages/backend/src/server/api/endpoints/mute/delete.ts +++ b/packages/backend/src/server/api/endpoints/mute/delete.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { MutingsRepository } from '@/models/index.js'; +import type { MutingsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { UserMutingService } from '@/core/UserMutingService.js'; @@ -47,9 +47,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.mutingsRepository) private mutingsRepository: MutingsRepository, diff --git a/packages/backend/src/server/api/endpoints/mute/list.ts b/packages/backend/src/server/api/endpoints/mute/list.ts index b458c7fd7010..2a41182ebc61 100644 --- a/packages/backend/src/server/api/endpoints/mute/list.ts +++ b/packages/backend/src/server/api/endpoints/mute/list.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { MutingsRepository } from '@/models/index.js'; +import type { MutingsRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { MutingEntityService } from '@/core/entities/MutingEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -38,9 +38,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.mutingsRepository) private mutingsRepository: MutingsRepository, diff --git a/packages/backend/src/server/api/endpoints/my/apps.ts b/packages/backend/src/server/api/endpoints/my/apps.ts index 37562ab9ed76..98c317346f1e 100644 --- a/packages/backend/src/server/api/endpoints/my/apps.ts +++ b/packages/backend/src/server/api/endpoints/my/apps.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { AppsRepository } from '@/models/index.js'; +import type { AppsRepository } from '@/models/_.js'; import { AppEntityService } from '@/core/entities/AppEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -34,9 +34,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.appsRepository) private appsRepository: AppsRepository, diff --git a/packages/backend/src/server/api/endpoints/notes.ts b/packages/backend/src/server/api/endpoints/notes.ts index 2eea9c6a8f7f..95ba5e8b64ce 100644 --- a/packages/backend/src/server/api/endpoints/notes.ts +++ b/packages/backend/src/server/api/endpoints/notes.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -39,9 +39,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.notesRepository) private notesRepository: NotesRepository, diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts index c12af10a9d67..1a82a4b5d7a7 100644 --- a/packages/backend/src/server/api/endpoints/notes/children.ts +++ b/packages/backend/src/server/api/endpoints/notes/children.ts @@ -5,7 +5,7 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -38,9 +38,8 @@ export const paramDef = { required: ['noteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.notesRepository) private notesRepository: NotesRepository, diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts index de36180110d0..677c0ea30769 100644 --- a/packages/backend/src/server/api/endpoints/notes/clips.ts +++ b/packages/backend/src/server/api/endpoints/notes/clips.ts @@ -5,7 +5,7 @@ import { In } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js'; +import type { ClipNotesRepository, ClipsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -44,9 +44,8 @@ export const paramDef = { required: ['noteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.clipsRepository) private clipsRepository: ClipsRepository, diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts index 7d9b37550157..b94a019da4c6 100644 --- a/packages/backend/src/server/api/endpoints/notes/conversation.ts +++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts @@ -4,8 +4,8 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { Note } from '@/models/entities/Note.js'; -import type { NotesRepository } from '@/models/index.js'; +import type { MiNote } from '@/models/Note.js'; +import type { NotesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -46,9 +46,8 @@ export const paramDef = { required: ['noteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -62,7 +61,7 @@ export default class extends Endpoint { throw err; }); - const conversation: Note[] = []; + const conversation: MiNote[] = []; let i = 0; const get = async (id: any) => { diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 5dd85e9481a1..9f8928112176 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -6,11 +6,11 @@ import ms from 'ms'; import { In } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { User } from '@/models/entities/User.js'; -import type { UsersRepository, NotesRepository, BlockingsRepository, DriveFilesRepository, ChannelsRepository } from '@/models/index.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; -import type { Note } from '@/models/entities/Note.js'; -import type { Channel } from '@/models/entities/Channel.js'; +import type { MiUser } from '@/models/User.js'; +import type { UsersRepository, NotesRepository, BlockingsRepository, DriveFilesRepository, ChannelsRepository } from '@/models/_.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; +import type { MiNote } from '@/models/Note.js'; +import type { MiChannel } from '@/models/Channel.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -162,9 +162,8 @@ export const paramDef = { ], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -185,14 +184,14 @@ export default class extends Endpoint { private noteCreateService: NoteCreateService, ) { super(meta, paramDef, async (ps, me) => { - let visibleUsers: User[] = []; + let visibleUsers: MiUser[] = []; if (ps.visibleUserIds) { visibleUsers = await this.usersRepository.findBy({ id: In(ps.visibleUserIds), }); } - let files: DriveFile[] = []; + let files: MiDriveFile[] = []; const fileIds = ps.fileIds ?? ps.mediaIds ?? null; if (fileIds != null) { files = await this.driveFilesRepository.createQueryBuilder('file') @@ -209,7 +208,7 @@ export default class extends Endpoint { } } - let renote: Note | null = null; + let renote: MiNote | null = null; if (ps.renoteId != null) { // Fetch renote to note renote = await this.notesRepository.findOneBy({ id: ps.renoteId }); @@ -234,7 +233,7 @@ export default class extends Endpoint { } } - let reply: Note | null = null; + let reply: MiNote | null = null; if (ps.replyId != null) { // Fetch reply reply = await this.notesRepository.findOneBy({ id: ps.replyId }); @@ -269,7 +268,7 @@ export default class extends Endpoint { } } - let channel: Channel | null = null; + let channel: MiChannel | null = null; if (ps.channelId != null) { channel = await this.channelsRepository.findOneBy({ id: ps.channelId, isArchived: false }); diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts index df4225746d68..74062a58f51f 100644 --- a/packages/backend/src/server/api/endpoints/notes/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/delete.ts @@ -5,7 +5,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteDeleteService } from '@/core/NoteDeleteService.js'; import { DI } from '@/di-symbols.js'; @@ -49,9 +49,8 @@ export const paramDef = { required: ['noteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts index e9831e813f31..7fdbb09216e0 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; -import type { NoteFavoritesRepository } from '@/models/index.js'; +import type { NoteFavoritesRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/GetterService.js'; @@ -49,9 +49,8 @@ export const paramDef = { required: ['noteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.noteFavoritesRepository) private noteFavoritesRepository: NoteFavoritesRepository, diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts index 97f354f81b23..8ab9775a2cb7 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts @@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/GetterService.js'; import { DI } from '@/di-symbols.js'; -import type { NoteFavoritesRepository } from '@/models/index.js'; +import type { NoteFavoritesRepository } from '@/models/_.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -40,9 +40,8 @@ export const paramDef = { required: ['noteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.noteFavoritesRepository) private noteFavoritesRepository: NoteFavoritesRepository, diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index 5ff9c916dc0f..5283b0e0bc8e 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -38,9 +38,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.notesRepository) private notesRepository: NotesRepository, diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index a6b0c72f589b..0b3b5c902e34 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -49,9 +49,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.notesRepository) private notesRepository: NotesRepository, diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index a0256f6c0303..e9ae5dc7553d 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -5,7 +5,7 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository, FollowingsRepository } from '@/models/index.js'; +import type { NotesRepository, FollowingsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; @@ -56,9 +56,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.notesRepository) private notesRepository: NotesRepository, diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index 07404fd4587a..af1e0398dc3b 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -5,7 +5,7 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -55,9 +55,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.notesRepository) private notesRepository: NotesRepository, diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts index 6c2900a706ae..65e7bd8cd518 100644 --- a/packages/backend/src/server/api/endpoints/notes/mentions.ts +++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts @@ -5,7 +5,7 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository, FollowingsRepository } from '@/models/index.js'; +import type { NotesRepository, FollowingsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -40,9 +40,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -64,6 +63,8 @@ export default class extends Endpoint { .where(`'{"${me.id}"}' <@ note.mentions`) .orWhere(`'{"${me.id}"}' <@ note.visibleUserIds`); })) + // Avoid scanning primary key index + .orderBy('CONCAT(note.id)', 'DESC') .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') diff --git a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts index 9802ae0cabda..29190af62a2c 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts @@ -5,7 +5,7 @@ import { Brackets, In } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository, MutingsRepository, PollsRepository, PollVotesRepository } from '@/models/index.js'; +import type { NotesRepository, MutingsRepository, PollsRepository, PollVotesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -35,9 +35,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.notesRepository) private notesRepository: NotesRepository, diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts index 57b65eece048..832f98faac13 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -4,8 +4,8 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository, PollsRepository, PollVotesRepository } from '@/models/index.js'; -import type { RemoteUser } from '@/models/entities/User.js'; +import type { UsersRepository, PollsRepository, PollVotesRepository } from '@/models/_.js'; +import type { MiRemoteUser } from '@/models/User.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/GetterService.js'; @@ -76,9 +76,8 @@ export const paramDef = { // TODO: ロジックをサービスに切り出す -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -164,7 +163,7 @@ export default class extends Endpoint { // リモート投票の場合リプライ送信 if (note.userHost != null) { - const pollOwner = await this.usersRepository.findOneByOrFail({ id: note.userId }) as RemoteUser; + const pollOwner = await this.usersRepository.findOneByOrFail({ id: note.userId }) as MiRemoteUser; this.queueService.deliver(me, this.apRendererService.addContext(await this.apRendererService.renderVote(me, vote, note, poll, pollOwner)), pollOwner.inbox, false); } diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts index ed1e598e6626..dae31364ee2d 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts @@ -4,8 +4,8 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { NoteReactionsRepository } from '@/models/index.js'; -import type { NoteReaction } from '@/models/entities/NoteReaction.js'; +import type { NoteReactionsRepository } from '@/models/_.js'; +import type { MiNoteReaction } from '@/models/NoteReaction.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteReactionEntityService } from '@/core/entities/NoteReactionEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -51,9 +51,8 @@ export const paramDef = { required: ['noteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.noteReactionsRepository) private noteReactionsRepository: NoteReactionsRepository, @@ -63,7 +62,7 @@ export default class extends Endpoint { super(meta, paramDef, async (ps, me) => { const query = { noteId: ps.noteId, - } as FindOptionsWhere; + } as FindOptionsWhere; if (ps.type) { // ローカルリアクションはホスト名が . とされているが diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts index c078f226ab83..12ea47fc7dc7 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts @@ -48,9 +48,8 @@ export const paramDef = { required: ['noteId', 'reaction'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private getterService: GetterService, private reactionService: ReactionService, diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts index bab8004755ab..b43ab044fad2 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts @@ -46,9 +46,8 @@ export const paramDef = { required: ['noteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private getterService: GetterService, private reactionService: ReactionService, diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index c5c2bc227d22..9f16181a30c2 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -47,9 +47,8 @@ export const paramDef = { required: ['noteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.notesRepository) private notesRepository: NotesRepository, diff --git a/packages/backend/src/server/api/endpoints/notes/replies.ts b/packages/backend/src/server/api/endpoints/notes/replies.ts index 706e65fc789c..70142c9818b3 100644 --- a/packages/backend/src/server/api/endpoints/notes/replies.ts +++ b/packages/backend/src/server/api/endpoints/notes/replies.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -37,9 +37,8 @@ export const paramDef = { required: ['noteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.notesRepository) private notesRepository: NotesRepository, diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts index 94ab6a4f268b..b00f5207d83e 100644 --- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts +++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts @@ -5,7 +5,7 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/_.js'; import { safeForSql } from '@/misc/safe-for-sql.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; @@ -63,9 +63,8 @@ export const paramDef = { ], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.notesRepository) private notesRepository: NotesRepository, diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index 478a78aa596d..4425d4593cf9 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -54,9 +54,8 @@ export const paramDef = { // TODO: ロジックをサービスに切り出す -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private noteEntityService: NoteEntityService, private searchService: SearchService, diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts index 65a8e1cdc2d2..5bb819654325 100644 --- a/packages/backend/src/server/api/endpoints/notes/show.ts +++ b/packages/backend/src/server/api/endpoints/notes/show.ts @@ -37,9 +37,8 @@ export const paramDef = { required: ['noteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private noteEntityService: NoteEntityService, private getterService: GetterService, diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts index b95e966de40f..b5fd47723cd7 100644 --- a/packages/backend/src/server/api/endpoints/notes/state.ts +++ b/packages/backend/src/server/api/endpoints/notes/state.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository, NoteThreadMutingsRepository, NoteFavoritesRepository } from '@/models/index.js'; +import type { NotesRepository, NoteThreadMutingsRepository, NoteFavoritesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; @@ -37,9 +37,8 @@ export const paramDef = { required: ['noteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.notesRepository) private notesRepository: NotesRepository, diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts index fb4e15f3301f..449a8386044b 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; -import type { NotesRepository, NoteThreadMutingsRepository } from '@/models/index.js'; +import type { NotesRepository, NoteThreadMutingsRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/GetterService.js'; @@ -42,9 +42,8 @@ export const paramDef = { required: ['noteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.notesRepository) private notesRepository: NotesRepository, diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts index 740b5f656c69..d3f1787ee479 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { NoteThreadMutingsRepository } from '@/models/index.js'; +import type { NoteThreadMutingsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/GetterService.js'; import { DI } from '@/di-symbols.js'; @@ -34,9 +34,8 @@ export const paramDef = { required: ['noteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.noteThreadMutingsRepository) private noteThreadMutingsRepository: NoteThreadMutingsRepository, diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 0d61ef981388..042115ab84b3 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -5,7 +5,7 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository, FollowingsRepository } from '@/models/index.js'; +import type { NotesRepository, FollowingsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; @@ -46,9 +46,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -70,7 +69,8 @@ export default class extends Endpoint { //#region Construct query const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) - .andWhere('note.id > :minId', { minId: this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 10))) }) // 10日前まで + // パフォーマンス上の利点が無さそう? + //.andWhere('note.id > :minId', { minId: this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 10))) }) // 10日前まで .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index 70496d8fd7d8..00cb9a0a2863 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -40,9 +40,8 @@ export const paramDef = { required: ['noteId', 'targetLang'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private noteEntityService: NoteEntityService, private getterService: GetterService, diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts index 1689d814fd9f..f67e9365fc83 100644 --- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts +++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts @@ -5,7 +5,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository, NotesRepository } from '@/models/index.js'; +import type { UsersRepository, NotesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteDeleteService } from '@/core/NoteDeleteService.js'; import { DI } from '@/di-symbols.js'; @@ -42,9 +42,8 @@ export const paramDef = { required: ['noteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index d4ec3287219b..6932073791ef 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -5,7 +5,7 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; +import type { NotesRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -58,9 +58,8 @@ export const paramDef = { required: ['listId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.notesRepository) private notesRepository: NotesRepository, diff --git a/packages/backend/src/server/api/endpoints/notifications/create.ts b/packages/backend/src/server/api/endpoints/notifications/create.ts index 9c98bf5b882b..268628cf761a 100644 --- a/packages/backend/src/server/api/endpoints/notifications/create.ts +++ b/packages/backend/src/server/api/endpoints/notifications/create.ts @@ -14,6 +14,11 @@ export const meta = { kind: 'write:notifications', + limit: { + duration: 1000 * 60, + max: 10, + }, + errors: { }, } as const; @@ -28,9 +33,8 @@ export const paramDef = { required: ['body'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private notificationService: NotificationService, ) { diff --git a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts index 596c2308762d..dc092c1f3a4b 100644 --- a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts @@ -21,9 +21,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private notificationService: NotificationService, ) { diff --git a/packages/backend/src/server/api/endpoints/notifications/test-notification.ts b/packages/backend/src/server/api/endpoints/notifications/test-notification.ts new file mode 100644 index 000000000000..8f5f8485c353 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/notifications/test-notification.ts @@ -0,0 +1,38 @@ +/* + * 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 { NotificationService } from '@/core/NotificationService.js'; + +export const meta = { + tags: ['notifications'], + + requireCredential: true, + + kind: 'write:notifications', + + limit: { + duration: 1000 * 60, + max: 10, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: {}, + required: [], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private notificationService: NotificationService, + ) { + super(meta, paramDef, async (ps, user) => { + this.notificationService.createNotification(user.id, 'test', {}); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/page-push.ts b/packages/backend/src/server/api/endpoints/page-push.ts index cbb45ca60dec..0a6851658650 100644 --- a/packages/backend/src/server/api/endpoints/page-push.ts +++ b/packages/backend/src/server/api/endpoints/page-push.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { PagesRepository } from '@/models/index.js'; +import type { PagesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; @@ -34,9 +34,8 @@ export const paramDef = { required: ['pageId', 'event'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.pagesRepository) private pagesRepository: PagesRepository, diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index a77057b4f7f2..b57d50eedd7c 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -5,9 +5,9 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import type { DriveFilesRepository, PagesRepository } from '@/models/index.js'; +import type { DriveFilesRepository, PagesRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; -import { Page } from '@/models/entities/Page.js'; +import { MiPage } from '@/models/Page.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -68,9 +68,8 @@ export const paramDef = { required: ['title', 'name', 'content', 'variables', 'script'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.pagesRepository) private pagesRepository: PagesRepository, @@ -103,7 +102,7 @@ export default class extends Endpoint { } }); - const page = await this.pagesRepository.insert(new Page({ + const page = await this.pagesRepository.insert(new MiPage({ id: this.idService.genId(), createdAt: new Date(), updatedAt: new Date(), diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts index a0aa72a697e8..1291c0d2099b 100644 --- a/packages/backend/src/server/api/endpoints/pages/delete.ts +++ b/packages/backend/src/server/api/endpoints/pages/delete.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { PagesRepository } from '@/models/index.js'; +import type { PagesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -39,9 +39,8 @@ export const paramDef = { required: ['pageId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.pagesRepository) private pagesRepository: PagesRepository, diff --git a/packages/backend/src/server/api/endpoints/pages/featured.ts b/packages/backend/src/server/api/endpoints/pages/featured.ts index 386fdc0d1a7c..1f43d6606c74 100644 --- a/packages/backend/src/server/api/endpoints/pages/featured.ts +++ b/packages/backend/src/server/api/endpoints/pages/featured.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { PagesRepository } from '@/models/index.js'; +import type { PagesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -31,9 +31,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.pagesRepository) private pagesRepository: PagesRepository, diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts index a88d523a6f99..bf6f7d65531c 100644 --- a/packages/backend/src/server/api/endpoints/pages/like.ts +++ b/packages/backend/src/server/api/endpoints/pages/like.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { PagesRepository, PageLikesRepository } from '@/models/index.js'; +import type { PagesRepository, PageLikesRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; @@ -48,9 +48,8 @@ export const paramDef = { required: ['pageId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.pagesRepository) private pagesRepository: PagesRepository, diff --git a/packages/backend/src/server/api/endpoints/pages/show.ts b/packages/backend/src/server/api/endpoints/pages/show.ts index 2e0a486d18a2..efb0bd0677ee 100644 --- a/packages/backend/src/server/api/endpoints/pages/show.ts +++ b/packages/backend/src/server/api/endpoints/pages/show.ts @@ -5,8 +5,8 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository, PagesRepository } from '@/models/index.js'; -import type { Page } from '@/models/entities/Page.js'; +import type { UsersRepository, PagesRepository } from '@/models/_.js'; +import type { MiPage } from '@/models/Page.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -45,9 +45,8 @@ export const paramDef = { ], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -58,7 +57,7 @@ export default class extends Endpoint { private pageEntityService: PageEntityService, ) { super(meta, paramDef, async (ps, me) => { - let page: Page | null = null; + let page: MiPage | null = null; if (ps.pageId) { page = await this.pagesRepository.findOneBy({ id: ps.pageId }); diff --git a/packages/backend/src/server/api/endpoints/pages/unlike.ts b/packages/backend/src/server/api/endpoints/pages/unlike.ts index b9a2666fb773..76444a59e4f1 100644 --- a/packages/backend/src/server/api/endpoints/pages/unlike.ts +++ b/packages/backend/src/server/api/endpoints/pages/unlike.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { PagesRepository, PageLikesRepository } from '@/models/index.js'; +import type { PagesRepository, PageLikesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -41,9 +41,8 @@ export const paramDef = { required: ['pageId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.pagesRepository) private pagesRepository: PagesRepository, diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index 975137cb0c96..bcc77ddc6336 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -6,7 +6,7 @@ import ms from 'ms'; import { Not } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { PagesRepository, DriveFilesRepository } from '@/models/index.js'; +import type { PagesRepository, DriveFilesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -73,9 +73,8 @@ export const paramDef = { required: ['pageId', 'title', 'name', 'content', 'variables', 'script'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.pagesRepository) private pagesRepository: PagesRepository, diff --git a/packages/backend/src/server/api/endpoints/ping.ts b/packages/backend/src/server/api/endpoints/ping.ts index 65f4d69a3588..ee2fe4883485 100644 --- a/packages/backend/src/server/api/endpoints/ping.ts +++ b/packages/backend/src/server/api/endpoints/ping.ts @@ -29,9 +29,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( ) { super(meta, paramDef, async () => { diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 4df10ae00c14..390042c81586 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -5,9 +5,9 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/_.js'; import * as Acct from '@/misc/acct.js'; -import type { User } from '@/models/entities/User.js'; +import type { MiUser } from '@/models/User.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { MetaService } from '@/core/MetaService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -35,9 +35,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -53,7 +52,7 @@ export default class extends Endpoint { host: acct.host ?? IsNull(), }))); - return await this.userEntityService.packMany(users.filter(x => x !== null) as User[], me, { detail: true }); + return await this.userEntityService.packMany(users.filter(x => x !== null) as MiUser[], me, { detail: true }); }); } } diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts index 4c99f14bd4ab..b197756acc7d 100644 --- a/packages/backend/src/server/api/endpoints/promo/read.ts +++ b/packages/backend/src/server/api/endpoints/promo/read.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { PromoReadsRepository } from '@/models/index.js'; +import type { PromoReadsRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; @@ -33,9 +33,8 @@ export const paramDef = { required: ['noteId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.promoReadsRepository) private promoReadsRepository: PromoReadsRepository, diff --git a/packages/backend/src/server/api/endpoints/renote-mute/create.ts b/packages/backend/src/server/api/endpoints/renote-mute/create.ts index 934a2c6a0981..f79bc0aea6a5 100644 --- a/packages/backend/src/server/api/endpoints/renote-mute/create.ts +++ b/packages/backend/src/server/api/endpoints/renote-mute/create.ts @@ -7,8 +7,8 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; -import type { RenoteMutingsRepository } from '@/models/index.js'; -import type { RenoteMuting } from '@/models/entities/RenoteMuting.js'; +import type { RenoteMutingsRepository } from '@/models/_.js'; +import type { MiRenoteMuting } from '@/models/RenoteMuting.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { ApiError } from '../../error.js'; @@ -55,9 +55,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.renoteMutingsRepository) private renoteMutingsRepository: RenoteMutingsRepository, @@ -95,7 +94,7 @@ export default class extends Endpoint { createdAt: new Date(), muterId: muter.id, muteeId: mutee.id, - } as RenoteMuting); + } as MiRenoteMuting); }); } } diff --git a/packages/backend/src/server/api/endpoints/renote-mute/delete.ts b/packages/backend/src/server/api/endpoints/renote-mute/delete.ts index 373f805fa42e..f4969896d97a 100644 --- a/packages/backend/src/server/api/endpoints/renote-mute/delete.ts +++ b/packages/backend/src/server/api/endpoints/renote-mute/delete.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RenoteMutingsRepository } from '@/models/index.js'; +import type { RenoteMutingsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { ApiError } from '../../error.js'; @@ -46,9 +46,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.renoteMutingsRepository) private renoteMutingsRepository: RenoteMutingsRepository, diff --git a/packages/backend/src/server/api/endpoints/renote-mute/list.ts b/packages/backend/src/server/api/endpoints/renote-mute/list.ts index c7da5479ed9d..493593ae2d6a 100644 --- a/packages/backend/src/server/api/endpoints/renote-mute/list.ts +++ b/packages/backend/src/server/api/endpoints/renote-mute/list.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RenoteMutingsRepository } from '@/models/index.js'; +import type { RenoteMutingsRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { RenoteMutingEntityService } from '@/core/entities/RenoteMutingEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -38,9 +38,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.renoteMutingsRepository) private renoteMutingsRepository: RenoteMutingsRepository, diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts index 2e1085f78ef6..adb160c58b03 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -6,7 +6,7 @@ import ms from 'ms'; import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { PasswordResetRequestsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { PasswordResetRequestsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; import type { Config } from '@/config.js'; @@ -40,9 +40,8 @@ export const paramDef = { required: ['username', 'email'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.config) private config: Config, diff --git a/packages/backend/src/server/api/endpoints/reset-db.ts b/packages/backend/src/server/api/endpoints/reset-db.ts index dcf3b8772a95..0eeee815802a 100644 --- a/packages/backend/src/server/api/endpoints/reset-db.ts +++ b/packages/backend/src/server/api/endpoints/reset-db.ts @@ -28,9 +28,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.db) private db: DataSource, diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts index 4bbd3d0ef18d..1858c922a0e6 100644 --- a/packages/backend/src/server/api/endpoints/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/reset-password.ts @@ -5,7 +5,7 @@ import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; -import type { UserProfilesRepository, PasswordResetRequestsRepository } from '@/models/index.js'; +import type { UserProfilesRepository, PasswordResetRequestsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; @@ -30,9 +30,8 @@ export const paramDef = { required: ['token', 'password'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.passwordResetRequestsRepository) private passwordResetRequestsRepository: PasswordResetRequestsRepository, diff --git a/packages/backend/src/server/api/endpoints/retention.ts b/packages/backend/src/server/api/endpoints/retention.ts index 34e584b9851e..dac6d6540759 100644 --- a/packages/backend/src/server/api/endpoints/retention.ts +++ b/packages/backend/src/server/api/endpoints/retention.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { RetentionAggregationsRepository } from '@/models/index.js'; +import type { RetentionAggregationsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; @@ -26,9 +26,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.retentionAggregationsRepository) private retentionAggregationsRepository: RetentionAggregationsRepository, diff --git a/packages/backend/src/server/api/endpoints/roles/list.ts b/packages/backend/src/server/api/endpoints/roles/list.ts index ee17b46a2aff..d1de73ad3233 100644 --- a/packages/backend/src/server/api/endpoints/roles/list.ts +++ b/packages/backend/src/server/api/endpoints/roles/list.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RolesRepository } from '@/models/index.js'; +import type { RolesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { RoleEntityService } from '@/core/entities/RoleEntityService.js'; @@ -23,9 +23,8 @@ export const paramDef = { ], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.rolesRepository) private rolesRepository: RolesRepository, diff --git a/packages/backend/src/server/api/endpoints/roles/notes.ts b/packages/backend/src/server/api/endpoints/roles/notes.ts index fe0354d545d1..6dc35907e1f2 100644 --- a/packages/backend/src/server/api/endpoints/roles/notes.ts +++ b/packages/backend/src/server/api/endpoints/roles/notes.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { NotesRepository, RolesRepository } from '@/models/index.js'; +import type { NotesRepository, RolesRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -50,9 +50,8 @@ export const paramDef = { required: ['roleId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.redis) private redisClient: Redis.Redis, diff --git a/packages/backend/src/server/api/endpoints/roles/show.ts b/packages/backend/src/server/api/endpoints/roles/show.ts index 742c6f950dd4..2afa0e7b7fc4 100644 --- a/packages/backend/src/server/api/endpoints/roles/show.ts +++ b/packages/backend/src/server/api/endpoints/roles/show.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { RolesRepository } from '@/models/index.js'; +import type { RolesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { RoleEntityService } from '@/core/entities/RoleEntityService.js'; @@ -32,9 +32,8 @@ export const paramDef = { required: ['roleId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.rolesRepository) private rolesRepository: RolesRepository, diff --git a/packages/backend/src/server/api/endpoints/roles/users.ts b/packages/backend/src/server/api/endpoints/roles/users.ts index 4bb962c4a61f..37aac908b53c 100644 --- a/packages/backend/src/server/api/endpoints/roles/users.ts +++ b/packages/backend/src/server/api/endpoints/roles/users.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Brackets } from 'typeorm'; -import type { RoleAssignmentsRepository, RolesRepository } from '@/models/index.js'; +import type { RoleAssignmentsRepository, RolesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; @@ -37,9 +37,8 @@ export const paramDef = { required: ['roleId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.rolesRepository) private rolesRepository: RolesRepository, diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts index 4c1e78151315..c8cb63e6b314 100644 --- a/packages/backend/src/server/api/endpoints/server-info.ts +++ b/packages/backend/src/server/api/endpoints/server-info.ts @@ -23,9 +23,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private metaService: MetaService, ) { diff --git a/packages/backend/src/server/api/endpoints/stats.ts b/packages/backend/src/server/api/endpoints/stats.ts index 6174d39aed9b..05468240d30c 100644 --- a/packages/backend/src/server/api/endpoints/stats.ts +++ b/packages/backend/src/server/api/endpoints/stats.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { InstancesRepository, NoteReactionsRepository } from '@/models/index.js'; +import type { InstancesRepository, NoteReactionsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import NotesChart from '@/core/chart/charts/notes.js'; @@ -57,9 +57,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.instancesRepository) private instancesRepository: InstancesRepository, diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index 7a979ea306e2..5cfbeab73feb 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IdService } from '@/core/IdService.js'; -import type { SwSubscriptionsRepository } from '@/models/index.js'; +import type { SwSubscriptionsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { MetaService } from '@/core/MetaService.js'; import { DI } from '@/di-symbols.js'; @@ -57,9 +57,8 @@ export const paramDef = { required: ['endpoint', 'auth', 'publickey'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.swSubscriptionsRepository) private swSubscriptionsRepository: SwSubscriptionsRepository, diff --git a/packages/backend/src/server/api/endpoints/sw/show-registration.ts b/packages/backend/src/server/api/endpoints/sw/show-registration.ts index 90800f4b64b3..126299e3f749 100644 --- a/packages/backend/src/server/api/endpoints/sw/show-registration.ts +++ b/packages/backend/src/server/api/endpoints/sw/show-registration.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { SwSubscriptionsRepository } from '@/models/index.js'; +import type { SwSubscriptionsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; @@ -43,9 +43,8 @@ export const paramDef = { required: ['endpoint'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.swSubscriptionsRepository) private swSubscriptionsRepository: SwSubscriptionsRepository, diff --git a/packages/backend/src/server/api/endpoints/sw/unregister.ts b/packages/backend/src/server/api/endpoints/sw/unregister.ts index 7b948988a604..f00fdd669760 100644 --- a/packages/backend/src/server/api/endpoints/sw/unregister.ts +++ b/packages/backend/src/server/api/endpoints/sw/unregister.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { SwSubscriptionsRepository } from '@/models/index.js'; +import type { SwSubscriptionsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; @@ -24,9 +24,8 @@ export const paramDef = { required: ['endpoint'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.swSubscriptionsRepository) private swSubscriptionsRepository: SwSubscriptionsRepository, diff --git a/packages/backend/src/server/api/endpoints/sw/update-registration.ts b/packages/backend/src/server/api/endpoints/sw/update-registration.ts index 061d180f5724..a1a97df0bea6 100644 --- a/packages/backend/src/server/api/endpoints/sw/update-registration.ts +++ b/packages/backend/src/server/api/endpoints/sw/update-registration.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { SwSubscriptionsRepository } from '@/models/index.js'; +import type { SwSubscriptionsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -52,9 +52,8 @@ export const paramDef = { required: ['endpoint'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.swSubscriptionsRepository) private swSubscriptionsRepository: SwSubscriptionsRepository, diff --git a/packages/backend/src/server/api/endpoints/test.ts b/packages/backend/src/server/api/endpoints/test.ts index c8d6cfecba3f..6d6d44f75248 100644 --- a/packages/backend/src/server/api/endpoints/test.ts +++ b/packages/backend/src/server/api/endpoints/test.ts @@ -26,9 +26,8 @@ export const paramDef = { required: ['required'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( ) { super(meta, paramDef, async (ps, me) => { diff --git a/packages/backend/src/server/api/endpoints/username/available.ts b/packages/backend/src/server/api/endpoints/username/available.ts index 38036a3244c8..e37df62c0c6b 100644 --- a/packages/backend/src/server/api/endpoints/username/available.ts +++ b/packages/backend/src/server/api/endpoints/username/available.ts @@ -5,9 +5,9 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { UsedUsernamesRepository, UsersRepository } from '@/models/index.js'; +import type { UsedUsernamesRepository, UsersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { localUsernameSchema } from '@/models/entities/User.js'; +import { localUsernameSchema } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; import { MetaService } from '@/core/MetaService.js'; @@ -36,9 +36,8 @@ export const paramDef = { required: ['username'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/users.ts b/packages/backend/src/server/api/endpoints/users.ts index 95ff7c97598c..21c585f1ad0a 100644 --- a/packages/backend/src/server/api/endpoints/users.ts +++ b/packages/backend/src/server/api/endpoints/users.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository } from '@/models/index.js'; +import type { UsersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -44,9 +44,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/users/achievements.ts b/packages/backend/src/server/api/endpoints/users/achievements.ts index 0eaff724b146..e4845d57bf0d 100644 --- a/packages/backend/src/server/api/endpoints/users/achievements.ts +++ b/packages/backend/src/server/api/endpoints/users/achievements.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UserProfilesRepository } from '@/models/index.js'; +import type { UserProfilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; export const meta = { @@ -20,9 +20,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, diff --git a/packages/backend/src/server/api/endpoints/users/clips.ts b/packages/backend/src/server/api/endpoints/users/clips.ts index 747f15f39447..725e07db393a 100644 --- a/packages/backend/src/server/api/endpoints/users/clips.ts +++ b/packages/backend/src/server/api/endpoints/users/clips.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { ClipsRepository } from '@/models/index.js'; +import type { ClipsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; @@ -37,9 +37,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.clipsRepository) private clipsRepository: ClipsRepository, diff --git a/packages/backend/src/server/api/endpoints/users/flashs.ts b/packages/backend/src/server/api/endpoints/users/flashs.ts new file mode 100644 index 000000000000..18026dcefbaa --- /dev/null +++ b/packages/backend/src/server/api/endpoints/users/flashs.ts @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import { FlashEntityService } from '@/core/entities/FlashEntityService.js'; +import type { FlashsRepository } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; + +export const meta = { + tags: ['users', 'flashs'], + + description: 'Show all flashs this user created.', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'Flash', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + userId: { type: 'string', format: 'misskey:id' }, + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + }, + required: ['userId'], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint { + constructor( + @Inject(DI.flashsRepository) + private flashsRepository: FlashsRepository, + + private flashEntityService: FlashEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.flashsRepository.createQueryBuilder('flash'), ps.sinceId, ps.untilId) + .andWhere('flash.userId = :userId', { userId: ps.userId }) + .andWhere('flash.visibility = \'public\''); + + const flashs = await query + .limit(ps.limit) + .getMany(); + + return await this.flashEntityService.packMany(flashs); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index 2684446fc912..b22fd2ff7a27 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -5,7 +5,7 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; @@ -66,9 +66,8 @@ export const paramDef = { ], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index 926aacdb9870..03487275a3df 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -5,7 +5,7 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; @@ -66,9 +66,8 @@ export const paramDef = { ], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts index 2aa14ed25db8..757af98e004e 100644 --- a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { GalleryPostsRepository } from '@/models/index.js'; +import type { GalleryPostsRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -37,9 +37,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.galleryPostsRepository) private galleryPostsRepository: GalleryPostsRepository, diff --git a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts index d3e324d5081d..d6fb65cecb1f 100644 --- a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -6,7 +6,7 @@ import { Not, In, IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import { maximum } from '@/misc/prelude/array.js'; -import type { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -58,9 +58,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.notesRepository) private notesRepository: NotesRepository, diff --git a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts index fe32009eb3a1..8e984c715b10 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts @@ -4,9 +4,9 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/index.js'; +import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; -import type { UserList } from '@/models/entities/UserList.js'; +import type { MiUserList } from '@/models/UserList.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/GetterService.js'; import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; @@ -71,7 +71,7 @@ export const paramDef = { } as const; @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, @@ -108,7 +108,7 @@ export default class extends Endpoint { createdAt: new Date(), userId: me.id, name: ps.name, - } as UserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0])); + } as MiUserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0])); const users = (await this.userListJoiningsRepository.findBy({ userListId: ps.listId, diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts index 425b05a3adbf..3a07a86ecc2d 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -4,9 +4,9 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserListsRepository } from '@/models/index.js'; +import type { UserListsRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; -import type { UserList } from '@/models/entities/UserList.js'; +import type { MiUserList } from '@/models/UserList.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -47,9 +47,8 @@ export const paramDef = { required: ['name'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, @@ -71,7 +70,7 @@ export default class extends Endpoint { createdAt: new Date(), userId: me.id, name: ps.name, - } as UserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0])); + } as MiUserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0])); return await this.userListEntityService.pack(userList); }); diff --git a/packages/backend/src/server/api/endpoints/users/lists/delete.ts b/packages/backend/src/server/api/endpoints/users/lists/delete.ts index adfd396d06f7..763f5afd9df3 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/delete.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserListsRepository } from '@/models/index.js'; +import type { UserListsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -35,9 +35,8 @@ export const paramDef = { required: ['listId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, diff --git a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts index 0c4a96560512..1707afee608a 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UserListFavoritesRepository, UserListsRepository } from '@/models/index.js'; +import type { UserListFavoritesRepository, UserListsRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { ApiError } from '@/server/api/error.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/list.ts b/packages/backend/src/server/api/endpoints/users/lists/list.ts index db481fc2e4f1..0e86dd3a6835 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/list.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserListsRepository, UsersRepository } from '@/models/index.js'; +import type { UserListsRepository, UsersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; import { ApiError } from '@/server/api/error.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts index 94f2d0a526a0..19694fe4217f 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; +import type { UserListsRepository, UserListJoiningsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { GetterService } from '@/server/api/GetterService.js'; @@ -47,9 +47,8 @@ export const paramDef = { required: ['listId', 'userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index da51f2328112..eae2eaccaa41 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; -import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/index.js'; +import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/GetterService.js'; import { UserListService } from '@/core/UserListService.js'; @@ -70,9 +70,8 @@ export const paramDef = { required: ['listId', 'userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index cb86e25d92ad..df44870b041e 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserListsRepository, UserListFavoritesRepository } from '@/models/index.js'; +import type { UserListsRepository, UserListFavoritesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts b/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts index 280a062855a9..23611ab8c4a2 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UserListFavoritesRepository, UserListsRepository } from '@/models/index.js'; +import type { UserListFavoritesRepository, UserListsRepository } from '@/models/_.js'; import { ApiError } from '@/server/api/error.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/api/endpoints/users/lists/update.ts b/packages/backend/src/server/api/endpoints/users/lists/update.ts index cbc18c8da8fb..eb6cfbaf26d4 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/update.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserListsRepository } from '@/models/index.js'; +import type { UserListsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -44,9 +44,8 @@ export const paramDef = { required: ['listId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index 1bd849284ae4..5934baef47e8 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -5,7 +5,7 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository } from '@/models/index.js'; +import type { NotesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -57,9 +57,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -81,9 +80,15 @@ export default class extends Endpoint { .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('note.channel', 'channel') .leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('renote.user', 'renoteUser'); + query.andWhere(new Brackets(qb => { + qb.orWhere('note.channelId IS NULL'); + qb.orWhere('channel.isSensitive = false'); + })); + this.queryService.generateVisibilityQuery(query, me); if (me) { this.queryService.generateMutedUserQuery(query, me, user); diff --git a/packages/backend/src/server/api/endpoints/users/pages.ts b/packages/backend/src/server/api/endpoints/users/pages.ts index 48c2b1230345..cf2f274c70d1 100644 --- a/packages/backend/src/server/api/endpoints/users/pages.ts +++ b/packages/backend/src/server/api/endpoints/users/pages.ts @@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; -import type { PagesRepository } from '@/models/index.js'; +import type { PagesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; export const meta = { @@ -37,9 +37,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.pagesRepository) private pagesRepository: PagesRepository, diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index d65cdcfdb94c..372ab80c4c64 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserProfilesRepository, NoteReactionsRepository } from '@/models/index.js'; +import type { UserProfilesRepository, NoteReactionsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteReactionEntityService } from '@/core/entities/NoteReactionEntityService.js'; @@ -50,9 +50,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index 1988d9795d77..1b30e99b15d4 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -5,7 +5,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository, FollowingsRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -40,9 +40,8 @@ export const paramDef = { required: [], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts index ff4722ebc54e..326042ed3d34 100644 --- a/packages/backend/src/server/api/endpoints/users/relation.ts +++ b/packages/backend/src/server/api/endpoints/users/relation.ts @@ -125,9 +125,8 @@ export const paramDef = { required: ['userId'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( private userEntityService: UserEntityService, ) { diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index 03bcc14f761e..50aa6fa09ed7 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -5,7 +5,7 @@ import sanitizeHtml from 'sanitize-html'; import { Inject, Injectable } from '@nestjs/common'; -import type { AbuseUserReportsRepository } from '@/models/index.js'; +import type { AbuseUserReportsRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; @@ -53,9 +53,8 @@ export const paramDef = { required: ['userId', 'comment'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.abuseUserReportsRepository) private abuseUserReportsRepository: AbuseUserReportsRepository, diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts index f627f2409ef6..74408cc64aab 100644 --- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts +++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts @@ -5,9 +5,9 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository, FollowingsRepository } from '@/models/index.js'; +import type { UsersRepository, FollowingsRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; -import type { User } from '@/models/entities/User.js'; +import type { MiUser } from '@/models/User.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -46,9 +46,8 @@ export const paramDef = { ], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.config) private config: Config, @@ -82,7 +81,7 @@ export default class extends Endpoint { const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日 - let users: User[] = []; + let users: MiUser[] = []; if (me) { const followingQuery = this.followingsRepository.createQueryBuilder('following') diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index 1ed812e67336..aff5b98779b8 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -5,8 +5,8 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository, UserProfilesRepository } from '@/models/index.js'; -import type { User } from '@/models/entities/User.js'; +import type { UsersRepository, UserProfilesRepository } from '@/models/_.js'; +import type { MiUser } from '@/models/User.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -42,9 +42,8 @@ export const paramDef = { required: ['query'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -60,7 +59,7 @@ export default class extends Endpoint { ps.query = ps.query.trim(); const isUsername = ps.query.startsWith('@'); - let users: User[] = []; + let users: MiUser[] = []; if (isUsername) { const usernameQuery = this.usersRepository.createQueryBuilder('user') diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 8909e478b3c9..389497301d39 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -5,8 +5,8 @@ import { In, IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository } from '@/models/index.js'; -import type { User } from '@/models/entities/User.js'; +import type { UsersRepository } from '@/models/_.js'; +import type { MiUser } from '@/models/User.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; @@ -79,9 +79,8 @@ export const paramDef = { ], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -111,7 +110,7 @@ export default class extends Endpoint { }); // リクエストされた通りに並べ替え - const _users: User[] = []; + const _users: MiUser[] = []; for (const id of ps.userIds) { _users.push(users.find(x => x.id === id)!); } @@ -127,7 +126,7 @@ export default class extends Endpoint { throw new ApiError(meta.errors.failedToResolveRemoteUser); }); } else { - const q: FindOptionsWhere = ps.userId != null + const q: FindOptionsWhere = ps.userId != null ? { id: ps.userId } : { usernameLower: ps.username!.toLowerCase(), host: IsNull() }; diff --git a/packages/backend/src/server/api/endpoints/users/update-memo.ts b/packages/backend/src/server/api/endpoints/users/update-memo.ts index a100c3aa9c62..194d4880524e 100644 --- a/packages/backend/src/server/api/endpoints/users/update-memo.ts +++ b/packages/backend/src/server/api/endpoints/users/update-memo.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; -import type { UserMemoRepository } from '@/models/index.js'; +import type { UserMemoRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { ApiError } from '../../error.js'; @@ -40,9 +40,8 @@ export const paramDef = { required: ['userId', 'memo'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() -export default class extends Endpoint { +export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.userMemosRepository) private userMemosRepository: UserMemoRepository, diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/Connection.ts similarity index 94% rename from packages/backend/src/server/api/stream/index.ts rename to packages/backend/src/server/api/stream/Connection.ts index 62eaf1abc150..fd91681fc197 100644 --- a/packages/backend/src/server/api/stream/index.ts +++ b/packages/backend/src/server/api/stream/Connection.ts @@ -4,14 +4,14 @@ */ import * as WebSocket from 'ws'; -import type { User } from '@/models/entities/User.js'; -import type { AccessToken } from '@/models/entities/AccessToken.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiAccessToken } from '@/models/AccessToken.js'; import type { Packed } from '@/misc/json-schema.js'; import type { NoteReadService } from '@/core/NoteReadService.js'; import type { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; import { CacheService } from '@/core/CacheService.js'; -import { UserProfile } from '@/models/index.js'; +import { MiUserProfile } from '@/models/_.js'; import type { ChannelsService } from './ChannelsService.js'; import type { EventEmitter } from 'events'; import type Channel from './channel.js'; @@ -20,21 +20,22 @@ import type { StreamEventEmitter, StreamMessages } from './types.js'; /** * Main stream connection */ +// eslint-disable-next-line import/no-default-export export default class Connection { - public user?: User; - public token?: AccessToken; + public user?: MiUser; + public token?: MiAccessToken; private wsConnection: WebSocket.WebSocket; public subscriber: StreamEventEmitter; private channels: Channel[] = []; private subscribingNotes: any = {}; private cachedNotes: Packed<'Note'>[] = []; - public userProfile: UserProfile | null = null; + public userProfile: MiUserProfile | null = null; public following: Set = new Set(); public followingChannels: Set = new Set(); public userIdsWhoMeMuting: Set = new Set(); public userIdsWhoBlockingMe: Set = new Set(); public userIdsWhoMeMutingRenotes: Set = new Set(); - private fetchIntervalId: NodeJS.Timer | null = null; + private fetchIntervalId: NodeJS.Timeout | null = null; constructor( private channelsService: ChannelsService, @@ -42,8 +43,8 @@ export default class Connection { private notificationService: NotificationService, private cacheService: CacheService, - user: User | null | undefined, - token: AccessToken | null | undefined, + user: MiUser | null | undefined, + token: MiAccessToken | null | undefined, ) { if (user) this.user = user; if (token) this.token = token; diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index d31891687d0b..ad32d08feecf 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -4,11 +4,12 @@ */ import { bindThis } from '@/decorators.js'; -import type Connection from './index.js'; +import type Connection from './Connection.js'; /** * Stream channel */ +// eslint-disable-next-line import/no-default-export export default abstract class Channel { protected connection: Connection; public id: string; diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index 06b62e97c73e..8bbba0b6dbb9 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -4,8 +4,8 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserListJoiningsRepository, UserListsRepository } from '@/models/index.js'; -import type { User } from '@/models/entities/User.js'; +import type { UserListJoiningsRepository, UserListsRepository } from '@/models/_.js'; +import type { MiUser } from '@/models/User.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import type { Packed } from '@/misc/json-schema.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -18,8 +18,8 @@ class UserListChannel extends Channel { public static shouldShare = false; public static requireCredential = false; private listId: string; - public listUsers: User['id'][] = []; - private listUsersClock: NodeJS.Timer; + public listUsers: MiUser['id'][] = []; + private listUsersClock: NodeJS.Timeout; constructor( private userListsRepository: UserListsRepository, diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index 751a23de8d53..90e0a61f26b1 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -3,51 +3,51 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { Channel } from '@/models/entities/Channel.js'; -import type { User } from '@/models/entities/User.js'; -import type { UserProfile } from '@/models/entities/UserProfile.js'; -import type { Note } from '@/models/entities/Note.js'; -import type { Antenna } from '@/models/entities/Antenna.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; -import type { DriveFolder } from '@/models/entities/DriveFolder.js'; -import type { UserList } from '@/models/entities/UserList.js'; -import type { AbuseUserReport } from '@/models/entities/AbuseUserReport.js'; -import type { Signin } from '@/models/entities/Signin.js'; -import type { Page } from '@/models/entities/Page.js'; +import type { MiChannel } from '@/models/Channel.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiUserProfile } from '@/models/UserProfile.js'; +import type { MiNote } from '@/models/Note.js'; +import type { MiAntenna } from '@/models/Antenna.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; +import type { MiDriveFolder } from '@/models/DriveFolder.js'; +import type { MiUserList } from '@/models/UserList.js'; +import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js'; +import type { MiSignin } from '@/models/Signin.js'; +import type { MiPage } from '@/models/Page.js'; import type { Packed } from '@/misc/json-schema.js'; -import type { Webhook } from '@/models/entities/Webhook.js'; -import type { Meta } from '@/models/entities/Meta.js'; -import { Role, RoleAssignment } from '@/models/index.js'; +import type { MiWebhook } from '@/models/Webhook.js'; +import type { MiMeta } from '@/models/Meta.js'; +import { MiRole, MiRoleAssignment } from '@/models/_.js'; import type Emitter from 'strict-event-emitter-types'; import type { EventEmitter } from 'events'; //#region Stream type-body definitions export interface InternalStreamTypes { - userChangeSuspendedState: { id: User['id']; isSuspended: User['isSuspended']; }; - userTokenRegenerated: { id: User['id']; oldToken: string; newToken: string; }; - remoteUserUpdated: { id: User['id']; }; - follow: { followerId: User['id']; followeeId: User['id']; }; - unfollow: { followerId: User['id']; followeeId: User['id']; }; - blockingCreated: { blockerId: User['id']; blockeeId: User['id']; }; - blockingDeleted: { blockerId: User['id']; blockeeId: User['id']; }; - policiesUpdated: Role['policies']; - roleCreated: Role; - roleDeleted: Role; - roleUpdated: Role; - userRoleAssigned: RoleAssignment; - userRoleUnassigned: RoleAssignment; - webhookCreated: Webhook; - webhookDeleted: Webhook; - webhookUpdated: Webhook; - antennaCreated: Antenna; - antennaDeleted: Antenna; - antennaUpdated: Antenna; - metaUpdated: Meta; - followChannel: { userId: User['id']; channelId: Channel['id']; }; - unfollowChannel: { userId: User['id']; channelId: Channel['id']; }; - updateUserProfile: UserProfile; - mute: { muterId: User['id']; muteeId: User['id']; }; - unmute: { muterId: User['id']; muteeId: User['id']; }; + userChangeSuspendedState: { id: MiUser['id']; isSuspended: MiUser['isSuspended']; }; + userTokenRegenerated: { id: MiUser['id']; oldToken: string; newToken: string; }; + remoteUserUpdated: { id: MiUser['id']; }; + follow: { followerId: MiUser['id']; followeeId: MiUser['id']; }; + unfollow: { followerId: MiUser['id']; followeeId: MiUser['id']; }; + blockingCreated: { blockerId: MiUser['id']; blockeeId: MiUser['id']; }; + blockingDeleted: { blockerId: MiUser['id']; blockeeId: MiUser['id']; }; + policiesUpdated: MiRole['policies']; + roleCreated: MiRole; + roleDeleted: MiRole; + roleUpdated: MiRole; + userRoleAssigned: MiRoleAssignment; + userRoleUnassigned: MiRoleAssignment; + webhookCreated: MiWebhook; + webhookDeleted: MiWebhook; + webhookUpdated: MiWebhook; + antennaCreated: MiAntenna; + antennaDeleted: MiAntenna; + antennaUpdated: MiAntenna; + metaUpdated: MiMeta; + followChannel: { userId: MiUser['id']; channelId: MiChannel['id']; }; + unfollowChannel: { userId: MiUser['id']; channelId: MiChannel['id']; }; + updateUserProfile: MiUserProfile; + mute: { muterId: MiUser['id']; muteeId: MiUser['id']; }; + unmute: { muterId: MiUser['id']; muteeId: MiUser['id']; }; } export interface BroadcastTypes { @@ -79,10 +79,10 @@ export interface MainStreamTypes { unfollow: Packed<'User'>; meUpdated: Packed<'User'>; pageEvent: { - pageId: Page['id']; + pageId: MiPage['id']; event: string; var: any; - userId: User['id']; + userId: MiUser['id']; user: Packed<'User'>; }; urlUploadFinished: { @@ -91,22 +91,22 @@ export interface MainStreamTypes { }; readAllNotifications: undefined; unreadNotification: Packed<'Notification'>; - unreadMention: Note['id']; + unreadMention: MiNote['id']; readAllUnreadMentions: undefined; - unreadSpecifiedNote: Note['id']; + unreadSpecifiedNote: MiNote['id']; readAllUnreadSpecifiedNotes: undefined; readAllAntennas: undefined; - unreadAntenna: Antenna; + unreadAntenna: MiAntenna; readAllAnnouncements: undefined; myTokenRegenerated: undefined; - signin: Signin; + signin: MiSignin; registryUpdated: { scope?: string[]; key: string; value: any | null; }; driveFileCreated: Packed<'DriveFile'>; - readAntenna: Antenna; + readAntenna: MiAntenna; receiveFollowRequest: Packed<'User'>; announcementCreated: { announcement: Packed<'Announcement'>; @@ -115,17 +115,17 @@ export interface MainStreamTypes { export interface DriveStreamTypes { fileCreated: Packed<'DriveFile'>; - fileDeleted: DriveFile['id']; + fileDeleted: MiDriveFile['id']; fileUpdated: Packed<'DriveFile'>; folderCreated: Packed<'DriveFolder'>; - folderDeleted: DriveFolder['id']; + folderDeleted: MiDriveFolder['id']; folderUpdated: Packed<'DriveFolder'>; } export interface NoteStreamTypes { pollVoted: { choice: number; - userId: User['id']; + userId: MiUser['id']; }; deleted: { deletedAt: Date; @@ -136,16 +136,16 @@ export interface NoteStreamTypes { name: string; url: string; } | null; - userId: User['id']; + userId: MiUser['id']; }; unreacted: { reaction: string; - userId: User['id']; + userId: MiUser['id']; }; } type NoteStreamEventTypes = { [key in keyof NoteStreamTypes]: { - id: Note['id']; + id: MiNote['id']; body: NoteStreamTypes[key]; }; }; @@ -156,7 +156,7 @@ export interface UserListStreamTypes { } export interface AntennaStreamTypes { - note: Note; + note: MiNote; } export interface RoleTimelineStreamTypes { @@ -165,9 +165,9 @@ export interface RoleTimelineStreamTypes { export interface AdminStreamTypes { newAbuseUserReport: { - id: AbuseUserReport['id']; - targetUserId: User['id'], - reporterId: User['id'], + id: MiAbuseUserReport['id']; + targetUserId: MiUser['id'], + reporterId: MiUser['id'], comment: string; }; } @@ -209,31 +209,31 @@ export type StreamMessages = { payload: EventUnionFromDictionary>; }; main: { - name: `mainStream:${User['id']}`; + name: `mainStream:${MiUser['id']}`; payload: EventUnionFromDictionary>; }; drive: { - name: `driveStream:${User['id']}`; + name: `driveStream:${MiUser['id']}`; payload: EventUnionFromDictionary>; }; note: { - name: `noteStream:${Note['id']}`; + name: `noteStream:${MiNote['id']}`; payload: EventUnionFromDictionary>; }; userList: { - name: `userListStream:${UserList['id']}`; + name: `userListStream:${MiUserList['id']}`; payload: EventUnionFromDictionary>; }; roleTimeline: { - name: `roleTimelineStream:${Role['id']}`; + name: `roleTimelineStream:${MiRole['id']}`; payload: EventUnionFromDictionary>; }; antenna: { - name: `antennaStream:${Antenna['id']}`; + name: `antennaStream:${MiAntenna['id']}`; payload: EventUnionFromDictionary>; }; admin: { - name: `adminStream:${User['id']}`; + name: `adminStream:${MiUser['id']}`; payload: EventUnionFromDictionary>; }; notes: { diff --git a/packages/backend/src/server/oauth/OAuth2ProviderService.ts b/packages/backend/src/server/oauth/OAuth2ProviderService.ts index 61c89a425875..c3a78561c295 100644 --- a/packages/backend/src/server/oauth/OAuth2ProviderService.ts +++ b/packages/backend/src/server/oauth/OAuth2ProviderService.ts @@ -23,10 +23,10 @@ import { kinds } from '@/misc/api-permissions.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; -import type { AccessTokensRepository, UsersRepository } from '@/models/index.js'; +import type { AccessTokensRepository, UsersRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { CacheService } from '@/core/CacheService.js'; -import type { LocalUser } from '@/models/entities/User.js'; +import type { MiLocalUser } from '@/models/User.js'; import { MemoryKVCache } from '@/misc/cache.js'; import { LoggerService } from '@/core/LoggerService.js'; import Logger from '@/logger.js'; @@ -125,7 +125,7 @@ async function discoverClientInformation(logger: Logger, httpRequestService: Htt let name = id; if (text) { const microformats = mf2(text, { baseUrl: res.url }); - const nameProperty = microformats.items.find(item => item.type?.includes('h-app') && item.properties.url?.includes(id))?.properties.name[0]; + const nameProperty = microformats.items.find(item => item.type?.includes('h-app') && item.properties.url.includes(id))?.properties.name[0]; if (typeof nameProperty === 'string') { name = nameProperty; } @@ -271,7 +271,7 @@ export class OAuth2ProviderService { throw new AuthorizationError('No user', 'invalid_request'); } const user = await this.cacheService.localUserByNativeTokenCache.fetch(token, - () => this.usersRepository.findOneBy({ token }) as Promise); + () => this.usersRepository.findOneBy({ token }) as Promise); if (!user) { throw new AuthorizationError('No such user', 'invalid_request'); } diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index ebc1b72761aa..7b1dd92d7332 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -31,13 +31,12 @@ import { PageEntityService } from '@/core/entities/PageEntityService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; -import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, Meta, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, MiMeta, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { deepClone } from '@/misc/clone.js'; import { bindThis } from '@/decorators.js'; import { FlashEntityService } from '@/core/entities/FlashEntityService.js'; import { RoleService } from '@/core/RoleService.js'; -import manifest from './manifest.json' assert { type: 'json' }; import { FeedService } from './FeedService.js'; import { UrlPreviewService } from './UrlPreviewService.js'; import { ClientLoggerService } from './ClientLoggerService.js'; @@ -110,23 +109,68 @@ export class ClientServerService { @bindThis private async manifestHandler(reply: FastifyReply) { - const res = deepClone(manifest); - const instance = await this.metaService.fetch(true); - res.short_name = instance.name ?? 'Misskey'; - res.name = instance.name ?? 'Misskey'; - if (instance.themeColor) res.theme_color = instance.themeColor; + let manifest = { + // 空文字列の場合右辺を使いたいため + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + 'short_name': instance.name || 'Misskey', + // 空文字列の場合右辺を使いたいため + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + 'name': instance.name || 'Misskey', + 'start_url': '/', + 'display': 'standalone', + 'background_color': '#313a42', + // 空文字列の場合右辺を使いたいため + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + 'theme_color': instance.themeColor || '#86b300', + 'icons': [{ + // 空文字列の場合右辺を使いたいため + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + 'src': instance.app192IconUrl || '/static-assets/icons/192.png', + 'sizes': '192x192', + 'type': 'image/png', + 'purpose': 'maskable', + }, { + // 空文字列の場合右辺を使いたいため + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + 'src': instance.app512IconUrl || '/static-assets/icons/512.png', + 'sizes': '512x512', + 'type': 'image/png', + 'purpose': 'maskable', + }, { + 'src': '/static-assets/splash.png', + 'sizes': '300x300', + 'type': 'image/png', + 'purpose': 'any', + }], + 'share_target': { + 'action': '/share/', + 'method': 'GET', + 'enctype': 'application/x-www-form-urlencoded', + 'params': { + 'title': 'title', + 'text': 'text', + 'url': 'url', + }, + }, + }; + + manifest = { + ...manifest, + ...JSON.parse(instance.manifestJsonOverride === '' ? '{}' : instance.manifestJsonOverride), + }; reply.header('Cache-Control', 'max-age=300'); - return (res); + return (manifest); } @bindThis - private generateCommonPugData(meta: Meta) { + private generateCommonPugData(meta: MiMeta) { return { instanceName: meta.name ?? 'Misskey', icon: meta.iconUrl, + appleTouchIcon: meta.app512IconUrl, themeColor: meta.themeColor, serverErrorImageUrl: meta.serverErrorImageUrl ?? 'https://xn--931a.moe/assets/error.jpg', infoImageUrl: meta.infoImageUrl ?? 'https://xn--931a.moe/assets/info.jpg', @@ -143,21 +187,23 @@ export class ClientServerService { // Authenticate fastify.addHook('onRequest', async (request, reply) => { - if (request.url === bullBoardPath || request.url.startsWith(bullBoardPath + '/')) { + // %71ueueとかでリクエストされたら困るため + const url = decodeURI(request.url); + if (url === bullBoardPath || url.startsWith(bullBoardPath + '/')) { const token = request.cookies.token; if (token == null) { - reply.code(401); - throw new Error('login required'); + reply.code(401).send('Login required'); + return; } const user = await this.usersRepository.findOneBy({ token }); if (user == null) { - reply.code(403); - throw new Error('no such user'); + reply.code(403).send('No such user'); + return; } const isAdministrator = await this.roleService.isAdministrator(user); if (!isAdministrator) { - reply.code(403); - throw new Error('access denied'); + reply.code(403).send('Access denied'); + return; } } }); diff --git a/packages/backend/src/server/web/FeedService.ts b/packages/backend/src/server/web/FeedService.ts index b92892e66be6..78551e800bf5 100644 --- a/packages/backend/src/server/web/FeedService.ts +++ b/packages/backend/src/server/web/FeedService.ts @@ -7,9 +7,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, IsNull } from 'typeorm'; import { Feed } from 'feed'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository, NotesRepository, UserProfilesRepository } from '@/models/index.js'; +import type { DriveFilesRepository, NotesRepository, UserProfilesRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; -import type { User } from '@/models/entities/User.js'; +import type { MiUser } from '@/models/User.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { bindThis } from '@/decorators.js'; @@ -35,7 +35,7 @@ export class FeedService { } @bindThis - public async packFeed(user: User) { + public async packFeed(user: MiUser) { const author = { link: `${this.config.url}/@${user.username}`, name: user.name ?? user.username, diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index 2b61c6bc2f20..ab74a1f7f48c 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -7,15 +7,15 @@ doctype html // - - _____ _ _ - | |_|___ ___| |_ ___ _ _ + _____ _ _ + | |_|___ ___| |_ ___ _ _ | | | | |_ -|_ -| '_| -_| | | |_|_|_|_|___|___|_,_|___|_ | |___| Thank you for using Misskey! If you are reading this message... how about joining the development? https://github.com/misskey-dev/misskey - + html @@ -28,14 +28,14 @@ html meta(property='og:site_name' content= instanceName || 'Misskey') meta(name='viewport' content='width=device-width, initial-scale=1') link(rel='icon' href= icon || '/favicon.ico') - link(rel='apple-touch-icon' href= icon || '/apple-touch-icon.png') + link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png') link(rel='manifest' href='/manifest.json') link(rel='search' type='application/opensearchdescription+xml' title=(title || "Misskey") href=`${url}/opensearch.xml`) link(rel='prefetch' href=serverErrorImageUrl) link(rel='prefetch' href=infoImageUrl) link(rel='prefetch' href=notFoundImageUrl) //- https://github.com/misskey-dev/misskey/issues/9842 - link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.25.0') + link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.32.0') link(rel='modulepreload' href=`/vite/${clientEntry.file}`) if !config.clientManifestExists diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index a3a8e77cd05d..0a28d88d08e7 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -3,7 +3,22 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app'] as const; +/** + * note - 通知オンにしているユーザーが投稿した + * follow - フォローされた + * mention - 投稿で自分が言及された + * reply - 投稿に返信された + * renote - 投稿がRenoteされた + * quote - 投稿が引用Renoteされた + * reaction - 投稿にリアクションされた + * pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した + * receiveFollowRequest - フォローリクエストされた + * followRequestAccepted - 自分の送ったフォローリクエストが承認された + * achievementEarned - 実績を獲得 + * app - アプリ通知 + * test - テスト通知(サーバー側) + */ +export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app', 'test'] as const; export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const; export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const; diff --git a/packages/backend/test/e2e/2fa.ts b/packages/backend/test/e2e/2fa.ts index 387249871ebf..ed967d2620da 100644 --- a/packages/backend/test/e2e/2fa.ts +++ b/packages/backend/test/e2e/2fa.ts @@ -9,8 +9,16 @@ import * as assert from 'assert'; import * as crypto from 'node:crypto'; import cbor from 'cbor'; import * as OTPAuth from 'otpauth'; -import { loadConfig } from '../../src/config.js'; -import { signup, api, post, react, startServer, waitFire } from '../utils.js'; +import { loadConfig } from '@/config.js'; +import { api, signup, startServer } from '../utils.js'; +import type { + AuthenticationResponseJSON, + AuthenticatorAssertionResponseJSON, + AuthenticatorAttestationResponseJSON, + PublicKeyCredentialCreationOptionsJSON, + PublicKeyCredentialRequestOptionsJSON, + RegistrationResponseJSON, +} from '@simplewebauthn/typescript-types'; import type { INestApplicationContext } from '@nestjs/common'; import type * as misskey from 'misskey-js'; @@ -47,21 +55,20 @@ describe('2要素認証', () => { const rpIdHash = (): Buffer => { return crypto.createHash('sha256') - .update(Buffer.from(config.hostname, 'utf-8')) + .update(Buffer.from(config.host, 'utf-8')) .digest(); }; const keyDoneParam = (param: { + token: string, keyName: string, - challengeId: string, - challenge: string, credentialId: Buffer, + creationOptions: PublicKeyCredentialCreationOptionsJSON, }): { - attestationObject: string, - challengeId: string, - clientDataJSON: string, + token: string, password: string, name: string, + credential: RegistrationResponseJSON, } => { // A COSE encoded public key const credentialPublicKey = cbor.encode(new Map([ @@ -76,7 +83,7 @@ describe('2要素認証', () => { // AuthenticatorAssertionResponse.authenticatorData // https://developer.mozilla.org/en-US/docs/Web/API/AuthenticatorAssertionResponse/authenticatorData const credentialIdLength = Buffer.allocUnsafe(2); - credentialIdLength.writeUInt16BE(param.credentialId.length); + credentialIdLength.writeUInt16BE(param.credentialId.length, 0); const authData = Buffer.concat([ rpIdHash(), // rpIdHash(32) Buffer.from([0x45]), // flags(1) @@ -88,20 +95,28 @@ describe('2要素認証', () => { ]); return { - attestationObject: cbor.encode({ - fmt: 'none', - attStmt: {}, - authData, - }).toString('hex'), - challengeId: param.challengeId, - clientDataJSON: JSON.stringify({ - type: 'webauthn.create', - challenge: param.challenge, - origin: config.scheme + '://' + config.host, - androidPackageName: 'org.mozilla.firefox', - }), password, + token: param.token, name: param.keyName, + credential: { + id: param.credentialId.toString('base64url'), + rawId: param.credentialId.toString('base64url'), + response: { + clientDataJSON: Buffer.from(JSON.stringify({ + type: 'webauthn.create', + challenge: param.creationOptions.challenge, + origin: config.scheme + '://' + config.host, + androidPackageName: 'org.mozilla.firefox', + }), 'utf-8').toString('base64url'), + attestationObject: cbor.encode({ + fmt: 'none', + attStmt: {}, + authData, + }).toString('base64url'), + }, + clientExtensionResults: {}, + type: 'public-key', + }, }; }; @@ -121,17 +136,12 @@ describe('2要素認証', () => { const signinWithSecurityKeyParam = (param: { keyName: string, - challengeId: string, - challenge: string, credentialId: Buffer, + requestOptions: PublicKeyCredentialRequestOptionsJSON, }): { - authenticatorData: string, - credentialId: string, - challengeId: string, - clientDataJSON: string, username: string, password: string, - signature: string, + credential: AuthenticationResponseJSON, 'g-recaptcha-response'?: string | null, 'hcaptcha-response'?: string | null, } => { @@ -144,10 +154,10 @@ describe('2要素認証', () => { ]); const clientDataJSONBuffer = Buffer.from(JSON.stringify({ type: 'webauthn.get', - challenge: param.challenge, + challenge: param.requestOptions.challenge, origin: config.scheme + '://' + config.host, androidPackageName: 'org.mozilla.firefox', - })); + }), 'utf-8'); const hashedclientDataJSON = crypto.createHash('sha256') .update(clientDataJSONBuffer) .digest(); @@ -156,13 +166,19 @@ describe('2要素認証', () => { .update(Buffer.concat([authenticatorData, hashedclientDataJSON])) .sign(privateKey); return { - authenticatorData: authenticatorData.toString('hex'), - credentialId: param.credentialId.toString('base64'), - challengeId: param.challengeId, - clientDataJSON: clientDataJSONBuffer.toString('hex'), username, password, - signature: signature.toString('hex'), + credential: { + id: param.credentialId.toString('base64url'), + rawId: param.credentialId.toString('base64url'), + response: { + clientDataJSON: clientDataJSONBuffer.toString('base64url'), + authenticatorData: authenticatorData.toString('base64url'), + signature: signature.toString('base64url'), + }, + clientExtensionResults: {}, + type: 'public-key', + }, 'g-recaptcha-response': null, 'hcaptcha-response': null, }; @@ -191,7 +207,7 @@ describe('2要素認証', () => { const doneResponse = await api('/i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); - assert.strictEqual(doneResponse.status, 204); + assert.strictEqual(doneResponse.status, 200); const usersShowResponse = await api('/users/show', { username, @@ -205,6 +221,12 @@ describe('2要素認証', () => { }); assert.strictEqual(signinResponse.status, 200); assert.notEqual(signinResponse.body.i, undefined); + + // 後片付け + await api('/i/2fa/unregister', { + password, + token: otpToken(registerResponse.body.secret), + }, alice); }); test('が設定でき、セキュリティキーでログインできる。', async () => { @@ -216,25 +238,26 @@ describe('2要素認証', () => { const doneResponse = await api('/i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); - assert.strictEqual(doneResponse.status, 204); + assert.strictEqual(doneResponse.status, 200); const registerKeyResponse = await api('/i/2fa/register-key', { password, + token: otpToken(registerResponse.body.secret), }, alice); assert.strictEqual(registerKeyResponse.status, 200); - assert.notEqual(registerKeyResponse.body.challengeId, undefined); + assert.notEqual(registerKeyResponse.body.rp, undefined); assert.notEqual(registerKeyResponse.body.challenge, undefined); const keyName = 'example-key'; const credentialId = crypto.randomBytes(0x41); const keyDoneResponse = await api('/i/2fa/key-done', keyDoneParam({ + token: otpToken(registerResponse.body.secret), keyName, - challengeId: registerKeyResponse.body.challengeId, - challenge: registerKeyResponse.body.challenge, credentialId, + creationOptions: registerKeyResponse.body, }), alice); assert.strictEqual(keyDoneResponse.status, 200); - assert.strictEqual(keyDoneResponse.body.id, credentialId.toString('hex')); + assert.strictEqual(keyDoneResponse.body.id, credentialId.toString('base64url')); assert.strictEqual(keyDoneResponse.body.name, keyName); const usersShowResponse = await api('/users/show', { @@ -248,19 +271,23 @@ describe('2要素認証', () => { }); assert.strictEqual(signinResponse.status, 200); assert.strictEqual(signinResponse.body.i, undefined); - assert.notEqual(signinResponse.body.challengeId, undefined); assert.notEqual(signinResponse.body.challenge, undefined); - assert.notEqual(signinResponse.body.securityKeys, undefined); - assert.strictEqual(signinResponse.body.securityKeys[0].id, credentialId.toString('hex')); + assert.notEqual(signinResponse.body.allowCredentials, undefined); + assert.strictEqual(signinResponse.body.allowCredentials[0].id, credentialId.toString('base64url')); const signinResponse2 = await api('/signin', signinWithSecurityKeyParam({ keyName, - challengeId: signinResponse.body.challengeId, - challenge: signinResponse.body.challenge, credentialId, + requestOptions: signinResponse.body, })); assert.strictEqual(signinResponse2.status, 200); assert.notEqual(signinResponse2.body.i, undefined); + + // 後片付け + await api('/i/2fa/unregister', { + password, + token: otpToken(registerResponse.body.secret), + }, alice); }); test('が設定でき、セキュリティキーでパスワードレスログインできる。', async () => { @@ -272,9 +299,10 @@ describe('2要素認証', () => { const doneResponse = await api('/i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); - assert.strictEqual(doneResponse.status, 204); + assert.strictEqual(doneResponse.status, 200); const registerKeyResponse = await api('/i/2fa/register-key', { + token: otpToken(registerResponse.body.secret), password, }, alice); assert.strictEqual(registerKeyResponse.status, 200); @@ -282,10 +310,10 @@ describe('2要素認証', () => { const keyName = 'example-key'; const credentialId = crypto.randomBytes(0x41); const keyDoneResponse = await api('/i/2fa/key-done', keyDoneParam({ + token: otpToken(registerResponse.body.secret), keyName, - challengeId: registerKeyResponse.body.challengeId, - challenge: registerKeyResponse.body.challenge, credentialId, + creationOptions: registerKeyResponse.body, }), alice); assert.strictEqual(keyDoneResponse.status, 200); @@ -310,14 +338,19 @@ describe('2要素認証', () => { const signinResponse2 = await api('/signin', { ...signinWithSecurityKeyParam({ keyName, - challengeId: signinResponse.body.challengeId, - challenge: signinResponse.body.challenge, credentialId, + requestOptions: signinResponse.body, }), password: '', }); assert.strictEqual(signinResponse2.status, 200); assert.notEqual(signinResponse2.body.i, undefined); + + // 後片付け + await api('/i/2fa/unregister', { + password, + token: otpToken(registerResponse.body.secret), + }, alice); }); test('が設定でき、設定したセキュリティキーの名前を変更できる。', async () => { @@ -329,9 +362,10 @@ describe('2要素認証', () => { const doneResponse = await api('/i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); - assert.strictEqual(doneResponse.status, 204); + assert.strictEqual(doneResponse.status, 200); const registerKeyResponse = await api('/i/2fa/register-key', { + token: otpToken(registerResponse.body.secret), password, }, alice); assert.strictEqual(registerKeyResponse.status, 200); @@ -339,27 +373,33 @@ describe('2要素認証', () => { const keyName = 'example-key'; const credentialId = crypto.randomBytes(0x41); const keyDoneResponse = await api('/i/2fa/key-done', keyDoneParam({ + token: otpToken(registerResponse.body.secret), keyName, - challengeId: registerKeyResponse.body.challengeId, - challenge: registerKeyResponse.body.challenge, credentialId, + creationOptions: registerKeyResponse.body, }), alice); assert.strictEqual(keyDoneResponse.status, 200); const renamedKey = 'other-key'; const updateKeyResponse = await api('/i/2fa/update-key', { name: renamedKey, - credentialId: credentialId.toString('hex'), + credentialId: credentialId.toString('base64url'), }, alice); assert.strictEqual(updateKeyResponse.status, 200); const iResponse = await api('/i', { }, alice); assert.strictEqual(iResponse.status, 200); - const securityKeys = iResponse.body.securityKeysList.filter(s => s.id === credentialId.toString('hex')); + const securityKeys = iResponse.body.securityKeysList.filter((s: { id: string; }) => s.id === credentialId.toString('base64url')); assert.strictEqual(securityKeys.length, 1); assert.strictEqual(securityKeys[0].name, renamedKey); assert.notEqual(securityKeys[0].lastUsed, undefined); + + // 後片付け + await api('/i/2fa/unregister', { + password, + token: otpToken(registerResponse.body.secret), + }, alice); }); test('が設定でき、設定したセキュリティキーを削除できる。', async () => { @@ -371,9 +411,10 @@ describe('2要素認証', () => { const doneResponse = await api('/i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); - assert.strictEqual(doneResponse.status, 204); + assert.strictEqual(doneResponse.status, 200); const registerKeyResponse = await api('/i/2fa/register-key', { + token: otpToken(registerResponse.body.secret), password, }, alice); assert.strictEqual(registerKeyResponse.status, 200); @@ -381,10 +422,10 @@ describe('2要素認証', () => { const keyName = 'example-key'; const credentialId = crypto.randomBytes(0x41); const keyDoneResponse = await api('/i/2fa/key-done', keyDoneParam({ + token: otpToken(registerResponse.body.secret), keyName, - challengeId: registerKeyResponse.body.challengeId, - challenge: registerKeyResponse.body.challenge, credentialId, + creationOptions: registerKeyResponse.body, }), alice); assert.strictEqual(keyDoneResponse.status, 200); @@ -394,6 +435,7 @@ describe('2要素認証', () => { assert.strictEqual(iResponse.status, 200); for (const key of iResponse.body.securityKeysList) { const removeKeyResponse = await api('/i/2fa/remove-key', { + token: otpToken(registerResponse.body.secret), password, credentialId: key.id, }, alice); @@ -412,6 +454,12 @@ describe('2要素認証', () => { }); assert.strictEqual(signinResponse.status, 200); assert.notEqual(signinResponse.body.i, undefined); + + // 後片付け + await api('/i/2fa/unregister', { + password, + token: otpToken(registerResponse.body.secret), + }, alice); }); test('が設定でき、設定解除できる。(パスワードのみでログインできる。)', async () => { @@ -423,7 +471,7 @@ describe('2要素認証', () => { const doneResponse = await api('/i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); - assert.strictEqual(doneResponse.status, 204); + assert.strictEqual(doneResponse.status, 200); const usersShowResponse = await api('/users/show', { username, @@ -432,6 +480,7 @@ describe('2要素認証', () => { assert.strictEqual(usersShowResponse.body.twoFactorEnabled, true); const unregisterResponse = await api('/i/2fa/unregister', { + token: otpToken(registerResponse.body.secret), password, }, alice); assert.strictEqual(unregisterResponse.status, 204); @@ -441,5 +490,11 @@ describe('2要素認証', () => { }); assert.strictEqual(signinResponse.status, 200); assert.notEqual(signinResponse.body.i, undefined); + + // 後片付け + await api('/i/2fa/unregister', { + password, + token: otpToken(registerResponse.body.secret), + }, alice); }); }); diff --git a/packages/backend/test/e2e/clips.ts b/packages/backend/test/e2e/clips.ts index 833b48f73160..dfdc044caacb 100644 --- a/packages/backend/test/e2e/clips.ts +++ b/packages/backend/test/e2e/clips.ts @@ -721,7 +721,7 @@ describe('クリップ', () => { await addNote({ clipId: aliceClip.id, noteId: aliceNote.id }); const res = await show({ clipId: aliceClip.id }); assert.strictEqual(res.lastClippedAt, new Date(res.lastClippedAt ?? '').toISOString()); - assert.deepStrictEqual(await notes({ clipId: aliceClip.id }), [aliceNote]); + assert.deepStrictEqual((await notes({ clipId: aliceClip.id })).map(x => x.id), [aliceNote.id]); // 他人の非公開ノートも突っ込める await addNote({ clipId: aliceClip.id, noteId: bobHomeNote.id }); @@ -861,8 +861,8 @@ describe('クリップ', () => { bobNote, bobHomeNote, ]; assert.deepStrictEqual( - res.sort(compareBy(s => s.id)), - expects.sort(compareBy(s => s.id))); + res.sort(compareBy(s => s.id)).map(x => x.id), + expects.sort(compareBy(s => s.id)).map(x => x.id)); }); test('を始端IDとlimitで取得できる。', async () => { @@ -881,8 +881,8 @@ describe('クリップ', () => { // Promise.allで返ってくる配列はID順で並んでないのでソートして厳密比較 const expects = [noteList[3], noteList[4], noteList[5]]; assert.deepStrictEqual( - res.sort(compareBy(s => s.id)), - expects.sort(compareBy(s => s.id))); + res.sort(compareBy(s => s.id)).map(x => x.id), + expects.sort(compareBy(s => s.id)).map(x => x.id)); }); test('をID範囲指定で取得できる。', async () => { @@ -901,8 +901,8 @@ describe('クリップ', () => { // Promise.allで返ってくる配列はID順で並んでないのでソートして厳密比較 const expects = [noteList[2], noteList[3]]; assert.deepStrictEqual( - res.sort(compareBy(s => s.id)), - expects.sort(compareBy(s => s.id))); + res.sort(compareBy(s => s.id)).map(x => x.id), + expects.sort(compareBy(s => s.id)).map(x => x.id)); }); test.todo('Remoteのノートもクリップできる。どうテストしよう?'); @@ -911,7 +911,7 @@ describe('クリップ', () => { const bobClip = await create({ isPublic: true }, { user: bob } ); await addNote({ clipId: bobClip.id, noteId: aliceNote.id }, { user: bob }); const res = await notes({ clipId: bobClip.id }); - assert.deepStrictEqual(res, [aliceNote]); + assert.deepStrictEqual(res.map(x => x.id), [aliceNote.id]); }); test('はPublicなクリップなら認証なしでも取得できる。(非公開ノートはhideされて返ってくる)', async () => { @@ -928,8 +928,8 @@ describe('クリップ', () => { hiddenNote(aliceFollowersNote), hiddenNote(aliceSpecifiedNote), ]; assert.deepStrictEqual( - res.sort(compareBy(s => s.id)), - expects.sort(compareBy(s => s.id))); + res.sort(compareBy(s => s.id)).map(x => x.id), + expects.sort(compareBy(s => s.id)).map(x => x.id)); }); test.todo('ブロック、ミュートされたユーザーからの設定&取得etc.'); diff --git a/packages/backend/test/e2e/endpoints.ts b/packages/backend/test/e2e/endpoints.ts index 55fbf9ad47fd..2ef3434bcab1 100644 --- a/packages/backend/test/e2e/endpoints.ts +++ b/packages/backend/test/e2e/endpoints.ts @@ -9,7 +9,7 @@ import * as assert from 'assert'; // node-fetch only supports it's own Blob yet // https://github.com/node-fetch/node-fetch/pull/1664 import { Blob } from 'node-fetch'; -import { User } from '@/models/index.js'; +import { MiUser } from '@/models/_.js'; import { startServer, signup, post, api, uploadFile, simpleGet, initTestDb } from '../utils.js'; import type { INestApplicationContext } from '@nestjs/common'; import type * as misskey from 'misskey-js'; @@ -298,7 +298,7 @@ describe('Endpoints', () => { assert.strictEqual(res.status, 200); const connection = await initTestDb(true); - const Users = connection.getRepository(User); + const Users = connection.getRepository(MiUser); const newBob = await Users.findOneByOrFail({ id: bob.id }); assert.strictEqual(newBob.followersCount, 0); assert.strictEqual(newBob.followingCount, 1); @@ -360,7 +360,7 @@ describe('Endpoints', () => { assert.strictEqual(res.status, 200); const connection = await initTestDb(true); - const Users = connection.getRepository(User); + const Users = connection.getRepository(MiUser); const newBob = await Users.findOneByOrFail({ id: bob.id }); assert.strictEqual(newBob.followersCount, 0); assert.strictEqual(newBob.followingCount, 0); diff --git a/packages/backend/test/e2e/fetch-resource.ts b/packages/backend/test/e2e/fetch-resource.ts index 96683ce5947e..1cbfec3e5f7f 100644 --- a/packages/backend/test/e2e/fetch-resource.ts +++ b/packages/backend/test/e2e/fetch-resource.ts @@ -34,6 +34,8 @@ describe('Webリソース', () => { let aliceGalleryPost: any; let aliceChannel: any; + let bob: misskey.entities.MeSignup; + type Request = { path: string, accept?: string, @@ -90,6 +92,8 @@ describe('Webリソース', () => { fileIds: [aliceUploadedFile.body.id], }); aliceChannel = await channel(alice, {}); + + bob = await signup({ username: 'alice' }); }, 1000 * 60 * 2); afterAll(async () => { @@ -163,9 +167,15 @@ describe('Webリソース', () => { }); describe.each([{ path: '/queue' }])('$path', ({ path }) => { + test('はログインしないとGETできない。', async () => await notOk({ + path, + status: 401, + })); + test('はadminでなければGETできない。', async () => await notOk({ path, - status: 500, // FIXME? 403ではない。 + cookie: cookie(bob), + status: 403, })); test('はadminならGETできる。', async () => await ok({ diff --git a/packages/backend/test/e2e/move.ts b/packages/backend/test/e2e/move.ts index faee6087da97..3f158f9f1330 100644 --- a/packages/backend/test/e2e/move.ts +++ b/packages/backend/test/e2e/move.ts @@ -7,7 +7,7 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import { loadConfig } from '@/config.js'; -import { User, UsersRepository } from '@/models/index.js'; +import { MiUser, UsersRepository } from '@/models/_.js'; import { jobQueue } from '@/boot/common.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; import { uploadFile, signup, startServer, initTestDb, api, sleep, successfulApiCall } from '../utils.js'; @@ -42,7 +42,7 @@ describe('Account Move', () => { dave = await signup({ username: 'dave' }); eve = await signup({ username: 'eve' }); frank = await signup({ username: 'frank' }); - Users = connection.getRepository(User); + Users = connection.getRepository(MiUser); }, 1000 * 60 * 2); afterAll(async () => { diff --git a/packages/backend/test/e2e/note.ts b/packages/backend/test/e2e/note.ts index 292abe5cffc2..961df99cc27f 100644 --- a/packages/backend/test/e2e/note.ts +++ b/packages/backend/test/e2e/note.ts @@ -6,7 +6,7 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { Note } from '@/models/entities/Note.js'; +import { MiNote } from '@/models/Note.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { signup, post, uploadUrl, startServer, initTestDb, api, uploadFile } from '../utils.js'; import type { INestApplicationContext } from '@nestjs/common'; @@ -22,7 +22,7 @@ describe('Note', () => { beforeAll(async () => { app = await startServer(); const connection = await initTestDb(true); - Notes = connection.getRepository(Note); + Notes = connection.getRepository(MiNote); alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); }, 1000 * 60 * 2); diff --git a/packages/backend/test/e2e/streaming.ts b/packages/backend/test/e2e/streaming.ts index 046629979a0b..77de1448828d 100644 --- a/packages/backend/test/e2e/streaming.ts +++ b/packages/backend/test/e2e/streaming.ts @@ -6,7 +6,7 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { Following } from '@/models/entities/Following.js'; +import { MiFollowing } from '@/models/Following.js'; import { connectStream, signup, api, post, startServer, initTestDb, waitFire } from '../utils.js'; import type { INestApplicationContext } from '@nestjs/common'; import type * as misskey from 'misskey-js'; @@ -46,7 +46,7 @@ describe('Streaming', () => { beforeAll(async () => { app = await startServer(); const connection = await initTestDb(true); - Followings = connection.getRepository(Following); + Followings = connection.getRepository(MiFollowing); ayano = await signup({ username: 'ayano' }); kyoko = await signup({ username: 'kyoko' }); diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts index 8afbcbe322e7..0b3f2002607c 100644 --- a/packages/backend/test/e2e/users.ts +++ b/packages/backend/test/e2e/users.ts @@ -102,6 +102,7 @@ describe('ユーザー', () => { birthday: user.birthday, lang: user.lang, fields: user.fields, + verifiedLinks: user.verifiedLinks, followersCount: user.followersCount, followingCount: user.followingCount, notesCount: user.notesCount, @@ -131,6 +132,7 @@ describe('ユーザー', () => { isBlocked: user.isBlocked ?? false, isMuted: user.isMuted ?? false, isRenoteMuted: user.isRenoteMuted ?? false, + notify: user.notify ?? 'none', }); }; @@ -152,6 +154,7 @@ describe('ユーザー', () => { preventAiLearning: user.preventAiLearning, isExplorable: user.isExplorable, isDeleted: user.isDeleted, + twoFactorBackupCodesStock: user.twoFactorBackupCodesStock, hideOnlineStatus: user.hideOnlineStatus, hasUnreadSpecifiedNotes: user.hasUnreadSpecifiedNotes, hasUnreadMentions: user.hasUnreadMentions, @@ -368,6 +371,7 @@ describe('ユーザー', () => { assert.strictEqual(response.birthday, null); assert.strictEqual(response.lang, null); assert.deepStrictEqual(response.fields, []); + assert.deepStrictEqual(response.verifiedLinks, []); assert.strictEqual(response.followersCount, 0); assert.strictEqual(response.followingCount, 0); assert.strictEqual(response.notesCount, 0); @@ -398,6 +402,7 @@ describe('ユーザー', () => { assert.strictEqual(response.preventAiLearning, true); assert.strictEqual(response.isExplorable, true); assert.strictEqual(response.isDeleted, false); + assert.strictEqual(response.twoFactorBackupCodesStock, 'none'); assert.strictEqual(response.hideOnlineStatus, false); assert.strictEqual(response.hasUnreadSpecifiedNotes, false); assert.strictEqual(response.hasUnreadMentions, false); @@ -490,7 +495,7 @@ describe('ユーザー', () => { { parameters: (): object => ({ mutedWords: [] }) }, { parameters: (): object => ({ mutedInstances: ['xxxx.xxxxx'] }) }, { parameters: (): object => ({ mutedInstances: [] }) }, - { parameters: (): object => ({ mutingNotificationTypes: ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app'] }) }, + { parameters: (): object => ({ mutingNotificationTypes: ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app'] }) }, { parameters: (): object => ({ mutingNotificationTypes: [] }) }, { parameters: (): object => ({ emailNotificationTypes: ['mention', 'reply', 'quote', 'follow', 'receiveFollowRequest'] }) }, { parameters: (): object => ({ emailNotificationTypes: [] }) }, diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts index 257a9c859f2d..07abe515c312 100644 --- a/packages/backend/test/misc/mock-resolver.ts +++ b/packages/backend/test/misc/mock-resolver.ts @@ -15,7 +15,7 @@ import type { LoggerService } from '@/core/LoggerService.js'; import type { MetaService } from '@/core/MetaService.js'; import type { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; -import type { NoteReactionsRepository, NotesRepository, PollsRepository, UsersRepository } from '@/models/index.js'; +import type { NoteReactionsRepository, NotesRepository, PollsRepository, UsersRepository } from '@/models/_.js'; type MockResponse = { type: string; diff --git a/packages/backend/test/tsconfig.json b/packages/backend/test/tsconfig.json index 21afe1aaf3fe..4597ff87803e 100644 --- a/packages/backend/test/tsconfig.json +++ b/packages/backend/test/tsconfig.json @@ -10,8 +10,8 @@ "declaration": false, "sourceMap": true, "target": "ES2022", - "module": "es2020", - "moduleResolution": "node16", + "module": "nodenext", + "moduleResolution": "nodenext", "allowSyntheticDefaultImports": true, "removeComments": false, "noLib": false, diff --git a/packages/backend/test/unit/AnnouncementService.ts b/packages/backend/test/unit/AnnouncementService.ts index c97e081ba5ab..721fbb734507 100644 --- a/packages/backend/test/unit/AnnouncementService.ts +++ b/packages/backend/test/unit/AnnouncementService.ts @@ -10,9 +10,9 @@ import { ModuleMocker } from 'jest-mock'; import { Test } from '@nestjs/testing'; import { GlobalModule } from '@/GlobalModule.js'; import { AnnouncementService } from '@/core/AnnouncementService.js'; -import type { Announcement, AnnouncementsRepository, AnnouncementReadsRepository, UsersRepository, User } from '@/models/index.js'; +import type { MiAnnouncement, AnnouncementsRepository, AnnouncementReadsRepository, UsersRepository, MiUser } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; -import { genAid } from '@/misc/id/aid.js'; +import { genAidx } from '@/misc/id/aidx.js'; import { CacheService } from '@/core/CacheService.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; @@ -30,10 +30,10 @@ describe('AnnouncementService', () => { let announcementReadsRepository: AnnouncementReadsRepository; let globalEventService: jest.Mocked; - function createUser(data: Partial = {}) { + function createUser(data: Partial = {}) { const un = secureRndstr(16); return usersRepository.insert({ - id: genAid(new Date()), + id: genAidx(new Date()), createdAt: new Date(), username: un, usernameLower: un, @@ -42,9 +42,9 @@ describe('AnnouncementService', () => { .then(x => usersRepository.findOneByOrFail(x.identifiers[0])); } - function createAnnouncement(data: Partial = {}) { + function createAnnouncement(data: Partial = {}) { return announcementsRepository.insert({ - id: genAid(new Date()), + id: genAidx(new Date()), createdAt: new Date(), updatedAt: null, title: 'Title', diff --git a/packages/backend/test/unit/FetchInstanceMetadataService.ts b/packages/backend/test/unit/FetchInstanceMetadataService.ts index 22ce023216fd..34200899d4c8 100644 --- a/packages/backend/test/unit/FetchInstanceMetadataService.ts +++ b/packages/backend/test/unit/FetchInstanceMetadataService.ts @@ -6,7 +6,6 @@ process.env.NODE_ENV = 'test'; import { jest } from '@jest/globals'; -import { ModuleMocker } from 'jest-mock'; import { Test } from '@nestjs/testing'; import { Redis } from 'ioredis'; import { GlobalModule } from '@/GlobalModule.js'; @@ -18,7 +17,6 @@ import { UtilityService } from '@/core/UtilityService.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; import type { TestingModule } from '@nestjs/testing'; -import type { MockFunctionMetadata } from 'jest-mock'; function mockRedis() { const hash = {}; @@ -35,9 +33,9 @@ describe('FetchInstanceMetadataService', () => { let fetchInstanceMetadataService: jest.Mocked; let federatedInstanceService: jest.Mocked; let httpRequestService: jest.Mocked; - let redisClient: jest.Mocked; + let redisClient: jest.Mocked; - beforeAll(async () => { + beforeEach(async () => { app = await Test .createTestingModule({ imports: [ @@ -64,11 +62,11 @@ describe('FetchInstanceMetadataService', () => { fetchInstanceMetadataService = app.get(FetchInstanceMetadataService); federatedInstanceService = app.get(FederatedInstanceService) as jest.Mocked; - redisClient = app.get(DI.redis) as jest.Mocked; + redisClient = app.get(DI.redis) as jest.Mocked; httpRequestService = app.get(HttpRequestService) as jest.Mocked; }); - afterAll(async () => { + afterEach(async () => { await app.close(); }); @@ -85,6 +83,7 @@ describe('FetchInstanceMetadataService', () => { expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(1); expect(httpRequestService.getJson).toHaveBeenCalled(); }); + test('Lock and don\'t update', async () => { redisClient.set = mockRedis(); const now = Date.now(); @@ -98,6 +97,7 @@ describe('FetchInstanceMetadataService', () => { expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(1); expect(httpRequestService.getJson).toHaveBeenCalledTimes(0); }); + test('Do nothing when lock not acquired', async () => { redisClient.set = mockRedis(); federatedInstanceService.fetch.mockReturnValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } }); diff --git a/packages/backend/test/unit/MetaService.ts b/packages/backend/test/unit/MetaService.ts index 2b6e52914e81..ab30f482831e 100644 --- a/packages/backend/test/unit/MetaService.ts +++ b/packages/backend/test/unit/MetaService.ts @@ -9,7 +9,7 @@ import { jest } from '@jest/globals'; import { ModuleMocker } from 'jest-mock'; import { Test } from '@nestjs/testing'; import { GlobalModule } from '@/GlobalModule.js'; -import type { MetasRepository } from '@/models/index.js'; +import type { MetasRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { MetaService } from '@/core/MetaService.js'; import { CoreModule } from '@/core/CoreModule.js'; diff --git a/packages/backend/test/unit/RelayService.ts b/packages/backend/test/unit/RelayService.ts index ae20acd06070..f780a25388cc 100644 --- a/packages/backend/test/unit/RelayService.ts +++ b/packages/backend/test/unit/RelayService.ts @@ -15,7 +15,7 @@ import { CreateSystemUserService } from '@/core/CreateSystemUserService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { QueueService } from '@/core/QueueService.js'; import { IdService } from '@/core/IdService.js'; -import type { RelaysRepository } from '@/models/index.js'; +import type { RelaysRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import type { TestingModule } from '@nestjs/testing'; import type { MockFunctionMetadata } from 'jest-mock'; diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts index fc6e23382bec..c6a14702ae50 100644 --- a/packages/backend/test/unit/RoleService.ts +++ b/packages/backend/test/unit/RoleService.ts @@ -11,10 +11,10 @@ import { Test } from '@nestjs/testing'; import * as lolex from '@sinonjs/fake-timers'; import { GlobalModule } from '@/GlobalModule.js'; import { RoleService } from '@/core/RoleService.js'; -import type { Role, RolesRepository, RoleAssignmentsRepository, UsersRepository, User } from '@/models/index.js'; +import type { MiRole, RolesRepository, RoleAssignmentsRepository, UsersRepository, MiUser } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { MetaService } from '@/core/MetaService.js'; -import { genAid } from '@/misc/id/aid.js'; +import { genAidx } from '@/misc/id/aidx.js'; import { CacheService } from '@/core/CacheService.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; @@ -34,10 +34,10 @@ describe('RoleService', () => { let metaService: jest.Mocked; let clock: lolex.InstalledClock; - function createUser(data: Partial = {}) { + function createUser(data: Partial = {}) { const un = secureRndstr(16); return usersRepository.insert({ - id: genAid(new Date()), + id: genAidx(new Date()), createdAt: new Date(), username: un, usernameLower: un, @@ -46,9 +46,9 @@ describe('RoleService', () => { .then(x => usersRepository.findOneByOrFail(x.identifiers[0])); } - function createRole(data: Partial = {}) { + function createRole(data: Partial = {}) { return rolesRepository.insert({ - id: genAid(new Date()), + id: genAidx(new Date()), createdAt: new Date(), updatedAt: new Date(), lastUsedAt: new Date(), @@ -204,7 +204,7 @@ describe('RoleService', () => { createdAt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 365)), followersCount: 10, }); - const role = await createRole({ + await createRole({ name: 'a', policies: { canManageCustomEmojis: { diff --git a/packages/backend/test/unit/S3Service.ts b/packages/backend/test/unit/S3Service.ts index eec84943228f..c1eafc96b752 100644 --- a/packages/backend/test/unit/S3Service.ts +++ b/packages/backend/test/unit/S3Service.ts @@ -10,8 +10,8 @@ import { UploadPartCommand, CompleteMultipartUploadCommand, CreateMultipartUploa import { mockClient } from 'aws-sdk-client-mock'; import { GlobalModule } from '@/GlobalModule.js'; import { CoreModule } from '@/core/CoreModule.js'; -import { S3Service } from '@/core/S3Service'; -import { Meta } from '@/models'; +import { S3Service } from '@/core/S3Service.js'; +import { MiMeta } from '@/models/_.js'; import type { TestingModule } from '@nestjs/testing'; describe('S3Service', () => { @@ -40,7 +40,7 @@ describe('S3Service', () => { test('upload a file', async () => { s3Mock.on(PutObjectCommand).resolves({}); - await s3Service.upload({ objectStorageRegion: 'us-east-1' } as Meta, { + await s3Service.upload({ objectStorageRegion: 'us-east-1' } as MiMeta, { Bucket: 'fake', Key: 'fake', Body: 'x', @@ -52,7 +52,7 @@ describe('S3Service', () => { s3Mock.on(UploadPartCommand).resolves({ ETag: '1' }); s3Mock.on(CompleteMultipartUploadCommand).resolves({ Bucket: 'fake', Key: 'fake' }); - await s3Service.upload({} as Meta, { + await s3Service.upload({} as MiMeta, { Bucket: 'fake', Key: 'fake', Body: 'x'.repeat(8 * 1024 * 1024 + 1), // デフォルトpartSizeにしている 8 * 1024 * 1024 を越えるサイズ @@ -62,7 +62,7 @@ describe('S3Service', () => { test('upload a file error', async () => { s3Mock.on(PutObjectCommand).rejects({ name: 'Fake Error' }); - await expect(s3Service.upload({ objectStorageRegion: 'us-east-1' } as Meta, { + await expect(s3Service.upload({ objectStorageRegion: 'us-east-1' } as MiMeta, { Bucket: 'fake', Key: 'fake', Body: 'x', @@ -72,7 +72,7 @@ describe('S3Service', () => { test('upload a large file error', async () => { s3Mock.on(UploadPartCommand).rejects(); - await expect(s3Service.upload({} as Meta, { + await expect(s3Service.upload({} as MiMeta, { Bucket: 'fake', Key: 'fake', Body: 'x'.repeat(8 * 1024 * 1024 + 1), // デフォルトpartSizeにしている 8 * 1024 * 1024 を越えるサイズ diff --git a/packages/backend/test/unit/activitypub.ts b/packages/backend/test/unit/activitypub.ts index 73209523b54c..dbc446d12de0 100644 --- a/packages/backend/test/unit/activitypub.ts +++ b/packages/backend/test/unit/activitypub.ts @@ -18,11 +18,11 @@ import { CoreModule } from '@/core/CoreModule.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { LoggerService } from '@/core/LoggerService.js'; import type { IActor, IApDocument, ICollection, IPost } from '@/core/activitypub/type.js'; -import { Meta, Note } from '@/models/index.js'; +import { MiMeta, MiNote } from '@/models/_.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; import { DownloadService } from '@/core/DownloadService.js'; import { MetaService } from '@/core/MetaService.js'; -import type { RemoteUser } from '@/models/entities/User.js'; +import type { MiRemoteUser } from '@/models/User.js'; import { MockResolver } from '../misc/mock-resolver.js'; const host = 'https://host1.test'; @@ -75,7 +75,7 @@ function createRandomFeaturedCollection(actor: NonTransientIActor, length: numbe async function createRandomRemoteUser( resolver: MockResolver, personService: ApPersonService, -): Promise { +): Promise { const actor = createRandomActor(); resolver.register(actor.id, actor); @@ -94,7 +94,7 @@ describe('ActivityPub', () => { cacheRemoteSensitiveFiles: true, blockedHosts: [] as string[], sensitiveWords: [] as string[], - } as Meta; + } as MiMeta; let meta = metaInitial; beforeAll(async () => { @@ -109,7 +109,7 @@ describe('ActivityPub', () => { }, }) .overrideProvider(MetaService).useValue({ - async fetch(): Promise { + async fetch(): Promise { return meta; }, }).compile(); @@ -199,7 +199,7 @@ describe('ActivityPub', () => { rendererService.renderAnnounce(null, { createdAt: new Date(0), visibility: 'followers', - } as Note); + } as MiNote); }); }); diff --git a/packages/backend/test/unit/chart.ts b/packages/backend/test/unit/chart.ts index a419083e7de6..036e73fd5e46 100644 --- a/packages/backend/test/unit/chart.ts +++ b/packages/backend/test/unit/chart.ts @@ -18,7 +18,7 @@ import { entity as TestGroupedChartEntity } from '@/core/chart/charts/entities/t import { entity as TestUniqueChartEntity } from '@/core/chart/charts/entities/test-unique.js'; import { entity as TestIntersectionChartEntity } from '@/core/chart/charts/entities/test-intersection.js'; import { loadConfig } from '@/config.js'; -import type { AppLockService } from '@/core/AppLockService'; +import type { AppLockService } from '@/core/AppLockService.js'; import Logger from '@/logger.js'; describe('Chart', () => { diff --git a/packages/backend/test/unit/misc/correct-filename.ts b/packages/backend/test/unit/misc/correct-filename.ts index 8138b2361fa2..0c4482e0bf72 100644 --- a/packages/backend/test/unit/misc/correct-filename.ts +++ b/packages/backend/test/unit/misc/correct-filename.ts @@ -6,42 +6,42 @@ import { correctFilename } from '@/misc/correct-filename.js'; describe(correctFilename, () => { - it('no ext to null', () => { - expect(correctFilename('test', null)).toBe('test.unknown'); - }); - it('no ext to jpg', () => { - expect(correctFilename('test', 'jpg')).toBe('test.jpg'); - }); - it('jpg to webp', () => { - expect(correctFilename('test.jpg', 'webp')).toBe('test.jpg.webp'); - }); - it('jpg to .webp', () => { - expect(correctFilename('test.jpg', '.webp')).toBe('test.jpg.webp'); - }); - it('jpeg to jpg', () => { - expect(correctFilename('test.jpeg', 'jpg')).toBe('test.jpeg'); - }); - it('JPEG to jpg', () => { - expect(correctFilename('test.JPEG', 'jpg')).toBe('test.JPEG'); - }); - it('jpg to jpg', () => { - expect(correctFilename('test.jpg', 'jpg')).toBe('test.jpg'); - }); - it('JPG to jpg', () => { - expect(correctFilename('test.JPG', 'jpg')).toBe('test.JPG'); - }); - it('tiff to tif', () => { - expect(correctFilename('test.tiff', 'tif')).toBe('test.tiff'); - }); - it('skip gz', () => { - expect(correctFilename('test.unitypackage', 'gz')).toBe('test.unitypackage'); - }); - it('skip text file', () => { - expect(correctFilename('test.txt', null)).toBe('test.txt'); - }); - it('unknown', () => { - expect(correctFilename('test.hoge', null)).toBe('test.hoge'); - }); + it('no ext to null', () => { + expect(correctFilename('test', null)).toBe('test.unknown'); + }); + it('no ext to jpg', () => { + expect(correctFilename('test', 'jpg')).toBe('test.jpg'); + }); + it('jpg to webp', () => { + expect(correctFilename('test.jpg', 'webp')).toBe('test.jpg.webp'); + }); + it('jpg to .webp', () => { + expect(correctFilename('test.jpg', '.webp')).toBe('test.jpg.webp'); + }); + it('jpeg to jpg', () => { + expect(correctFilename('test.jpeg', 'jpg')).toBe('test.jpeg'); + }); + it('JPEG to jpg', () => { + expect(correctFilename('test.JPEG', 'jpg')).toBe('test.JPEG'); + }); + it('jpg to jpg', () => { + expect(correctFilename('test.jpg', 'jpg')).toBe('test.jpg'); + }); + it('JPG to jpg', () => { + expect(correctFilename('test.JPG', 'jpg')).toBe('test.JPG'); + }); + it('tiff to tif', () => { + expect(correctFilename('test.tiff', 'tif')).toBe('test.tiff'); + }); + it('skip gz', () => { + expect(correctFilename('test.unitypackage', 'gz')).toBe('test.unitypackage'); + }); + it('skip text file', () => { + expect(correctFilename('test.txt', null)).toBe('test.txt'); + }); + it('unknown', () => { + expect(correctFilename('test.hoge', null)).toBe('test.hoge'); + }); test('non ascii with space', () => { expect(correctFilename('ファイル 名前', 'jpg')).toBe('ファイル 名前.jpg'); }); diff --git a/packages/backend/test/unit/misc/id.ts b/packages/backend/test/unit/misc/id.ts index 8cc2ea425f02..57b4ea99474e 100644 --- a/packages/backend/test/unit/misc/id.ts +++ b/packages/backend/test/unit/misc/id.ts @@ -6,6 +6,7 @@ import { ulid } from 'ulid'; import { describe, test, expect } from '@jest/globals'; import { aidRegExp, genAid, parseAid } from '@/misc/id/aid.js'; +import { aidxRegExp, genAidx, parseAidx } from '@/misc/id/aidx.js'; import { genMeid, meidRegExp, parseMeid } from '@/misc/id/meid.js'; import { genMeidg, meidgRegExp, parseMeidg } from '@/misc/id/meidg.js'; import { genObjectId, objectIdRegExp, parseObjectId } from '@/misc/id/object-id.js'; @@ -19,6 +20,13 @@ describe('misc:id', () => { expect(parseAid(gotAid).date.getTime()).toBe(date.getTime()); }); + test('aidx', () => { + const date = new Date(); + const gotAidx = genAidx(date); + expect(gotAidx).toMatch(aidxRegExp); + expect(parseAidx(gotAidx).date.getTime()).toBe(date.getTime()); + }); + test('meid', () => { const date = new Date(); const gotMeid = genMeid(date); diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index 0a24a4706653..adc532bbe7bc 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -8,7 +8,7 @@ import { readFile } from 'node:fs/promises'; import { isAbsolute, basename } from 'node:path'; import { inspect } from 'node:util'; import WebSocket, { ClientOptions } from 'ws'; -import fetch, { Blob, File, RequestInit } from 'node-fetch'; +import fetch, { File, RequestInit } from 'node-fetch'; import { DataSource } from 'typeorm'; import { JSDOM } from 'jsdom'; import { DEFAULT_POLICIES } from '@/core/RoleService.js'; diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json index 93944a68d52d..2b15a5cc7a3f 100644 --- a/packages/backend/tsconfig.json +++ b/packages/backend/tsconfig.json @@ -10,8 +10,8 @@ "declaration": false, "sourceMap": false, "target": "ES2022", - "module": "ESNext", - "moduleResolution": "node16", + "module": "nodenext", + "moduleResolution": "nodenext", "allowSyntheticDefaultImports": true, "removeComments": false, "noLib": false, @@ -33,8 +33,9 @@ "node" ], "typeRoots": [ + "./src/@types", "./node_modules/@types", - "./src/@types" + "./node_modules" ], "lib": [ "esnext" diff --git a/packages/frontend/.eslintrc.js b/packages/frontend/.eslintrc.cjs similarity index 100% rename from packages/frontend/.eslintrc.js rename to packages/frontend/.eslintrc.cjs diff --git a/packages/frontend/.storybook/changes.ts b/packages/frontend/.storybook/changes.ts index a1275132be37..0cc648fbae2c 100644 --- a/packages/frontend/.storybook/changes.ts +++ b/packages/frontend/.storybook/changes.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + import fs from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; import path from 'node:path'; diff --git a/packages/frontend/.storybook/fakes.ts b/packages/frontend/.storybook/fakes.ts index a4289cff7da7..811c243926c9 100644 --- a/packages/frontend/.storybook/fakes.ts +++ b/packages/frontend/.storybook/fakes.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + import type { entities } from 'misskey-js' export function abuseUserReport() { @@ -84,6 +89,7 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host = 'mi value: 'https://misskey-hub.net', }, ], + verifiedLinks: [], followersCount: 1024, followingCount: 16, hasPendingFollowRequestFromYou: false, @@ -110,9 +116,11 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host = 'mi publicReactions: false, securityKeys: false, twoFactorEnabled: false, + twoFactorBackupCodesStock: 'none', updatedAt: null, uri: null, url: null, + notify: 'none', }; } diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx index d47d8672c758..d61df9e7bede 100644 --- a/packages/frontend/.storybook/generate.tsx +++ b/packages/frontend/.storybook/generate.tsx @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + import { existsSync, readFileSync } from 'node:fs'; import { writeFile } from 'node:fs/promises'; import { basename, dirname } from 'node:path/posix'; diff --git a/packages/frontend/.storybook/main.ts b/packages/frontend/.storybook/main.ts index b64979980ada..a450f8b46be9 100644 --- a/packages/frontend/.storybook/main.ts +++ b/packages/frontend/.storybook/main.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + import { resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import type { StorybookConfig } from '@storybook/vue3-vite'; diff --git a/packages/frontend/.storybook/manager.ts b/packages/frontend/.storybook/manager.ts index 5653deee84cf..8f501111d0a9 100644 --- a/packages/frontend/.storybook/manager.ts +++ b/packages/frontend/.storybook/manager.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + import { addons } from '@storybook/manager-api'; import { create } from '@storybook/theming/create'; diff --git a/packages/frontend/.storybook/mocks.ts b/packages/frontend/.storybook/mocks.ts index 4091e39686fb..b60755feea2d 100644 --- a/packages/frontend/.storybook/mocks.ts +++ b/packages/frontend/.storybook/mocks.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + import { type SharedOptions, rest } from 'msw'; export const onUnhandledRequest = ((req, print) => { diff --git a/packages/frontend/.storybook/preload-locale.ts b/packages/frontend/.storybook/preload-locale.ts index 2b7362b88d72..349cc135080a 100644 --- a/packages/frontend/.storybook/preload-locale.ts +++ b/packages/frontend/.storybook/preload-locale.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + import { writeFile } from 'node:fs/promises'; import locales from '../../../locales/index.js'; diff --git a/packages/frontend/.storybook/preload-theme.ts b/packages/frontend/.storybook/preload-theme.ts index 42fbeff738ed..ad2cf18a353c 100644 --- a/packages/frontend/.storybook/preload-theme.ts +++ b/packages/frontend/.storybook/preload-theme.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + import { readFile, writeFile } from 'node:fs/promises'; import JSON5 from 'json5'; diff --git a/packages/frontend/.storybook/preview-head.html b/packages/frontend/.storybook/preview-head.html index f6a9a4875de3..ed38e496477a 100644 --- a/packages/frontend/.storybook/preview-head.html +++ b/packages/frontend/.storybook/preview-head.html @@ -1,6 +1,6 @@ - + diff --git a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts index e3638593019e..8232759ba045 100644 --- a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts +++ b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts @@ -13,7 +13,7 @@ import { userDetailed } from '../../.storybook/fakes'; import { commonHandlers } from '../../.storybook/mocks'; import MkAutocomplete from './MkAutocomplete.vue'; import MkInput from './MkInput.vue'; -import { tick } from '@/scripts/test-utils'; +import { tick } from '@/scripts/test-utils.js'; const common = { render(args) { return { diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index 0b9f93c6eeac..7c4f9105594b 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -41,16 +41,16 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue index d28b37121d51..14e59acad20d 100644 --- a/packages/frontend/src/components/MkCaptcha.vue +++ b/packages/frontend/src/components/MkCaptcha.vue @@ -12,8 +12,8 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkFoldableSection.vue b/packages/frontend/src/components/MkFoldableSection.vue index da58586e2463..ed3cb0868b06 100644 --- a/packages/frontend/src/components/MkFoldableSection.vue +++ b/packages/frontend/src/components/MkFoldableSection.vue @@ -30,8 +30,8 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 2a00b8db8ed3..0bcf98cca26a 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -11,7 +11,12 @@ SPDX-License-Identifier: AGPL-3.0-only v-hotkey="keymap" :class="$style.root" > - +
+
+ {{ i18n.ts.loadConversation }} +
+ +
@@ -125,7 +130,43 @@ SPDX-License-Identifier: AGPL-3.0-only - +
+ + + +
+
+
+
+ {{ i18n.ts.loadReplies }} +
+ +
+
+ + + +
+
+
+ +
+ + + +
+
@@ -141,7 +182,7 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkNotePreview.vue b/packages/frontend/src/components/MkNotePreview.vue index 3cc84283c01d..fc6ea89085b2 100644 --- a/packages/frontend/src/components/MkNotePreview.vue +++ b/packages/frontend/src/components/MkNotePreview.vue @@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only + + diff --git a/packages/frontend/src/components/MkReactionEffect.vue b/packages/frontend/src/components/MkReactionEffect.vue index f373d179e87b..88e262d88006 100644 --- a/packages/frontend/src/components/MkReactionEffect.vue +++ b/packages/frontend/src/components/MkReactionEffect.vue @@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only - - diff --git a/packages/frontend/src/components/MkRetentionHeatmap.vue b/packages/frontend/src/components/MkRetentionHeatmap.vue index 737ddff6adc5..3dc9a94ae2de 100644 --- a/packages/frontend/src/components/MkRetentionHeatmap.vue +++ b/packages/frontend/src/components/MkRetentionHeatmap.vue @@ -15,11 +15,11 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkUserList.vue b/packages/frontend/src/components/MkUserList.vue index 1cd491a9d58e..56a61dce2380 100644 --- a/packages/frontend/src/components/MkUserList.vue +++ b/packages/frontend/src/components/MkUserList.vue @@ -23,8 +23,8 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/page/page.note.vue b/packages/frontend/src/components/page/page.note.vue index 82ffcfc867c3..d052ce2c1f86 100644 --- a/packages/frontend/src/components/page/page.note.vue +++ b/packages/frontend/src/components/page/page.note.vue @@ -16,7 +16,7 @@ import * as Misskey from 'misskey-js'; import { NoteBlock } from './block.type'; import MkNote from '@/components/MkNote.vue'; import MkNoteDetailed from '@/components/MkNoteDetailed.vue'; -import * as os from '@/os'; +import * as os from '@/os.js'; const props = defineProps<{ block: NoteBlock, diff --git a/packages/frontend/src/components/page/page.text.vue b/packages/frontend/src/components/page/page.text.vue index 9cb32acb8117..35021be95ef1 100644 --- a/packages/frontend/src/components/page/page.text.vue +++ b/packages/frontend/src/components/page/page.text.vue @@ -15,8 +15,8 @@ import { defineAsyncComponent } from 'vue'; import * as mfm from 'mfm-js'; import * as Misskey from 'misskey-js'; import { TextBlock } from './block.type'; -import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm'; -import { $i } from '@/account'; +import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js'; +import { $i } from '@/account.js'; const MkUrlPreview = defineAsyncComponent(() => import('@/components/MkUrlPreview.vue')); diff --git a/packages/frontend/src/components/page/page.vue b/packages/frontend/src/components/page/page.vue index 265ee7146d8d..ab37ca69ad30 100644 --- a/packages/frontend/src/components/page/page.vue +++ b/packages/frontend/src/components/page/page.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> diff --git a/packages/frontend/src/config.ts b/packages/frontend/src/config.ts index 9ce3f8fa15d5..60fc8c9d3488 100644 --- a/packages/frontend/src/config.ts +++ b/packages/frontend/src/config.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { miLocalStorage } from './local-storage'; +import { miLocalStorage } from '@/local-storage.js'; const address = new URL(location.href); const siteName = document.querySelector('meta[property="og:site_name"]')?.content; diff --git a/packages/frontend/src/const.ts b/packages/frontend/src/const.ts index 19bad95c87eb..15038b106370 100644 --- a/packages/frontend/src/const.ts +++ b/packages/frontend/src/const.ts @@ -54,7 +54,7 @@ https://github.com/sindresorhus/file-type/blob/main/core.js https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers */ -export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app'] as const; +export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app'] as const; export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const; export const ROLE_POLICIES = [ diff --git a/packages/frontend/src/custom-emojis.ts b/packages/frontend/src/custom-emojis.ts index 771f21c5b4fe..8ecd1bd2ebe0 100644 --- a/packages/frontend/src/custom-emojis.ts +++ b/packages/frontend/src/custom-emojis.ts @@ -5,9 +5,9 @@ import { shallowRef, computed, markRaw, watch } from 'vue'; import * as Misskey from 'misskey-js'; -import { api, apiGet } from './os'; -import { useStream } from '@/stream'; -import { get, set } from '@/scripts/idb-proxy'; +import { api, apiGet } from '@/os.js'; +import { useStream } from '@/stream.js'; +import { get, set } from '@/scripts/idb-proxy.js'; const storageCache = await get('emojis'); export const customEmojis = shallowRef(Array.isArray(storageCache) ? storageCache : []); diff --git a/packages/frontend/src/directives/click-anime.ts b/packages/frontend/src/directives/click-anime.ts index f9c1fb4d21a4..2b3cdb27a508 100644 --- a/packages/frontend/src/directives/click-anime.ts +++ b/packages/frontend/src/directives/click-anime.ts @@ -4,7 +4,7 @@ */ import { Directive } from 'vue'; -import { defaultStore } from '@/store'; +import { defaultStore } from '@/store.js'; export default { mounted(el: HTMLElement, binding, vn) { diff --git a/packages/frontend/src/directives/follow-append.ts b/packages/frontend/src/directives/follow-append.ts index 1da28c718f81..ae3e31e291aa 100644 --- a/packages/frontend/src/directives/follow-append.ts +++ b/packages/frontend/src/directives/follow-append.ts @@ -4,7 +4,7 @@ */ import { Directive } from 'vue'; -import { getScrollContainer, getScrollPosition } from '@/scripts/scroll'; +import { getScrollContainer, getScrollPosition } from '@/scripts/scroll.js'; export default { mounted(src, binding, vn) { diff --git a/packages/frontend/src/directives/ripple.ts b/packages/frontend/src/directives/ripple.ts index 4a11fbd406d6..cabd155c8709 100644 --- a/packages/frontend/src/directives/ripple.ts +++ b/packages/frontend/src/directives/ripple.ts @@ -4,7 +4,7 @@ */ import MkRippleEffect from '@/components/MkRippleEffect.vue'; -import { popup } from '@/os'; +import { popup } from '@/os.js'; export default { mounted(el, binding, vn) { diff --git a/packages/frontend/src/directives/tooltip.ts b/packages/frontend/src/directives/tooltip.ts index f0e3cdabe4aa..5d6ec2928b1e 100644 --- a/packages/frontend/src/directives/tooltip.ts +++ b/packages/frontend/src/directives/tooltip.ts @@ -7,8 +7,8 @@ // ただディレクティブ内でonUnmountedなどのcomposition api使えるのか不明 import { defineAsyncComponent, Directive, ref } from 'vue'; -import { isTouchUsing } from '@/scripts/touch'; -import { popup, alert } from '@/os'; +import { isTouchUsing } from '@/scripts/touch.js'; +import { popup, alert } from '@/os.js'; const start = isTouchUsing ? 'touchstart' : 'mouseenter'; const end = isTouchUsing ? 'touchend' : 'mouseleave'; diff --git a/packages/frontend/src/directives/user-preview.ts b/packages/frontend/src/directives/user-preview.ts index 5fd65b0587fd..e0fd10047a6d 100644 --- a/packages/frontend/src/directives/user-preview.ts +++ b/packages/frontend/src/directives/user-preview.ts @@ -4,7 +4,7 @@ */ import { defineAsyncComponent, Directive, ref } from 'vue'; -import { popup } from '@/os'; +import { popup } from '@/os.js'; export class UserPreview { private el; diff --git a/packages/frontend/src/filters/date.ts b/packages/frontend/src/filters/date.ts index 1d878e297061..23541f109426 100644 --- a/packages/frontend/src/filters/date.ts +++ b/packages/frontend/src/filters/date.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { dateTimeFormat } from '@/scripts/intl-const'; +import { dateTimeFormat } from '@/scripts/intl-const.js'; export default (d: Date | number | undefined) => dateTimeFormat.format(d); export const dateString = (d: string) => dateTimeFormat.format(new Date(d)); diff --git a/packages/frontend/src/filters/number.ts b/packages/frontend/src/filters/number.ts index 6e0fde15b0b5..d0e4f4991f27 100644 --- a/packages/frontend/src/filters/number.ts +++ b/packages/frontend/src/filters/number.ts @@ -3,6 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { numberFormat } from '@/scripts/intl-const'; +import { numberFormat } from '@/scripts/intl-const.js'; export default n => n == null ? 'N/A' : numberFormat.format(n); diff --git a/packages/frontend/src/filters/user.ts b/packages/frontend/src/filters/user.ts index e0f1d3b7838a..a7a13bec6e58 100644 --- a/packages/frontend/src/filters/user.ts +++ b/packages/frontend/src/filters/user.ts @@ -3,15 +3,15 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import * as misskey from 'misskey-js'; -import * as Acct from 'misskey-js/built/acct'; -import { url } from '@/config'; +import * as Misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; +import { url } from '@/config.js'; export const acct = (user: misskey.Acct) => { - return Acct.toString(user); + return Misskey.acct.toString(user); }; -export const userName = (user: misskey.entities.User) => { +export const userName = (user: Misskey.entities.User) => { return user.name || user.username; }; diff --git a/packages/frontend/src/i18n.ts b/packages/frontend/src/i18n.ts index 995171b10c5a..858db74dacf9 100644 --- a/packages/frontend/src/i18n.ts +++ b/packages/frontend/src/i18n.ts @@ -4,9 +4,9 @@ */ import { markRaw } from 'vue'; -import type { Locale } from '../../../locales'; -import { locale } from '@/config'; -import { I18n } from '@/scripts/i18n'; +import type { Locale } from '../../../locales/index.js'; +import { locale } from '@/config.js'; +import { I18n } from '@/scripts/i18n.js'; export const i18n = markRaw(new I18n(locale)); diff --git a/packages/frontend/src/instance.ts b/packages/frontend/src/instance.ts index 83ec91759447..cbfd95951c5b 100644 --- a/packages/frontend/src/instance.ts +++ b/packages/frontend/src/instance.ts @@ -5,9 +5,9 @@ import { computed, reactive } from 'vue'; import * as Misskey from 'misskey-js'; -import { api } from './os'; -import { miLocalStorage } from './local-storage'; -import { DEFAULT_INFO_IMAGE_URL, DEFAULT_NOT_FOUND_IMAGE_URL, DEFAULT_SERVER_ERROR_IMAGE_URL } from '@/const'; +import { api } from '@/os.js'; +import { miLocalStorage } from '@/local-storage.js'; +import { DEFAULT_INFO_IMAGE_URL, DEFAULT_NOT_FOUND_IMAGE_URL, DEFAULT_SERVER_ERROR_IMAGE_URL } from '@/const.js'; // TODO: 他のタブと永続化されたstateを同期 diff --git a/packages/frontend/src/local-storage.ts b/packages/frontend/src/local-storage.ts index d9dce2859969..0d73885b6884 100644 --- a/packages/frontend/src/local-storage.ts +++ b/packages/frontend/src/local-storage.ts @@ -41,4 +41,12 @@ export const miLocalStorage = { getItem: (key: Keys): string | null => window.localStorage.getItem(key), setItem: (key: Keys, value: string): void => window.localStorage.setItem(key, value), removeItem: (key: Keys): void => window.localStorage.removeItem(key), + getItemAsJson: (key: Keys): any | undefined => { + const item = miLocalStorage.getItem(key); + if (item === null) { + return undefined; + } + return JSON.parse(item); + }, + setItemAsJson: (key: Keys, value: any): void => window.localStorage.setItem(key, JSON.stringify(value)), }; diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts index 4719678fcced..7f182a98f71f 100644 --- a/packages/frontend/src/navbar.ts +++ b/packages/frontend/src/navbar.ts @@ -4,14 +4,14 @@ */ import { computed, reactive } from 'vue'; -import { $i } from './account'; -import { miLocalStorage } from './local-storage'; -import { openInstanceMenu } from './ui/_common_/common'; -import { lookup } from './scripts/lookup'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; -import { ui } from '@/config'; -import { unisonReload } from '@/scripts/unison-reload'; +import { $i } from '@/account.js'; +import { miLocalStorage } from '@/local-storage.js'; +import { openInstanceMenu } from '@/ui/_common_/common.js'; +import { lookup } from '@/scripts/lookup.js'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; +import { ui } from '@/config.js'; +import { unisonReload } from '@/scripts/unison-reload.js'; export const navbarItemDef = reactive({ notifications: { diff --git a/packages/frontend/src/nirax.ts b/packages/frontend/src/nirax.ts index 66ae39fa5688..9755bdcb18fe 100644 --- a/packages/frontend/src/nirax.ts +++ b/packages/frontend/src/nirax.ts @@ -7,7 +7,7 @@ import { EventEmitter } from 'eventemitter3'; import { Component, onMounted, shallowRef, ShallowRef } from 'vue'; -import { safeURIDecode } from '@/scripts/safe-uri-decode'; +import { safeURIDecode } from '@/scripts/safe-uri-decode.js'; type RouteDef = { path: string; diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index 2e80e3d8dfd3..8aed5797e14e 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -5,26 +5,26 @@ // TODO: なんでもかんでもos.tsに突っ込むのやめたいのでよしなに分割する -import { pendingApiRequestsCount, api, apiGet } from '@/scripts/api'; +import { pendingApiRequestsCount, api, apiGet } from '@/scripts/api.js'; export { pendingApiRequestsCount, api, apiGet }; import { Component, markRaw, Ref, ref, defineAsyncComponent } from 'vue'; import { EventEmitter } from 'eventemitter3'; import insertTextAtCursor from 'insert-text-at-cursor'; import * as Misskey from 'misskey-js'; -import { i18n } from './i18n'; +import { i18n } from '@/i18n.js'; import MkPostFormDialog from '@/components/MkPostFormDialog.vue'; import MkWaitingDialog from '@/components/MkWaitingDialog.vue'; import MkPageWindow from '@/components/MkPageWindow.vue'; import MkToast from '@/components/MkToast.vue'; import MkDialog from '@/components/MkDialog.vue'; +import MkPasswordDialog from '@/components/MkPasswordDialog.vue'; import MkEmojiPickerDialog from '@/components/MkEmojiPickerDialog.vue'; import MkEmojiPickerWindow from '@/components/MkEmojiPickerWindow.vue'; import MkPopupMenu from '@/components/MkPopupMenu.vue'; import MkContextMenu from '@/components/MkContextMenu.vue'; -import { MenuItem } from '@/types/menu'; -import copyToClipboard from './scripts/copy-to-clipboard'; -import { showMovedDialog } from './scripts/show-moved-dialog'; -import { DriveFile } from 'misskey-js/built/entities'; +import { MenuItem } from '@/types/menu.js'; +import copyToClipboard from '@/scripts/copy-to-clipboard.js'; +import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; export const openingWindowsCount = ref(0); @@ -334,6 +334,18 @@ export function inputDate(props: { }); } +export function authenticateDialog(): Promise<{ canceled: true; result: undefined; } | { + canceled: false; result: { password: string; token: string | null; }; +}> { + return new Promise((resolve, reject) => { + popup(MkPasswordDialog, {}, { + done: result => { + resolve(result ? { canceled: false, result } : { canceled: true, result: undefined }); + }, + }, 'closed'); + }); +} + export function select(props: { title?: string | null; text?: string | null; @@ -420,7 +432,7 @@ export async function selectUser(opts: { includeSelf?: boolean } = {}) { }); } -export async function selectDriveFile(multiple: boolean): Promise { +export async function selectDriveFile(multiple: boolean): Promise { return new Promise((resolve, reject) => { popup(defineAsyncComponent(() => import('@/components/MkDriveSelectDialog.vue')), { type: 'file', diff --git a/packages/frontend/src/pages/_error_.vue b/packages/frontend/src/pages/_error_.vue index efc0244dc251..7a3e2f444b52 100644 --- a/packages/frontend/src/pages/_error_.vue +++ b/packages/frontend/src/pages/_error_.vue @@ -26,16 +26,16 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/pages/search.user.vue b/packages/frontend/src/pages/search.user.vue index 8a71ed00b7c8..1a7501adc610 100644 --- a/packages/frontend/src/pages/search.user.vue +++ b/packages/frontend/src/pages/search.user.vue @@ -30,13 +30,13 @@ import MkUserList from '@/components/MkUserList.vue'; import MkInput from '@/components/MkInput.vue'; import MkRadios from '@/components/MkRadios.vue'; import MkButton from '@/components/MkButton.vue'; -import { i18n } from '@/i18n'; -import * as os from '@/os'; +import { i18n } from '@/i18n.js'; +import * as os from '@/os.js'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; -import { $i } from '@/account'; -import { instance } from '@/instance'; +import { $i } from '@/account.js'; +import { instance } from '@/instance.js'; import MkInfo from '@/components/MkInfo.vue'; -import { useRouter } from '@/router'; +import { useRouter } from '@/router.js'; const router = useRouter(); diff --git a/packages/frontend/src/pages/search.vue b/packages/frontend/src/pages/search.vue index ab04120bbe27..e205fe850f71 100644 --- a/packages/frontend/src/pages/search.vue +++ b/packages/frontend/src/pages/search.vue @@ -24,11 +24,11 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/pages/settings/2fa.vue b/packages/frontend/src/pages/settings/2fa.vue index d2998ceae7b9..8a89a3a86daa 100644 --- a/packages/frontend/src/pages/settings/2fa.vue +++ b/packages/frontend/src/pages/settings/2fa.vue @@ -8,20 +8,29 @@ SPDX-License-Identifier: AGPL-3.0-only
- + + {{ i18n.ts._2fa.backupCodeUsedWarning }} + + + {{ i18n.ts._2fa.backupCodesExhaustedWarning }} + + + + +
- {{ i18n.ts.unregister }} + {{ i18n.ts.unregister }}
- {{ i18n.ts._2fa.registerTOTP }} + {{ i18n.ts._2fa.registerTOTP }} @@ -29,16 +38,14 @@ SPDX-License-Identifier: AGPL-3.0-only
- {{ i18n.ts._2fa.securityKeyInfo }}
-
- {{ i18n.ts._2fa.chromePasskeyNotSupported }} + {{ i18n.ts._2fa.securityKeyInfo }}
- + {{ i18n.ts._2fa.securityKeyNotSupported }} - + {{ i18n.ts._2fa.registerTOTPBeforeKey }} @@ -66,16 +73,15 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/pages/user/clips.vue b/packages/frontend/src/pages/user/clips.vue index 753662843ea1..eaae4725165f 100644 --- a/packages/frontend/src/pages/user/clips.vue +++ b/packages/frontend/src/pages/user/clips.vue @@ -18,11 +18,11 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/pages/user/follow-list.vue b/packages/frontend/src/pages/user/follow-list.vue index f8b9d70e8e2a..19b729035352 100644 --- a/packages/frontend/src/pages/user/follow-list.vue +++ b/packages/frontend/src/pages/user/follow-list.vue @@ -15,12 +15,12 @@ SPDX-License-Identifier: AGPL-3.0-only