diff --git a/.config/example.yml b/.config/example.yml index 3c9c3bc0d7..7fea929374 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -2,6 +2,63 @@ # Misskey configuration #━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# ┌──────────────────────────────┐ +#───┘ a boring but important thing └──────────────────────────── + +# +# First of all, let me tell you a story that may possibly be +# boring to you and possibly important to you. +# +# Misskey is licensed under the AGPLv3 license. This license is +# known to be often misunderstood. Please read the following +# instructions carefully and select the appropriate option so +# that you do not negligently cause a license violation. +# + +# -------- +# Option 1: If you host Misskey AS-IS (without any changes to +# the source code. forks are not included). +# +# Step 1: Congratulations! You don't need to do anything. + +# -------- +# Option 2: If you have made changes to the source code (forks +# are included) and publish a Git repository of source +# code. There should be no access restrictions on +# this repository. Strictly speaking, it doesn't have +# to be a Git repository, but you'll probably use Git! +# +# Step 1: Build and run the Misskey server first. +# Step 2: Open in +# your browser with the administrator account. +# Step 3: Enter the URL of your Git repository in the +# "Repository URL" field. + +# -------- +# Option 3: If neither of the above applies to you. +# (In this case, the source code should be published +# on the Misskey interface. IT IS NOT ENOUGH TO +# DISCLOSE THE SOURCE CODE WEHN A USER REQUESTS IT BY +# E-MAIL OR OTHER MEANS. If you are not satisfied +# with this, it is recommended that you read the +# license again carefully. Anyway, enabling this +# option will automatically generate and publish a +# tarball at build time, protecting you from +# inadvertent license violations. (There is no legal +# guarantee, of course.) The tarball will generated +# from the root directory of your codebase. So it is +# also recommended to check directory +# once after building and before activating the server +# to avoid ACCIDENTAL LEAKING OF SENSITIVE INFORMATION. +# To prevent certain files from being included in the +# tarball, add a glob pattern after line 15 in +# . DO NOT FORGET TO BUILD AFTER +# ENABLING THIS OPTION!) +# +# Step 1: Uncomment the following line. +# +# publishTarballInsteadOfProvideRepositoryUrl: true + # ┌─────┐ #───┘ URL └───────────────────────────────────────────────────── @@ -118,7 +175,7 @@ redis: # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── -# You can set scope to local (default value) or global +# You can set scope to local (default value) or global # (include notes from remote). #meilisearch: @@ -214,7 +271,7 @@ proxyRemoteFiles: true signToActivityPubGet: true # For security reasons, uploading attachments from the intranet is prohibited, -# but exceptions can be made from the following settings. Default value is "undefined". +# but exceptions can be made from the following settings. Default value is "undefined". # Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)). #allowedPrivateNetworks: [ # '127.0.0.1/32' diff --git a/.github/workflows/api-misskey-js.yml b/.github/workflows/api-misskey-js.yml index e52cbc33e4..1b7b68b14f 100644 --- a/.github/workflows/api-misskey-js.yml +++ b/.github/workflows/api-misskey-js.yml @@ -20,7 +20,7 @@ jobs: - run: corepack enable - name: Setup Node.js - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/changelog-check.yml b/.github/workflows/changelog-check.yml index d4cdf64f70..f254af0d1f 100644 --- a/.github/workflows/changelog-check.yml +++ b/.github/workflows/changelog-check.yml @@ -14,7 +14,7 @@ jobs: - name: Checkout head uses: actions/checkout@v4.1.1 - name: Setup Node.js - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: '.node-version' diff --git a/.github/workflows/check-misskey-js-autogen.yml b/.github/workflows/check-misskey-js-autogen.yml index 4aaa8a5798..8fad129115 100644 --- a/.github/workflows/check-misskey-js-autogen.yml +++ b/.github/workflows/check-misskey-js-autogen.yml @@ -19,7 +19,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v4.1.1 with: submodules: true ref: ${{ github.event.pull_request.head.sha }} @@ -31,7 +31,7 @@ jobs: - name: setup node id: setup-node - uses: actions/setup-node@v4 + uses: actions/setup-node@v4.0.2 with: node-version-file: '.node-version' cache: pnpm @@ -48,7 +48,7 @@ jobs: wait-interval: 30 - name: Download artifact - uses: actions/github-script@v7 + uses: actions/github-script@v7.0.1 with: script: | const fs = require('fs'); diff --git a/.github/workflows/deploy-test-environment.yml b/.github/workflows/deploy-test-environment.yml index 62a4d018d4..77cdcfaf88 100644 --- a/.github/workflows/deploy-test-environment.yml +++ b/.github/workflows/deploy-test-environment.yml @@ -23,16 +23,35 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/preview') outputs: + is-allowed-user: ${{ steps.check-allowed-users.outputs.is-allowed-user }} pr-ref: ${{ steps.get-ref.outputs.pr-ref }} wait_time: ${{ steps.get-wait-time.outputs.wait_time }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v4.1.1 + + - name: Check allowed users + id: check-allowed-users + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ORG_ID: ${{ github.repository_owner_id }} + COMMENT_AUTHOR: ${{ github.event.comment.user.login }} + run: | + MEMBERSHIP_STATUS=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/organizations/$ORG_ID/public_members/$COMMENT_AUTHOR" \ + -o /dev/null -w '%{http_code}\n' -s) + if [ "$MEMBERSHIP_STATUS" -eq 204 ]; then + echo "is-allowed-user=true" > $GITHUB_OUTPUT + else + echo "is-allowed-user=false" > $GITHUB_OUTPUT + fi - name: Get PR ref id: get-ref env: - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | PR_NUMBER=$(jq --raw-output .issue.number $GITHUB_EVENT_PATH) PR_REF=$(gh pr view $PR_NUMBER --json headRefName -q '.headRefName') @@ -40,13 +59,15 @@ jobs: - name: Extract wait time id: get-wait-time + env: + COMMENT_BODY: ${{ github.event.comment.body }} run: | - COMMENT_BODY="${{ github.event.comment.body }}" WAIT_TIME=$(echo "$COMMENT_BODY" | grep -oP '(?<=/preview\s)\d+' || echo "1800") echo "wait_time=$WAIT_TIME" > $GITHUB_OUTPUT deploy-test-environment-pr-comment: needs: get-pr-ref + if: needs.get-pr-ref.outputs.is-allowed-user == 'true' uses: joinmisskey/misskey-tga/.github/workflows/deploy-test-environment.yml@main with: repository: ${{ github.repository }} diff --git a/.github/workflows/docker-develop.yml b/.github/workflows/docker-develop.yml index a43789b754..cb84849580 100644 --- a/.github/workflows/docker-develop.yml +++ b/.github/workflows/docker-develop.yml @@ -6,38 +6,83 @@ on: - develop workflow_dispatch: +env: + REGISTRY_IMAGE: misskey/misskey + jobs: - push_to_registry: - name: Push Docker image to Docker Hub + # see https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners + build: + name: Build runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + platform: + - linux/amd64 + - linux/arm64 if: github.repository == 'misskey-dev/misskey' steps: + - name: Prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Check out the repo uses: actions/checkout@v4.1.1 - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v3.0.0 - with: - platforms: linux/amd64,linux/arm64 - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: misskey/misskey + uses: docker/setup-buildx-action@v3 - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - - name: Build and Push to Docker Hub + - name: Build and push by digest + id: build uses: docker/build-push-action@v5 with: - builder: ${{ steps.buildx.outputs.name }} context: . push: true - platforms: ${{ steps.buildx.outputs.platforms }} + platforms: ${{ matrix.platform }} provenance: false - tags: misskey/misskey:develop labels: develop cache-from: type=gha cache-to: type=gha,mode=max + outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + runs-on: ubuntu-latest + needs: + - build + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create --tag ${{ env.REGISTRY_IMAGE }}:develop \ + $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *) + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:develop diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 08cb91c2d0..23c1bdbc16 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -5,45 +5,101 @@ on: types: [published] workflow_dispatch: -jobs: - push_to_registry: - name: Push Docker image to Docker Hub - runs-on: ubuntu-latest +env: + REGISTRY_IMAGE: misskey/misskey + TAGS: | + type=edge + type=ref,event=pr + type=ref,event=branch + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} +jobs: + # see https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners + build: + name: Build + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + platform: + - linux/amd64 + - linux/arm64 steps: + - name: Prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Check out the repo uses: actions/checkout@v4.1.1 - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v3.0.0 - with: - platforms: linux/amd64,linux/arm64 + uses: docker/setup-buildx-action@v3 - name: Docker meta id: meta uses: docker/metadata-action@v5 with: - images: misskey/misskey - tags: | - type=edge - type=ref,event=pr - type=ref,event=branch - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} + images: ${{ env.REGISTRY_IMAGE }} + tags: ${{ env.TAGS }} - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and Push to Docker Hub + id: build uses: docker/build-push-action@v5 with: - builder: ${{ steps.buildx.outputs.name }} context: . push: true - platforms: ${{ steps.buildx.outputs.platforms }} + platforms: ${{ matrix.platform }} provenance: false - tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max + outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + runs-on: ubuntu-latest + needs: + - build + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY_IMAGE }} + tags: ${{ env.TAGS }} + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *) + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} diff --git a/.github/workflows/get-api-diff.yml b/.github/workflows/get-api-diff.yml index 3f229c77a6..e737b89b42 100644 --- a/.github/workflows/get-api-diff.yml +++ b/.github/workflows/get-api-diff.yml @@ -37,7 +37,7 @@ jobs: version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5c36547323..31e974edaa 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,7 +31,7 @@ jobs: with: version: 8 run_install: false - - uses: actions/setup-node@v4.0.1 + - uses: actions/setup-node@v4.0.2 with: node-version-file: '.node-version' cache: 'pnpm' @@ -58,7 +58,7 @@ jobs: with: version: 7 run_install: false - - uses: actions/setup-node@v4.0.1 + - uses: actions/setup-node@v4.0.2 with: node-version-file: '.node-version' cache: 'pnpm' @@ -84,7 +84,7 @@ jobs: with: version: 7 run_install: false - - uses: actions/setup-node@v4.0.1 + - uses: actions/setup-node@v4.0.2 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/on-release-created.yml b/.github/workflows/on-release-created.yml index d2508f1b77..069534bd53 100644 --- a/.github/workflows/on-release-created.yml +++ b/.github/workflows/on-release-created.yml @@ -20,7 +20,7 @@ jobs: node-version: [20.10.0] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4.1.1 with: submodules: true - name: Install pnpm @@ -29,7 +29,7 @@ jobs: version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v4.0.2 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/pr-preview-deploy.yml b/.github/workflows/pr-preview-deploy.yml index cb9a4ebfc8..964d24c3d7 100644 --- a/.github/workflows/pr-preview-deploy.yml +++ b/.github/workflows/pr-preview-deploy.yml @@ -13,7 +13,7 @@ jobs: github.event.client_payload.slash_command.sha != '' && contains(github.event.client_payload.pull_request.head.sha, github.event.client_payload.slash_command.sha) steps: - - uses: actions/github-script@v7 + - uses: actions/github-script@v7.0.1 id: check-id env: number: ${{ github.event.client_payload.pull_request.number }} @@ -37,7 +37,7 @@ jobs: return check[0].id; - - uses: actions/github-script@v7 + - uses: actions/github-script@v7.0.1 env: check_id: ${{ steps.check-id.outputs.result }} details_url: ${{ github.server_url }}/${{ github.repository }}/runs/${{ github.run_id }} @@ -72,7 +72,7 @@ jobs: timeout: 15m # Update check run called "integration-fork" - - uses: actions/github-script@v7 + - uses: actions/github-script@v7.0.1 id: update-check-run if: ${{ always() }} env: diff --git a/.github/workflows/pr-preview-destroy.yml b/.github/workflows/pr-preview-destroy.yml index 47d9eb313a..8967eb2f94 100644 --- a/.github/workflows/pr-preview-destroy.yml +++ b/.github/workflows/pr-preview-destroy.yml @@ -10,7 +10,7 @@ jobs: destroy-preview-environment: runs-on: ubuntu-latest steps: - - uses: actions/github-script@v7 + - uses: actions/github-script@v7.0.1 id: check-conclusion env: number: ${{ github.event.number }} diff --git a/.github/workflows/report-api-diff.yml b/.github/workflows/report-api-diff.yml index 54da8b4a83..df9cc279e8 100644 --- a/.github/workflows/report-api-diff.yml +++ b/.github/workflows/report-api-diff.yml @@ -16,7 +16,7 @@ jobs: # api-artifact steps: - name: Download artifact - uses: actions/github-script@v7 + uses: actions/github-script@v7.0.1 with: script: | const fs = require('fs'); diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml index eba1607956..87481b12cf 100644 --- a/.github/workflows/storybook.yml +++ b/.github/workflows/storybook.yml @@ -16,12 +16,12 @@ jobs: NODE_OPTIONS: "--max_old_space_size=7168" steps: - - uses: actions/checkout@v3.6.0 + - uses: actions/checkout@v4.1.1 if: github.event_name != 'pull_request_target' with: fetch-depth: 0 submodules: true - - uses: actions/checkout@v3.6.0 + - uses: actions/checkout@v4.1.1 if: github.event_name == 'pull_request_target' with: fetch-depth: 0 @@ -34,12 +34,12 @@ jobs: 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 + uses: pnpm/action-setup@v3 with: version: 8 run_install: false - name: Use Node.js 20.x - uses: actions/setup-node@v3.8.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: '.node-version' cache: 'pnpm' @@ -95,7 +95,7 @@ jobs: env: CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} - name: Notify that Chromatic detects changes - uses: actions/github-script@v6.4.0 + uses: actions/github-script@v7.0.1 if: github.event_name != 'pull_request_target' && steps.chromatic_push.outputs.success == 'false' with: github-token: ${{ secrets.GITHUB_TOKEN }} @@ -107,7 +107,7 @@ jobs: body: 'Chromatic detects changes. Please [review the changes on Chromatic](https://www.chromatic.com/builds?appId=6428f7d7b962f0b79f97d6e4).' }) - name: Upload Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: storybook path: packages/frontend/storybook-static diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index 7bc3ad9a8f..49a6a39805 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -46,7 +46,7 @@ jobs: version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -96,7 +96,7 @@ jobs: version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml index 93def1164e..1e020b7368 100644 --- a/.github/workflows/test-frontend.yml +++ b/.github/workflows/test-frontend.yml @@ -38,7 +38,7 @@ jobs: version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -96,7 +96,7 @@ jobs: version: 7 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-misskey-js.yml b/.github/workflows/test-misskey-js.yml index 70ef45692a..f73bd0b08f 100644 --- a/.github/workflows/test-misskey-js.yml +++ b/.github/workflows/test-misskey-js.yml @@ -30,7 +30,7 @@ jobs: - run: corepack enable - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-production.yml b/.github/workflows/test-production.yml index eac0a51c66..77af08b6fe 100644 --- a/.github/workflows/test-production.yml +++ b/.github/workflows/test-production.yml @@ -28,7 +28,7 @@ jobs: version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/validate-api-json.yml b/.github/workflows/validate-api-json.yml index 08044322c9..36ed8d273f 100644 --- a/.github/workflows/validate-api-json.yml +++ b/.github/workflows/validate-api-json.yml @@ -29,7 +29,7 @@ jobs: version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/CHANGELOG.md b/CHANGELOG.md index f41188a4da..32c9bd0aec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,18 +12,21 @@ --> -## 202x.x.x (Unreleased) +## 2024.2.0 ### Note -- 外部サイトからプラグインをインストールする場合のパスが`/install-extentions`から`/install-extensions`に変わります。現時点では以前のパスも利用できますが、非推奨です。 +- 外部サイトからプラグインをインストールする場合のパスが`/install-extentions`から`/install-extensions`に変わります。以前のパスからは自動でリダイレクトされるようになっていますが、新しいパスに変更することをお勧めします。 ### General - Feat: [mCaptcha](https://github.com/mCaptcha/mCaptcha)のサポートを追加 -- Fix: リストライムラインの「リノートを表示」が正しく機能しない問題を修正 - Feat: Add support for TrueMail +- Feat: AGPLv3ライセンスに誤って違反するのを防止する機能を追加 + - 管理者がrepositoryUrlを変更したり、またはソースコードを直接頒布することを選択できるようになります + - 本体のソースコードに改変を加えた際に、ライセンスに基づく適切な案内を表示します +- Enhance: モデレーターはすべてのユーザーのリアクション一覧を見られるように +- Fix: リストライムラインの「リノートを表示」が正しく機能しない問題を修正 - Fix: リモートユーザーのリアクション一覧がすべて見えてしまうのを修正 * すべてのリモートユーザーのリアクション一覧を見えないようにします -- Enhance: モデレーターはすべてのユーザーのリアクション一覧を見られるように - Fix: 特定のキーワード及び正規表現にマッチする文字列を含むノートが投稿された際、エラーに出来るような設定項目を追加 #13207 * デフォルトは空欄なので適用前と同等の動作になります @@ -57,6 +60,10 @@ - リモートのユーザーにローカルのみのカスタム絵文字をリアクションしようとした場合 - センシティブなリアクションを認めていないユーザーにセンシティブなカスタム絵文字をリアクションしようとした場合 - ロールが必要な絵文字をリアクションしようとした場合 +- Enhance: ページ遷移時にPlayerを閉じるように +- Enhance: 通報ページのユーザをクリックした際にユーザをウィンドウで開くように +- Enhance: ノートの通報時にリモートのノートであっても自インスタンスにおけるノートのリンクを含むように +- Enhance: オフライン表示のデザインを改善・多言語対応 - Fix: ネイティブモードの絵文字がモノクロにならないように - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正 - Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正 @@ -67,7 +74,6 @@ - Fix: デッキのプロファイル作成時に名前を空にできる問題を修正 - Fix: テーマ作成時に名称が空欄でも作成できてしまう問題を修正 - Fix: プラグインで`Plugin:register_note_post_interruptor`を使用すると、ノートが投稿できなくなる問題を修正 -- Enhance: ページ遷移時にPlayerを閉じるように - Fix: iOSで大きな画像を変換してアップロードできない問題を修正 - Fix: 「アニメーション画像を再生しない」もしくは「データセーバー(アイコン)」を有効にしていても、アイコンデコレーションのアニメーションが停止されない問題を修正 - Fix: 画像をクロップするとクロップ後の解像度が異様に低くなる問題の修正 @@ -79,11 +85,12 @@ - Fix: Summaly proxy利用時にプレイヤーが動作しないことがあるのを修正 #13196 ### Server -- Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました +- Enhance: 連合先のレートリミットを超過した際にリトライするようになりました - Enhance: ActivityPub Deliver queueでBodyを事前処理するように (#12916) - Enhance: クリップをエクスポートできるように - Enhance: `/files`のファイルに対してHTTP Rangeリクエストを行えるように - Enhance: `api.json`のOpenAPI Specificationを3.1.0に更新 +- Enhance: 連合向けのノート配信を軽量化 #13192 - Fix: `drive/files/update`でファイル名のバリデーションが機能していない問題を修正 - Fix: `notes/create`で、`text`が空白文字のみで構成されているか`null`であって、かつ`text`だけであるリクエストに対するレスポンスが400になるように変更 - Fix: `notes/create`で、`text`が空白文字のみで構成されていてかつリノート、ファイルまたは投票を含んでいるリクエストに対するレスポンスの`text`が`""`から`null`になるように変更 @@ -91,10 +98,7 @@ - Fix: properly handle cc followers - Fix: ジョブに関する設定の名前を修正 relashionshipJobPerSec -> relationshipJobPerSec - Fix: コントロールパネル->モデレーション->「誰でも新規登録できるようにする」の初期値をONからOFFに変更 #13122 -- Enhance: 連合向けのノート配信を軽量化 #13192 - -### Service Worker -- Enhance: オフライン表示のデザインを改善・多言語対応 +- Fix: リモートユーザーが復活してもキャッシュにより該当ユーザーのActivityが受け入れられないのを修正 #13273 ## 2023.12.2 diff --git a/Dockerfile b/Dockerfile index a8d3dbcd89..ee3a30a3c1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,13 +27,13 @@ COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"] COPY --link ["packages/misskey-reversi/package.json", "./packages/misskey-reversi/"] COPY --link ["packages/misskey-bubble-game/package.json", "./packages/misskey-bubble-game/"] +ARG NODE_ENV=production + RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \ pnpm i --frozen-lockfile --aggregate-output COPY --link . ./ -ARG NODE_ENV=production - RUN git submodule update --init RUN pnpm build RUN rm -rf .git/ @@ -57,6 +57,8 @@ COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"] COPY --link ["packages/misskey-reversi/package.json", "./packages/misskey-reversi/"] COPY --link ["packages/misskey-bubble-game/package.json", "./packages/misskey-bubble-game/"] +ARG NODE_ENV=production + RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \ pnpm i --frozen-lockfile --aggregate-output diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index 47f131032a..b0f7408587 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -1011,6 +1011,7 @@ expired: "منتهية صلاحيته" icon: "الصورة الرمزية" replies: "رد" renotes: "أعد النشر" +sourceCode: "الشفرة المصدرية" flip: "اقلب" lastNDays: "آخر {n} أيام" _initialAccountSetting: diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index 42edad1fd0..2a23cda06b 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -855,6 +855,7 @@ youFollowing: "অনুসরণ করা হচ্ছে" icon: "প্রোফাইল ছবি" replies: "জবাব" renotes: "রিনোট" +sourceCode: "সোর্স কোড" flip: "উল্টান" _role: priority: "অগ্রাধিকার" diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 656afb7610..af5329dc7e 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -1167,6 +1167,7 @@ hideRepliesToOthersInTimelineAll: "Ocultar les teves respostes a tots els usuari confirmShowRepliesAll: "Aquesta opció no té marxa enrere. Vols mostrar les teves respostes a tots els que segueixes a la teva línia de temps?" confirmHideRepliesAll: "Aquesta opció no té marxa enrere. Vols ocultar les teves respostes a tots els usuaris que segueixes a la línia de temps?" externalServices: "Serveis externs" +sourceCode: "Codi font" impressum: "Impressum" impressumUrl: "Adreça URL impressum" impressumDescription: "A països, com Alemanya, la inclusió de la informació de contacte de l'operador (un Impressum) és requereix de manera legal per llocs comercials." diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index 7da9461af1..3161ff275a 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -1095,6 +1095,7 @@ iHaveReadXCarefullyAndAgree: "Přečetl jsem si text \"{x}\" a souhlasím s ním icon: "Avatar" replies: "Odpovědět" renotes: "Přeposlat" +sourceCode: "Zdrojový kód" flip: "Otočit" lastNDays: "Posledních {n} dnů" _initialAccountSetting: diff --git a/locales/de-DE.yml b/locales/de-DE.yml index a4412395f6..f733fa1ee9 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -1158,6 +1158,7 @@ hideRepliesToOthersInTimelineAll: "Antworten von allen momentan gefolgten Benutz confirmShowRepliesAll: "Dies ist eine unwiderrufliche Aktion. Wirklich Antworten von allen momentan gefolgten Benutzern in der Chronik anzeigen?" confirmHideRepliesAll: "Dies ist eine unwiderrufliche Aktion. Wirklich Antworten von allen momentan gefolgten Benutzern nicht in der Chronik anzeigen?" externalServices: "Externe Dienste" +sourceCode: "Quellcode" impressum: "Impressum" impressumUrl: "Impressums-URL" impressumDescription: "In manchen Ländern, wie Deutschland und dessen Umgebung, ist die Angabe von Betreiberinformationen (ein Impressum) bei kommerziellem Betrieb zwingend." diff --git a/locales/en-US.yml b/locales/en-US.yml index f82ce21906..084f6b23b9 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1165,6 +1165,7 @@ hideRepliesToOthersInTimelineAll: "Hide replies to others from everyone you foll confirmShowRepliesAll: "This operation is irreversible. Would you really like to show replies to others from everyone you follow in your timeline?" confirmHideRepliesAll: "This operation is irreversible. Would you really like to hide replies to others from everyone you follow in your timeline?" externalServices: "External Services" +sourceCode: "Source code" impressum: "Impressum" impressumUrl: "Impressum URL" impressumDescription: "In some countries, like germany, the inclusion of operator contact information (an Impressum) is legally required for commercial websites." @@ -1975,8 +1976,12 @@ _permissions: "read:admin:abuse-user-reports": "View user reports" "write:admin:delete-account": "Delete user account" "write:admin:delete-all-files-of-a-user": "Delete all files of a user" + "read:admin:index-stats": "View database index stats" + "read:admin:table-stats": "View database table stats" + "read:admin:user-ips": "View user IP addresses" "read:admin:meta": "View instance metadata" "write:admin:reset-password": "Reset user password" + "write:admin:resolve-abuse-user-report": "Resolve user report" "write:admin:send-email": "Send email" "read:admin:server-info": "View server info" "read:admin:show-moderation-log": "View moderation log" @@ -1997,6 +2002,26 @@ _permissions: "write:admin:announcements": "Manage announcements" "read:admin:announcements": "View announcements" "write:admin:avatar-decorations": "Manage avatar decorations" + "read:admin:avatar-decorations": "View avatar decorations" + "write:admin:federation": "Manage federation data" + "write:admin:account": "Manage user account" + "read:admin:account": "View user account" + "write:admin:emoji": "Manage emoji" + "read:admin:emoji": "View emoji" + "write:admin:queue": "Manage job queue" + "read:admin:queue": "View job queue info" + "write:admin:promo": "Manage promotion notes" + "write:admin:drive": "Manage user drive" + "read:admin:drive": "View user drive info" + "read:admin:stream": "Use WebSocket API for Admin" + "write:admin:ad": "Manage ads" + "read:admin:ad": "View ads" + "write:invite-codes": "Create invite codes" + "read:invite-codes": "Get invite codes" + "write:clip-favorite": "Manage favorited clips" + "read:clip-favorite": "View favorited clips" + "read:federation": "Get federation data" + "write:report-abuse": "Report violation" _auth: shareAccessTitle: "Granting application permissions" shareAccess: "Would you like to authorize \"{name}\" to access this account?" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 89961b24cb..2952e89f83 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -1166,6 +1166,7 @@ hideRepliesToOthersInTimelineAll: "Ocultar tus respuestas a otros usuarios que s confirmShowRepliesAll: "Esta operación es irreversible. ¿Confirmas que quieres mostrar tus respuestas a otros usuarios que sigues en tu línea de tiempo?" confirmHideRepliesAll: "Esta operación es irreversible. ¿Confirmas que quieres ocultar tus respuestas a otros usuarios que sigues en tu línea de tiempo?" externalServices: "Servicios Externos" +sourceCode: "Código fuente" impressum: "Impressum" impressumUrl: "Impressum URL" impressumDescription: "En algunos países, como Alemania, la inclusión del operador de datos (el Impressum) es requerido legalmente para sitios web comerciales." diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 1ebb632051..35fac49cdd 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -2,7 +2,7 @@ _lang_: "Français" headlineMisskey: "Réseau relié par des notes" introMisskey: "Bienvenue ! Misskey est un service de microblogage décentralisé, libre et ouvert.\nÉcrivez des « notes » et partagez ce qui se passe à l’instant présent, autour de vous avec les autres 📡\nLa fonction « réactions », vous permet également d’ajouter une réaction rapide aux notes des autres utilisateur·rice·s 👍\nExplorons un nouveau monde 🚀" -poweredByMisskeyDescription: "{nom} est l'un des services propulsés par la plateforme ouverte Misskey (appelée \"instance Misskey\")." +poweredByMisskeyDescription: "{name} est l'un des services propulsés par la plateforme ouverte Misskey (appelée \"instance Misskey\")." monthAndDay: "{day}/{month}" search: "Rechercher" notifications: "Notifications" @@ -1136,6 +1136,7 @@ hideRepliesToOthersInTimelineAll: "Masquer les réponses de toutes les personnes confirmShowRepliesAll: "Cette opération est irréversible. Voulez-vous vraiment afficher les réponses de toutes les personnes que vous suivez dans le fil ?" confirmHideRepliesAll: "Cette opération est irréversible. Voulez-vous vraiment masquer les réponses de toutes les personnes que vous suivez dans le fil ?" externalServices: "Services externes" +sourceCode: "Code source" impressum: "Impressum" impressumUrl: "URL de l'impressum" impressumDescription: "Dans certains pays comme l'Allemagne, il est obligatoire d'afficher les informations sur l'opérateur d'un site (un impressum)." @@ -1175,7 +1176,7 @@ _initialAccountSetting: profileSetting: "Paramètres du profil" privacySetting: "Paramètres de confidentialité" initialAccountSettingCompleted: "Configuration du profil terminée avec succès !" - youCanContinueTutorial: "Vous pouvez procéder au tutoriel sur l'utilisation de {nom}(Misskey) ou vous arrêter ici et commencer à l'utiliser immédiatement." + youCanContinueTutorial: "Vous pouvez procéder au tutoriel sur l'utilisation de {name}(Misskey) ou vous arrêter ici et commencer à l'utiliser immédiatement." startTutorial: "Démarrer le tutoriel" skipAreYouSure: "Désirez-vous ignorer la configuration du profil ?" _initialTutorial: diff --git a/locales/id-ID.yml b/locales/id-ID.yml index b38e95596b..58a248996b 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -81,7 +81,7 @@ exportRequested: "Kamu telah meminta ekspor. Ini akan memakan waktu sesaat. Sete importRequested: "Kamu telah meminta impor. Ini akan memakan waktu sesaat." lists: "Daftar" noLists: "Kamu tidak memiliki daftar apapun" -note: "Catat" +note: "Catatan" notes: "Catatan" following: "Ikuti" followers: "Pengikut" @@ -381,8 +381,10 @@ enableHcaptcha: "Nyalakan hCaptcha" hcaptchaSiteKey: "Site Key" hcaptchaSecretKey: "Secret Key" mcaptcha: "mCaptcha" +enableMcaptcha: "Nyalakan mCaptcha" mcaptchaSiteKey: "Site key" mcaptchaSecretKey: "Secret Key" +mcaptchaInstanceUrl: "URL instansi mCaptcha" recaptcha: "reCAPTCHA" enableRecaptcha: "Nyalakan reCAPTCHA" recaptchaSiteKey: "Site key" @@ -630,6 +632,7 @@ medium: "Sedang" small: "Kecil" generateAccessToken: "Buat token akses" permission: "Izin" +adminPermission: "Wewenang Izin Admin" enableAll: "Aktifkan semua" disableAll: "Nonaktifkan semua" tokenRequested: "Berikan ijin akses ke akun" @@ -1038,6 +1041,7 @@ resetPasswordConfirm: "Yakin untuk mereset kata sandimu?" sensitiveWords: "Kata sensitif" sensitiveWordsDescription: "Visibilitas dari semua catatan mengandung kata yang telah diatur akan dijadikan \"Beranda\" secara otomatis. Kamu dapat mendaftarkan kata tersebut lebih dari satu dengan menuliskannya di baris baru." sensitiveWordsDescription2: "Menggunakan spasi akan membuat ekspresi AND dan kata kunci disekitarnya dengan garis miring akan mengubahnya menjadi ekspresi reguler." +prohibitedWords: "Kata yang dilarang" prohibitedWordsDescription2: "Menggunakan spasi akan membuat ekspresi AND dan kata kunci disekitarnya dengan garis miring akan mengubahnya menjadi ekspresi reguler." hiddenTags: "Tagar tersembunyi" hiddenTagsDescription: "Pilih tanda yang mana akan tidak diperlihatkan dalam daftar tren.\nTanda lebih dari satu dapat didaftarkan dengan tiap baris." @@ -1057,6 +1061,8 @@ limitWidthOfReaction: "Batasi lebar maksimum reaksi dan tampilkan dalam ukuran t noteIdOrUrl: "ID catatan atau URL" video: "Video" videos: "Video" +audio: "Suara" +audioFiles: "Berkas Suara" dataSaver: "Penghemat data" accountMigration: "Pemindahan akun" accountMoved: "Pengguna ini telah berpindah ke akun baru:" @@ -1160,6 +1166,7 @@ hideRepliesToOthersInTimelineAll: "Sembuyikan balasan ke lainnya dari semua oran confirmShowRepliesAll: "Operasi ini tidak dapat diubah. Apakah kamu yakin untuk menampilkan balasan ke lainnya dari semua orang yang kamu ikuti di lini masa?" confirmHideRepliesAll: "Operasi ini tidak dapat diubah. Apakah kamu yakin untuk menyembunyikan balasan ke lainnya dari semua orang yang kamu ikuti di lini masa?" externalServices: "Layanan eksternal" +sourceCode: "Sumber kode" impressum: "Impressum" impressumUrl: "Tautan Impressum" impressumDescription: "Pada beberapa negara seperti Jerman, inklusi dari informasi kontak operator (sebuah Impressum) diperlukan secara legal untuk situs web komersil." @@ -1191,10 +1198,21 @@ addMfmFunction: "Tambahkan dekorasi" enableQuickAddMfmFunction: "Tampilkan pemilih MFM tingkat lanjut" bubbleGame: "Bubble Game" sfx: "Efek Suara" +soundWillBePlayed: "Suara yang akan dimainkan" +showReplay: "Lihat tayangan ulang" +replay: "Tayangan ulang" +replaying: "Menayangkan Ulang" +ranking: "Peringkat" lastNDays: "{n} hari terakhir" backToTitle: "Ke Judul" +hemisphere: "Letak kamu tinggal" +withSensitive: "Lampirkan catatan dengan berkas sensitif" +userSaysSomethingSensitive: "Postingan oleh {name} mengandung konten sensitif" +enableHorizontalSwipe: "Geser untuk mengganti tab" _bubbleGame: howToPlay: "Cara bermain" + _howToPlay: + section1: "Atur posisi dan jatuhkan obyek ke dalam kotak." _announcement: forExistingUsers: "Hanya pengguna yang telah ada" forExistingUsersDescription: "Pengumuman ini akan dimunculkan ke pengguna yang sudah ada dari titik waktu publikasi jika dinyalakan. Apabila dimatikan, mereka yang baru mendaftar setelah publikasi ini akan juga melihatnya." @@ -1256,6 +1274,8 @@ _initialTutorial: note: "Baru aja makan donat berlapis coklat 🍩😋" _howToMakeAttachmentsSensitive: title: "Bagaimana menandai lampiran sebagai sensitif?" + _done: + title: "Kamu telah menyelesaikan tutorial! 🎉" _serverRules: description: "Daftar peraturan akan ditampilkan sebelum pendaftaran. Mengatur ringkasan dari Syarat dan Ketentuan sangat direkomendasikan." _serverSettings: @@ -1900,6 +1920,55 @@ _permissions: "write:flash": "Sunting Play" "read:flash-likes": "Lihat daftar Play yang disukai" "write:flash-likes": "Sunting daftar Play yang disukai" + "read:admin:abuse-user-reports": "Lihat laporan pengguna" + "write:admin:delete-account": "Hapus akun pengguna" + "write:admin:delete-all-files-of-a-user": "Hapus semua berkas dari seorang pengguna" + "read:admin:index-stats": "Lihat statistik indeks basis data" + "read:admin:table-stats": "Lihat statistik tabel basis data" + "read:admin:user-ips": "Lihat alamat IP pengguna" + "read:admin:meta": "Lihat metadata instansi" + "write:admin:reset-password": "Atur ulang kata sandi pengguna" + "write:admin:resolve-abuse-user-report": "Selesaikan laporan pengguna" + "write:admin:send-email": "Mengirim surel" + "read:admin:server-info": "Lihat informasi peladen" + "read:admin:show-moderation-log": "Lihat log moderasi" + "read:admin:show-user": "Lihat informasi pengguna privat" + "read:admin:show-users": "Lihat informasi pengguna privat" + "write:admin:suspend-user": "Tangguhkan pengguna" + "write:admin:unset-user-avatar": "Hapus avatar pengguna" + "write:admin:unset-user-banner": "Hapus banner pengguna" + "write:admin:unsuspend-user": "Batalkan penangguhan pengguna" + "write:admin:meta": "Kelola metadata instansi" + "write:admin:user-note": "Kelola moderasi catatan" + "write:admin:roles": "Kelola peran" + "read:admin:roles": "Lihat peran" + "write:admin:relays": "Kelola relay" + "read:admin:relays": "Lihat relay" + "write:admin:invite-codes": "Kelola kode undangan" + "read:admin:invite-codes": "Lihat kode undangan" + "write:admin:announcements": "Kelola pengumuman" + "read:admin:announcements": "Lihat Pengumuman" + "write:admin:avatar-decorations": "Kelola dekorasi avatar" + "read:admin:avatar-decorations": "Lihat dekorasi avatar" + "write:admin:federation": "Kelola data federasi" + "write:admin:account": "Kelola akun pengguna" + "read:admin:account": "Lihat akun pengguna" + "write:admin:emoji": "Kelola emoji" + "read:admin:emoji": "Lihat emoji" + "write:admin:queue": "Kelola antrian kerja" + "read:admin:queue": "Lihat informasi antrian kerja" + "write:admin:promo": "Kelola catatan promosi" + "write:admin:drive": "Kelola drive pengguna" + "read:admin:drive": "Kelola informasi drive pengguna" + "read:admin:stream": "Gunakan API WebSocket untuk Admin" + "write:admin:ad": "Kelola iklan" + "read:admin:ad": "Lihat iklan" + "write:invite-codes": "Membuat kode undangan" + "read:invite-codes": "Mendapatkan kode undangan" + "write:clip-favorite": "Kelola klip yang difavoritkan" + "read:clip-favorite": "Lihat klip yang difavoritkan" + "read:federation": "Mendapatkan data federasi" + "write:report-abuse": "Melaporkan pelanggaran" _auth: shareAccessTitle: "Mendapatkan ijin akses aplikasi" shareAccess: "Apakah kamu ingin mengijinkan \"{name}\" untuk mengakses akun ini?" @@ -1954,6 +2023,7 @@ _widgets: _userList: chooseList: "Pilih daftar" clicker: "Pengeklik" + birthdayFollowings: "Pengguna yang merayakan hari ulang tahunnya hari ini" _cw: hide: "Sembunyikan" show: "Lihat konten" @@ -2320,6 +2390,41 @@ _dataSaver: _code: title: "Penyorotan kode" description: "Jika notasi penyorotan kode digunakan di MFM, dll. Fungsi tersebut tidak akan dimuat apabila tidak diketuk. Penyorotan sintaks membutuhkan pengunduhan berkas definisi penyorotan untuk setiap bahasa pemrograman. Oleh sebab itu, menonaktifkan pemuatan otomatis dari berkas ini dilakukan untuk mengurangi jumlah komunikasi data." +_hemisphere: + N: "Bumi belahan utara" + S: "Bumi belahan selatan" + caption: "Digunakan dalam beberapa pengaturan klien untuk menentukan musim." _reversi: + reversi: "Reversi" + gameSettings: "Pengaturan permainan" + chooseBoard: "Pilih papan" + blackOrWhite: "Hitam/Putih" + blackIs: "{name} bermain sebagai Hitam" + rules: "Aturan" + thisGameIsStartedSoon: "Permainan akan segera dimulai" + waitingForOther: "Menunggu langkah giliran dari lawan" + waitingForMe: "Menungguh langkah giliran dari kamu" + waitingBoth: "Bersiap" + ready: "Siap" + cancelReady: "Belum siap" + opponentTurn: "Giliran lawan" + myTurn: "Giliran kamu" + turnOf: "Giliran {name}" + pastTurnOf: "Giliran {name}" + surrender: "Menyerah" + surrendered: "Telah menyerah" + timeout: "Waktu habis" + drawn: "Seri" + won: "{name} menang" + black: "Hitam" + white: "Putih" total: "Jumlah" + turnCount: "Langkah ke {count}" + myGames: "Rondeku" + allGames: "Semua ronde" + ended: "Selesai" + playing: "Sedang bermain" + isLlotheo: "Pemain dengan batu yang sedikit menang (Llotheo)" + loopedMap: "Peta melingkar" + canPutEverywhere: "Keping dapat ditaruh dimana saja" diff --git a/locales/index.d.ts b/locales/index.d.ts index 8f4c9d18e4..1bc99ab849 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -3980,6 +3980,10 @@ export interface Locale extends ILocale { * Misskeyは{host}が使用している無料のソフトウェアです。これからも開発を続けられるように、ぜひ寄付をお願いします! */ "pleaseDonate": ParameterizedString<"host">; + /** + * 対応するソースコードは{anchor}から利用可能です。 + */ + "correspondingSourceIsAvailable": ParameterizedString<"anchor">; /** * ロール */ @@ -4684,6 +4688,34 @@ export interface Locale extends ILocale { * 外部サービス */ "externalServices": string; + /** + * ソースコード + */ + "sourceCode": string; + /** + * ソースコードはまだ提供されていません。この問題の修正について管理者に問い合わせてください。 + */ + "sourceCodeIsNotYetProvided": string; + /** + * リポジトリURL + */ + "repositoryUrl": string; + /** + * ソースコードが公開されているリポジトリがある場合、そのURLを記入します。Misskeyを現状のまま(ソースコードにいかなる変更も加えずに)使用している場合は https://github.com/misskey-dev/misskey と記入します。 + */ + "repositoryUrlDescription": string; + /** + * リポジトリを公開していない場合、代わりにtarballを提供する必要があります。詳細は.config/example.ymlを参照してください。 + */ + "repositoryUrlOrTarballRequired": string; + /** + * フィードバック + */ + "feedback": string; + /** + * フィードバックURL + */ + "feedbackUrl": string; /** * 運営者情報 */ @@ -6813,6 +6845,14 @@ export interface Locale extends ILocale { * ソースコード */ "source": string; + /** + * オリジナル + */ + "original": string; + /** + * {name}はオリジナルのMisskeyを改変したバージョンを使用しています。 + */ + "thisIsModifiedVersion": ParameterizedString<"name">; /** * Misskeyを翻訳 */ diff --git a/locales/it-IT.yml b/locales/it-IT.yml index f344ca39ef..378036af6d 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -1167,6 +1167,7 @@ hideRepliesToOthersInTimelineAll: "Nascondi le risposte dei tuoi follow nella TL confirmShowRepliesAll: "Questa è una attività irreversibile. Vuoi davvero includere tutte le risposte dei following in TL?" confirmHideRepliesAll: "Questa è una attività irreversibile. Vuoi davvero escludere tutte le risposte dei following in TL?" externalServices: "Servizi esterni" +sourceCode: "Codice sorgente" impressum: "Dichiarazione di proprietà" impressumUrl: "URL della dichiarazione di proprietà" impressumDescription: "La dichiarazione di proprietà, è obbligatoria in alcuni paesi come la Germania (Impressum)." diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 5348502425..5993ec80d0 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -991,6 +991,7 @@ neverShow: "今後表示しない" remindMeLater: "また後で" didYouLikeMisskey: "Misskeyを気に入っていただけましたか?" pleaseDonate: "Misskeyは{host}が使用している無料のソフトウェアです。これからも開発を続けられるように、ぜひ寄付をお願いします!" +correspondingSourceIsAvailable: "対応するソースコードは{anchor}から利用可能です。" roles: "ロール" role: "ロール" noRole: "ロールはありません" @@ -1167,6 +1168,13 @@ hideRepliesToOthersInTimelineAll: "TLに現在フォロー中の人全員の返 confirmShowRepliesAll: "この操作は元に戻せません。本当にTLに現在フォロー中の人全員の返信を含めるようにしますか?" confirmHideRepliesAll: "この操作は元に戻せません。本当にTLに現在フォロー中の人全員の返信を含めないようにしますか?" externalServices: "外部サービス" +sourceCode: "ソースコード" +sourceCodeIsNotYetProvided: "ソースコードはまだ提供されていません。この問題の修正について管理者に問い合わせてください。" +repositoryUrl: "リポジトリURL" +repositoryUrlDescription: "ソースコードが公開されているリポジトリがある場合、そのURLを記入します。Misskeyを現状のまま(ソースコードにいかなる変更も加えずに)使用している場合は https://github.com/misskey-dev/misskey と記入します。" +repositoryUrlOrTarballRequired: "リポジトリを公開していない場合、代わりにtarballを提供する必要があります。詳細は.config/example.ymlを参照してください。" +feedback: "フィードバック" +feedbackUrl: "フィードバックURL" impressum: "運営者情報" impressumUrl: "運営者情報URL" impressumDescription: "ドイツなどの一部の国と地域では表示が義務付けられています(Impressum)。" @@ -1778,6 +1786,8 @@ _aboutMisskey: contributors: "コントリビューター" allContributors: "全てのコントリビューター" source: "ソースコード" + original: "オリジナル" + thisIsModifiedVersion: "{name}はオリジナルのMisskeyを改変したバージョンを使用しています。" translation: "Misskeyを翻訳" donate: "Misskeyに寄付" morePatrons: "他にも多くの方が支援してくれています。ありがとうございます🥰" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index c676bf4fdb..4b5f98e803 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -1165,6 +1165,7 @@ hideRepliesToOthersInTimelineAll: "タイムラインに今フォローしとる confirmShowRepliesAll: "これは元に戻せへんから慎重に決めてや。本当にタイムラインに今フォローしとる全員の返信を入れるか?" confirmHideRepliesAll: "これは元に戻せへんから慎重に決めてや。本当にタイムラインに今フォローしとる全員の返信を入れへんのか?" externalServices: "他のサイトのサービス" +sourceCode: "ソースコード" impressum: "運営者の情報" impressumUrl: "運営者の情報URL" impressumDescription: "ドイツとかの一部んところではな、表示が義務付けられてんねん(Impressum)。" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 1231209b36..c4646b6a86 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1041,6 +1041,8 @@ resetPasswordConfirm: "비밀번호를 재설정하시겠습니까?" sensitiveWords: "민감한 단어" sensitiveWordsDescription: "설정한 단어가 포함된 노트의 공개 범위를 '홈'으로 강제합니다. 개행으로 구분하여 여러 개를 지정할 수 있습니다." sensitiveWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다." +prohibitedWords: "금지 워드" +prohibitedWordsDescription: "설정된 워드가 포함되는 노트를 작성하려고 하면, 에러가 발생하도록 합니다. 줄바꿈으로 구분지어 복수 설정할 수 있습니다." prohibitedWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다." hiddenTags: "숨긴 해시태그" hiddenTagsDescription: "설정한 태그를 트렌드에 표시하지 않도록 합니다. 줄 바꿈으로 하나씩 나눠서 설정할 수 있습니다." @@ -1165,6 +1167,7 @@ hideRepliesToOthersInTimelineAll: "타임라인에 현재 팔로우 중인 사 confirmShowRepliesAll: "이 조작은 되돌릴 수 없습니다. 정말로 타임라인에 현재 팔로우 중인 사람 전원의 답글이 나오게 하시겠습니까?" confirmHideRepliesAll: "이 조작은 되돌릴 수 없습니다. 정말로 타임라인에 현재 팔로우 중인 사람 전원의 답글이 나오지 않게 하시겠습니까?" externalServices: "외부 서비스" +sourceCode: "소스 코드" impressum: "운영자 정보" impressumUrl: "운영자 정보 URL" impressumDescription: "독일 등의 일부 나라와 지역에서는 꼭 표시해야 합니다(Impressum)." diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index 9de413eb3b..99eb1f3028 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -871,6 +871,7 @@ youFollowing: "Śledzeni" icon: "Awatar" replies: "Odpowiedz" renotes: "Udostępnij" +sourceCode: "Kod źródłowy" flip: "Odwróć" _role: priority: "Priorytet" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index d014b7fc25..6141eba5f0 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -1082,6 +1082,7 @@ icon: "Аватар" replies: "Ответы" renotes: "Репост" loadReplies: "Показать ответы" +sourceCode: "Исходный код" flip: "Переворот" lastNDays: "Последние {n} сут" _initialAccountSetting: diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index 7856809bf8..251496b10b 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -919,6 +919,7 @@ youFollowing: "Sledované" icon: "Avatar" replies: "Odpovedať" renotes: "Preposlať" +sourceCode: "Zdrojový kód" flip: "Preklopiť" lastNDays: "Posledných {n} dní" _role: diff --git a/locales/th-TH.yml b/locales/th-TH.yml index 90a3e93d64..b5a54a39ec 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -1041,6 +1041,8 @@ resetPasswordConfirm: "รีเซ็ตรหัสผ่านของคุ sensitiveWords: "คำที่มีเนื้อหาละเอียดอ่อน" sensitiveWordsDescription: "การเปิดเผยโน้ตทั้งหมดที่มีคำที่กำหนดค่าไว้จะถูกตั้งค่าเป็น \"หน้าแรก\" โดยอัตโนมัติ คุณยังสามารถแสดงหลายรายการได้โดยแยกรายการโดยใช้ตัวแบ่งบรรทัดได้นะ" sensitiveWordsDescription2: "การใช้ช่องว่างนั้นอาจจะสร้างนิพจน์ AND และคำหลักที่มีเครื่องหมายทับล้อมรอบจะเปลี่ยนเป็นนิพจน์ทั่วไปนะ" +prohibitedWords: "คำต้องห้าม" +prohibitedWordsDescription: "จะแจ้งเตือนว่าเกิดข้อผิดพลาดเมื่อพยายามโพสต์โน้ตที่มีคำที่กำหนดไว้ สามารถตั้งได้หลายคำด้วยการขึ้นบรรทัดใหม่" prohibitedWordsDescription2: "การใช้ช่องว่างนั้นอาจจะสร้างนิพจน์ AND และคำหลักที่มีเครื่องหมายทับล้อมรอบจะเปลี่ยนเป็นนิพจน์ทั่วไปนะ" hiddenTags: "แฮชแท็กที่ซ่อนอยู่" hiddenTagsDescription: "เลือกแท็กที่จะไม่แสดงในรายการเทรนด์ สามารถลงทะเบียนหลายแท็กได้โดยขึ้นบรรทัดใหม่" @@ -1165,6 +1167,7 @@ hideRepliesToOthersInTimelineAll: "ซ่อนตอบกลับจากท confirmShowRepliesAll: "การดำเนินการนี้ไม่สามารถย้อนกลับได้ คุณต้องการแสดงการตอบกลับผู้อื่นจากผู้ใช้ทุกคนที่คุณติดตามอยู่ในไทม์ไลน์ของคุณหรือไม่?" confirmHideRepliesAll: "การดำเนินการนี้ไม่สามารถย้อนกลับได้ คุณต้องการซ่อนการตอบกลับผู้อื่นจากผู้ใช้ทุกคนที่คุณติดตามอยู่ในไทม์ไลน์ของคุณหรือไม่?" externalServices: "บริการภายนอก" +sourceCode: "ซอร์สโค้ด" impressum: "อิมเพรสชั่น" impressumUrl: "URL อิมเพรสชั่น" impressumDescription: "การติดป้ายกำกับ (Impressum) มีผลบังคับใช้ในบางประเทศและภูมิภาค เช่น ประเทศเยอรมนี" @@ -1203,6 +1206,9 @@ replaying: "กำลังรีเพลย์" ranking: "อันดับ" lastNDays: "ล่าสุด {n} วันที่แล้ว" backToTitle: "กลับไปหน้าไตเติ้ล" +hemisphere: "พื้นที่ที่อาศัยอยู่" +withSensitive: "แสดงโน้ตที่มีไฟล์ที่ระบุว่ามีเนื้อหาละเอียดอ่อน" +userSaysSomethingSensitive: "โพสต์ที่มีไฟล์เนื้อหาละเอียดอ่อนของ {name}" enableHorizontalSwipe: "ปัดเพื่อสลับแท็บ" _bubbleGame: howToPlay: "วิธีเล่น" @@ -2439,6 +2445,53 @@ _dataSaver: _code: title: "ไฮไลต์โค้ด" description: "หากใช้สัญลักษณ์ไฮไลต์โค้ดใน MFM ฯลฯ สัญลักษณ์เหล่านั้นจะไม่โหลดจนกว่าจะแตะ การไฮไลต์ไวยากรณ์(syntax)จำเป็นต้องดาวน์โหลดไฟล์คำจำกัดความของไฮไลต์สำหรับแต่ละภาษา ดังนั้นการปิดใช้งานการโหลดไฟล์เหล่านี้โดยอัตโนมัติจึงคาดว่าจะช่วยลดปริมาณข้อมูลการสื่อสารได้" +_hemisphere: + N: "ซีกโลกเหนือ" + S: "ซีกโลกใต้" + caption: "ใช้เพื่อกำหนดฤดูกาลของไคลเอ็นต์" _reversi: + reversi: "รีเวอร์ซี" + gameSettings: "ตั้งค่าการเล่น" + chooseBoard: "เลือกกระดาน" + blackOrWhite: "ดำ/ขาว" + blackIs: "{name}เป็นสีดำ" + rules: "กฎ" + thisGameIsStartedSoon: "การเล่นจะเริ่มแล้ว" + waitingForOther: "กำลังรออีกฝ่ายเตรียมตัวให้เสร็จ" + waitingForMe: "กำลังรอฝ่ายคุณเตรียมตัวให้เสร็จ" + waitingBoth: "กรุณาเตรียมตัว" + ready: "เตรียมตัวพร้อมแล้ว" + cancelReady: "ยกเลิกการเตรียมตัวพร้อม" + opponentTurn: "ตาอีกฝ่าย" + myTurn: "ตาของคุณ" + turnOf: "ตาของ{name}" + pastTurnOf: "ตาของ{name}" + surrender: "ยอมแพ้" + surrendered: "ยอมแพ้แล้ว" + timeout: "หมดเวลาแล้ว" + drawn: "เสมอ" + won: "{name}ชนะ" + black: "ดำ" + white: "ขาว" total: "รวมทั้งหมด" + turnCount: "ตาที่{count}" + myGames: "การเล่นของตัวเอง" + allGames: "การเล่นของทุกคน" + ended: "จบ" + playing: "กำลังเล่น" + isLlotheo: "คนที่มีตัวหมากน้อยกว่าชนะ (Roseo)" + loopedMap: "ลูปแมป" + canPutEverywhere: "โหมดที่สามารถวางได้ทุกที่" + timeLimitForEachTurn: "จำกัดเวลาต่อแต่ละตา" + freeMatch: "ฟรีแมตช์" + lookingForPlayer: "กำลังมองหาคู่ต่อสู้อยู่" + gameCanceled: "ยกเลิกการเล่นแล้ว" + shareToTlTheGameWhenStart: "โพสต์ลงไทม์ไลน์เมื่อเริ่มการเล่น" + iStartedAGame: "เริ่มเล่นหมากรีเวอร์ซีแล้ว! #MisskeyReversi" + opponentHasSettingsChanged: "อีกฝ่ายเปลี่ยนการตั้งค่า" + allowIrregularRules: "อนุญาตกฎที่ไม่ปรกติ (โหมดฟรีทุกอย่าง)" + disallowIrregularRules: "ไม่อนุญาตกฎที่ไม่ปรกติ" +_offlineScreen: + title: "ออฟไลน์ - ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ได้" + header: "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ได้" diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 7679ad56d7..df36f43c06 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -911,6 +911,7 @@ youFollowing: "Підписки" icon: "Аватар" replies: "Відповісти" renotes: "Поширити" +sourceCode: "Вихідний код" flip: "Перевернути" lastNDays: "Останні {n} днів" _achievements: diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index 6de15fc11f..7cfdde3204 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -1045,6 +1045,7 @@ 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" verifiedLink: "Chúng tôi đã xác nhận bạn là chủ sở hữu của đường dẫn này" +sourceCode: "Mã nguồn" flip: "Lật" lastNDays: "{n} ngày trước" _announcement: diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 4a36e30db8..d0891f0678 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -11,7 +11,7 @@ password: "密码" forgotPassword: "忘记密码" fetchingAsApObject: "在联邦宇宙查询中..." ok: "OK" -gotIt: "我明白了" +gotIt: "好" cancel: "取消" noThankYou: "不用,谢谢" enterUsername: "输入用户名" @@ -1041,6 +1041,7 @@ resetPasswordConfirm: "确定重置密码?" sensitiveWords: "敏感词" sensitiveWordsDescription: "将包含设置词的帖子的可见范围设置为首页。可以通过用换行符分隔来设置多个。" sensitiveWordsDescription2: "AND 条件用空格分隔,正则表达式用斜线包裹。" +prohibitedWords: "禁用词" prohibitedWordsDescription2: "AND 条件用空格分隔,正则表达式用斜线包裹。" hiddenTags: "隐藏标签" hiddenTagsDescription: "设定的标签将不会在时间线上显示。可使用换行来设置多个标签。" @@ -1165,6 +1166,7 @@ hideRepliesToOthersInTimelineAll: "在时间线中隐藏现在关注的所有人 confirmShowRepliesAll: "此操作不可撤销。确认要在时间线中包含现在关注的所有人的回复吗?" confirmHideRepliesAll: "此操作不可撤销。确认要在时间线中隐藏现在关注的所有人的回复吗?" externalServices: "外部服务" +sourceCode: "源代码" impressum: "运营商信息" impressumUrl: "运营商信息地址" impressumDescription: "德国等国家和地区有义务展示此类信息(Impressum)。" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 3e3a1a14ee..2762a612f5 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -66,7 +66,7 @@ showMore: "載入更多" showLess: "關閉" youGotNewFollower: "您有新的追隨者" receiveFollowRequest: "您有新的追隨請求" -followRequestAccepted: "追隨請求已接受" +followRequestAccepted: "追隨請求已被接受" mention: "提及" mentions: "提及" directNotes: "私訊" @@ -604,7 +604,7 @@ inboxUrl: "收件夾URL" addedRelays: "已加入的中繼器" serviceworkerInfo: "如要使用推播通知,需要啟用此選項並設定金鑰。" deletedNote: "已刪除的貼文" -invisibleNote: "私密的貼文" +invisibleNote: "私人貼文" enableInfiniteScroll: "啟用自動滾動頁面模式" visibility: "可見性" poll: "票選活動" @@ -1167,6 +1167,7 @@ hideRepliesToOthersInTimelineAll: "在時間軸不包含追隨中所有人的回 confirmShowRepliesAll: "進行此操作後無法復原。您真的希望時間軸「包含」您目前追隨的所有人的回覆嗎?" confirmHideRepliesAll: "進行此操作後無法復原。您真的希望時間軸「不包含」您目前追隨的所有人的回覆嗎?" externalServices: "外部服務" +sourceCode: "原始碼" impressum: "營運者資訊" impressumUrl: "營運者資訊網址" impressumDescription: "在德國與部份地區必須要明確顯示營運者資訊。" @@ -1195,7 +1196,7 @@ overwriteContentConfirm: "確定要覆蓋目前的內容嗎?" seasonalScreenEffect: "隨季節變換畫面的呈現" decorate: "設置頭像裝飾" addMfmFunction: "插入MFM功能語法" -enableQuickAddMfmFunction: "顯示高級MFM選擇器" +enableQuickAddMfmFunction: "顯示高級 MFM 選擇器" bubbleGame: "氣泡遊戲" sfx: "音效" soundWillBePlayed: "將播放音效" @@ -2096,7 +2097,7 @@ _poll: deadlineTime: "小時" duration: "時長" votesCount: "{n} 票" - totalVotes: "一共{n}票" + totalVotes: "合計 {n} 票" vote: "投票" showResult: "顯示結果" voted: "已投票" diff --git a/package.json b/package.json index c670e232e4..3f94448db7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2024.2.0-beta.11", + "version": "2024.2.0", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/backend/migration/1707808106310-MakeRepositoryUrlNullable.js b/packages/backend/migration/1707808106310-MakeRepositoryUrlNullable.js new file mode 100644 index 0000000000..335b14976c --- /dev/null +++ b/packages/backend/migration/1707808106310-MakeRepositoryUrlNullable.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class MakeRepositoryUrlNullable1707808106310 { + name = 'MakeRepositoryUrlNullable1707808106310' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "repositoryUrl" DROP NOT NULL`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "repositoryUrl" SET NOT NULL`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index ee1bf676cb..86a52faa05 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -185,13 +185,12 @@ "@jest/globals": "29.7.0", "@misskey-dev/eslint-plugin": "1.0.0", "@nestjs/platform-express": "10.3.1", - "@simplewebauthn/typescript-types": "8.3.4", + "@simplewebauthn/types": "9.0.1", "@swc/jest": "0.2.31", "@types/accepts": "1.3.7", "@types/archiver": "6.0.2", "@types/bcryptjs": "2.4.6", "@types/body-parser": "1.19.5", - "@types/cbor": "6.0.0", "@types/color-convert": "2.0.3", "@types/content-disposition": "0.5.8", "@types/fluent-ffmpeg": "2.1.24", @@ -218,7 +217,6 @@ "@types/rename": "1.0.7", "@types/sanitize-html": "2.9.5", "@types/semver": "7.5.6", - "@types/sharp": "0.32.0", "@types/simple-oauth2": "5.0.7", "@types/sinonjs__fake-timers": "8.1.5", "@types/tinycolor2": "1.4.6", diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 308da4481b..0ca1fa55c1 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -57,6 +57,8 @@ type Source = { scope?: 'local' | 'global' | string[]; }; + publishTarballInsteadOfProvideRepositoryUrl?: boolean; + proxy?: string; proxySmtp?: string; proxyBypassHosts?: string[]; @@ -145,6 +147,7 @@ export type Config = { signToActivityPubGet: boolean | undefined; version: string; + publishTarballInsteadOfProvideRepositoryUrl: boolean; host: string; hostname: string; scheme: string; @@ -209,6 +212,7 @@ export function loadConfig(): Config { return { version, + publishTarballInsteadOfProvideRepositoryUrl: !!config.publishTarballInsteadOfProvideRepositoryUrl, url: url.origin, port: config.port ?? parseInt(process.env.PORT ?? '', 10), socket: config.socket, diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts index ef3abec191..263df56476 100644 --- a/packages/backend/src/core/CacheService.ts +++ b/packages/backend/src/core/CacheService.ts @@ -16,10 +16,10 @@ import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() export class CacheService implements OnApplicationShutdown { - public userByIdCache: MemoryKVCache; - public localUserByNativeTokenCache: MemoryKVCache; + public userByIdCache: MemoryKVCache; + public localUserByNativeTokenCache: MemoryKVCache; public localUserByIdCache: MemoryKVCache; - public uriPersonCache: MemoryKVCache; + public uriPersonCache: MemoryKVCache; public userProfileCache: RedisKVCache; public userMutingsCache: RedisKVCache>; public userBlockingCache: RedisKVCache>; @@ -56,41 +56,10 @@ export class CacheService implements OnApplicationShutdown { ) { //this.onMessage = this.onMessage.bind(this); - const localUserByIdCache = new MemoryKVCache(1000 * 60 * 60 * 6 /* 6h */); - this.localUserByIdCache = localUserByIdCache; - - // ローカルユーザーならlocalUserByIdCacheにデータを追加し、こちらにはid(文字列)だけを追加する - const userByIdCache = new MemoryKVCache(1000 * 60 * 60 * 6 /* 6h */, { - toMapConverter: user => { - if (user.host === null) { - localUserByIdCache.set(user.id, user as MiLocalUser); - return user.id; - } - - return user; - }, - fromMapConverter: userOrId => typeof userOrId === 'string' ? localUserByIdCache.get(userOrId) : userOrId, - }); - this.userByIdCache = userByIdCache; - - this.localUserByNativeTokenCache = new MemoryKVCache(Infinity, { - toMapConverter: user => { - if (user === null) return null; - - localUserByIdCache.set(user.id, user); - return user.id; - }, - fromMapConverter: id => id === null ? null : localUserByIdCache.get(id), - }); - this.uriPersonCache = new MemoryKVCache(Infinity, { - toMapConverter: user => { - if (user === null) return null; - - userByIdCache.set(user.id, user); - return user.id; - }, - fromMapConverter: id => id === null ? null : userByIdCache.get(id), - }); + this.userByIdCache = new MemoryKVCache(Infinity); + this.localUserByNativeTokenCache = new MemoryKVCache(Infinity); + this.localUserByIdCache = new MemoryKVCache(Infinity); + this.uriPersonCache = new MemoryKVCache(Infinity); this.userProfileCache = new RedisKVCache(this.redisClient, 'userProfile', { lifetime: 1000 * 60 * 30, // 30m @@ -160,16 +129,25 @@ export class CacheService implements OnApplicationShutdown { switch (type) { case 'userChangeSuspendedState': case 'remoteUserUpdated': { - const user = await this.usersRepository.findOneByOrFail({ id: body.id }); - this.userByIdCache.set(user.id, user); - for (const [k, v] of this.uriPersonCache.cache.entries()) { - if (v.value === user.id) { - this.uriPersonCache.set(k, user); + const user = await this.usersRepository.findOneBy({ id: body.id }); + if (user == null) { + this.userByIdCache.delete(body.id); + for (const [k, v] of this.uriPersonCache.cache.entries()) { + if (v.value?.id === body.id) { + this.uriPersonCache.delete(k); + } + } + } else { + this.userByIdCache.set(user.id, user); + for (const [k, v] of this.uriPersonCache.cache.entries()) { + if (v.value?.id === user.id) { + this.uriPersonCache.set(k, user); + } + } + if (this.userEntityService.isLocalUser(user)) { + this.localUserByNativeTokenCache.set(user.token!, user); + this.localUserByIdCache.set(user.id, user); } - } - if (this.userEntityService.isLocalUser(user)) { - this.localUserByNativeTokenCache.set(user.token!, user); - this.localUserByIdCache.set(user.id, user); } break; } diff --git a/packages/backend/src/core/HttpRequestService.ts b/packages/backend/src/core/HttpRequestService.ts index b36b9f6e3c..7f3cac7c58 100644 --- a/packages/backend/src/core/HttpRequestService.ts +++ b/packages/backend/src/core/HttpRequestService.ts @@ -14,9 +14,16 @@ import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { StatusError } from '@/misc/status-error.js'; import { bindThis } from '@/decorators.js'; +import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js'; +import type { IObject } from '@/core/activitypub/type.js'; import type { Response } from 'node-fetch'; import type { URL } from 'node:url'; +export type HttpRequestSendOptions = { + throwErrorWhenResponseNotOk: boolean; + validators?: ((res: Response) => void)[]; +}; + @Injectable() export class HttpRequestService { /** @@ -104,6 +111,23 @@ export class HttpRequestService { } } + @bindThis + public async getActivityJson(url: string): Promise { + const res = await this.send(url, { + method: 'GET', + headers: { + Accept: 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"', + }, + timeout: 5000, + size: 1024 * 256, + }, { + throwErrorWhenResponseNotOk: true, + validators: [validateContentTypeSetAsActivityPub], + }); + + return await res.json() as IObject; + } + @bindThis public async getJson(url: string, accept = 'application/json, */*', headers?: Record): Promise { const res = await this.send(url, { @@ -132,17 +156,20 @@ export class HttpRequestService { } @bindThis - public async send(url: string, args: { - method?: string, - body?: string, - headers?: Record, - timeout?: number, - size?: number, - } = {}, extra: { - throwErrorWhenResponseNotOk: boolean; - } = { - throwErrorWhenResponseNotOk: true, - }): Promise { + public async send( + url: string, + args: { + method?: string, + body?: string, + headers?: Record, + timeout?: number, + size?: number, + } = {}, + extra: HttpRequestSendOptions = { + throwErrorWhenResponseNotOk: true, + validators: [], + }, + ): Promise { const timeout = args.timeout ?? 5000; const controller = new AbortController(); @@ -166,6 +193,12 @@ export class HttpRequestService { throw new StatusError(`${res.status} ${res.statusText}`, res.status, res.statusText); } + if (res.ok) { + for (const validator of (extra.validators ?? [])) { + validator(res); + } + } + return res; } } diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index 5c79ac6e05..5014156a5c 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -30,12 +30,12 @@ import { RoleService } from '@/core/RoleService.js'; import { FeaturedService } from '@/core/FeaturedService.js'; import { trackPromise } from '@/misc/promise-tracker.js'; -const FALLBACK = '❤'; +const FALLBACK = '\u2764'; const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16; const legacies: Record = { 'like': '👍', - 'love': '❤', // ここに記述する場合は異体字セレクタを入れない + 'love': '\u2764', // ハート、異体字セレクタを入れない 'laugh': '😆', 'hmm': '🤔', 'surprise': '😮', @@ -120,7 +120,7 @@ export class ReactionService { let reaction = _reaction ?? FALLBACK; if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && (user.host != null))) { - reaction = '❤️'; + reaction = '\u2764'; } else if (_reaction) { const custom = reaction.match(isCustomEmojiRegexp); if (custom) { diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts index 36487373b4..4d11865906 100644 --- a/packages/backend/src/core/WebAuthnService.ts +++ b/packages/backend/src/core/WebAuthnService.ts @@ -26,7 +26,7 @@ import type { PublicKeyCredentialDescriptorFuture, PublicKeyCredentialRequestOptionsJSON, RegistrationResponseJSON, -} from '@simplewebauthn/typescript-types'; +} from '@simplewebauthn/types'; @Injectable() export class WebAuthnService { diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts index 143f89c368..f6b70ead44 100644 --- a/packages/backend/src/core/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts @@ -106,12 +106,12 @@ export class ApDbResolverService implements OnApplicationShutdown { return await this.cacheService.userByIdCache.fetchMaybe( parsed.id, - () => this.usersRepository.findOneBy({ id: parsed.id }).then(x => x ?? undefined), + () => this.usersRepository.findOneBy({ id: parsed.id, isDeleted: false }).then(x => x ?? undefined), ) as MiLocalUser | undefined ?? null; } else { return await this.cacheService.uriPersonCache.fetch( parsed.uri, - () => this.usersRepository.findOneBy({ uri: parsed.uri }), + () => this.usersRepository.findOneBy({ uri: parsed.uri, isDeleted: false }), ) as MiRemoteUser | null; } } @@ -136,8 +136,12 @@ export class ApDbResolverService implements OnApplicationShutdown { if (key == null) return null; + const user = await this.cacheService.findUserById(key.userId).catch(() => null) as MiRemoteUser | null; + if (user == null) return null; + if (user.isDeleted) return null; + return { - user: await this.cacheService.findUserById(key.userId) as MiRemoteUser, + user, key, }; } @@ -151,6 +155,7 @@ export class ApDbResolverService implements OnApplicationShutdown { key: MiUserPublickey | null; } | null> { const user = await this.apPersonService.resolvePerson(uri) as MiRemoteUser; + if (user.isDeleted) return null; const key = await this.publicKeyByUserIdCache.fetch( user.id, diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index 526b04b292..1cc54b6ff6 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -35,6 +35,8 @@ import { ApResolverService } from './ApResolverService.js'; import { ApAudienceService } from './ApAudienceService.js'; import { ApPersonService } from './models/ApPersonService.js'; import { ApQuestionService } from './models/ApQuestionService.js'; +import { CacheService } from '@/core/CacheService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; import type { Resolver } from './ApResolverService.js'; import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove } from './type.js'; @@ -82,6 +84,8 @@ export class ApInboxService { private apPersonService: ApPersonService, private apQuestionService: ApQuestionService, private queueService: QueueService, + private cacheService: CacheService, + private globalEventService: GlobalEventService, ) { this.logger = this.apLoggerService.logger; } @@ -479,6 +483,8 @@ export class ApInboxService { isDeleted: true, }); + this.globalEventService.publishInternalEvent('remoteUserUpdated', { id: actor.id }); + return `ok: queued ${job.name} ${job.id}`; } diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts index 202e07814e..93ac8ce9a7 100644 --- a/packages/backend/src/core/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/activitypub/ApRequestService.ts @@ -14,6 +14,7 @@ import { HttpRequestService } from '@/core/HttpRequestService.js'; import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; import type Logger from '@/logger.js'; +import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js'; type Request = { url: string; @@ -70,7 +71,7 @@ export class ApRequestCreator { url: u.href, method: 'GET', headers: this.#objectAssignWithLcKey({ - 'Accept': 'application/activity+json, application/ld+json', + 'Accept': 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"', 'Date': new Date().toUTCString(), 'Host': new URL(args.url).host, }, args.additionalHeaders), @@ -195,6 +196,9 @@ export class ApRequestService { const res = await this.httpRequestService.send(url, { method: req.request.method, headers: req.request.headers, + }, { + throwErrorWhenResponseNotOk: true, + validators: [validateContentTypeSetAsActivityPub], }); return await res.json(); diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts index db44c042e7..bb3c40f093 100644 --- a/packages/backend/src/core/activitypub/ApResolverService.ts +++ b/packages/backend/src/core/activitypub/ApResolverService.ts @@ -105,7 +105,7 @@ export class Resolver { const object = (this.user ? await this.apRequestService.signedGet(value, this.user) as IObject - : await this.httpRequestService.getJson(value, 'application/activity+json, application/ld+json')) as IObject; + : await this.httpRequestService.getActivityJson(value)) as IObject; if ( Array.isArray(object['@context']) ? diff --git a/packages/backend/src/core/activitypub/LdSignatureService.ts b/packages/backend/src/core/activitypub/LdSignatureService.ts index f958e9d16e..9de184336f 100644 --- a/packages/backend/src/core/activitypub/LdSignatureService.ts +++ b/packages/backend/src/core/activitypub/LdSignatureService.ts @@ -8,6 +8,7 @@ import { Injectable } from '@nestjs/common'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { bindThis } from '@/decorators.js'; import { CONTEXTS } from './misc/contexts.js'; +import { validateContentTypeSetAsJsonLD } from './misc/validator.js'; import type { JsonLdDocument } from 'jsonld'; import type { JsonLd, RemoteDocument } from 'jsonld/jsonld-spec.js'; @@ -133,7 +134,10 @@ class LdSignature { }, timeout: this.loderTimeout, }, - { throwErrorWhenResponseNotOk: false }, + { + throwErrorWhenResponseNotOk: false, + validators: [validateContentTypeSetAsJsonLD], + }, ).then(res => { if (!res.ok) { throw new Error(`${res.status} ${res.statusText}`); diff --git a/packages/backend/src/core/activitypub/misc/validator.ts b/packages/backend/src/core/activitypub/misc/validator.ts new file mode 100644 index 0000000000..690beeffef --- /dev/null +++ b/packages/backend/src/core/activitypub/misc/validator.ts @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { Response } from 'node-fetch'; + +export function validateContentTypeSetAsActivityPub(response: Response): void { + const contentType = (response.headers.get('content-type') ?? '').toLowerCase(); + + if (contentType === '') { + throw new Error('Validate content type of AP response: No content-type header'); + } + if ( + contentType.startsWith('application/activity+json') || + (contentType.startsWith('application/ld+json;') && contentType.includes('https://www.w3.org/ns/activitystreams')) + ) { + return; + } + throw new Error('Validate content type of AP response: Content type is not application/activity+json or application/ld+json'); +} + +const plusJsonSuffixRegex = /^\s*(application|text)\/[a-zA-Z0-9\.\-\+]+\+json\s*(;|$)/; + +export function validateContentTypeSetAsJsonLD(response: Response): void { + const contentType = (response.headers.get('content-type') ?? '').toLowerCase(); + + if (contentType === '') { + throw new Error('Validate content type of JSON LD: No content-type header'); + } + if ( + contentType.startsWith('application/ld+json') || + contentType.startsWith('application/json') || + plusJsonSuffixRegex.test(contentType) + ) { + return; + } + throw new Error('Validate content type of JSON LD: Content type is not application/ld+json or application/json'); +} diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts index e0f7ea6ed9..7f4d1521b5 100644 --- a/packages/backend/src/misc/cache.ts +++ b/packages/backend/src/misc/cache.ts @@ -186,28 +186,14 @@ export class RedisSingleCache { // TODO: メモリ節約のためあまり参照されないキーを定期的に削除できるようにする? -function nothingToDo(value: T): V { - return value as unknown as V; -} - -export class MemoryKVCache { - public cache: Map; +export class MemoryKVCache { + public cache: Map; private lifetime: number; private gcIntervalHandle: NodeJS.Timeout; - private toMapConverter: (value: T) => V; - private fromMapConverter: (cached: V) => T | undefined; - constructor(lifetime: MemoryKVCache['lifetime'], options: { - toMapConverter: (value: T) => V; - fromMapConverter: (cached: V) => T | undefined; - } = { - toMapConverter: nothingToDo, - fromMapConverter: nothingToDo, - }) { + constructor(lifetime: MemoryKVCache['lifetime']) { this.cache = new Map(); this.lifetime = lifetime; - this.toMapConverter = options.toMapConverter; - this.fromMapConverter = options.fromMapConverter; this.gcIntervalHandle = setInterval(() => { this.gc(); @@ -218,7 +204,7 @@ export class MemoryKVCache { public set(key: string, value: T): void { this.cache.set(key, { date: Date.now(), - value: this.toMapConverter(value), + value, }); } @@ -230,7 +216,7 @@ export class MemoryKVCache { this.cache.delete(key); return undefined; } - return this.fromMapConverter(cached.value); + return cached.value; } @bindThis @@ -241,10 +227,9 @@ export class MemoryKVCache { /** * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします - * fetcherの引数はcacheに保存されている値があれば渡されます */ @bindThis - public async fetch(key: string, fetcher: (value: V | undefined) => Promise, validator?: (cachedValue: T) => boolean): Promise { + public async fetch(key: string, fetcher: () => Promise, validator?: (cachedValue: T) => boolean): Promise { const cachedValue = this.get(key); if (cachedValue !== undefined) { if (validator) { @@ -259,7 +244,7 @@ export class MemoryKVCache { } // Cache MISS - const value = await fetcher(this.cache.get(key)?.value); + const value = await fetcher(); this.set(key, value); return value; } @@ -267,10 +252,9 @@ export class MemoryKVCache { /** * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします - * fetcherの引数はcacheに保存されている値があれば渡されます */ @bindThis - public async fetchMaybe(key: string, fetcher: (value: V | undefined) => Promise, validator?: (cachedValue: T) => boolean): Promise { + public async fetchMaybe(key: string, fetcher: () => Promise, validator?: (cachedValue: T) => boolean): Promise { const cachedValue = this.get(key); if (cachedValue !== undefined) { if (validator) { @@ -285,7 +269,7 @@ export class MemoryKVCache { } // Cache MISS - const value = await fetcher(this.cache.get(key)?.value); + const value = await fetcher(); if (value !== undefined) { this.set(key, value); } diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts index dcd6dfeed6..2463ffc8bf 100644 --- a/packages/backend/src/misc/json-schema.ts +++ b/packages/backend/src/misc/json-schema.ts @@ -41,7 +41,17 @@ import { import { packedFlashSchema } from '@/models/json-schema/flash.js'; import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js'; import { packedSigninSchema } from '@/models/json-schema/signin.js'; -import { packedRoleLiteSchema, packedRoleSchema, packedRolePoliciesSchema } from '@/models/json-schema/role.js'; +import { + packedRoleLiteSchema, + packedRoleSchema, + packedRolePoliciesSchema, + packedRoleCondFormulaLogicsSchema, + packedRoleCondFormulaValueNot, + packedRoleCondFormulaValueIsLocalOrRemoteSchema, + packedRoleCondFormulaValueCreatedSchema, + packedRoleCondFormulaFollowersOrFollowingOrNotesSchema, + packedRoleCondFormulaValueSchema, +} from '@/models/json-schema/role.js'; import { packedAdSchema } from '@/models/json-schema/ad.js'; import { packedReversiGameLiteSchema, packedReversiGameDetailedSchema } from '@/models/json-schema/reversi-game.js'; @@ -83,6 +93,12 @@ export const refs = { EmojiDetailedAdmin: packedEmojiDetailedAdminSchema, Flash: packedFlashSchema, Signin: packedSigninSchema, + RoleCondFormulaLogics: packedRoleCondFormulaLogicsSchema, + RoleCondFormulaValueNot: packedRoleCondFormulaValueNot, + RoleCondFormulaValueIsLocalOrRemote: packedRoleCondFormulaValueIsLocalOrRemoteSchema, + RoleCondFormulaValueCreated: packedRoleCondFormulaValueCreatedSchema, + RoleCondFormulaFollowersOrFollowingOrNotes: packedRoleCondFormulaFollowersOrFollowingOrNotesSchema, + RoleCondFormulaValue: packedRoleCondFormulaValueSchema, RoleLite: packedRoleLiteSchema, Role: packedRoleSchema, RolePolicies: packedRolePoliciesSchema, diff --git a/packages/backend/src/models/BubbleGameRecord.ts b/packages/backend/src/models/BubbleGameRecord.ts index fe780122fd..686e39c118 100644 --- a/packages/backend/src/models/BubbleGameRecord.ts +++ b/packages/backend/src/models/BubbleGameRecord.ts @@ -48,7 +48,7 @@ export class MiBubbleGameRecord { @Column('jsonb', { default: [], }) - public logs: any[]; + public logs: number[][]; @Column('boolean', { default: false, diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 63503fbe06..6ed0ec6ce5 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -357,9 +357,9 @@ export class MiMeta { @Column('varchar', { length: 1024, default: 'https://github.com/misskey-dev/misskey', - nullable: false, + nullable: true, }) - public repositoryUrl: string; + public repositoryUrl: string | null; @Column('varchar', { length: 1024, diff --git a/packages/backend/src/models/Role.ts b/packages/backend/src/models/Role.ts index 78ecccee39..fa05ea8637 100644 --- a/packages/backend/src/models/Role.ts +++ b/packages/backend/src/models/Role.ts @@ -69,7 +69,7 @@ type CondFormulaValueNotesMoreThanOrEq = { value: number; }; -export type RoleCondFormulaValue = +export type RoleCondFormulaValue = { id: string } & ( CondFormulaValueAnd | CondFormulaValueOr | CondFormulaValueNot | @@ -82,7 +82,8 @@ export type RoleCondFormulaValue = CondFormulaValueFollowingLessThanOrEq | CondFormulaValueFollowingMoreThanOrEq | CondFormulaValueNotesLessThanOrEq | - CondFormulaValueNotesMoreThanOrEq; + CondFormulaValueNotesMoreThanOrEq +); @Entity('role') export class MiRole { diff --git a/packages/backend/src/models/json-schema/reversi-game.ts b/packages/backend/src/models/json-schema/reversi-game.ts index 566ab8d2cd..cb37200384 100644 --- a/packages/backend/src/models/json-schema/reversi-game.ts +++ b/packages/backend/src/models/json-schema/reversi-game.ts @@ -47,12 +47,12 @@ export const packedReversiGameLiteSchema = { user1: { type: 'object', optional: false, nullable: false, - ref: 'User', + ref: 'UserLite', }, user2: { type: 'object', optional: false, nullable: false, - ref: 'User', + ref: 'UserLite', }, winnerId: { type: 'string', @@ -62,7 +62,7 @@ export const packedReversiGameLiteSchema = { winner: { type: 'object', optional: false, nullable: true, - ref: 'User', + ref: 'UserLite', }, surrenderedUserId: { type: 'string', @@ -165,12 +165,12 @@ export const packedReversiGameDetailedSchema = { user1: { type: 'object', optional: false, nullable: false, - ref: 'User', + ref: 'UserLite', }, user2: { type: 'object', optional: false, nullable: false, - ref: 'User', + ref: 'UserLite', }, winnerId: { type: 'string', @@ -180,7 +180,7 @@ export const packedReversiGameDetailedSchema = { winner: { type: 'object', optional: false, nullable: true, - ref: 'User', + ref: 'UserLite', }, surrenderedUserId: { type: 'string', @@ -226,6 +226,9 @@ export const packedReversiGameDetailedSchema = { items: { type: 'array', optional: false, nullable: false, + items: { + type: 'number', + }, }, }, map: { diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts index 55348d4f3d..ef6b279bee 100644 --- a/packages/backend/src/models/json-schema/role.ts +++ b/packages/backend/src/models/json-schema/role.ts @@ -1,3 +1,129 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export const packedRoleCondFormulaLogicsSchema = { + type: 'object', + properties: { + id: { + type: 'string', optional: false, + }, + type: { + type: 'string', + nullable: false, optional: false, + enum: ['and', 'or'], + }, + values: { + type: 'array', + nullable: false, optional: false, + items: { + ref: 'RoleCondFormulaValue', + }, + }, + }, +} as const; + +export const packedRoleCondFormulaValueNot = { + type: 'object', + properties: { + id: { + type: 'string', optional: false, + }, + type: { + type: 'string', + nullable: false, optional: false, + enum: ['not'], + }, + value: { + type: 'object', + optional: false, + ref: 'RoleCondFormulaValue', + }, + }, +} as const; + +export const packedRoleCondFormulaValueIsLocalOrRemoteSchema = { + type: 'object', + properties: { + id: { + type: 'string', optional: false, + }, + type: { + type: 'string', + nullable: false, optional: false, + enum: ['isLocal', 'isRemote'], + }, + }, +} as const; + +export const packedRoleCondFormulaValueCreatedSchema = { + type: 'object', + properties: { + id: { + type: 'string', optional: false, + }, + type: { + type: 'string', + nullable: false, optional: false, + enum: [ + 'createdLessThan', + 'createdMoreThan', + ], + }, + sec: { + type: 'number', + nullable: false, optional: false, + }, + }, +} as const; + +export const packedRoleCondFormulaFollowersOrFollowingOrNotesSchema = { + type: 'object', + properties: { + id: { + type: 'string', optional: false, + }, + type: { + type: 'string', + nullable: false, optional: false, + enum: [ + 'followersLessThanOrEq', + 'followersMoreThanOrEq', + 'followingLessThanOrEq', + 'followingMoreThanOrEq', + 'notesLessThanOrEq', + 'notesMoreThanOrEq', + ], + }, + value: { + type: 'number', + nullable: false, optional: false, + }, + }, +} as const; + +export const packedRoleCondFormulaValueSchema = { + type: 'object', + oneOf: [ + { + ref: 'RoleCondFormulaLogics', + }, + { + ref: 'RoleCondFormulaValueNot', + }, + { + ref: 'RoleCondFormulaValueIsLocalOrRemote', + }, + { + ref: 'RoleCondFormulaValueCreated', + }, + { + ref: 'RoleCondFormulaFollowersOrFollowingOrNotes', + }, + ], +} as const; + export const packedRolePoliciesSchema = { type: 'object', optional: false, nullable: false, @@ -174,6 +300,7 @@ export const packedRoleSchema = { condFormula: { type: 'object', optional: false, nullable: false, + ref: 'RoleCondFormulaValue', }, isPublic: { type: 'boolean', diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index dae8f26075..c7f86635da 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -3,16 +3,38 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -const notificationRecieveConfig = { +export const notificationRecieveConfig = { type: 'object', - nullable: false, optional: true, - properties: { - type: { - type: 'string', - nullable: false, optional: false, - enum: ['all', 'following', 'follower', 'mutualFollow', 'list', 'never'], + oneOf: [ + { + type: 'object', + nullable: false, + properties: { + type: { + type: 'string', + nullable: false, + enum: ['all', 'following', 'follower', 'mutualFollow', 'never'], + }, + }, + required: ['type'], }, - }, + { + type: 'object', + nullable: false, + properties: { + type: { + type: 'string', + nullable: false, + enum: ['list'], + }, + userListId: { + type: 'string', + format: 'misskey:id', + }, + }, + required: ['type', 'userListId'], + }, + ], } as const; export const packedUserLiteSchema = { @@ -546,15 +568,20 @@ export const packedMeDetailedOnlySchema = { type: 'object', nullable: false, optional: false, properties: { - app: notificationRecieveConfig, - quote: notificationRecieveConfig, - reply: notificationRecieveConfig, - follow: notificationRecieveConfig, - renote: notificationRecieveConfig, - mention: notificationRecieveConfig, - reaction: notificationRecieveConfig, - pollEnded: notificationRecieveConfig, - receiveFollowRequest: notificationRecieveConfig, + note: { optional: true, ...notificationRecieveConfig }, + follow: { optional: true, ...notificationRecieveConfig }, + mention: { optional: true, ...notificationRecieveConfig }, + reply: { optional: true, ...notificationRecieveConfig }, + renote: { optional: true, ...notificationRecieveConfig }, + quote: { optional: true, ...notificationRecieveConfig }, + reaction: { optional: true, ...notificationRecieveConfig }, + pollEnded: { optional: true, ...notificationRecieveConfig }, + receiveFollowRequest: { optional: true, ...notificationRecieveConfig }, + followRequestAccepted: { optional: true, ...notificationRecieveConfig }, + roleAssigned: { optional: true, ...notificationRecieveConfig }, + achievementEarned: { optional: true, ...notificationRecieveConfig }, + app: { optional: true, ...notificationRecieveConfig }, + test: { optional: true, ...notificationRecieveConfig }, }, }, emailNotificationTypes: { diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index 8aa173e97f..edac9b3beb 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -22,7 +22,7 @@ import { WebAuthnService } from '@/core/WebAuthnService.js'; import { UserAuthService } from '@/core/UserAuthService.js'; import { RateLimiterService } from './RateLimiterService.js'; import { SigninService } from './SigninService.js'; -import type { AuthenticationResponseJSON } from '@simplewebauthn/typescript-types'; +import type { AuthenticationResponseJSON } from '@simplewebauthn/types'; import type { FastifyReply, FastifyRequest } from 'fastify'; @Injectable() 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 28b09955d4..b6f0f22d60 100644 --- a/packages/backend/src/server/api/endpoints/admin/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/admin/delete-account.ts @@ -15,9 +15,6 @@ export const meta = { requireCredential: true, requireAdmin: true, kind: 'write:admin:delete-account', - - res: { - }, } as const; export const paramDef = { 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 25d7776936..459d8880fa 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 @@ -84,6 +84,24 @@ export const meta = { properties: { type: 'object', optional: false, nullable: false, + properties: { + width: { + type: 'number', + optional: true, nullable: false, + }, + height: { + type: 'number', + optional: true, nullable: false, + }, + orientation: { + type: 'number', + optional: true, nullable: false, + }, + avgColor: { + type: 'string', + optional: true, nullable: false, + }, + }, }, storedInternal: { type: 'boolean', 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 14942da24a..eb85fca179 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 @@ -18,6 +18,18 @@ export const meta = { res: { type: 'object', optional: false, nullable: false, + additionalProperties: { + type: 'object', + properties: { + count: { + type: 'number', + }, + size: { + type: 'number', + }, + }, + required: ['count', 'size'], + }, example: { migrations: { count: 66, diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index ef569722e0..88c5907bcc 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -429,7 +429,7 @@ export const meta = { }, repositoryUrl: { type: 'string', - optional: false, nullable: false, + optional: false, nullable: true, }, summalyProxy: { type: 'string', 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 1b1af24ac2..45758d4f50 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/users.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/users.ts @@ -17,7 +17,7 @@ export const meta = { tags: ['admin', 'role', 'users'], requireCredential: false, - requireAdmin: true, + requireModerator: true, kind: 'read:admin:roles', errors: { 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 ecb5f347af..5a1c05f41a 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -10,6 +10,7 @@ import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; import { RoleEntityService } from '@/core/entities/RoleEntityService.js'; import { IdService } from '@/core/IdService.js'; +import { notificationRecieveConfig } from '@/models/json-schema/user.js'; export const meta = { tags: ['admin'], @@ -21,6 +22,157 @@ export const meta = { res: { type: 'object', nullable: false, optional: false, + properties: { + email: { + type: 'string', + optional: false, nullable: true, + }, + emailVerified: { + type: 'boolean', + optional: false, nullable: false, + }, + autoAcceptFollowed: { + type: 'boolean', + optional: false, nullable: false, + }, + noCrawle: { + type: 'boolean', + optional: false, nullable: false, + }, + preventAiLearning: { + type: 'boolean', + optional: false, nullable: false, + }, + alwaysMarkNsfw: { + type: 'boolean', + optional: false, nullable: false, + }, + autoSensitive: { + type: 'boolean', + optional: false, nullable: false, + }, + carefulBot: { + type: 'boolean', + optional: false, nullable: false, + }, + injectFeaturedNote: { + type: 'boolean', + optional: false, nullable: false, + }, + receiveAnnouncementEmail: { + type: 'boolean', + optional: false, nullable: false, + }, + mutedWords: { + type: 'array', + optional: false, nullable: false, + items: { + anyOf: [ + { + type: 'string', + }, + { + type: 'array', + items: { + type: 'string', + }, + }, + ], + }, + }, + mutedInstances: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + }, + }, + notificationRecieveConfig: { + type: 'object', + optional: false, nullable: false, + properties: { + note: { optional: true, ...notificationRecieveConfig }, + follow: { optional: true, ...notificationRecieveConfig }, + mention: { optional: true, ...notificationRecieveConfig }, + reply: { optional: true, ...notificationRecieveConfig }, + renote: { optional: true, ...notificationRecieveConfig }, + quote: { optional: true, ...notificationRecieveConfig }, + reaction: { optional: true, ...notificationRecieveConfig }, + pollEnded: { optional: true, ...notificationRecieveConfig }, + receiveFollowRequest: { optional: true, ...notificationRecieveConfig }, + followRequestAccepted: { optional: true, ...notificationRecieveConfig }, + roleAssigned: { optional: true, ...notificationRecieveConfig }, + achievementEarned: { optional: true, ...notificationRecieveConfig }, + app: { optional: true, ...notificationRecieveConfig }, + test: { optional: true, ...notificationRecieveConfig }, + }, + }, + isModerator: { + type: 'boolean', + optional: false, nullable: false, + }, + isSilenced: { + type: 'boolean', + optional: false, nullable: false, + }, + isSuspended: { + type: 'boolean', + optional: false, nullable: false, + }, + isHibernated: { + type: 'boolean', + optional: false, nullable: false, + }, + lastActiveDate: { + type: 'string', + optional: false, nullable: true, + }, + moderationNote: { + type: 'string', + optional: false, nullable: false, + }, + signins: { + type: 'array', + optional: false, nullable: false, + items: { + ref: 'Signin', + }, + }, + policies: { + type: 'object', + optional: false, nullable: false, + ref: 'RolePolicies', + }, + roles: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + ref: 'Role', + }, + }, + roleAssigns: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + properties: { + createdAt: { + type: 'string', + optional: false, nullable: false, + }, + expiresAt: { + type: 'string', + optional: false, nullable: true, + }, + roleId: { + type: 'string', + optional: false, nullable: false, + }, + }, + }, + }, + }, }, } as const; @@ -89,7 +241,7 @@ export default class extends Endpoint { // eslint- isSilenced: isSilenced, isSuspended: user.isSuspended, isHibernated: user.isHibernated, - lastActiveDate: user.lastActiveDate, + lastActiveDate: user.lastActiveDate ? user.lastActiveDate.toISOString() : null, moderationNote: profile.moderationNote ?? '', signins, policies: await this.roleService.getUserPolicies(user.id), 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 51075ed3c6..bffceef815 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -104,8 +104,8 @@ export const paramDef = { swPublicKey: { type: 'string', nullable: true }, swPrivateKey: { type: 'string', nullable: true }, tosUrl: { type: 'string', nullable: true }, - repositoryUrl: { type: 'string' }, - feedbackUrl: { type: 'string' }, + repositoryUrl: { type: 'string', nullable: true }, + feedbackUrl: { type: 'string', nullable: true }, impressumUrl: { type: 'string', nullable: true }, privacyPolicyUrl: { type: 'string', nullable: true }, useObjectStorage: { type: 'boolean' }, @@ -402,7 +402,7 @@ export default class extends Endpoint { // eslint- } if (ps.repositoryUrl !== undefined) { - set.repositoryUrl = ps.repositoryUrl; + set.repositoryUrl = URL.canParse(ps.repositoryUrl!) ? ps.repositoryUrl : null; } if (ps.feedbackUrl !== undefined) { diff --git a/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts b/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts index 398b49ec1b..ab877bbe20 100644 --- a/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts +++ b/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts @@ -24,9 +24,19 @@ export const meta = { type: 'object', optional: false, nullable: false, properties: { - id: { type: 'string', format: 'misskey:id' }, - score: { type: 'integer' }, - user: { ref: 'UserLite' }, + id: { + type: 'string', format: 'misskey:id', + optional: false, nullable: false, + }, + score: { + type: 'integer', + optional: false, nullable: false, + }, + user: { + type: 'object', + optional: true, nullable: false, + ref: 'UserLite', + }, }, }, }, diff --git a/packages/backend/src/server/api/endpoints/bubble-game/register.ts b/packages/backend/src/server/api/endpoints/bubble-game/register.ts index 7314995a1a..0a999e42cd 100644 --- a/packages/backend/src/server/api/endpoints/bubble-game/register.ts +++ b/packages/backend/src/server/api/endpoints/bubble-game/register.ts @@ -29,9 +29,6 @@ export const meta = { id: 'eb627bc7-574b-4a52-a860-3c3eae772b88', }, }, - - res: { - }, } as const; export const paramDef = { @@ -39,7 +36,15 @@ export const paramDef = { properties: { score: { type: 'integer', minimum: 0 }, seed: { type: 'string', minLength: 1, maxLength: 1024 }, - logs: { type: 'array' }, + logs: { + type: 'array', + items: { + type: 'array', + items: { + type: 'number', + }, + }, + }, gameMode: { type: 'string' }, gameVersion: { type: 'integer' }, }, 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 1d73cd1f76..2a30e8b0c3 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts @@ -15,6 +15,19 @@ export const meta = { requireCredential: true, secure: true, + + res: { + type: 'object', + properties: { + backupCodes: { + type: 'array', + optional: false, + items: { + type: 'string', + }, + }, + }, + }, } as const; export const paramDef = { 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 4ef2a90b8e..9391aee5e0 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 @@ -47,7 +47,7 @@ export const meta = { properties: { id: { type: 'string', - nullable: true, + optional: true, }, }, }, diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts index efa541b9ab..91c8597b1b 100644 --- a/packages/backend/src/server/api/endpoints/i/apps.ts +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -21,26 +21,31 @@ export const meta = { properties: { id: { type: 'string', + optional: false, format: 'misskey:id', }, name: { type: 'string', + optional: true, }, createdAt: { type: 'string', + optional: false, format: 'date-time', }, lastUsedAt: { type: 'string', + optional: true, format: 'date-time', }, permission: { type: 'array', + optional: false, uniqueItems: true, items: { - type: 'string' + type: 'string', }, - } + }, }, }, }, 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 dff18d3f45..0b4faf5ef8 100644 --- a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts +++ b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts @@ -23,23 +23,27 @@ export const meta = { id: { type: 'string', format: 'misskey:id', + optional: false, }, name: { type: 'string', + optional: false, }, callbackUrl: { type: 'string', - nullable: true, + optional: false, nullable: true, }, permission: { type: 'array', + optional: false, uniqueItems: true, items: { - type: 'string' + type: 'string', }, }, isAuthorized: { type: 'boolean', + optional: true, }, }, }, 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 ffcf869fcf..d53c390460 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 @@ -22,7 +22,16 @@ export const meta = { res: { type: 'object', - } + properties: { + updatedAt: { + type: 'string', + optional: false, + }, + value: { + optional: false, + }, + }, + }, } as const; export const paramDef = { @@ -50,7 +59,7 @@ export default class extends Endpoint { // eslint- } return { - updatedAt: item.updatedAt, + updatedAt: item.updatedAt.toISOString(), value: item.value, }; }); 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 9b21e22185..3fe339606d 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 @@ -13,6 +13,9 @@ export const meta = { res: { type: 'object', + additionalProperties: { + type: 'string', + }, }, } as const; 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 8bfbe5227c..28f158c62d 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/keys.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/keys.ts @@ -10,6 +10,13 @@ import { RegistryApiService } from '@/core/RegistryApiService.js'; export const meta = { requireCredential: true, kind: 'read:account', + + res: { + type: 'array', + items: { + type: 'string', + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 708b2f103f..bf6c53d8eb 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -33,6 +33,7 @@ import { HttpRequestService } from '@/core/HttpRequestService.js'; import type { Config } from '@/config.js'; import { safeForSql } from '@/misc/safe-for-sql.js'; import { AvatarDecorationService } from '@/core/AvatarDecorationService.js'; +import { notificationRecieveConfig } from '@/models/json-schema/user.js'; import { ApiLoggerService } from '../../ApiLoggerService.js'; import { ApiError } from '../../error.js'; @@ -184,7 +185,26 @@ export const paramDef = { mutedInstances: { type: 'array', items: { type: 'string', } }, - notificationRecieveConfig: { type: 'object' }, + notificationRecieveConfig: { + type: 'object', + nullable: false, + properties: { + note: notificationRecieveConfig, + follow: notificationRecieveConfig, + mention: notificationRecieveConfig, + reply: notificationRecieveConfig, + renote: notificationRecieveConfig, + quote: notificationRecieveConfig, + reaction: notificationRecieveConfig, + pollEnded: notificationRecieveConfig, + receiveFollowRequest: notificationRecieveConfig, + followRequestAccepted: notificationRecieveConfig, + roleAssigned: notificationRecieveConfig, + achievementEarned: notificationRecieveConfig, + app: notificationRecieveConfig, + test: notificationRecieveConfig, + }, + }, emailNotificationTypes: { type: 'array', items: { type: 'string', } }, 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 c02fad449e..535a3ea308 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts @@ -33,7 +33,7 @@ export const meta = { properties: { id: { type: 'string', - format: 'misskey:id' + format: 'misskey:id', }, userId: { type: 'string', @@ -45,7 +45,7 @@ export const meta = { items: { type: 'string', enum: webhookEventTypes, - } + }, }, url: { type: 'string' }, secret: { type: 'string' }, @@ -108,7 +108,7 @@ export default class extends Endpoint { // eslint- url: webhook.url, secret: webhook.secret, active: webhook.active, - latestSentAt: webhook.latestSentAt?.toISOString(), + latestSentAt: webhook.latestSentAt ? webhook.latestSentAt.toISOString() : null, latestStatus: webhook.latestStatus, }; }); 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 0e751989bd..fe07afb2d0 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts @@ -23,7 +23,7 @@ export const meta = { properties: { id: { type: 'string', - format: 'misskey:id' + format: 'misskey:id', }, userId: { type: 'string', @@ -35,7 +35,7 @@ export const meta = { items: { type: 'string', enum: webhookEventTypes, - } + }, }, url: { type: 'string' }, secret: { type: 'string' }, @@ -43,8 +43,8 @@ export const meta = { latestSentAt: { type: 'string', format: 'date-time', nullable: true }, latestStatus: { type: 'integer', nullable: true }, }, - } - } + }, + }, } as const; export const paramDef = { @@ -73,7 +73,7 @@ export default class extends Endpoint { // eslint- url: webhook.url, secret: webhook.secret, active: webhook.active, - latestSentAt: webhook.latestSentAt?.toISOString(), + latestSentAt: webhook.latestSentAt ? webhook.latestSentAt.toISOString() : null, latestStatus: webhook.latestStatus, } )); diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts index f895844e5c..5ddb79caf2 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts @@ -30,7 +30,7 @@ export const meta = { properties: { id: { type: 'string', - format: 'misskey:id' + format: 'misskey:id', }, userId: { type: 'string', @@ -42,7 +42,7 @@ export const meta = { items: { type: 'string', enum: webhookEventTypes, - } + }, }, url: { type: 'string' }, secret: { type: 'string' }, @@ -85,7 +85,7 @@ export default class extends Endpoint { // eslint- url: webhook.url, secret: webhook.secret, active: webhook.active, - latestSentAt: webhook.latestSentAt?.toISOString(), + latestSentAt: webhook.latestSentAt ? webhook.latestSentAt.toISOString() : null, latestStatus: webhook.latestStatus, }; }); diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 6bcd7f6b1f..834158baf4 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -37,6 +37,10 @@ export const meta = { type: 'string', optional: false, nullable: false, }, + providesTarball: { + type: 'boolean', + optional: false, nullable: false, + }, name: { type: 'string', optional: false, nullable: false, @@ -69,12 +73,12 @@ export const meta = { }, repositoryUrl: { type: 'string', - optional: false, nullable: false, + optional: false, nullable: true, default: 'https://github.com/misskey-dev/misskey', }, feedbackUrl: { type: 'string', - optional: false, nullable: false, + optional: false, nullable: true, default: 'https://github.com/misskey-dev/misskey/issues/new', }, defaultDarkTheme: { @@ -352,6 +356,7 @@ export default class extends Endpoint { // eslint- maintainerEmail: instance.maintainerEmail, version: this.config.version, + providesTarball: this.config.publishTarballInsteadOfProvideRepositoryUrl, name: instance.name, shortName: instance.shortName, diff --git a/packages/backend/src/server/api/endpoints/reversi/cancel-match.ts b/packages/backend/src/server/api/endpoints/reversi/cancel-match.ts index 99a2a3078b..dd6f273e01 100644 --- a/packages/backend/src/server/api/endpoints/reversi/cancel-match.ts +++ b/packages/backend/src/server/api/endpoints/reversi/cancel-match.ts @@ -14,9 +14,6 @@ export const meta = { errors: { }, - - res: { - }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/reversi/match.ts b/packages/backend/src/server/api/endpoints/reversi/match.ts index 2d58e7b157..aa8b8a7d72 100644 --- a/packages/backend/src/server/api/endpoints/reversi/match.ts +++ b/packages/backend/src/server/api/endpoints/reversi/match.ts @@ -30,6 +30,9 @@ export const meta = { }, res: { + type: 'object', + optional: true, + ref: 'ReversiGameDetailed', }, } as const; diff --git a/packages/backend/src/server/api/endpoints/test.ts b/packages/backend/src/server/api/endpoints/test.ts index 2c801227ca..9231f0ab94 100644 --- a/packages/backend/src/server/api/endpoints/test.ts +++ b/packages/backend/src/server/api/endpoints/test.ts @@ -18,24 +18,28 @@ export const meta = { properties: { id: { type: 'string', - format: 'misskey:id' + format: 'misskey:id', + optional: true, nullable: false, }, required: { type: 'boolean', + optional: false, nullable: false, }, string: { type: 'string', + optional: true, nullable: false, }, default: { type: 'string', + optional: true, nullable: false, }, nullableDefault: { type: 'string', default: 'hello', - nullable: true, + optional: true, nullable: true, }, - } - } + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/test/e2e/2fa.ts b/packages/backend/test/e2e/2fa.ts index c8daee09a8..87a3c227d6 100644 --- a/packages/backend/test/e2e/2fa.ts +++ b/packages/backend/test/e2e/2fa.ts @@ -18,7 +18,7 @@ import type { PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialRequestOptionsJSON, RegistrationResponseJSON, -} from '@simplewebauthn/typescript-types'; +} from '@simplewebauthn/types'; import type * as misskey from 'misskey-js'; describe('2要素認証', () => { diff --git a/packages/backend/test/e2e/fetch-validate-ap-deny.ts b/packages/backend/test/e2e/fetch-validate-ap-deny.ts new file mode 100644 index 0000000000..434a9fe209 --- /dev/null +++ b/packages/backend/test/e2e/fetch-validate-ap-deny.ts @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +process.env.NODE_ENV = 'test'; + +import { validateContentTypeSetAsActivityPub, validateContentTypeSetAsJsonLD } from '@/core/activitypub/misc/validator.js'; +import { signup, uploadFile, relativeFetch } from '../utils.js'; +import type * as misskey from 'misskey-js'; + +describe('validateContentTypeSetAsActivityPub/JsonLD (deny case)', () => { + let alice: misskey.entities.SignupResponse; + let aliceUploadedFile: any; + + beforeAll(async () => { + alice = await signup({ username: 'alice' }); + aliceUploadedFile = await uploadFile(alice); + }, 1000 * 60 * 2); + + test('ActivityStreams: ファイルはエラーになる', async () => { + const res = await relativeFetch(aliceUploadedFile.webpublicUrl); + + function doValidate() { + validateContentTypeSetAsActivityPub(res); + } + + expect(doValidate).toThrow('Content type is not'); + }); + + test('JSON-LD: ファイルはエラーになる', async () => { + const res = await relativeFetch(aliceUploadedFile.webpublicUrl); + + function doValidate() { + validateContentTypeSetAsJsonLD(res); + } + + expect(doValidate).toThrow('Content type is not'); + }); +}); diff --git a/packages/backend/test/unit/activitypub.ts b/packages/backend/test/unit/activitypub.ts index 88ff49b119..b4b06b06bd 100644 --- a/packages/backend/test/unit/activitypub.ts +++ b/packages/backend/test/unit/activitypub.ts @@ -203,7 +203,7 @@ describe('ActivityPub', () => { describe('Renderer', () => { test('Render an announce with visibility: followers', () => { - rendererService.renderAnnounce(null, { + rendererService.renderAnnounce('https://example.com/notes/00example', { id: genAidx(Date.now()), visibility: 'followers', } as MiNote); diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index d5da8e0226..a2220ffae6 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -13,10 +13,11 @@ import fetch, { File, RequestInit } from 'node-fetch'; import { DataSource } from 'typeorm'; import { JSDOM } from 'jsdom'; import { DEFAULT_POLICIES } from '@/core/RoleService.js'; +import { Packed } from '@/misc/json-schema.js'; +import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js'; import { entities } from '../src/postgres.js'; import { loadConfig } from '../src/config.js'; import type * as misskey from 'misskey-js'; -import { Packed } from '@/misc/json-schema.js'; export { server as startServer, jobQueue as startJobQueue } from '@/boot/common.js'; @@ -123,9 +124,9 @@ export function randomString(chars = 'abcdefghijklmnopqrstuvwxyz0123456789', len function timeoutPromise(p: Promise, timeout: number): Promise { return Promise.race([ p, - new Promise((reject) =>{ - setTimeout(() => { reject(new Error('timed out')); }, timeout) - }) as never + new Promise((reject) => { + setTimeout(() => { reject(new Error('timed out')); }, timeout); + }) as never, ]); } @@ -327,7 +328,6 @@ export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadO }); const body = res.status !== 204 ? await res.json() as misskey.Endpoints['drive/files/create']['res'] : null; - return { status: res.status, headers: res.headers, @@ -343,7 +343,7 @@ export const uploadUrl = async (user: UserToken, url: string): Promise msg.type === 'urlUploadFinished' && msg.body.marker === marker, (msg) => msg.body.file as Packed<'DriveFile'>, - 60 * 1000 + 60 * 1000, ); await api('drive/files/upload-from-url', { @@ -434,20 +434,20 @@ export const waitFire = async (user: UserToken, channel: string, trgr: () => any * @returns 時間内に正常に処理できた場合に通知からextractorを通した値を得る */ export function makeStreamCatcher( - user: UserToken, - channel: string, - cond: (message: Record) => boolean, - extractor: (message: Record) => T, - timeout = 60 * 1000): Promise { - let ws: WebSocket + user: UserToken, + channel: string, + cond: (message: Record) => boolean, + extractor: (message: Record) => T, + timeout = 60 * 1000): Promise { + let ws: WebSocket; const p = new Promise(async (resolve) => { ws = await connectStream(user, channel, (msg) => { if (cond(msg)) { - resolve(extractor(msg)) + resolve(extractor(msg)); } }); }).finally(() => { - ws?.close(); + ws.close(); }); return timeoutPromise(p, timeout); @@ -476,6 +476,14 @@ export const simpleGet = async (path: string, accept = '*/*', cookie: any = unde 'text/html; charset=utf-8', ]; + if (res.ok && ( + accept.startsWith('application/activity+json') || + (accept.startsWith('application/ld+json') && accept.includes('https://www.w3.org/ns/activitystreams')) + )) { + // validateContentTypeSetAsActivityPubのテストを兼ねる + validateContentTypeSetAsActivityPub(res); + } + const body = jsonTypes.includes(res.headers.get('content-type') ?? '') ? await res.json() : htmlTypes.includes(res.headers.get('content-type') ?? '') ? new JSDOM(await res.text()) : diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 956c643594..91a391ac08 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -28,7 +28,7 @@ "@tabler/icons-webfont": "2.44.0", "@twemoji/parser": "15.0.0", "@vitejs/plugin-vue": "5.0.3", - "@vue/compiler-sfc": "3.4.15", + "@vue/compiler-sfc": "3.4.18", "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.2", "astring": "1.8.6", "broadcast-channel": "7.0.0", @@ -72,7 +72,7 @@ "uuid": "9.0.1", "v-code-diff": "1.7.2", "vite": "5.1.0", - "vue": "3.4.15", + "vue": "3.4.18", "vuedraggable": "next" }, "devDependencies": { diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index afe8e2ac1b..b19d45a35e 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -11,6 +11,7 @@ import { alert, confirm, popup, post, toast } from '@/os.js'; import { useStream } from '@/stream.js'; import * as sound from '@/scripts/sound.js'; import { $i, signout, updateAccount } from '@/account.js'; +import { fetchInstance, instance } from '@/instance.js'; import { ColdDeviceStorage, defaultStore } from '@/store.js'; import { makeHotkey } from '@/scripts/hotkey.js'; import { reactionPicker } from '@/scripts/reaction-picker.js'; @@ -234,6 +235,13 @@ export async function mainBoot() { } } + fetchInstance().then(() => { + const modifiedVersionMustProminentlyOfferInAgplV3Section13Read = miLocalStorage.getItem('modifiedVersionMustProminentlyOfferInAgplV3Section13Read'); + if (modifiedVersionMustProminentlyOfferInAgplV3Section13Read !== 'true' && instance.repositoryUrl !== 'https://github.com/misskey-dev/misskey') { + popup(defineAsyncComponent(() => import('@/components/MkSourceCodeAvailablePopup.vue')), {}, {}, 'closed'); + } + }); + if ('Notification' in window) { // 許可を得ていなかったらリクエスト if (Notification.permission === 'default') { diff --git a/packages/frontend/src/components/MkAbuseReport.vue b/packages/frontend/src/components/MkAbuseReport.vue index 733796ec21..271b94feaa 100644 --- a/packages/frontend/src/components/MkAbuseReport.vue +++ b/packages/frontend/src/components/MkAbuseReport.vue @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only