diff --git a/.config/example.yml b/.config/example.yml index 39f1322131..45c7dd8032 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -38,7 +38,7 @@ # Option 3: If neither of the above applies to you. # (In this case, the source code should be published # on the CherryPick interface. IT IS NOT ENOUGH TO -# DISCLOSE THE SOURCE CODE WEHN A USER REQUESTS IT BY +# DISCLOSE THE SOURCE CODE WHEN 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 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6ea82e6d0c..52fb1fabbc 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -19,7 +19,6 @@ "editorconfig.editorconfig", "dbaeumer.vscode-eslint", "Vue.volar", - "Vue.vscode-typescript-vue-plugin", "Orta.vscode-jest", "dbaeumer.vscode-eslint", "mrmlnc.vscode-json5" diff --git a/.github/workflows/check-cherrypick-js-autogen.yml b/.github/workflows/check-cherrypick-js-autogen.yml index 285eb1ea28..f159ced2a1 100644 --- a/.github/workflows/check-cherrypick-js-autogen.yml +++ b/.github/workflows/check-cherrypick-js-autogen.yml @@ -5,24 +5,23 @@ on: branches: - master - develop + - improve-cherrypick-js-autogen-check paths: - packages/backend/** jobs: - check-cherrypick-js-autogen: + # pull_request_target safety: permissions: read-all, and there are no secrets used in this job + generate-cherrypick-js: runs-on: ubuntu-latest permissions: - pull-requests: write - - env: - api_json_name: "api-head.json" - + contents: read + if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }} steps: - name: checkout uses: actions/checkout@v4.1.1 with: submodules: true - ref: ${{ github.event.pull_request.head.sha }} + ref: refs/pull/${{ github.event.pull_request.number }}/merge - name: setup pnpm uses: pnpm/action-setup@v3 @@ -39,79 +38,81 @@ jobs: - name: install dependencies run: pnpm i --frozen-lockfile - - name: wait get-api-diff - uses: lewagon/wait-on-check-action@v1.3.3 + # generate api.json + - name: Copy Config + run: cp .config/example.yml .config/default.yml + - name: Build + run: pnpm build + - name: Generate API JSON + run: pnpm --filter backend generate-api-json + + # build cherrypick js + - name: Build cherrypick-js + run: |- + cp packages/backend/built/api.json packages/cherrypick-js/generator/api.json + pnpm run --filter cherrypick-js-type-generator generate + + # packages/cherrypick-js/generator/built/autogen + - name: Upload Generated + uses: actions/upload-artifact@v4 with: - ref: ${{ github.event.pull_request.head.sha }} - check-regexp: get-from-cherrypick .+ - repo-token: ${{ secrets.GITHUB_TOKEN }} - wait-interval: 30 + name: generated-cherrypick-js + path: packages/cherrypick-js/generator/built/autogen - - name: Download artifact - uses: actions/github-script@v7.0.1 + # pull_request_target safety: permissions: read-all, and there are no secrets used in this job + get-actual-cherrypick-js: + runs-on: ubuntu-latest + permissions: + contents: read + if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }} + steps: + - name: checkout + uses: actions/checkout@v4.1.1 with: - script: | - const fs = require('fs'); - - const workflows = await github.rest.actions.listWorkflowRunsForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - head_sha: `${{ github.event.pull_request.head.sha }}` - }).then(x => x.data.workflow_runs); - - console.log(workflows.map(x => ({name: x.name, title: x.display_title}))); - - const run_id = workflows.find(x => x.name.includes("Get api.json from CherryPick")).id; - - let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: run_id, - }); - - let matchArtifacts = allArtifacts.data.artifacts.filter((artifact) => { - return artifact.name.startsWith("api-artifact-") || artifact.name == "api-artifact" - }); - - await Promise.all(matchArtifacts.map(async (artifact) => { - let download = await github.rest.actions.downloadArtifact({ - owner: context.repo.owner, - repo: context.repo.repo, - artifact_id: artifact.id, - archive_format: 'zip', - }); - await fs.promises.writeFile(`${process.env.GITHUB_WORKSPACE}/${artifact.name}.zip`, Buffer.from(download.data)); - })); - - - name: unzip artifacts - run: |- - find . -mindepth 1 -maxdepth 1 -type f -name '*.zip' -exec unzip {} -d . ';' - ls -la + submodules: true + ref: refs/pull/${{ github.event.pull_request.number }}/merge - - name: get head checksum - run: |- - checksum=$(realpath head_checksum) + - name: Upload From Merged + uses: actions/upload-artifact@v4 + with: + name: actual-cherrypick-js + path: packages/cherrypick-js/src/autogen - cd packages/cherrypick-js/src - find autogen -type f -exec sh -c 'echo $(sed -E "s/^\s+\*\s+generatedAt:.+$//" {} | sha256sum | cut -d" " -f 1) {}' \; > $checksum - cd ../../.. + # pull_request_target safety: nothing is cloned from repository + comment-cherrypick-js-autogen: + runs-on: ubuntu-latest + needs: [generate-cherrypick-js, get-actual-cherrypick-js] + permissions: + pull-requests: write + steps: + - name: download generated-cherrypick-js + uses: actions/download-artifact@v4 + with: + name: generated-cherrypick-js + path: cherrypick-js-generated - - name: build autogen - run: |- - checksum=$(realpath ${api_json_name}_checksum) - mv $api_json_name packages/cherrypick-js/generator/api.json + - name: download actual-cherrypick-js + uses: actions/download-artifact@v4 + with: + name: actual-cherrypick-js + path: cherrypick-js-actual + + - name: check cherrypick-js changes + id: check-changes + run: | + diff -r -u --label=generated --label=on-tree ./cherrypick-js-generated ./cherrypick-js-actual > cherrypick-js.diff || true - cd packages/cherrypick-js/generator - pnpm run generate - cd built - find autogen -type f -exec sh -c 'echo $(sed -E "s/^\s+\*\s+generatedAt:.+$//" {} | sha256sum | cut -d" " -f 1) {}' \; > $checksum - cd ../../../.. + if [ -s cherrypick-js.diff ]; then + echo "changes=true" >> $GITHUB_OUTPUT + else + echo "changes=false" >> $GITHUB_OUTPUT + fi - - name: check update for type definitions - run: diff head_checksum ${api_json_name}_checksum + - name: Print full diff + run: cat ./cherrypick-js.diff - name: send message - if: failure() + if: steps.check-changes.outputs.changes == 'true' uses: thollander/actions-comment-pull-request@v2 with: comment_tag: check-cherrypick-js-autogen @@ -125,7 +126,7 @@ jobs: ``` - name: send message - if: success() + if: steps.check-changes.outputs.changes == 'false' uses: thollander/actions-comment-pull-request@v2 with: comment_tag: check-cherrypick-js-autogen diff --git a/.github/workflows/check-spdx-license-id.yml b/.github/workflows/check-spdx-license-id.yml new file mode 100644 index 0000000000..6cd8bf60d5 --- /dev/null +++ b/.github/workflows/check-spdx-license-id.yml @@ -0,0 +1,75 @@ +name: Check SPDX-License-Identifier + +on: + push: + branches: + - master + - develop + pull_request: + +jobs: + check-spdx-license-id: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + - name: Check + run: | + counter=0 + + search() { + local directory="$1" + find "$directory" -type f \ + '(' \ + -name "*.cjs" -and -not -name '*.config.cjs' -o \ + -name "*.html" -o \ + -name "*.js" -and -not -name '*.config.js' -o \ + -name "*.mjs" -and -not -name '*.config.mjs' -o \ + -name "*.scss" -o \ + -name "*.ts" -and -not -name '*.config.ts' -o \ + -name "*.vue" \ + ')' -and \ + -not -name '*eslint*' + } + + check() { + local file="$1" + if ! ( + grep -q "SPDX-FileCopyrightText: syuilo and misskey-project" "$file" || + grep -q "SPDX-License-Identifier: AGPL-3.0-only" "$file" + ); then + echo "Missing: $file" + ((counter++)) + fi + } + + directories=( + "cypress/e2e" + "packages/backend/migration" + "packages/backend/src" + "packages/backend/test" + "packages/frontend/.storybook" + "packages/frontend/@types" + "packages/frontend/lib" + "packages/frontend/public" + "packages/frontend/src" + "packages/frontend/test" + "packages/misskey-bubble-game/src" + "packages/misskey-reversi/src" + "packages/sw/src" + "scripts" + ) + + for directory in "${directories[@]}"; do + for file in $(search $directory); do + check "$file" + done + done + + if [ $counter -gt 0 ]; then + echo "SPDX-License-Identifier is missing in $counter files." + exit 1 + else + echo "SPDX-License-Identifier is certainly described in all target files!" + exit 0 + fi diff --git a/.github/workflows/deploy-test-environment.yml b/.github/workflows/deploy-test-environment.yml index 77cdcfaf88..66b15beb91 100644 --- a/.github/workflows/deploy-test-environment.yml +++ b/.github/workflows/deploy-test-environment.yml @@ -50,12 +50,9 @@ jobs: - name: Get PR ref id: get-ref - env: - 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') - echo "pr-ref=$PR_REF" > $GITHUB_OUTPUT + PR_REF="refs/pull/${{ github.event.issue.number }}/head" + echo "pr-ref=$PR_REF" >> $GITHUB_OUTPUT - name: Extract wait time id: get-wait-time diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index fc51a8d5f7..621094fd61 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -92,6 +92,6 @@ jobs: - run: pnpm i --frozen-lockfile - run: pnpm --filter cherrypick-js run build if: ${{ matrix.workspace == 'backend' }} - - run: pnpm --filter misskey-reversi run build:tsc + - run: pnpm --filter misskey-reversi run build if: ${{ matrix.workspace == 'backend' }} - run: pnpm --filter ${{ matrix.workspace }} run typecheck diff --git a/.github/workflows/release-edit-with-push.yml b/.github/workflows/release-edit-with-push.yml new file mode 100644 index 0000000000..944b98eb7c --- /dev/null +++ b/.github/workflows/release-edit-with-push.yml @@ -0,0 +1,40 @@ +name: "Release Manager: sync changelog with PR" + +on: + push: + branches: + - release/** + paths: + - 'CHANGELOG.md' + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + edit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # headがrelease/かつopenのPRを1つ取得 + - name: Get PR + run: | + echo "pr_number=$(gh pr list --limit 1 --head "${{ github.ref_name }}" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT + id: get_pr + - name: Get target version + uses: misskey-dev/release-manager-actions/.github/actions/get-target-version@v1 + id: v + # CHANGELOG.mdの内容を取得 + - name: Get changelog + uses: misskey-dev/release-manager-actions/.github/actions/get-changelog@v1 + with: + version: ${{ steps.v.outputs.target_version }} + id: changelog + # PRのnotesを更新 + - name: Update PR + run: | + gh pr edit ${{ steps.get_pr.outputs.pr_number }} --body "${{ steps.changelog.outputs.changelog }}" diff --git a/.github/workflows/release-with-dispatch.yml b/.github/workflows/release-with-dispatch.yml new file mode 100644 index 0000000000..1a954739d9 --- /dev/null +++ b/.github/workflows/release-with-dispatch.yml @@ -0,0 +1,122 @@ +name: "Release Manager [Dispatch]" + +on: + workflow_dispatch: + inputs: + ## Specify the type of the next release. + #version_increment_type: + # type: choice + # description: 'VERSION INCREMENT TYPE' + # default: 'patch' + # required: false + # options: + # - 'major' + # - 'minor' + # - 'patch' + merge: + type: boolean + description: 'MERGE RELEASE BRANCH TO MAIN' + default: false + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + get-pr: + runs-on: ubuntu-latest + outputs: + pr_number: ${{ steps.get_pr.outputs.pr_number }} + steps: + - uses: actions/checkout@v4 + # headがrelease/かつopenのPRを1つ取得 + - name: Get PRs + run: | + echo "pr_number=$(gh pr list --limit 1 --search "head:release/ is:open" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT + id: get_pr + + merge: + uses: misskey-dev/release-manager-actions/.github/workflows/merge.yml@v1 + needs: get-pr + if: ${{ needs.get-pr.outputs.pr_number != '' && inputs.merge == true }} + with: + pr_number: ${{ needs.get-pr.outputs.pr_number }} + package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }} + # Text to prepend to the changelog + # The first line must be `## Unreleased` + changes_template: | + ## Unreleased + + ### General + - + + ### Client + - + + ### Server + - + + use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }} + secrets: + RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }} + RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + RULESET_EDIT_APP_ID: ${{ secrets.RULESET_EDIT_APP_ID }} + RULESET_EDIT_APP_PRIVATE_KEY: ${{ secrets.RULESET_EDIT_APP_PRIVATE_KEY }} + + create-prerelease: + uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v1 + needs: get-pr + if: ${{ needs.get-pr.outputs.pr_number != '' && inputs.merge != true }} + with: + pr_number: ${{ needs.get-pr.outputs.pr_number }} + package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }} + use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }} + secrets: + RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }} + RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + + create-target: + uses: misskey-dev/release-manager-actions/.github/workflows/create-target.yml@v1 + needs: get-pr + if: ${{ needs.get-pr.outputs.pr_number == '' }} + with: + # The script for version increment. + # process.env.CURRENT_VERSION: The current version. + # + # Misskey calender versioning (yyyy.MM.patch) example + version_increment_script: | + const now = new Date(); + const year = now.toLocaleDateString('en-US', { year: 'numeric', timeZone: 'Asia/Tokyo' }); + const month = now.toLocaleDateString('en-US', { month: 'numeric', timeZone: 'Asia/Tokyo' }); + const [major, minor, _patch] = process.env.CURRENT_VERSION.split('.'); + const patch = Number(_patch.split('-')[0]); + if (Number.isNaN(patch)) { + console.error('Invalid patch version', year, month, process.env.CURRENT_VERSION, major, minor, _patch); + throw new Error('Invalid patch version'); + } + if (year !== major || month !== minor) { + return `${year}.${month}.0`; + } else { + return `${major}.${minor}.${patch + 1}`; + } + ##Semver example + #version_increment_script: | + # const [major, minor, patch] = process.env.CURRENT_VERSION.split('.'); + # if ("${{ inputs.version_increment_type }}" === "major") { + # return `${Number(major) + 1}.0.0`; + # } else if ("${{ inputs.version_increment_type }}" === "minor") { + # return `${major}.${Number(minor) + 1}.0`; + # } else { + # return `${major}.${minor}.${Number(patch) + 1}`; + # } + package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }} + use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }} + secrets: + RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }} + RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + RULESET_EDIT_APP_ID: ${{ secrets.RULESET_EDIT_APP_ID }} + RULESET_EDIT_APP_PRIVATE_KEY: ${{ secrets.RULESET_EDIT_APP_PRIVATE_KEY }} diff --git a/.github/workflows/release-with-ready.yml b/.github/workflows/release-with-ready.yml new file mode 100644 index 0000000000..b64ed20791 --- /dev/null +++ b/.github/workflows/release-with-ready.yml @@ -0,0 +1,38 @@ +name: "Release Manager: release RC when ready for review" + +on: + pull_request: + types: [ready_for_review] + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + check: + runs-on: ubuntu-latest + outputs: + ref: ${{ steps.get_pr.outputs.ref }} + steps: + - uses: actions/checkout@v4 + # PR情報を取得 + - name: Get PR + run: | + pr_json=$(gh pr view ${{ github.event.pull_request.number }} --json isDraft,headRefName) + echo "ref=$(echo $pr_json | jq -r '.headRefName')" >> $GITHUB_OUTPUT + id: get_pr + release: + uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v1 + needs: check + if: startsWith(needs.check.outputs.ref, 'release/') + with: + pr_number: ${{ github.event.pull_request.number }} + package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }} + use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }} + secrets: + RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }} + RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml index 245a9495da..293af1f6ec 100644 --- a/.github/workflows/storybook.yml +++ b/.github/workflows/storybook.yml @@ -87,12 +87,13 @@ jobs: if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then echo "skip=true" >> $GITHUB_OUTPUT fi - BRANCH="${{ github.event.pull_request.head.user.login }}:${{ github.event.pull_request.head.ref }}" - if [ "$BRANCH" = "kokonect-link:${{ github.event.pull_request.head.ref }}" ]; then - BRANCH="${{ github.event.pull_request.head.ref }}" + BRANCH="${{ github.event.pull_request.head.user.login }}:$HEAD_REF" + if [ "$BRANCH" = "kokonect-link:$HEAD_REF" ]; then + BRANCH="$HEAD_REF" fi pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static --branch-name $BRANCH $(echo "$CHROMATIC_PARAMETER") env: + HEAD_REF: ${{ github.event.pull_request.head.ref }} CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} - name: Notify that Chromatic detects changes uses: actions/github-script@v7.0.1 diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index faea72cef1..5495dac98e 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -45,6 +45,8 @@ jobs: with: version: 8 run_install: false + - name: Install FFmpeg + uses: FedericoCarboni/setup-ffmpeg@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4.0.2 with: diff --git a/.vscode/extensions.json b/.vscode/extensions.json index baca8db246..3cdf81e339 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -3,9 +3,7 @@ "editorconfig.editorconfig", "dbaeumer.vscode-eslint", "Vue.volar", - "Vue.vscode-typescript-vue-plugin", "Orta.vscode-jest", - "dbaeumer.vscode-eslint", "mrmlnc.vscode-json5" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index e2a82b1ffe..0ceec23acd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,7 +7,7 @@ "*.test.ts": "typescript" }, "jest.jestCommandLine": "pnpm run jest", - "jest.autoRun": "off", + "jest.runMode": "on-demand", "editor.codeActionsOnSave": { "source.fixAll": "explicit" }, diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bd93080f4..b14f194bc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,86 @@ - -## 202x.x.x (unreleased) +## 2024.3.0 ### General +- Enhance: 投稿者のロールに応じて、一つのノートに含むことのできるメンションとダイレクト投稿の宛先の人数に上限を設定できるように + * デフォルトのメンション上限は20アカウントに設定されます。(管理者はベースロールの設定で変更可能です。) + * 連合の問い合わせに応答しないサーバーのリモートユーザーへのメンションは、上限の人数に含めない実装になっています。 +- Enhance: 通知がミュート、凍結を考慮するようになりました +- Enhance: サーバーごとにモデレーションノートを残せるように +- Enhance: コンディショナルロールの条件に「マニュアルロールへのアサイン」を追加 +- Enhance: 通知の受信設定に「フォロー中またはフォロワー」を追加 +- Enhance: 通知の履歴をリセットできるように +- Fix: ダイレクトなノートに対してはダイレクトでしか返信できないように ### Client - Enhance: ノート作成画面のファイル添付メニューの区切り線の位置を調整 @@ -21,9 +88,24 @@ - Fix: MFMのオートコンプリートが出るべき状況で出ないことがある問題を修正 - Fix: チャートのラベルが消えている問題を修正 - Fix: 画面表示後最初の音声再生が爆音になることがある問題を修正 +- Fix: 設定のバックアップ作成時に名前を入力しなかった場合、ローカライゼーションがおかしくなる問題を修正 +- Fix: ページ`/admin/emojis`の絵文字編集ダイアログで「リアクションとして使えるロール」を追加する際に何も選択せずOKを押下すると画面が固まる問題を修正 +- Fix: 絵文字サジェストの順位で、絵文字自体の名前が同じものよりもタグで一致しているものが優先されてしまう問題を修正 +- Fix: ユーザの情報のポップアップが消えなくなることがある問題を修正 ### Server +- Enhance: エンドポイント`flash/update`の`flashId`以外のパラメータは必須ではなくなりました - Fix: nodeinfoにenableMcaptchaとenableTurnstileが無いのを修正 +- Fix: 破損した通知をクライアントに送信しないように + * 通知欄が無限にリロードされる問題が改善する可能性があります +- Fix: 禁止キーワードを含むノートがDelayed Queueに追加されて再処理される問題を修正 +- Fix: 自分がフォローしていないアカウントのフォロワー限定ノートが閲覧できることがある問題を修正 +- Fix: タイムラインのオプションで「リノートを表示」を無効にしている際、投票のみの引用リノートが流れてこない問題を修正 +- Fix: エンドポイント`admin/emoji/update`の各種修正 + - 必須パラメータを`id`または`name`のいずれかのみに + - `id`の代わりに`name`で絵文字を指定可能に(`id`・`name`両指定時は従来通り`name`を変更する挙動) + - `category`および`licence`が指定なしの時勝手にnullに上書きされる挙動を修正 +- Fix: 通知の受信設定で「相互フォロー」が正しく動作しない問題を修正 ## 2024.2.0 diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index d7b4e58856..0de6f20c6a 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -23,6 +23,38 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE # 릴리즈 노트 이 문서는 CherryPick의 변경 사항만 포함합니다. +## 4.8.0 +출시일: 2024/5/20
+기반 Misskey 버전: 2024.3.1
+Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#202431](CHANGELOG.md#202431) 문서를 참고하십시오. + +### General +- Change: '타임라인에 다른 사람에게 보내는 답글을 포함'의 기본값을 비활성으로 설정 + +### Client +- Enhance: 노트 메뉴에 '새 탭에서 열기' 추가 +- Fix: (Friendly) 타임라인 헤더와 알림 영역의 헤더 타이틀이 잘못 표시될 수 있음 (kokonect-link/cherrypick#461) +- Fix: (Friendly) 윈도우 크기를 조정하면 타임라인 탭의 하이라이트가 잘못된 위치에 표시될 수 있음 (kokonect-link/cherrypick#415) +- Fix: (Friendly) 일부 페이지에서 알림 영역의 디자인이 잘못 표시될 수 있음 + - 사용자 프로필 페이지 + - 노트 상세 페이지 +- Fix: 노트를 내보낼 때 노트의 편집 기록을 포함 +- Fix: MkA 컴포넌트를 사용하는 항목이 작동하지 않을 수 있음 (kokonect-link/cherrypick#422) + - 알림의 읽음 표시, 테스트 알림, 기본 업로드 위치 등 +- Fix: 코드 복사 버튼을 누르면 노트 상세 페이지가 표시될 수 있음 (kokonect-link/cherrypick#414) +- Fix: 창으로 제어판을 열었을 때 뒤로 가기 버튼이 잘못된 작동을 야기할 수 있음 (kokonect-link/cherrypick#407) +- Fix: 서버 이름이 매우 긴 경우, CherryPick에 대하여(MkSourceCodeAvailablePopup) 대화 상자의 디자인이 잘못 표시될 수 있음 +- Fix: '노트를 클릭하여 자세히 표시' 기능을 활성화하면 일부 기능이 올바르게 작동하지 않을 수 있음 (kokonect-link/cherrypick#451) + - 타임라인에서 노트의 프로필 아이콘을 클릭하면 프로필로 이동하지 않음 + - 노트 본문 내의 프로필 아이콘을 클릭하면 화면에 아무것도 표시되지 않음 + +### Server +- Enhance: 디버깅을 보다 편하게 할 수 있도록 vite 생성 파일의 이름 개선 +- Fix: 엔드포인트 `users/translate` 에러 개선 +- Fix: Mastodon 사용자에게 대화를 보낼 때 [#objectobject] 태그가 추가될 수 있음 + +--- + ## 4.7.0 출시일: 2024/3/19
기반 Misskey 버전: 2024.2.0
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 854f8e5f77..93038ad9ed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -316,6 +316,98 @@ export const handlers = [ Don't forget to re-run the `.storybook/generate.js` script after adding, editing, or removing the above files. +## Nest + +### Nest Service Circular dependency / Nestでサービスの循環参照でエラーが起きた場合 + +#### forwardRef +まずは簡単に`forwardRef`を試してみる + +```typescript +export class FooService { + constructor( + @Inject(forwardRef(() => BarService)) + private barService: BarService + ) { + } +} +``` + +#### OnModuleInit +できなければ`OnModuleInit`を使う + +```typescript +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; +import { BarService } from '@/core/BarService'; + +@Injectable() +export class FooService implements OnModuleInit { + private barService: BarService // constructorから移動してくる + + constructor( + private moduleRef: ModuleRef, + ) { + } + + async onModuleInit() { + this.barService = this.moduleRef.get(BarService.name); + } + + public async niceMethod() { + return await this.barService.incredibleMethod({ hoge: 'fuga' }); + } +} +``` + +##### Service Unit Test +テストで`onModuleInit`を呼び出す必要がある + +```typescript +// import ... + +describe('test', () => { + let app: TestingModule; + let fooService: FooService; // for test case + let barService: BarService; // for test case + + beforeEach(async () => { + app = await Test.createTestingModule({ + imports: ..., + providers: [ + FooService, + { // mockする (mockは必須ではないかもしれない) + provide: BarService, + useFactory: () => ({ + incredibleMethod: jest.fn(), + }), + }, + { // Provideにする + provide: BarService.name, + useExisting: BarService, + }, + ], + }) + .useMocker(... + .compile(); + + fooService = app.get(FooService); + barService = app.get(BarService) as jest.Mocked; + + // onModuleInitを実行する + await fooService.onModuleInit(); + }); + + test('nice', () => { + await fooService.niceMethod(); + + expect(barService.incredibleMethod).toHaveBeenCalled(); + expect(barService.incredibleMethod.mock.lastCall![0]) + .toEqual({ hoge: 'fuga' }); + }); +}) +``` + ## Notes ### Misskeyのドメイン固有の概念は`Mi`をprefixする diff --git a/COPYING b/COPYING index 57ad466702..53d498fb43 100644 --- a/COPYING +++ b/COPYING @@ -1,5 +1,5 @@ Unless otherwise stated this repository is -Copyright © 2014-2024 syuilo and noridev and other misskey, cherrypick contributors +Copyright © 2014-2024 syuilo & noridev and contributors And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE. diff --git a/README.md b/README.md index 839faf85b7..111b5eeda6 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@
- CherryPick logo + CherryPick logo -**🌎 **[CherryPick](https://misskey-hub.net/)** is an open source, decentralized social media platform that's free forever! 🚀** +**🌎 **CherryPick** is an open source, federated social media platform that's free forever! 🚀** + +[Learn more](https://misskey-hub.net/) --- @@ -22,41 +24,6 @@ become a patron ---- - -[![codecov](https://codecov.io/gh/kokonect-link/cherrypick/branch/develop/graph/badge.svg?token=3BRDXE34O0)](https://codecov.io/gh/kokonect-link/cherrypick) - -
- -
- - - -## ✨ Features -- **ActivityPub support**\ -Not on CherryPick? No problem! Not only can CherryPick instances talk to each other, but you can make friends with people on other networks like Mastodon and Misskey and Pixelfed! -- **Reactions**\ -You can add emoji reactions to any post! No longer are you bound by a like button, show everyone exactly how you feel with the tap of a button. -- **Drive**\ -With CherryPick's built in drive, you get cloud storage right in your social media, where you can upload any files, make folders, and find media from posts you've made! -- **Rich Web UI**\ - CherryPick has a rich and easy to use Web UI! - It is highly customizable, from changing the layout and adding widgets to making custom themes. - Furthermore, plugins can be created using AiScript, an original programming language. -- And much more... - -
- -
- -## Documentation - -CherryPick Documentation can be found at [Misskey Hub](https://misskey-hub.net/docs/), some of the links and graphics above also lead to specific portions of it. - -## Sponsors - -
- RSS3
## Thanks diff --git a/cypress/e2e/basic.cy.js b/cypress/e2e/basic.cy.ts similarity index 98% rename from cypress/e2e/basic.cy.js rename to cypress/e2e/basic.cy.ts index 6531c9f1b4..f0ea7a3d44 100644 --- a/cypress/e2e/basic.cy.js +++ b/cypress/e2e/basic.cy.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + describe('Before setup instance', () => { beforeEach(() => { cy.resetState(); diff --git a/cypress/e2e/router.cy.js b/cypress/e2e/router.cy.ts similarity index 91% rename from cypress/e2e/router.cy.js rename to cypress/e2e/router.cy.ts index 6de27be5f4..8d8fb3af31 100644 --- a/cypress/e2e/router.cy.js +++ b/cypress/e2e/router.cy.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + describe('Router transition', () => { describe('Redirect', () => { // サーバの初期化。ルートのテストに関しては各describeごとに1度だけ実行で十分だと思う(使いまわした方が早い) diff --git a/cypress/e2e/widgets.cy.js b/cypress/e2e/widgets.cy.ts similarity index 95% rename from cypress/e2e/widgets.cy.js rename to cypress/e2e/widgets.cy.ts index df6ec8357d..847801a69f 100644 --- a/cypress/e2e/widgets.cy.js +++ b/cypress/e2e/widgets.cy.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* flaky describe('After user signed in', () => { beforeEach(() => { diff --git a/cypress/support/commands.js b/cypress/support/commands.ts similarity index 87% rename from cypress/support/commands.js rename to cypress/support/commands.ts index 91a4d7abe6..281f2e6ccd 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.ts @@ -30,9 +30,13 @@ Cypress.Commands.add('visitHome', () => { }) Cypress.Commands.add('resetState', () => { - cy.window(win => { + // iframe.contentWindow.indexedDB.deleteDatabase() がchromeのバグで使用できないため、indexedDBを無効化している。 + // see https://github.com/misskey-dev/misskey/issues/13605#issuecomment-2053652123 + /* + cy.window().then(win => { win.indexedDB.deleteDatabase('keyval-store'); }); + */ cy.request('POST', '/api/reset-db', {}).as('reset'); cy.get('@reset').its('status').should('equal', 204); cy.reload(true); diff --git a/cypress/support/e2e.js b/cypress/support/e2e.ts similarity index 100% rename from cypress/support/e2e.js rename to cypress/support/e2e.ts diff --git a/cypress/support/index.ts b/cypress/support/index.ts new file mode 100644 index 0000000000..c1bed21979 --- /dev/null +++ b/cypress/support/index.ts @@ -0,0 +1,19 @@ +declare global { + namespace Cypress { + interface Chainable { + login(username: string, password: string): Chainable; + + registerUser( + username: string, + password: string, + isAdmin?: boolean + ): Chainable; + + resetState(): Chainable; + + visitHome(): Chainable; + } + } +} + +export {} diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json new file mode 100644 index 0000000000..6fe7f32cc4 --- /dev/null +++ b/cypress/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "lib": ["dom", "es5"], + "target": "es5", + "types": ["cypress", "node"] + }, + "include": ["./**/*.ts"] +} diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index 38f6a21d62..a0e68be172 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -1028,6 +1028,7 @@ renotes: "أعد النشر" sourceCode: "الشفرة المصدرية" flip: "اقلب" lastNDays: "آخر {n} أيام" +surrender: "ألغِ" _initialAccountSetting: accountCreated: "نجح إنشاء حسابك!" letsStartAccountSetup: "إذا كنت جديدًا لنعدّ حسابك الشخصي." diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 5fdac14f69..8733add3d9 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -1210,6 +1210,7 @@ hemisphere: "Geolocalització" withSensitive: "Incloure notes amb fitxers sensibles" userSaysSomethingSensitive: "La publicació de {name} conte material sensible" enableHorizontalSwipe: "Lliscar per canviar de pestanya" +surrender: "Cancel·lar " _bubbleGame: howToPlay: "Com es juga" _howToPlay: diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index 44a104a2d5..df38695694 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -1098,6 +1098,7 @@ renotes: "Přeposlat" sourceCode: "Zdrojový kód" flip: "Otočit" lastNDays: "Posledních {n} dnů" +surrender: "Zrušit" _initialAccountSetting: accountCreated: "Váš účet byl úspěšně vytvořen!" letsStartAccountSetup: "Pro začátek si nastavte svůj profil." diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 8b4c21f36a..0d6761d0ea 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -1198,6 +1198,7 @@ decorate: "Dekorieren" addMfmFunction: "MFM hinzufügen" sfx: "Soundeffekte" lastNDays: "Letzten {n} Tage" +surrender: "Abbrechen" _announcement: forExistingUsers: "Nur für existierende Nutzer" forExistingUsersDescription: "Ist diese Option aktiviert, wird diese Ankündigung nur Nutzern angezeigt, die zum Zeitpunkt der Ankündigung bereits registriert sind. Ist sie deaktiviert, wird sie auch Nutzern, die sich nach dessen Veröffentlichung registrieren, angezeigt." diff --git a/locales/en-US.yml b/locales/en-US.yml index b44bd6c1df..8fe129b5f3 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1096,6 +1096,7 @@ neverShow: "Don't show again" remindMeLater: "Maybe later" didYouLikeMisskey: "Have you taken a liking to CherryPick?" pleaseDonate: "{host} uses the free software, CherryPick. We would highly appreciate your donations so development of CherryPick can continue!" +correspondingSourceIsAvailable: "The corresponding source code is available at {anchor}" roles: "Roles" role: "Role" noRole: "Role not found" @@ -1147,6 +1148,8 @@ resetPasswordConfirm: "Really reset your password?" sensitiveWords: "Sensitive words" sensitiveWordsDescription: "The visibility of all notes containing any of the configured words will be set to \"Home\" automatically. You can list multiple by separating them via line breaks." sensitiveWordsDescription2: "Using spaces will create AND expressions and surrounding keywords with slashes will turn them into a regular expression." +prohibitedWords: "Prohibited words" +prohibitedWordsDescription: "Enables an error when attempting to post a note containing the set word(s). Multiple words can be set, separated by a new line." prohibitedWordsDescription2: "Using spaces will create AND expressions and surrounding keywords with slashes will turn them into a regular expression." hiddenTags: "Hidden hashtags" hiddenTagsDescription: "Select tags which will not shown on trend list.\nMultiple tags could be registered by lines." @@ -1269,6 +1272,7 @@ showRenotes: "Show renotes" edited: "Edited" notificationRecieveConfig: "Notification Settings" mutualFollow: "Mutual follow" +followingOrFollower: "Following or follower" fileAttachedOnly: "Only notes with files" showRepliesToOthersInTimeline: "Show replies to others in timeline" hideRepliesToOthersInTimeline: "Hide replies to others from timeline" @@ -1278,6 +1282,12 @@ confirmShowRepliesAll: "This operation is irreversible. Would you really like to 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" +sourceCodeIsNotYetProvided: "Source code is not yet available. Contact the administrator to fix this problem." +repositoryUrl: "Repository URL" +repositoryUrlDescription: "If you are using CherryPick as is (without any changes to the source code), enter https://github.com/kokonect-link/cherrypick" +repositoryUrlOrTarballRequired: "If you have not published a repository, you must provide a tarball instead. See .config/example.yml for more information." +feedback: "Feedback" +feedbackUrl: "Feedback URL" 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." @@ -1313,6 +1323,8 @@ soundWillBePlayed: "Sound will be played" showReplay: "View Replay" replay: "Replay" replaying: "Showing replay" +endReplay: "Exit Replay" +copyReplayData: "Copy replay data" ranking: "Ranking" lastNDays: "Last {n} days" backToTitle: "Go back to title" @@ -1320,6 +1332,9 @@ hemisphere: "Where are you located" withSensitive: "Include notes with sensitive files" userSaysSomethingSensitive: "Post by {name} contains sensitive content" enableHorizontalSwipe: "Swipe to switch tabs" +loading: "Loading" +surrender: "Cancel" +gameRetry: "Retry" showUnreadNotificationsCount: "Show the number of unread notifications" showCatOnly: "Show only cats" additionalPermissionsForFlash: "Allow to add permission to Play" @@ -1344,6 +1359,15 @@ _messaging: direct: "Direct Message" _bubbleGame: howToPlay: "How to play" + hold: "Hold" + _score: + score: "Score" + scoreYen: "Amount of money earned" + highScore: "High score" + maxChain: "Maximum number of chains" + yen: "{yen} Yen" + estimatedQty: "{qty} Pieces" + scoreSweets: "{onigiriQtyWithUnit} Onigiri" _howToPlay: section1: "Adjust the position and drop the object into the box." section2: "When two objects of the same type touch each other, they will change into a different object and you score points." @@ -1850,6 +1874,7 @@ _role: ltlAvailable: "Can view the local timeline" canPublicNote: "Can send public notes" canEditNote: "Note editing" + mentionMax: "Maximum number of mentions in a note" canInvite: "Can create instance invite codes" inviteLimit: "Invite limit" inviteLimitCycle: "Invite limit cooldown" @@ -1873,6 +1898,7 @@ _role: canUseTranslator: "Translator usage" avatarDecorationLimit: "Maximum number of avatar decorations that can be applied" _condition: + roleAssignedTo: "Assigned to manual roles" isLocal: "Local user" isRemote: "Remote user" createdLessThan: "Less than X has passed since account creation" @@ -1977,6 +2003,8 @@ _aboutMisskey: contributors: "Main contributors" allContributors: "All contributors" source: "Source code" + original: "Original" + thisIsModifiedVersion: "{name} uses a modified version of the original Misskey." translation: "Translate Misskey" donate: "Donate to Misskey" morePatrons: "We also appreciate the support of many other helpers not listed here. Thank you! 🥰" @@ -2589,6 +2617,7 @@ _notification: reactedBySomeUsers: "{n} users reacted" renotedBySomeUsers: "Renote from {n} users" followedBySomeUsers: "Followed by {n} users" + flushNotification: "Clear notifications" _types: all: "All" note: "New notes" @@ -2687,6 +2716,7 @@ _moderationLogTypes: resetPassword: "Password reset" suspendRemoteInstance: "Remote instance suspended" unsuspendRemoteInstance: "Remote instance unsuspended" + updateRemoteInstanceNote: "Moderation note updated for remote instance." markSensitiveDriveFile: "File marked as sensitive" unmarkSensitiveDriveFile: "File unmarked as sensitive" resolveAbuseReport: "Report resolved" @@ -2807,6 +2837,8 @@ _reversi: opponentHasSettingsChanged: "The opponent has changed their settings." allowIrregularRules: "Irregular rules (completely free)" disallowIrregularRules: "No irregular rules" + showBoardLabels: "Display row and column numbering on the board" + useAvatarAsStone: "Turn stones into user avatars" _offlineScreen: title: "Offline - cannot connect to the server" header: "Unable to connect to the server" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 7fd6b05f52..664927e8a5 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -1223,6 +1223,7 @@ hemisphere: "Región" withSensitive: "Mostrar notas que contengan material sensible" userSaysSomethingSensitive: "La publicación de {name} contiene material sensible" enableHorizontalSwipe: "Deslice para cambiar de pestaña" +surrender: "detener" _bubbleGame: howToPlay: "Cómo jugar" _howToPlay: diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 80038714be..1cc57c8ca8 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -380,8 +380,11 @@ hcaptcha: "hCaptcha" enableHcaptcha: "Activer hCaptcha" hcaptchaSiteKey: "Clé du site" hcaptchaSecretKey: "Clé secrète" +mcaptcha: "mCaptcha" +enableMcaptcha: "Activer mCaptcha" mcaptchaSiteKey: "Clé du site" mcaptchaSecretKey: "Clé secrète" +mcaptchaInstanceUrl: "URL de l'instance de mCaptcha" recaptcha: "reCAPTCHA" enableRecaptcha: "Activer reCAPTCHA" recaptchaSiteKey: "Clé du site" @@ -535,7 +538,7 @@ hideThisNote: "Masquer cette note" showFeaturedNotesInTimeline: "Afficher les notes des Tendances dans le fil d'actualité" objectStorage: "Stockage d'objets" useObjectStorage: "Utiliser le stockage d'objets" -objectStorageBaseUrl: "Base URL" +objectStorageBaseUrl: "URL de base" objectStorageBaseUrlDesc: "Préfixe d’URL utilisé pour construire l’URL vers le référencement d’objet (média). Spécifiez son URL si vous utilisez un CDN ou un proxy, sinon spécifiez l’adresse accessible au public selon le guide de service que vous allez utiliser. P.ex. 'https://.s3.amazonaws.com' pour AWS S3 et 'https://storage.googleapis.com/' pour GCS." objectStorageBucket: "Bucket" objectStorageBucketDesc: "Veuillez spécifier le nom du compartiment utilisé sur le service configuré." @@ -639,7 +642,8 @@ large: "Grand" medium: "Moyen" small: "Petit" generateAccessToken: "Générer un jeton d'accès" -permission: "Autorisations " +permission: "Autorisations " +adminPermission: "Droits de l'administrateur" enableAll: "Tout activer" disableAll: "Tout désactiver" tokenRequested: "Autoriser l'accès au compte" @@ -1045,12 +1049,18 @@ nonSensitiveOnlyForLocalLikeOnlyForRemote: "Non sensibles seulement (mentions j' rolesAssignedToMe: "Rôles attribués à moi" resetPasswordConfirm: "Souhaitez-vous réinitialiser votre mot de passe ?" sensitiveWords: "Mots sensibles" +sensitiveWordsDescription2: "Séparer par une espace pour créer une expression AND ; entourer de barres obliques pour créer une expression régulière." +prohibitedWords: "Mots interdits" +prohibitedWordsDescription2: "Séparer par une espace pour créer une expression AND ; entourer de barres obliques pour créer une expression régulière." hiddenTags: "Hashtags cachés" hiddenTagsDescription: "Les hashtags définis ne s'afficheront pas dans les tendances. Vous pouvez définir plusieurs hashtags en faisant un saut de ligne." notesSearchNotAvailable: "La recherche de notes n'est pas disponible." license: "Licence" +unfavoriteConfirm: "Vraiment supprimer des favoris ?" myClips: "Mes clips" drivecleaner: "Nettoyeur du Disque" +retryAllQueuesNow: "Réessayer tous les fils d'attente immédiatement" +retryAllQueuesConfirmTitle: "Vraiment réessayer ?" retryAllQueuesConfirmText: "Cela peut augmenter temporairement la charge du serveur." enableChartsForRemoteUser: "Générer les graphiques pour les utilisateurs distants" enableChartsForFederatedInstances: "Générer les graphiques pour les instances distantes" @@ -1060,6 +1070,8 @@ limitWidthOfReaction: "Limiter la largeur maximale des réactions et les affiche noteIdOrUrl: "Identifiant de la note ou URL" video: "Vidéo" videos: "Vidéos" +audio: "Audio" +audioFiles: "Fichiers audio" dataSaver: "Économiseur de données" accountMigration: "Migration de compte" accountMoved: "Cet·te utilisateur·rice a migré son compte vers :" @@ -1098,7 +1110,10 @@ specifyUser: "Spécifier l'utilisateur·rice" failedToPreviewUrl: "Aperçu d'URL échoué" update: "Mettre à jour" rolesThatCanBeUsedThisEmojiAsReaction: "Rôles qui peuvent utiliser cet émoji comme réaction" +rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Si aucun rôle n'est spécifié, tout le monde peut utiliser cet émoji comme réaction." +rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Il faut un rôle public." cancelReactionConfirm: "Supprimez la réaction ?" +changeReactionConfirm: "Changer la réaction ?" later: "Plus tard" goToMisskey: "Retour vers CherryPick" additionalEmojiDictionary: "Dictionnaires d'émojis additionnels" @@ -1124,11 +1139,13 @@ used: "Utilisé" expired: "Expiré" doYouAgree: "Êtes-vous d’accord ?" beSureToReadThisAsItIsImportant: "Assurez-vous de le lire ; c'est important." +iHaveReadXCarefullyAndAgree: "J'ai lu le contenu de « {x} » et donne mon accord." dialog: "Dialogue" icon: "Avatar" forYou: "Pour vous" currentAnnouncements: "Annonces actuelles" pastAnnouncements: "Annonces passées" +youHaveUnreadAnnouncements: "Il y a des annonces non lues." replies: "Réponses" renotes: "Renotes" loadReplies: "Inclure les réponses" @@ -1143,6 +1160,7 @@ showRenotes: "Afficher les renotes" edited: "Modifié" notificationRecieveConfig: "Paramètres des notifications" mutualFollow: "Abonnement mutuel" +fileAttachedOnly: "Avec fichiers joints seulement" showRepliesToOthersInTimeline: "Afficher les réponses aux autres dans le fil" hideRepliesToOthersInTimeline: "Masquer les réponses aux autres dans le fil" showRepliesToOthersInTimelineAll: "Afficher les réponses de toutes les personnes que vous suivez dans le fil" @@ -1151,6 +1169,11 @@ confirmShowRepliesAll: "Cette opération est irréversible. Voulez-vous vraiment 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" +sourceCodeIsNotYetProvided: "Le code source n'est pas encore disponible. Veuillez signaler ce problème aux administrateurs." +repositoryUrl: "URL du dépôt" +repositoryUrlDescription: "Entrez l'URL du dépôt où se trouve le code source ici. Si vous utilisez CherryPick tel quel (sans changer le code source), entrez https://github.com/kokonect-link/cherrypick" +feedback: "Commentaires" +feedbackUrl: "URL pour les commentaires" 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)." @@ -1178,7 +1201,32 @@ remainingN: "Restants : {n}" overwriteContentConfirm: "Voulez-vous remplacer le contenu actuel ?" seasonalScreenEffect: "Effet d'écran saisonnier" decorate: "Décorer" +addMfmFunction: "Insérer MFM" +enableQuickAddMfmFunction: "Afficher le sélecteur de MFM avancé" +bubbleGame: "Jeu de bulles" +sfx: "Effets sonores" +soundWillBePlayed: "Le son sera joué" +showReplay: "Voir le replay" +replay: "Rediffusion" +replaying: "En cours de rediffusion" +endReplay: "Arrêter la rediffusion" +copyReplayData: "Copier les données de la rediffusion" +ranking: "Classement" lastNDays: "Derniers {n} jours" +backToTitle: "Retourner au titre" +hemisphere: "Votre région" +enableHorizontalSwipe: "Glisser pour changer d'onglet" +loading: "Chargement en cours" +surrender: "Annuler" +gameRetry: "Réessayer" +_bubbleGame: + howToPlay: "Comment jouer" + hold: "Réserver" + _score: + score: "Score" + scoreYen: "Montant gagné" + highScore: "Meilleur score" + yen: "{yen} yens" _announcement: forExistingUsers: "Pour les utilisateurs existants seulement" readConfirmTitle: "Marquer comme lu ?" @@ -1316,10 +1364,13 @@ _achievements: title: "Régulier III" description: "Se connecter pour un total de 400 jours" _login500: + title: "Expert I" description: "Se connecter pour un total de 500 jours" _login600: + title: "Expert II" description: "Se connecter pour un total de 600 jours" _login700: + title: "Expert III" description: "Se connecter pour un total de 700 jours" _login800: description: "Se connecter pour un total de 800 jours" @@ -1416,9 +1467,12 @@ _role: description: "Description du rôle" permission: "Rôle et autorisations" assignTarget: "Attribuer" + manual: "Manuel" manualRoles: "Rôles manuels" + conditional: "Conditionnel" conditionalRoles: "Rôles conditionnels" condition: "Condition" + isConditionalRole: "Ceci est un rôle conditionnel." isPublic: "Rôle public" options: "Options" policies: "Stratégies" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index 547c554bf5..22469815e7 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -1223,6 +1223,7 @@ hemisphere: "Letak kamu tinggal" withSensitive: "Lampirkan catatan dengan berkas sensitif" userSaysSomethingSensitive: "Postingan oleh {name} mengandung konten sensitif" enableHorizontalSwipe: "Geser untuk mengganti tab" +surrender: "Batalkan" _bubbleGame: howToPlay: "Cara bermain" _howToPlay: diff --git a/locales/index.d.ts b/locales/index.d.ts index 1b1e1e4cc7..dab32f2c94 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1946,6 +1946,10 @@ export interface Locale extends ILocale { * 除外キーワード */ "antennaExcludeKeywords": string; + /** + * Botアカウントを除外 + */ + "antennaExcludeBots": string; /** * スペースで区切るとAND指定になり、改行で区切るとOR指定になります */ @@ -2370,6 +2374,10 @@ export interface Locale extends ILocale { * ノートのアクションをホバー時のみ表示する */ "showNoteActionsOnlyHover": string; + /** + * ノートのリアクション数を表示する + */ + "showReactionsCount": string; /** * 履歴はありません */ @@ -5111,6 +5119,10 @@ export interface Locale extends ILocale { * 相互フォロー */ "mutualFollow": string; + /** + * フォロー中またはフォロワー + */ + "followingOrFollower": string; /** * ファイル付きのみ */ @@ -5311,6 +5323,14 @@ export interface Locale extends ILocale { * リプレイ中 */ "replaying": string; + /** + * リプレイを終了 + */ + "endReplay": string; + /** + * リプレイデータをコピー + */ + "copyReplayData": string; /** * ランキング */ @@ -5339,6 +5359,50 @@ export interface Locale extends ILocale { * スワイプしてタブを切り替える */ "enableHorizontalSwipe": string; + /** + * 読み込み中 + */ + "loading": string; + /** + * やめる + */ + "surrender": string; + /** + * リトライ + */ + "gameRetry": string; + /** + * 使用しない場合は空欄にしてください + */ + "notUsePleaseLeaveBlank": string; + /** + * ワンタイムパスワードを使う + */ + "useTotp": string; + /** + * バックアップコードを使う + */ + "useBackupCode": string; + /** + * アプリを起動 + */ + "launchApp": string; + /** + * 動画・音声の再生にブラウザのUIを使用する + */ + "useNativeUIForVideoAudioPlayer": string; + /** + * オリジナルのファイル名を保持 + */ + "keepOriginalFilename": string; + /** + * この設定をオフにすると、アップロード時にファイル名が自動でランダム文字列に置き換えられます。 + */ + "keepOriginalFilenameDescription": string; + /** + * 説明文はありません + */ + "noDescription": string; /** * 未読の通知の数を表示する */ @@ -5424,6 +5488,40 @@ export interface Locale extends ILocale { * 遊び方 */ "howToPlay": string; + /** + * ホールド + */ + "hold": string; + "_score": { + /** + * スコア + */ + "score": string; + /** + * 稼いだ金額 + */ + "scoreYen": string; + /** + * ハイスコア + */ + "highScore": string; + /** + * 最大チェーン数 + */ + "maxChain": string; + /** + * {yen}円 + */ + "yen": ParameterizedString<"yen">; + /** + * {qty}個分 + */ + "estimatedQty": ParameterizedString<"qty">; + /** + * おにぎり {onigiriQtyWithUnit} + */ + "scoreSweets": ParameterizedString<"onigiriQtyWithUnit">; + }; "_howToPlay": { /** * 位置を調整してハコにモノを落とします。 @@ -7244,6 +7342,10 @@ export interface Locale extends ILocale { * ノートの編集 */ "canEditNote": string; + /** + * ノート内の最大メンション数 + */ + "mentionMax": string; /** * サーバー招待コードの発行 */ @@ -7334,6 +7436,10 @@ export interface Locale extends ILocale { "avatarDecorationLimit": string; }; "_condition": { + /** + * マニュアルロールにアサイン済み + */ + "roleAssignedTo": string; /** * ローカルユーザー */ @@ -7611,6 +7717,10 @@ export interface Locale extends ILocale { * ソースを表示 */ "viewSource": string; + /** + * ログを表示 + */ + "viewLog": string; }; "_preferencesBackups": { /** @@ -8682,13 +8792,9 @@ export interface Locale extends ILocale { */ "step1": ParameterizedString<"a" | "b">; /** - * 次に、表示されているQRコードをアプリでスキャンします。 + * 次に、表示されているQRコードをアプリでスキャンするか、ボタンをクリックして端末上でアプリを開きます。 */ "step2": string; - /** - * QRコードをクリックすると、お使いの端末にインストールされている認証アプリやキーリングに登録できます。 - */ - "step2Click": string; /** * デスクトップアプリを使用する場合は次のURIを入力します */ @@ -8781,6 +8887,10 @@ export interface Locale extends ILocale { * バックアップコードが全て使用されました。認証アプリを利用できない場合、これ以上アカウントにアクセスできなくなります。認証アプリを再登録してください。 */ "backupCodesExhaustedWarning": string; + /** + * 詳細なガイドはこちら + */ + "moreDetailedGuideHere": string; }; "_permissions": { /** @@ -9799,6 +9909,10 @@ export interface Locale extends ILocale { * 説明 */ "summary": string; + /** + * 非公開に設定するとプロフィールに表示されなくなりますが、URLを知っている人は引き続きアクセスできます。 + */ + "visibilityDescription": string; }; "_pages": { /** @@ -9970,6 +10084,14 @@ export interface Locale extends ILocale { * ボタン */ "button": string; + /** + * 動的ブロック + */ + "dynamic": string; + /** + * このブロックは廃止されています。今後は{play}を利用してください。 + */ + "dynamicDescription": ParameterizedString<"play">; /** * ノート埋め込み */ @@ -10089,6 +10211,10 @@ export interface Locale extends ILocale { * {n}人がリアクションしました */ "reactedBySomeUsers": ParameterizedString<"n">; + /** + * {n}人がいいねしました + */ + "likedBySomeUsers": ParameterizedString<"n">; /** * {n}人がリノートしました */ @@ -10097,6 +10223,10 @@ export interface Locale extends ILocale { * {n}人にフォローされました */ "followedBySomeUsers": ParameterizedString<"n">; + /** + * 通知の履歴をリセットする + */ + "flushNotification": string; "_types": { /** * すべて @@ -10422,7 +10552,7 @@ export interface Locale extends ILocale { */ "updateServerSettings": string; /** - * モデレーションノート更新 + * ユーザーのモデレーションノート更新 */ "updateUserNote": string; /** @@ -10469,6 +10599,10 @@ export interface Locale extends ILocale { * リモートサーバーを再開 */ "unsuspendRemoteInstance": string; + /** + * リモートサーバーのモデレーションノート更新 + */ + "updateRemoteInstanceNote": string; /** * ファイルをセンシティブ付与 */ @@ -10909,6 +11043,14 @@ export interface Locale extends ILocale { * 変則なし */ "disallowIrregularRules": string; + /** + * 盤面に行・列番号を表示 + */ + "showBoardLabels": string; + /** + * 石をアイコンにする + */ + "useAvatarAsStone": string; }; "_offlineScreen": { /** @@ -10920,6 +11062,74 @@ export interface Locale extends ILocale { */ "header": string; }; + "_urlPreviewSetting": { + /** + * URLプレビューの設定 + */ + "title": string; + /** + * URLプレビューを有効にする + */ + "enable": string; + /** + * プレビュー取得時のタイムアウト(ms) + */ + "timeout": string; + /** + * プレビュー取得の所要時間がこの値を超えた場合、プレビューは生成されません。 + */ + "timeoutDescription": string; + /** + * Content-Lengthの最大値(byte) + */ + "maximumContentLength": string; + /** + * Content-Lengthがこの値を超えた場合、プレビューは生成されません。 + */ + "maximumContentLengthDescription": string; + /** + * Content-Lengthが取得できた場合のみプレビューを生成 + */ + "requireContentLength": string; + /** + * 相手サーバがContent-Lengthを返さない場合、プレビューは生成されません。 + */ + "requireContentLengthDescription": string; + /** + * User-Agent + */ + "userAgent": string; + /** + * プレビュー取得時に使用されるUser-Agentを設定します。空欄の場合、デフォルトのUser-Agentが使用されます。 + */ + "userAgentDescription": string; + /** + * プレビューを生成するプロキシのエンドポイント + */ + "summaryProxy": string; + /** + * CherryPick本体ではなく、サマリープロキシを使用してプレビューを生成します。 + */ + "summaryProxyDescription": string; + /** + * プロキシには下記パラメータがクエリ文字列として連携されます。プロキシ側がこれらをサポートしない場合、設定値は無視されます。 + */ + "summaryProxyDescription2": string; + }; + "_mediaControls": { + /** + * ピクチャインピクチャ + */ + "pip": string; + /** + * 再生速度 + */ + "playbackRate": string; + /** + * ループ再生 + */ + "loop": string; + }; "_abuse": { "_resolver": { /** diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 3ae8cab0c2..83abacba64 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -1005,6 +1005,7 @@ neverShow: "Non mostrare più" remindMeLater: "Rimanda" didYouLikeMisskey: "Ti piace CherryPick?" pleaseDonate: "CherryPick è il software libero utilizzato su {host}. Offrendo una donazione è più facile continuare a svilupparlo!" +correspondingSourceIsAvailable: "" roles: "Ruoli" role: "Ruolo" noRole: "Ruolo non trovato" @@ -1182,6 +1183,12 @@ confirmShowRepliesAll: "Questa è una attività irreversibile. Vuoi davvero incl confirmHideRepliesAll: "Questa è una attività irreversibile. Vuoi davvero escludere tutte le risposte dei following in TL?" externalServices: "Servizi esterni" sourceCode: "Codice sorgente" +sourceCodeIsNotYetProvided: "" +repositoryUrl: "URL della repository" +repositoryUrlDescription: "Se esiste un repository il cui il codice sorgente è disponibile pubblicamente, inserisci il suo URL. Se stai utilizzando CherryPick così com'è (senza alcuna modifica al codice sorgente), inserisci https://github.com/kokonect-link/cherrypick." +repositoryUrlOrTarballRequired: "Se non disponi di un repository pubblico, dovrai fornire un file tarball (tar). Vedere .config/example.yml per i dettagli." +feedback: "Feedback" +feedbackUrl: "URL di feedback" impressum: "Dichiarazione di proprietà" impressumUrl: "URL della dichiarazione di proprietà" impressumDescription: "La dichiarazione di proprietà, è obbligatoria in alcuni paesi come la Germania (Impressum)." @@ -1213,7 +1220,7 @@ addMfmFunction: "Aggiungi decorazioni" enableQuickAddMfmFunction: "Attiva il selettore di funzioni MFM" bubbleGame: "Bubble Game" sfx: "Effetti sonori" -soundWillBePlayed: "Verrà riprodotto il suono" +soundWillBePlayed: "Con musica ed effetti sonori" showReplay: "Vedi i replay" replay: "Replay" replaying: "Replay in corso" @@ -1224,12 +1231,13 @@ hemisphere: "Geolocalizzazione" withSensitive: "Mostra le Note con allegati espliciti" userSaysSomethingSensitive: "Note da {name} con allegati espliciti" enableHorizontalSwipe: "Trascina per invertire i tab" +surrender: "Annulla" _bubbleGame: howToPlay: "Come giocare" _howToPlay: - section1: "Regola la posizione e rilascia l'oggetto nella casella." - section2: "Ottieni un punteggio, quando due oggetti dello stesso tipo si toccano e si trasformano in un oggetto diverso." - section3: "Se gli oggetti traboccano dalla scatola, il gioco finisce. Cerca di ottenere un punteggio elevato fondendo gli oggetti, evitando che escano dalla scatola!" + section1: "Scegli la posizione e rilascia l'oggetto nel contenitore." + section2: "Se due oggetti dello stesso tipo si toccano, si trasformano in un oggetto diverso, aumentando il punteggio." + section3: "Se gli oggetti escono dal limite superiore del contenitore, il gioco finisce. Cerca di ottenere un punteggio elevato fondendo gli oggetti, evitando che escano dal contenitore!" _announcement: forExistingUsers: "Solo ai profili attuali" forExistingUsersDescription: "L'annuncio sarà visibile solo ai profili esistenti in questo momento. Se disabilitato, sarà visibile anche ai profili che verranno creati dopo la pubblicazione di questo annuncio." @@ -1773,6 +1781,8 @@ _aboutMisskey: contributors: "Principali sostenitori" allContributors: "Tutti i sostenitori" source: "Codice sorgente" + original: "Originale" + thisIsModifiedVersion: "{name} sta usando una versione modificata diversa da CherryPick originale." translation: "Tradurre Misskey" donate: "Sostieni Misskey" morePatrons: "Apprezziamo sinceramente il supporto di tante altre persone. Grazie mille! 🥰" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index b209a2b4ad..02079410a8 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -481,6 +481,7 @@ name: "名前" antennaSource: "受信ソース" antennaKeywords: "受信キーワード" antennaExcludeKeywords: "除外キーワード" +antennaExcludeBots: "Botアカウントを除外" antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" notifyAntenna: "新しいノートを通知する" withFileAntenna: "ファイルが添付されたノートのみ" @@ -587,6 +588,7 @@ disableDrawer: "メニューをドロワーで表示しない" youHaveNoGroups: "グループがありません" joinOrCreateGroup: "既存のグループに招待してもらうか、新しくグループを作成してください。" showNoteActionsOnlyHover: "ノートのアクションをホバー時のみ表示する" +showReactionsCount: "ノートのリアクション数を表示する" noHistory: "履歴はありません" signinHistory: "ログイン履歴" enableAdvancedMfm: "高度なMFMを有効にする" @@ -1272,6 +1274,7 @@ showRenotes: "リノートを表示" edited: "編集済み" notificationRecieveConfig: "通知の受信設定" mutualFollow: "相互フォロー" +followingOrFollower: "フォロー中またはフォロワー" fileAttachedOnly: "ファイル付きのみ" showRepliesToOthersInTimeline: "TLに他の人への返信を含める" hideRepliesToOthersInTimeline: "TLに他の人への返信を含めない" @@ -1322,6 +1325,8 @@ soundWillBePlayed: "サウンドが再生されます" showReplay: "リプレイを見る" replay: "リプレイ" replaying: "リプレイ中" +endReplay: "リプレイを終了" +copyReplayData: "リプレイデータをコピー" ranking: "ランキング" lastNDays: "直近{n}日" backToTitle: "タイトルへ" @@ -1329,6 +1334,17 @@ hemisphere: "お住まいの地域" withSensitive: "センシティブなファイルを含むノートを表示" userSaysSomethingSensitive: "{name}のセンシティブなファイルを含む投稿" enableHorizontalSwipe: "スワイプしてタブを切り替える" +loading: "読み込み中" +surrender: "やめる" +gameRetry: "リトライ" +notUsePleaseLeaveBlank: "使用しない場合は空欄にしてください" +useTotp: "ワンタイムパスワードを使う" +useBackupCode: "バックアップコードを使う" +launchApp: "アプリを起動" +useNativeUIForVideoAudioPlayer: "動画・音声の再生にブラウザのUIを使用する" +keepOriginalFilename: "オリジナルのファイル名を保持" +keepOriginalFilenameDescription: "この設定をオフにすると、アップロード時にファイル名が自動でランダム文字列に置き換えられます。" +noDescription: "説明文はありません" showUnreadNotificationsCount: "未読の通知の数を表示する" showCatOnly: "キャット付きのみ" additionalPermissionsForFlash: "Playへの追加許可" @@ -1358,6 +1374,15 @@ _messaging: _bubbleGame: howToPlay: "遊び方" + hold: "ホールド" + _score: + score: "スコア" + scoreYen: "稼いだ金額" + highScore: "ハイスコア" + maxChain: "最大チェーン数" + yen: "{yen}円" + estimatedQty: "{qty}個分" + scoreSweets: "おにぎり {onigiriQtyWithUnit}" _howToPlay: section1: "位置を調整してハコにモノを落とします。" section2: "同じ種類のモノがくっつくと別のモノに変化して、スコアが得られます。" @@ -1878,6 +1903,7 @@ _role: ltlAvailable: "ローカルタイムラインの閲覧" canPublicNote: "パブリック投稿の許可" canEditNote: "ノートの編集" + mentionMax: "ノート内の最大メンション数" canInvite: "サーバー招待コードの発行" inviteLimit: "招待コードの作成可能数" inviteLimitCycle: "招待コードの発行間隔" @@ -1901,6 +1927,7 @@ _role: canUseTranslator: "翻訳機能の利用" avatarDecorationLimit: "アイコンデコレーションの最大取付個数" _condition: + roleAssignedTo: "マニュアルロールにアサイン済み" isLocal: "ローカルユーザー" isRemote: "リモートユーザー" createdLessThan: "アカウント作成から~以内" @@ -1987,6 +2014,7 @@ _plugin: installWarn: "信頼できないプラグインはインストールしないでください。" manage: "プラグインの管理" viewSource: "ソースを表示" + viewLog: "ログを表示" _preferencesBackups: list: "作成したバックアップ" @@ -2284,8 +2312,7 @@ _2fa: alreadyRegistered: "既に設定は完了しています。" registerTOTP: "認証アプリの設定を開始" step1: "まず、{a}や{b}などの認証アプリをお使いのデバイスにインストールします。" - step2: "次に、表示されているQRコードをアプリでスキャンします。" - step2Click: "QRコードをクリックすると、お使いの端末にインストールされている認証アプリやキーリングに登録できます。" + step2: "次に、表示されているQRコードをアプリでスキャンするか、ボタンをクリックして端末上でアプリを開きます。" step2Uri: "デスクトップアプリを使用する場合は次のURIを入力します" step3Title: "確認コードを入力" step3: "アプリに表示されている確認コード(トークン)を入力します。" @@ -2309,6 +2336,7 @@ _2fa: backupCodesDescription: "認証アプリが使用できなくなった場合、以下のバックアップコードを使ってアカウントにアクセスできます。これらのコードは必ず安全な場所に保管してください。各コードは一回だけ使用できます。" backupCodeUsedWarning: "バックアップコードが使用されました。認証アプリが使えなくなっている場合、なるべく早く認証アプリを再設定してください。" backupCodesExhaustedWarning: "バックアップコードが全て使用されました。認証アプリを利用できない場合、これ以上アカウントにアクセスできなくなります。認証アプリを再登録してください。" + moreDetailedGuideHere: "詳細なガイドはこちら" _permissions: "read:account": "アカウントの情報を見る" @@ -2587,6 +2615,7 @@ _play: title: "タイトル" script: "スクリプト" summary: "説明" + visibilityDescription: "非公開に設定するとプロフィールに表示されなくなりますが、URLを知っている人は引き続きアクセスできます。" _pages: newPage: "ページの作成" @@ -2632,6 +2661,8 @@ _pages: section: "セクション" image: "画像" button: "ボタン" + dynamic: "動的ブロック" + dynamicDescription: "このブロックは廃止されています。今後は{play}を利用してください。" note: "ノート埋め込み" _note: @@ -2666,8 +2697,10 @@ _notification: sendTestNotification: "テスト通知を送信する" notificationWillBeDisplayedLikeThis: "通知はこのように表示されます" reactedBySomeUsers: "{n}人がリアクションしました" + likedBySomeUsers: "{n}人がいいねしました" renotedBySomeUsers: "{n}人がリノートしました" followedBySomeUsers: "{n}人にフォローされました" + flushNotification: "通知の履歴をリセットする" _types: all: "すべて" @@ -2763,7 +2796,7 @@ _moderationLogTypes: updateCustomEmoji: "カスタム絵文字更新" deleteCustomEmoji: "カスタム絵文字削除" updateServerSettings: "サーバー設定更新" - updateUserNote: "モデレーションノート更新" + updateUserNote: "ユーザーのモデレーションノート更新" deleteDriveFile: "ファイルを削除" deleteNote: "ノートを削除" createGlobalAnnouncement: "全体のお知らせを作成" @@ -2775,6 +2808,7 @@ _moderationLogTypes: resetPassword: "パスワードをリセット" suspendRemoteInstance: "リモートサーバーを停止" unsuspendRemoteInstance: "リモートサーバーを再開" + updateRemoteInstanceNote: "リモートサーバーのモデレーションノート更新" markSensitiveDriveFile: "ファイルをセンシティブ付与" unmarkSensitiveDriveFile: "ファイルをセンシティブ解除" resolveAbuseReport: "通報を解決" @@ -2901,11 +2935,33 @@ _reversi: opponentHasSettingsChanged: "相手が設定を変更しました" allowIrregularRules: "変則許可 (完全フリー)" disallowIrregularRules: "変則なし" + showBoardLabels: "盤面に行・列番号を表示" + useAvatarAsStone: "石をアイコンにする" _offlineScreen: title: "オフライン - サーバーに接続できません" header: "サーバーに接続できません" +_urlPreviewSetting: + title: "URLプレビューの設定" + enable: "URLプレビューを有効にする" + timeout: "プレビュー取得時のタイムアウト(ms)" + timeoutDescription: "プレビュー取得の所要時間がこの値を超えた場合、プレビューは生成されません。" + maximumContentLength: "Content-Lengthの最大値(byte)" + maximumContentLengthDescription: "Content-Lengthがこの値を超えた場合、プレビューは生成されません。" + requireContentLength: "Content-Lengthが取得できた場合のみプレビューを生成" + requireContentLengthDescription: "相手サーバがContent-Lengthを返さない場合、プレビューは生成されません。" + userAgent: "User-Agent" + userAgentDescription: "プレビュー取得時に使用されるUser-Agentを設定します。空欄の場合、デフォルトのUser-Agentが使用されます。" + summaryProxy: "プレビューを生成するプロキシのエンドポイント" + summaryProxyDescription: "CherryPick本体ではなく、サマリープロキシを使用してプレビューを生成します。" + summaryProxyDescription2: "プロキシには下記パラメータがクエリ文字列として連携されます。プロキシ側がこれらをサポートしない場合、設定値は無視されます。" + +_mediaControls: + pip: "ピクチャインピクチャ" + playbackRate: "再生速度" + loop: "ループ再生" + _abuse: _resolver: 1hour: "一時間" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index e8870c42bf..9caf6c29aa 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -1005,6 +1005,7 @@ neverShow: "今後表示しない" remindMeLater: "また後で" didYouLikeMisskey: "CherryPick気に入ってくれた?" pleaseDonate: "CherryPickは{host}が使うとる無料のソフトウェアやで。これからも開発を続けれるように、寄付したってな~。" +correspondingSourceIsAvailable: "{anchor}" roles: "ロール" role: "ロール" noRole: "ロールはありまへん" @@ -1222,6 +1223,7 @@ hemisphere: "住んでる地域" withSensitive: "センシティブなファイルを含むノートを表示" userSaysSomethingSensitive: "{name}のセンシティブなファイルを含む投稿" enableHorizontalSwipe: "スワイプしてタブを切り替える" +surrender: "やめとく" _bubbleGame: howToPlay: "遊び方" _howToPlay: diff --git a/locales/ko-GS.yml b/locales/ko-GS.yml index c99f9ffb3c..d31990fe52 100644 --- a/locales/ko-GS.yml +++ b/locales/ko-GS.yml @@ -640,6 +640,7 @@ icon: "아바타" replies: "답하기" renotes: "리노트" attach: "옇기" +surrender: "아이예" _initialAccountSetting: startTutorial: "길라잡이 하기" _initialTutorial: diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index acbc41a785..b02ef40e53 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1096,6 +1096,7 @@ neverShow: "다시 보지 않기" remindMeLater: "나중에 알림" didYouLikeMisskey: "CherryPick이 마음에 드시나요?" pleaseDonate: "CherryPick은 {host}에서 사용하는 무료 소프트웨어에요. 후원을 통해 앞으로도 개발을 계속할 수 있게 도와주세요!" +correspondingSourceIsAvailable: "소스 코드는 {anchor} 에서 받으실 수 있어요." roles: "역할" role: "역할" noRole: "역할이 없어요" @@ -1280,6 +1281,12 @@ confirmShowRepliesAll: "이 조작은 되돌릴 수 없어요! 정말로 타임 confirmHideRepliesAll: "이 조작은 되돌릴 수 없어요! 정말로 타임라인에 현재 팔로우 중인 모든 사람의 답글을 표시하지 않도록 설정할까요?" externalServices: "외부 서비스" sourceCode: "소스 코드" +sourceCodeIsNotYetProvided: "소스 코드를 아직 제공하지 않았어요. 이 문제를 해결하려면 관리자에게 문의해 주세요." +repositoryUrl: "저장소 URL" +repositoryUrlDescription: "소스 코드를 공개한 저장소가 있는 경우 그 URL을 적어주세요. CherryPick을 원본 그대로(소스 코드를 어떤 식으로도 변경하지 않고) 사용하는 경우, https://github.com/kokonect-link/cherrypick 라고 적어주세요." +repositoryUrlOrTarballRequired: "저장소를 공개하지 않는 경우, 대신 tarball을 제공해야 해요. 자세한 내용은 .config/example.yml을 참조해 주세요." +feedback: "피드백" +feedbackUrl: "피드백 URL" impressum: "운영자 정보" impressumUrl: "운영자 정보 URL" impressumDescription: "독일 등의 일부 나라와 지역에서는 꼭 표시해야 해요(Impressum)." @@ -1322,6 +1329,7 @@ hemisphere: "거주 지역" withSensitive: "민감한 파일이 포함된 노트 표시" userSaysSomethingSensitive: "{name}님의 민감한 파일이 포함된 게시물" enableHorizontalSwipe: "밀어서 탭 전환" +surrender: "그만두기" showUnreadNotificationsCount: "읽지 않은 알림 수 표시" showCatOnly: "고양이만 보기" additionalPermissionsForFlash: "Play에 대한 추가 권한" @@ -1979,6 +1987,8 @@ _aboutMisskey: contributors: "주요 기여자" allContributors: "모든 기여자" source: "소스 코드" + original: "원본" + thisIsModifiedVersion: "{name}에서는 원본 CherryPick을 수정해서 사용하고 있어요." translation: "Misskey를 번역하기" donate: "Misskey에 기부하기" morePatrons: "이 외에도 다른 많은 분들이 도움을 주시고 계십니다. 감사합니다🥰" @@ -2689,9 +2699,10 @@ _moderationLogTypes: resetPassword: "비밀번호 재설정" suspendRemoteInstance: "리모트 서버를 정지" unsuspendRemoteInstance: "리모트 서버의 정지를 해제" + updateRemoteInstanceNote: "리모트 서버의 조정 기록 갱신" markSensitiveDriveFile: "파일을 열람 주의로 설정" unmarkSensitiveDriveFile: "파일의 열람 주의를 해제" - resolveAbuseReport: "신고 해결" + resolveAbuseReport: "신고 처리" createInvitation: "초대 코드 생성" createAd: "광고 생성" deleteAd: "광고 삭제" diff --git a/locales/no-NO.yml b/locales/no-NO.yml index 205a7dc188..0da1ce8e4d 100644 --- a/locales/no-NO.yml +++ b/locales/no-NO.yml @@ -463,6 +463,7 @@ options: "Alternativ" icon: "Avatar" replies: "Svar" renotes: "Renote" +surrender: "Avbryt" _initialAccountSetting: theseSettingsCanEditLater: "Du kan endre disse innstillingene senere." _achievements: diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml index d0fd50a384..0940948519 100644 --- a/locales/pt-PT.yml +++ b/locales/pt-PT.yml @@ -1011,6 +1011,7 @@ renotes: "Repostar" keepScreenOn: "Manter a tela do dispositivo sempre ligada" flip: "Inversão" lastNDays: "Últimos {n} dias" +surrender: "Cancelar" _initialAccountSetting: followUsers: "Siga usuários que lhe interessam para criar a sua linha do tempo." _serverSettings: diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index 360ee71a63..d8db08a9ce 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -1099,6 +1099,7 @@ loadReplies: "Показать ответы" sourceCode: "Исходный код" flip: "Переворот" lastNDays: "Последние {n} сут" +surrender: "Этот пост не может быть отменен." _initialAccountSetting: accountCreated: "Аккаунт успешно создан!" letsStartAccountSetup: "Давайте настроим вашу учётную запись." diff --git a/locales/th-TH.yml b/locales/th-TH.yml index bae55a0cee..09c3cc8943 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -8,12 +8,12 @@ search: "ค้นหา" notifications: "การเเจ้งเตือน" username: "ชื่อผู้ใช้" password: "รหัสผ่าน" -forgotPassword: "ลืมรหัสผ่านใช่ไหม" +forgotPassword: "ลืมรหัสผ่าน" fetchingAsApObject: "กำลังดึงข้อมูลจากสหพันธ์..." ok: "ตกลง" gotIt: "เข้าใจแล้ว !" cancel: "ยกเลิก" -noThankYou: "ไม่เป็นไร" +noThankYou: "ไม่เอาดีกว่า" enterUsername: "กรอกชื่อผู้ใช้" renotedBy: "รีโน้ตโดย {user}" noNotes: "ไม่มีโน้ต" @@ -31,16 +31,16 @@ login: "เข้าสู่ระบบ" loggingIn: "กำลังเข้าสู่ระบบ" logout: "ออกจากระบบ" signup: "สร้างบัญชีผู้ใช้" -uploading: "กำลังอัพโหลด..." +uploading: "กำลังอัปโหลด" save: "บันทึก" users: "ผู้ใช้งาน" addUser: "เพิ่มผู้ใช้" favorite: "รายการโปรด" favorites: "รายการโปรด" unfavorite: "ลบออกจากรายการโปรด" -favorited: "เพิ่มแล้วในรายการโปรด" -alreadyFavorited: "เพิ่มในรายการโปรดอยู่แล้ว" -cantFavorite: "ไม่สามารถเพิ่มในรายการโปรดได้" +favorited: "เพิ่มลงรายการโปรดแล้ว" +alreadyFavorited: "เพิ่มลงรายการโปรดอยู่แล้ว" +cantFavorite: "ไม่สามารถเพิ่มลงรายการโปรดได้" pin: "ปักหมุด" unpin: "เลิกปักหมุด" copyContent: "คัดลอกเนื้อหา" @@ -65,18 +65,18 @@ loadMore: "แสดงเพิ่มเติม" showMore: "แสดงเพิ่มเติม" showLess: "ปิด" youGotNewFollower: "ได้ติดตามคุณ" -receiveFollowRequest: "คำขอผู้ติดตามที่ได้รับ" -followRequestAccepted: "อนุมัติการติดตามแล้ว" +receiveFollowRequest: "มีคำขอติดตามส่งมาหา" +followRequestAccepted: "การติดตามได้รับการอนุมัติแล้ว" mention: "กล่าวถึง" mentions: "พูดถึง" -directNotes: "ไดเร็คโน้ต" +directNotes: "โพสต์แบบไดเร็กต์" importAndExport: "นำเข้า / ส่งออก" import: "นำเข้า" export: "ส่งออก" files: "ไฟล์" download: "ดาวน์โหลด" -driveFileDeleteConfirm: "ต้องการลบไฟล์ “{name}” ใช่หรือไม่? โน้ตที่แนบมากับไฟล์นี้ก็จะถูกลบไปด้วย" -unfollowConfirm: "ต้องการเลิกติดตาม {name}?" +driveFileDeleteConfirm: "ต้องการลบไฟล์ “{name}” ใช่ไหม? โน้ตที่แนบมากับไฟล์นี้ก็จะถูกลบไปด้วย" +unfollowConfirm: "ต้องการเลิกติดตาม {name} ใช่ไหม?" exportRequested: "คุณได้ร้องขอการส่งออก อาจใช้เวลาสักครู่ และจะถูกเพิ่มในไดรฟ์ของคุณเมื่อเสร็จสิ้นแล้ว" importRequested: "คุณได้ร้องขอการนำเข้า การดำเนินการนี้อาจใช้เวลาสักครู่" lists: "รายชื่อ" @@ -128,9 +128,9 @@ emojiPickerDisplay: "แสดงตัวจิ้มเอโมจิ" overwriteFromPinnedEmojisForReaction: "เขียนทับการตั้งค่ารีแอคชั่น" overwriteFromPinnedEmojis: "เขียนทับการตั้งค่าทั่วไป" reactionSettingDescription2: "ลากเพื่อจัดลำดับใหม่ คลิกที่เอโมจินั้นเพื่อลบ กด “+” เพื่อเพิ่ม" -rememberNoteVisibility: "จดจำการตั้งค่าการมองเห็นตัวโน้ต" -attachCancel: "ลบไฟล์ออกที่แนบมา" -deleteFile: "ลบไฟล์ออกแล้ว" +rememberNoteVisibility: "จำการตั้งค่าการมองเห็นโน้ต" +attachCancel: "ยกเลิกแนบไฟล์" +deleteFile: "ลบไฟล์ออก" markAsSensitive: "ทำเครื่องหมายว่ามีเนื้อหาละเอียดอ่อน" unmarkAsSensitive: "ยกเลิกทำเครื่องหมายว่ามีเนื้อหาละเอียดอ่อน" enterFileName: "พิมพ์ชื่อไฟล์" @@ -138,14 +138,14 @@ mute: "ปิดเสียง" unmute: "ยกเลิกการปิดเสียง" renoteMute: "ปิดเสียงรีโน้ต" renoteUnmute: "เปิดเสียง รีโน้ต" -block: "บล็อค" -unblock: "เลิกปิดกั้น" -suspend: "ถูกระงับ" -unsuspend: "ยกเลิกระงับ" -blockConfirm: "ต้องการบล็อกบัญชีนี้?" -unblockConfirm: "ต้องการปลดบล็อคบัญชีนี้?" -suspendConfirm: "ต้องการระงับบัญชีนี้?" -unsuspendConfirm: "ต้องการยกเลิกการระงับบัญชีนี้?" +block: "บล็อก" +unblock: "เลิกบล็อก" +suspend: "ระงับ" +unsuspend: "เลิกระงับ" +blockConfirm: "ต้องการบล็อกบัญชีนี้ใช่ไหม?" +unblockConfirm: "ต้องการเลิกบล็อกบัญชีนี้ใช่ไหม?" +suspendConfirm: "ต้องการระงับบัญชีนี้ใช่ไหม?" +unsuspendConfirm: "ต้องการยกเลิกการระงับบัญชีนี้ใช่ไหม?" selectList: "เลือกรายชื่อ" editList: "แก้ไขรายชื่อ" selectChannel: "เลือกช่อง" @@ -162,13 +162,13 @@ emojiUrl: "URL ของเอโมจิ" addEmoji: "แทรกเอโมจิ" settingGuide: "การตั้งค่าที่แนะนำ" cacheRemoteFiles: "แคชไฟล์ระยะไกล" -cacheRemoteFilesDescription: "เมื่อปิดใช้งานการตั้งค่านี้ ไฟล์ระยะไกลนั้นจะถูกโหลดโดยตรงจากอินสแตนซ์ระยะไกล แต่กรณีการปิดใช้งานนี้จะช่วยลดปริมาณการใช้พื้นที่จัดเก็บข้อมูล แต่เพิ่มปริมาณการใช้งาน เพราะเนื่องจากจะไม่มีการสร้างภาพขนาดย่อ" +cacheRemoteFilesDescription: "หากเปิดใช้งาน ไฟล์ระยะไกลจะถูกแคชไว้ ทำให้แสดงภาพเร็วขึ้น แต่ก็ใช้พื้นที่เก็บข้อมูลของเซิร์ฟเวอร์มากขึ้นเช่นกัน สำหรับขีดจำกัดที่ผู้ใช้ระยะไกลถูกแคชไว้จะขึ้นอยู่กับความจุไดรฟ์ตามบทบาทของเขา เมื่อเกินแล้วไฟล์เก่าจะถูกลบออกและเก็บเป็นลิงก์แทน หากปิดใช้งาน ไฟล์ระยะไกลจะถูกเก็บเป็นลิงก์ตั้งแต่ต้น เราแนะนำให้ตั้งค่า proxyRemoteFiles ใน default.yml เป็น true เพื่อสร้างธัมบ์เนลและปกป้องความเป็นส่วนตัวของผู้ใช้" youCanCleanRemoteFilesCache: "คุณสามารถล้างแคชได้โดยคลิกที่ปุ่ม 🗑️ ในมุมมองการจัดการไฟล์" -cacheRemoteSensitiveFiles: "แคชไฟล์ระยะไกลที่มีเครื่องหมายว่ามีเนื้อหาละเอียดอ่อน" +cacheRemoteSensitiveFiles: "แคชไฟล์ระยะไกลที่มีเนื้อหาละเอียดอ่อน" cacheRemoteSensitiveFilesDescription: "เมื่อปิดการใช้งานการตั้งค่านี้ ไฟล์ระยะไกลที่มีเครื่องหมายว่ามีเนื้อหาละเอียดอ่อนนั้นจะถูกโหลดโดยตรงจากอินสแตนซ์ระยะไกลโดยที่ไม่มีการแคช" -flagAsBot: "ทำเครื่องหมายบอกว่าบัญชีนี้เป็นบอท" +flagAsBot: "ทำเครื่องหมายบอกว่าบัญชีนี้เป็นบอต" flagAsBotDescription: "การเปิดใช้งานตัวเลือกนี้หากบัญชีนี้ถูกควบคุมโดยนักเขียนโปรแกรม หรือ ถ้าหากเปิดใช้งาน มันจะทำหน้าที่เป็นแฟล็กสำหรับนักพัฒนารายอื่นๆ และเพื่อป้องกันการโต้ตอบแบบไม่มีที่สิ้นสุดกับบอทตัวอื่นๆ และยังสามารถปรับเปลี่ยนระบบภายในของ CherryPick เพื่อปฏิบัติต่อบัญชีนี้เป็นบอท" -flagAsCat: "เมี้ยววววววว!!!!!!!!!!! (ทำเครื่องหมายว่าบัญชีนี้เป็นแมว)" +flagAsCat: "เมี้ยววววววววววววววว!!!!!!!!!!!" flagAsCatDescription: "เหมียวเหมียวเมี้ยว??" flagShowTimelineReplies: "แสดงตอบกลับ ในไทม์ไลน์" flagShowTimelineRepliesDescription: "แสดงการตอบกลับของผู้ใช้งานไปยังโน้ตของผู้ใช้งานรายอื่นๆในไทม์ไลน์หากได้เปิดเอาไว้" @@ -180,7 +180,7 @@ showOnRemote: "ดูบนอินสแตนซ์ระยะไกล" general: "ทั่วไป" wallpaper: "ภาพพื้นหลัง" setWallpaper: "ตั้งค่าภาพพื้นหลัง" -removeWallpaper: "น้ำภาพพื้นหลังออก" +removeWallpaper: "นำภาพพื้นหลังออก" searchWith: "ค้นหา: {q}" youHaveNoLists: "คุณไม่มีรายชื่อใดๆ " followConfirm: "ต้องการติดตาม {name} ใช่ไหม?" @@ -189,11 +189,11 @@ proxyAccountDescription: "บัญชีพร็อกซี่ คือ บ host: "โฮสต์" selectUser: "เลือกผู้ใช้งาน" recipient: "ผู้รับ" -annotation: "ความคิดเห็น" +annotation: "หมายเหตุประกอบ" federation: "สหพันธ์" instances: "อินสแตนซ์" -registeredAt: "จดทะเบียนที่" -latestRequestReceivedAt: "ได้รับคำขอล่าสุดไปแล้ว" +registeredAt: "วันที่ลงทะเบียน" +latestRequestReceivedAt: "คำขอล่าสุดที่ได้รับ" latestStatus: "สถานะล่าสุด" storageUsage: "พื้นที่จัดเก็บข้อมูลที่ใช้ไป" charts: "โดดเด่น" @@ -215,10 +215,10 @@ disk: "ดิสก์" instanceInfo: "ข้อมูลอินสแตนซ์" statistics: "สถิติการใช้งาน" clearQueue: "ล้างคิว" -clearQueueConfirmTitle: "คุณแน่ใจแล้วหรอว่าต้องการที่จะล้างคิว?" +clearQueueConfirmTitle: "ต้องการล้างคิวใช่ไหม?" clearQueueConfirmText: "โพสต์ที่ยังค้างในคิวจะไม่ถูกจัดส่งอีกต่อไป โดยปกติแล้วการดำเนินการนี้ไม่จำเป็น" clearCachedFiles: "ล้างแคช" -clearCachedFilesConfirm: "ต้องการลบไฟล์ระยะไกลที่แคชไว้ทั้งหมด?" +clearCachedFilesConfirm: "ต้องการลบไฟล์ระยะไกลที่แคชไว้ทั้งหมดใช่ไหม?" blockedInstances: "อินสแตนซ์ที่ถูกบล็อก" blockedInstancesDescription: "ระบุชื่อโฮสต์ของอินสแตนซ์ที่คุณต้องการบล็อก อินสแตนซ์ที่อยู่ในรายการนั้นจะไม่สามารถพูดคุยกับอินสแตนซ์นี้ได้อีกต่อไป" silencedInstances: "ปิดปากอินสแตนซ์นี้แล้ว" @@ -228,7 +228,7 @@ mutedUsers: "ผู้ใช้ที่ถูกปิดเสียง" blockedUsers: "ผู้ใช้ที่ถูกบล็อก" noUsers: "ไม่พบผู้ใช้งาน" editProfile: "แก้ไขโปรไฟล์" -noteDeleteConfirm: "ต้องการลบโน้ตนี้?" +noteDeleteConfirm: "ต้องการลบโน้ตนี้ใช่ไหม?" pinLimitExceeded: "คุณไม่สามารถปักหมุดโน้ตเพิ่มเติมใดๆได้อีก" intro: "การติดตั้ง CherryPick เสร็จสิ้นแล้วนะ! โปรดสร้างผู้ใช้งานที่เป็นผู้ดูแลระบบ" done: "เสร็จสิ้น" @@ -237,7 +237,7 @@ preview: "แสดงตัวอย่าง" default: "ค่าเริ่มต้น" defaultValueIs: "ค่าเริ่มต้น: {value}" noCustomEmojis: "ไม่มีเอโมจิ" -noJobs: "ไม่มีชิ้นงาน" +noJobs: "ไม่มีงาน" federating: "สหพันธ์" blocked: "ถูกบล็อก" suspended: "ถูกระงับ" @@ -261,11 +261,11 @@ usernameOrUserId: "ชื่อผู้ใช้หรือรหัสผู noSuchUser: "ไม่พบผู้ใช้" lookup: "การค้นหา" announcements: "ประกาศ" -imageUrl: "url รูปภาพ" +imageUrl: "URL รูปภาพ" remove: "ลบ" removed: "ถูกลบไปแล้ว" -removeAreYouSure: "ต้องการที่จะลบ “{x}” ออก?" -deleteAreYouSure: "ต้องการลบ {x} หรือไม่คะ?" +removeAreYouSure: "ต้องการลบ “{x}” ใช่ไหม?" +deleteAreYouSure: "ต้องการลบ “{x}” ใช่ไหม?" resetAreYouSure: "รีเซ็ตเลยไหม?" areYouSure: "แน่ใจแล้วใช่ไหมคะ?" saved: "บันทึกแล้ว" @@ -275,7 +275,7 @@ keepOriginalUploading: "เก็บภาพต้นฉบับ" keepOriginalUploadingDescription: "เก็บภาพต้นฉบับไว้เมื่ออัปโหลดภาพ หากปิด รูปภาพสำหรับการเผยแพร่ทางเว็บจะถูกสร้างขึ้นในเบราว์เซอร์เมื่อทำการอัปโหลด" fromDrive: "จากไดรฟ์" fromUrl: "จาก URL" -uploadFromUrl: "อัพโหลดจาก URL" +uploadFromUrl: "อัปโหลดจาก URL" uploadFromUrlDescription: "URL ของไฟล์ที่คุณต้องการอัปโหลด" uploadFromUrlRequested: "ร้องขอการอัปโหลดแล้ว" uploadFromUrlMayTakeTime: "การอัปโหลดอาจใช้เวลาสักครู่จึงจะเสร็จสมบูรณ์" @@ -289,7 +289,7 @@ agree: "ยอมรับ" agreeBelow: "ฉันยอมรับถึงด้านล่าง" basicNotesBeforeCreateAccount: "หมายเหตุสำคัญ" termsOfService: "เงื่อนไขการให้บริการ" -start: "เริ่มต้น​ใช้งาน​" +start: "เริ่ม" home: "หน้าแรก" remoteUserCaution: "ข้อมูลอาจไม่สมบูรณ์เนื่องจากผู้ใช้รายนี้มาจากอินสแตนซ์ระยะไกล" activity: "กิจกรรม" @@ -333,11 +333,11 @@ rename: "เปลี่ยนชื่อ" avatar: "ไอคอน" banner: "แบนเนอร์" displayOfSensitiveMedia: "แสดงสื่อที่มีเนื้อหาละเอียดอ่อน" -whenServerDisconnected: "สูญเสียการเชื่อมต่อกับเซิร์ฟเวอร์" -disconnectedFromServer: "ถูกตัดการเชื่อมต่อออกจากเซิร์ฟเวอร์" +whenServerDisconnected: "เมื่อสูญเสียการเชื่อมต่อกับเซิร์ฟเวอร์" +disconnectedFromServer: "การเชื่อมต่อเซิร์ฟเวอร์ถูกตัด" reload: "รีโหลด" doNothing: "เมิน" -reloadConfirm: "นายต้องการรีเฟรชไทม์ไลน์หรือป่าว?" +reloadConfirm: "รีโหลดเลยไหม?" watch: "ดู" unwatch: "หยุดดู" accept: "ยอมรับ" @@ -347,7 +347,7 @@ instanceName: "ชื่ออินสแตนซ์" instanceDescription: "คำอธิบายอินสแตนซ์" maintainerName: "ผู้ดูแล" maintainerEmail: "อีเมลผู้ดูแลระบบ" -tosUrl: "เงื่อนไขการให้บริการ URL" +tosUrl: "URL เงื่อนไขการให้บริการ" thisYear: "ปีนี้" thisMonth: "เดือนนี้" today: "วันนี้" @@ -370,7 +370,7 @@ inMb: "เป็นเมกะไบต์" bannerUrl: "URL รูปภาพแบนเนอร์" backgroundImageUrl: "URL ภาพพื้นหลัง" basicInfo: "ข้อมูลเบื้องต้น" -pinnedUsers: "ผู้ใช้งานที่ได้รับการปักหมุด" +pinnedUsers: "ผู้ใช้ที่ถูกปักหมุด" pinnedUsersDescription: "ป้อนชื่อผู้ใช้ที่คุณต้องการปักหมุดในหน้า “ค้นพบ” ฯลฯ คั่นด้วยการขึ้นบรรทัดใหม่" pinnedPages: "หน้าเพจที่ปักหมุด" pinnedPagesDescription: "ป้อนเส้นทางของหน้าเพจที่คุณต้องการปักหมุดไว้ที่หน้าแรกของอินสแตนซ์นี้ คั่นด้วยขึ้นบรรทัดใหม่" @@ -409,16 +409,16 @@ caseSensitive: "อักษรพิมพ์ใหญ่-พิมพ์เล withReplies: "รวมตอบกลับ" connectedTo: "บัญชีดังต่อไปนี้มีการเชื่อมต่อกัน" notesAndReplies: "โพสต์และการตอบกลับ" -withFiles: "รวบรวมไฟล์" +withFiles: "มีไฟล์" silence: "ถูกปิดปาก" -silenceConfirm: "ต้องการที่จะ ปิดปาก ผู้ใช้รายนี้?" +silenceConfirm: "ต้องการปิดปากผู้ใช้รายนี้ใช่ไหม?" unsilence: "ยกเลิกการปิดปาก" -unsilenceConfirm: "ต้องการยกเลิกปิดปากผู้ใช้รายนี้?" +unsilenceConfirm: "ต้องการเลิกปิดปากผู้ใช้รายนี้ใช่ไหม?" popularUsers: "ผู้ใช้ที่เป็นที่นิยม" recentlyUpdatedUsers: "ผู้ใช้ที่เพิ่งใช้งานล่าสุด" recentlyRegisteredUsers: "ผู้ใช้ที่เข้าร่วมใหม่" recentlyDiscoveredUsers: "ผู้ใช้ที่เพิ่งค้นพบใหม่" -exploreUsersCount: "มีผู้ใช้ {จำนวน} ราย" +exploreUsersCount: "มีผู้ใช้ {count} ราย" exploreFediverse: "สำรวจสหพันธ์" popularTags: "แท็กยอดนิยม" userList: "ลิสต์" @@ -435,7 +435,7 @@ moderation: "การกลั่นกรอง" moderationNote: "โน้ตการกลั่นกรอง" addModerationNote: "เพิ่มโน้ตการกลั่นกรอง" moderationLogs: "ปูมการแก้ไข" -nUsersMentioned: "กล่าวถึงโดยผู้ใช้ {n} รายนี้" +nUsersMentioned: "กล่าวถึงโดยผู้ใช้ {n} ราย" securityKeyAndPasskey: "ความปลอดภัยและรหัสผ่าน" securityKey: "กุญแจความปลอดภัย" lastUsed: "ใช้ล่าสุด" @@ -449,7 +449,7 @@ reduceUiAnimation: "ลดภาพเคลื่อนไหว UI" share: "แบ่งปัน" notFound: "ไม่พบหน้าที่ต้องการ" notFoundDescription: "ไม่พบหน้าตาม URL ที่ระบุ" -uploadFolder: "โฟลเดอร์เริ่มต้นสำหรับอัพโหลด" +uploadFolder: "โฟลเดอร์เริ่มต้นสำหรับอัปโหลด" markAsReadAllNotifications: "ทำเครื่องหมายการแจ้งเตือนทั้งหมดว่าอ่านแล้ว" markAsReadAllUnreadNotes: "ทำเครื่องหมายโน้ตทั้งหมดว่าอ่านแล้ว" markAsReadAllTalkMessages: "ทำเครื่องหมายข้อความทั้งหมดว่าอ่านแล้ว" @@ -472,7 +472,7 @@ text: "ข้อความ" enable: "เปิดใช้งาน" next: "ถัด​ไป" retype: "พิมพ์รหัสอีกครั้ง" -noteOf: "โน้ต โดย {user}" +noteOf: "โน้ตของ {user}" inviteToGroup: "ชวนเข้ากลุ่ม" quoteAttached: "อ้างอิง" quoteQuestion: "ต้องการที่จะแนบมันเพื่ออ้างอิงใช่ไหม?" @@ -481,7 +481,7 @@ newMessageExists: "คุณมีข้อความใหม่" onlyOneFileCanBeAttached: "สามารถแนบไฟล์ได้เพียงไฟล์เดียวต่อ 1 ข้อความ" signinRequired: "กรุณาลงทะเบียนหรือลงชื่อเข้าใช้ก่อนดำเนินการต่อ" invitations: "คำเชิญ" -invitationCode: "รหัสคำเชิญ" +invitationCode: "รหัสเชิญ" checking: "Checking" available: "พร้อมใช้งาน" unavailable: "ไม่พร้อมใช้" @@ -569,7 +569,7 @@ popout: "ป๊อปเอาต์" volume: "ระดับเสียง" masterVolume: "ระดับเสียงหลัก" notUseSound: "ไม่ใช้เสียง" -useSoundOnlyWhenActive: "มีเสียงออกเฉพาะเมื่อ CherryPick ทำงานอยู่" +useSoundOnlyWhenActive: "มีเสียงออกเฉพาะตอนกำลังใช้ CherryPick อยู่เท่านั้น" details: "รายละเอียด" chooseEmoji: "เลือกเอโมจิ" unableToProcess: "ไม่สามารถดำเนินการให้เสร็จสิ้นได้" @@ -582,8 +582,8 @@ installedDate: "วันที่ติดตั้ง" lastUsedDate: "ใช้งานครั้งล่าสุด" state: "สถานะ" sort: "เรียงลำดับ" -ascendingOrder: "เรียงจากน้อยไปมาก" -descendingOrder: "เรียงจากมากไปน้อย" +ascendingOrder: "เรียงลำดับขึ้น" +descendingOrder: "เรียงลำดับลง" scratchpad: "Scratchpad" scratchpadDescription: "Scratchpad เป็นการจัดเตรียมสภาพแวดล้อมสำหรับการทดลอง AiScript แต่คุณสามารถเขียน ดำเนินการ และตรวจสอบผลลัพธ์ของการโต้ตอบกับ CherryPick มันได้ด้วยนะ" output: "เอาท์พุต" @@ -591,15 +591,15 @@ script: "สคริปต์" disablePagesScript: "ปิดการใช้งาน AiScript บนเพจ" updateRemoteUser: "อัปเดตข้อมูลผู้ใช้งานระยะไกล" unsetUserAvatar: "เลิกตั้งอวตาร" -unsetUserAvatarConfirm: "ต้องการเลิกตั้งอวตาร?" +unsetUserAvatarConfirm: "ต้องการเลิกตั้งอวตารใข่ไหม?" unsetUserBanner: "เลิกตั้งแบนเนอร์" unsetUserBannerConfirm: "ต้องการเลิกตั้งแบนเนอร์?" deleteAllFiles: "ลบไฟล์ทั้งหมด" -deleteAllFilesConfirm: "ต้องการลบไฟล์ทั้งหมดหรือไม่?" +deleteAllFilesConfirm: "ต้องการลบไฟล์ทั้งหมดใช่ไหม?" removeAllFollowing: "เลิกติดตามผู้ใช้ที่ติดตามทั้งหมด" removeAllFollowingDescription: "เลิกติดตามทั้งหมดจาก {host} โปรดเรียกใช้สิ่งนี้เมื่ออินสแตนซ์ดังกล่าวได้สูญหายตายจากไปแล้ว" userSuspended: "ผู้ใช้รายนี้ถูกระงับการใช้งาน" -userSilenced: "ผู้ใช้รายนี้กำลังถูกปิดกั้น" +userSilenced: "ผู้ใช้รายนี้ถูกปิดปากอยู่" yourAccountSuspendedTitle: "บัญชีนี้นั้นถูกระงับ" yourAccountSuspendedDescription: "บัญชีนี้ถูกระงับ เนื่องจากละเมิดข้อกำหนดในการให้บริการของเซิร์ฟเวอร์หรืออาจจะละเมิดหลักเกณฑ์ชุมชน หรือ อาจจะโดนร้องเรียนเรื่องการละเมิดลิขสิทธิ์และอื่นๆอย่างต่อเนื่องซ้ำๆ หากคุณคิดว่าไม่ได้ทำผิดจริงๆหรือตัดสินผิดพลาด ได้โปรดกรุณาติดต่อผู้ดูแลระบบหากคุณต้องการทราบเหตุผลโดยละเอียดเพิ่มเติม และขอความกรุณาอย่าสร้างบัญชีใหม่" tokenRevoked: "โทเค็นไม่ถูกต้อง" @@ -612,7 +612,7 @@ addItem: "เพิ่มรายการ" rearrange: "จัดใหม่" relays: "รีเลย์" addRelay: "เพิ่มรีเลย์" -inboxUrl: "อินบ็อกซ์ URL" +inboxUrl: "URL ของอินบ็อกซ์" addedRelays: "เพิ่มรีเลย์แล้ว" serviceworkerInfo: "ต้องเปิดใช้งานสำหรับการแจ้งเตือนแบบพุช" deletedNote: "โน้ตที่ถูกลบ" @@ -629,7 +629,7 @@ description: "รายละเอียด" describeFile: "เพิ่มแคปชั่น" enterFileDescription: "ใส่แคปชั่น" author: "ผู้เขียน" -leaveConfirm: "คุณมีการเปลี่ยนแปลงที่ไม่ได้บันทึกนะ นายต้องการทิ้งการเปลี่ยนแปลงเหล่านั้นหรอ?" +leaveConfirm: "มีการเปลี่ยนแปลงที่ยังไม่ได้บันทึก ต้องการละทิ้งมันใช่ไหม?" manage: "การจัดการ" plugins: "ปลั๊กอิน" preferencesBackups: "ตั้งค่าการสำรองข้อมูล" @@ -676,7 +676,7 @@ display: "แสดงผล" copy: "คัดลอก" metrics: "เมตริก" overview: "ภาพรวม" -logs: "บันทึกข้อมูลระบบ" +logs: "ปูม" delayed: "ดีเลย์" database: "ฐานข้อมูล" channel: "ช่อง" @@ -684,26 +684,26 @@ create: "สร้าง" notificationSetting: "ตั้งค่าการแจ้งเตือน" notificationSettingDesc: "เลือกประเภทการแจ้งเตือนที่ต้องการจะแสดง" useGlobalSetting: "ใช้การตั้งค่าส่วนกลาง" -useGlobalSettingDesc: "หากเปิดไว้ ระบบจะใช้การตั้งค่าการแจ้งเตือนของบัญชีของคุณ หากปิดอยู่ สามารถทำการกำหนดค่าแต่ละรายการได้นะ" +useGlobalSettingDesc: "เมื่อเปิดใช้งาน ใช้การตั้งค่าการแจ้งเตือนจากบัญชีคุณ เมื่อปิดใช้งาน สามารถตั้งค่าได้อย่างอิสระ" other: "อื่น ๆ" regenerateLoginToken: "สร้างโทเค็นการเข้าสู่ระบบอีกครั้ง" regenerateLoginTokenDescription: "สร้างโทเค็นใหม่ที่ใช้ภายในระหว่างการเข้าสู่ระบบ โดยตามหลักปกติแล้วการดำเนินการนี้ไม่จำเป็น หากสร้างใหม่ อุปกรณ์ทั้งหมดจะถูกออกจากระบบนะ" -theKeywordWhenSearchingForCustomEmoji: "คีย์เวิร์ดสำหรับใช้ค้นหาอีโมจิที่กำหนดเอง" +theKeywordWhenSearchingForCustomEmoji: "คีย์เวิร์ดสำหรับใช้ค้นหาเอโมจิที่กำหนดเอง" setMultipleBySeparatingWithSpace: "คั่นหลายรายการด้วยช่องว่าง" -fileIdOrUrl: "ไฟล์ ID หรือ URL" +fileIdOrUrl: "ID ของไฟล์ หรือ URL" behavior: "พฤติกรรม" sample: "ตัวอย่าง" abuseReports: "รายงาน" reportAbuse: "รายงาน" reportAbuseRenote: "รายงานรีโน้ต" -reportAbuseOf: "รายงาน {ชื่อ}" +reportAbuseOf: "รายงาน {name}" fillAbuseReportDescription: "กรุณากรอกรายละเอียดเกี่ยวกับรายงานนี้ หากเป็นเรื่องเกี่ยวกับโน้ตโดยเฉพาะ ได้โปรดระบุ URL" abuseReported: "เราได้ส่งรายงานของคุณไปแล้ว ขอบคุณมากๆนะ" -reporter: "นักข่าว" +reporter: "ผู้รายงาน" reporteeOrigin: "รายงานต้นทาง" -reporterOrigin: "นักข่าวต้นทาง" +reporterOrigin: "แหล่งผู้รายงาน" forwardReport: "ส่งต่อรายงานไปยังอินสแตนซ์ระยะไกล" -forwardReportIsAnonymous: "แทนที่จะเป็นบัญชีของคุณ บัญชีระบบที่ไม่ระบุตัวตนจะแสดงเป็นนักข่าวที่อินสแตนซ์ระยะไกล" +forwardReportIsAnonymous: "ข้อมูลของคุณจะไม่ปรากฏบนอินสแตนซ์ระยะไกลและปรากฏเป็นบัญชีระบบที่ไม่ระบุชื่อ" send: "ส่ง" abuseMarkAsResolved: "ทำเครื่องหมายรายงานว่าแก้ไขแล้ว" openInNewTab: "เปิดในแท็บใหม่" @@ -711,7 +711,7 @@ openInSideView: "เปิดในมุมมองด้านข้าง" defaultNavigationBehaviour: "พฤติกรรมการนำทางที่เป็นค่าเริ่มต้น" editTheseSettingsMayBreakAccount: "การแก้ไขการตั้งค่าเหล่านี้อาจทำให้บัญชีของคุณเสียหายนะ" instanceTicker: "ข้อมูลอินสแตนซ์ของโน้ต" -waitingFor: "กำลังรอคอย {x}" +waitingFor: "กำลังรอ {x}" random: "สุ่มค่า" system: "ระบบ" switchUi: "สลับ UI" @@ -721,7 +721,7 @@ createNew: "สร้างใหม่" optional: "ไม่บังคับ" createNewClip: "สร้างคลิปใหม่" unclip: "ลบคลิป" -confirmToUnclipAlreadyClippedNote: "โน้ตนี้เป็นส่วนหนึ่งของคลิป \"{name}\" แล้ว คุณต้องการลบออกจากคลิปนี้แทนอย่างงั้นหรอ?" +confirmToUnclipAlreadyClippedNote: "โน้ตนี้เป็นส่วนหนึ่งของคลิป “{name}” อยู่แล้ว ต้องการนำมันออกจากคลิปใช่ไหม?" public: "สาธารณะ" private: "ส่วนตัว" i18nInfo: "CherryPick กำลังได้รับการแปลเป็นภาษาต่างๆ โดยอาสาสมัคร คุณสามารถช่วยเหลือได้ที่ {link}" @@ -744,7 +744,7 @@ driveFilesCount: "จำนวนไฟล์ไดรฟ์" driveUsage: "การใช้พื้นที่ไดรฟ์" noCrawle: "ปฏิเสธการจัดทำดัชนีของโปรแกรมรวบรวมข้อมูล" noCrawleDescription: "ขอให้เครื่องมือค้นหาไม่จัดทำดัชนีหน้าโปรไฟล์ โน้ต หน้าเพจ ฯลฯ" -lockedAccountInfo: "เว้นแต่ว่าคุณจะต้องตั้งค่าการเปิดเผยโน้ตเป็น \"ผู้ติดตามเท่านั้น\" โน้ตย่อของคุณจะปรากฏแก่ทุกคน ถึงแม้ว่าคุณจะเป็นกำหนดให้ผู้ติดตามต้องได้รับการอนุมัติด้วยตนเองก็ตาม" +lockedAccountInfo: "แม้ว่าการอนุมัติการติดตามถูกเปิดใช้งานอยู่ทุกคนก็ยังคงสามารถเห็นโน้ตของคุณได้ เว้นแต่ว่าคุณจะเปลี่ยนการเปิดเผยโน้ตของคุณเป็น “เฉพาะผู้ติดตาม”" alwaysMarkSensitive: "ทำเครื่องหมายว่ามีเนื้อหาละเอียดอ่อนเป็นค่าเริ่มต้น" loadRawImages: "โหลดภาพต้นฉบับแทนการแสดงภาพขนาดย่อ" disableShowingAnimatedImages: "ไม่ต้องเล่นภาพเคลื่อนไหว" @@ -780,29 +780,29 @@ nNotes: "{n} โน้ต" sendErrorReports: "ส่งรายงานว่าข้อผิดพลาด" sendErrorReportsDescription: "เมื่อเปิดใช้งาน ข้อมูลข้อผิดพลาดโดยรายละเอียดนั้นจะถูกแชร์ให้กับ CherryPick เมื่อเกิดปัญหา ซึ่งช่วยปรับปรุงคุณภาพของ CherryPick\nซึ่งจะรวมถึงข้อมูล เช่น เวอร์ชั่นของระบบปฏิบัติการ เบราว์เซอร์ที่คุณใช้ กิจกรรมของคุณใน CherryPick เป็นต้น" myTheme: "ธีมของฉัน" -backgroundColor: "ภาพพื้นหลัง" -accentColor: "รูปแบบสี" +backgroundColor: "สีพื้นหลัง" +accentColor: "สีหลัก" textColor: "สีข้อความ" saveAs: "บันทึกเป็น..." advanced: "ขั้นสูง" advancedSettings: "การตั้งค่าขั้นสูง" value: "ค่า" createdAt: "สร้างเมื่อ" -updatedAt: "อัพเดทล่าสุด" +updatedAt: "อัปเดตล่าสุด" saveConfirm: "บันทึกเปลี่ยนแปลงมั้ย?" deleteConfirm: "ลบจริงๆเหรอ?" invalidValue: "ค่านี้ไม่ถูกต้อง" registry: "ทะเบียน" closeAccount: "ปิด บัญชี" currentVersion: "เวอร์ชั่นปัจจุบัน" -latestVersion: "รุ่นปัจจุบัน" +latestVersion: "เวอร์ชั่นล่าสุด" youAreRunningUpToDateClient: "คุณกำลังใช้ไคลเอ็นต์เวอร์ชันใหม่ล่าสุดนะ" newVersionOfClientAvailable: "มีไคลเอ็นต์เวอร์ชันใหม่กว่าของคุณพร้อมใช้งานนะ" usageAmount: "การใช้งาน" capacity: "ความจุ" inUse: "ใช้แล้ว" editCode: "แก้ไขโค้ด" -apply: "ตกลง" +apply: "นำไปใช้" receiveAnnouncementFromInstance: "รับการแจ้งเตือนจากอินสแตนซ์นี้" emailNotification: "การแจ้งเตือนทางอีเมล" publish: "เผยแพร่" @@ -814,7 +814,7 @@ showingPastTimeline: "กำลังแสดงผลไทม์ไลน์ clear: "ล้าง" markAllAsRead: "ทำเครื่องหมายทั้งหมดว่าอ่านแล้ว" goBack: "ย้อนกลับ" -unlikeConfirm: "เลิกถูกใจจริงๆ หรือ?" +unlikeConfirm: "ต้องการเลิกถูกใจใช่ไหม?" fullView: "มุมมองแบบเต็ม" quitFullView: "ออกจากมุมมองแบบเต็ม" addDescription: "เพิ่มคำอธิบาย" @@ -825,12 +825,12 @@ userInfo: "ข้อมูลผู้ใช้" unknown: "ไม่ทราบสถานะ" onlineStatus: "สถานะออนไลน์" hideOnlineStatus: "ซ่อนสถานะออนไลน์" -hideOnlineStatusDescription: "การซ่อนสถานะออนไลน์ของคุณช่วยลดความสะดวกของคุณสมบัติบางอย่าง เช่น การค้นหา อ่ะนะ" +hideOnlineStatusDescription: "การซ่อนสถานะออนไลน์อาจทำให้ฟังก์ชันบางอย่าง เช่น การค้นหา สะดวกน้อยลง" online: "ออนไลน์" active: "ใช้งานอยู่" offline: "ออฟไลน์" notRecommended: "ไม่แนะนำ" -botProtection: "การป้องกัน Bot (or AI)" +botProtection: "การป้องกัน Bot" instanceBlocking: "อินสแตนซ์ที่ถูกบล็อก" selectAccount: "เลือกบัญชี" switchAccount: "สลับบัญชีผู้ใช้" @@ -892,7 +892,7 @@ itsOff: "ปิดใช้งาน" on: "เปิด" off: "ปิด" emailRequiredForSignup: "จำเป็นต้องการใช้ที่อยู่อีเมลสำหรับการสมัคร" -unread: "ไม่ได้อ่าน" +unread: "ยังไม่ได้อ่าน" filter: "กรอง" controlPanel: "แผงควบคุม" manageAccounts: "จัดการบัญชี" @@ -900,13 +900,13 @@ makeReactionsPublic: "ตั้งค่าประวัติการรี makeReactionsPublicDescription: "การทำเช่นนี้จะทำให้รายการรีแอคชั่นของคุณที่ผ่านมาทั้งหมดปรากฏต่อสาธารณะ" classic: "คลาสสิค" muteThread: "ปิดเสียงเธรด" -unmuteThread: "เปิดเสียงเธรด" +unmuteThread: "เลิกปิดเสียงเธรด" followingVisibility: "การมองเห็นที่เรากำลังติดตาม" followersVisibility: "การมองเห็นผู้ที่กำลังติดตามเรา" continueThread: "ดูความต่อเนื่องเธรด" deleteAccountConfirm: "การดำเนินการนี้จะลบบัญชีของคุณอย่างถาวรเลยนะ แน่ใจหรอดำเนินการ?" incorrectPassword: "รหัสผ่านไม่ถูกต้อง" -voteConfirm: "ยืนยันการโหวต “{choice}” ไหม?" +voteConfirm: "ต้องการโหวต “{choice}” ใช่ไหม?" hide: "ซ่อน" leaveGroup: "ออกจากกลุ่ม" leaveGroupConfirm: "คุณแน่ใจหรอว่าต้องการออกจาก \"{name}\"" @@ -955,13 +955,13 @@ deleteAccount: "ลบบัญชี" document: "เอกสาร" numberOfPageCache: "จำนวนหน้าเพจที่แคช" numberOfPageCacheDescription: "การเพิ่มจำนวนนี้จะช่วยเพิ่มความสะดวกให้กับผู้ใช้งาน แต่จะทำให้เซิร์ฟเวอร์โหลดมากขึ้นและต้องใช้หน่วยความจำมากขึ้นอีกด้วย" -logoutConfirm: "ต้องการออกจากระบบ?" -lastActiveDate: "ใช้งานล่าสุดที่" +logoutConfirm: "ต้องการออกจากระบบใช่ไหม?" +lastActiveDate: "ใช้งานล่าสุดเมื่อ" statusbar: "แถบสถานะ" pleaseSelect: "ตัวเลือก" -reverse: "ย้อนกลับ" +reverse: "พลิก" colored: "สี" -refreshInterval: "รอบการอัพเดต" +refreshInterval: "ความถี่ในการอัปเดต" label: "ป้ายชื่อ" type: "รูปแบบ" speed: "ความเร็ว" @@ -988,8 +988,8 @@ unsubscribePushNotification: "ปิดการแจ้งเตือนแ pushNotificationAlreadySubscribed: "การแจ้งเตือนแบบพุชได้เปิดใช้งานแล้ว" pushNotificationNotSupported: "เบราว์เซอร์หรืออินสแตนซ์ของคุณนั้นไม่รองรับการแจ้งเตือนแบบพุช" sendPushNotificationReadMessage: "ลบการแจ้งเตือนแบบพุชเมื่ออ่านการแจ้งเตือนหรือข้อความที่เกี่ยวข้องแล้ว" -sendPushNotificationReadMessageCaption: "การแจ้งเตือนที่มีข้อความ \"{emptyPushNotificationMessage}\" จะแสดงขึ้นมาในช่วงระยะเวลาสั้นๆ การดำเนินการนี้อาจทำให้เพิ่มการใช้งานแบตเตอรี่ของอุปกรณ์ถ้าหากมีนะ" -windowMaximize: "ขยายใหญ่สุดแล้ว" +sendPushNotificationReadMessageCaption: "อาจทำให้อุปกรณ์ของคุณใช้พลังงานมากขึ้น" +windowMaximize: "ขยายใหญ่สุด" windowMinimize: "ย่อเล็กที่สุด" windowRestore: "เลิกทำ" caption: "คำอธิบาย" @@ -1005,6 +1005,7 @@ neverShow: "ไม่ต้องแสดงข้อความนี้อ remindMeLater: "ไว้ครั้งหน้าแล้วกัน" didYouLikeMisskey: "คุณชอบ CherryPick ไหม?" pleaseDonate: "CherryPick เป็นซอฟต์แวร์ฟรีที่ใช้งานโดย {host} เราขอขอบคุณการสนับสนุนของคุณอย่างสูงเพื่อให้การพัฒนา CherryPick สามารถดำเนินต่อไปได้!" +correspondingSourceIsAvailable: "ซอร์สโค้ดที่เกี่ยวข้องมีอยู่ที่ {anchor}" roles: "บทบาท" role: "บทบาท" noRole: "ไม่พบบทบาท" @@ -1073,7 +1074,7 @@ enableChartsForFederatedInstances: "สร้างแผนภูมิข้ showClipButtonInNoteFooter: "เพิ่ม “คลิป” ไปยังเมนูสั่งการของโน้ต" reactionsDisplaySize: "ขนาดของรีแอคชั่น" limitWidthOfReaction: "จำกัดความกว้างสูงสุดของรีแอคชั่นและแสดงให้เล็กลง" -noteIdOrUrl: "โน้ต ID หรือ URL" +noteIdOrUrl: "ID ของโน้ต หรือ URL" video: "วีดีโอ" videos: "วีดีโอ" audio: "เสียง" @@ -1095,7 +1096,7 @@ leftBottom: "ล่างซ้าย" rightBottom: "ล่างขวา" stackAxis: "ทิศทางการซ้อน" vertical: "แนวตั้ง" -horizontal: "ด้านข้าง" +horizontal: "แนวนอน" position: "ตำแหน่ง" serverRules: "กฎของเซิร์ฟเวอร์" pleaseConfirmBelowBeforeSignup: "โปรดยืนยันที่ด้านล่างก่อนสมัครใช้งาน" @@ -1111,17 +1112,17 @@ thisChannelArchived: "ช่องนี้ถูกเก็บถาวรแ displayOfNote: "การแสดงโน้ต" initialAccountSetting: "ตั้งค่าโปรไฟล์" youFollowing: "ติดตามแล้ว" -preventAiLearning: "ปฏิเสธการใช้งาน ในการเรียนรู้ของเครื่อง (Generative AI)" -preventAiLearningDescription: "การส่งคำร้องขอโปรแกรมรวบรวมข้อมูลไม่ให้ใช้ข้อความที่โพสต์หรือรูปภาพ ฯลฯ ในชุดข้อมูลแมชชีนเลิร์นนิง (Predictive / Generative AI) สิ่งนี้นั้นทำได้โดยการเพิ่มแฟล็กการตอบสนอง \"noai\" HTML ให้กับเนื้อหาที่เกี่ยวข้อง แต่อย่างไรก็ตามแล้ว การป้องกันโดยสมบูรณ์นั้นไม่สามารถทำได้ผ่านแฟล็กนี้เนื่องจากอาจจะทำให้ถูกเพิกเฉยได้" +preventAiLearning: "ปฏิเสธการเรียนรู้ด้วย generative AI" +preventAiLearningDescription: "ส่งคำร้องขอไม่ให้ใช้ ข้อความในโน้ตที่โพสต์, หรือเนื้อหารูปภาพ ฯลฯ ในการเรียนรู้ของเครื่อง(machine learning) / Predictive AI / Generative AI โดยการเพิ่มแฟล็ก “noai” ลง HTML-Response ให้กับเนื้อหาที่เกี่ยวข้อง แต่ทั้งนี้ ไม่ได้ป้องกัน AI จากการเรียนรู้ได้อย่างสมบูรณ์ เนื่องจากมี AI บางตัวเท่านั้นที่จะเคารพคำขอดังกล่าว" options: "ตัวเลือกบทบาท" specifyUser: "ผู้ใช้เฉพาะ" failedToPreviewUrl: "ไม่สามารถดูตัวอย่างได้" update: "อัปเดต" rolesThatCanBeUsedThisEmojiAsReaction: "บทบาทที่สามารถใช้เอโมจินี้เป็นรีแอคชั่นได้" -rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "ถ้าหากไม่ได้ระบุบทบาท ทุกคนนั้นก็สามารถใช้เอโมจินี้เพื่อรีแอคชั่นได้นะ" +rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "ถ้าหากไม่ได้ระบุบทบาท ใคร ๆ ก็สามารถใช้เอโมจินี้เพื่อรีแอคชั่นได้" rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "บทบาทเหล่านี้ต้องเป็นสาธารณะ" -cancelReactionConfirm: "ต้องการลบรีแอคชั่นของคุณจริงๆหรอ?" -changeReactionConfirm: "ต้องการเปลี่ยนรีแอคชั่นของคุณจริงๆหรอ?" +cancelReactionConfirm: "ต้องการลบรีแอคชั่นใช่ไหม?" +changeReactionConfirm: "ต้องการเปลี่ยนรีแอคชั่นใช่ไหม?" later: "ไว้ทีหลัง" goToMisskey: "ถึง CherryPick" additionalEmojiDictionary: "พจนานุกรมเอโมจิเพิ่มเติม" @@ -1130,20 +1131,20 @@ branding: "แบรนดิ้ง" enableServerMachineStats: "เผยแพร่สถานะฮาร์ดแวร์ของเซิร์ฟเวอร์" enableIdenticonGeneration: "เปิดใช้งานผู้ใช้สร้างตัวระบุ" turnOffToImprovePerformance: "การปิดส่วนนี้สามารถเพิ่มประสิทธิภาพได้" -createInviteCode: "สร้างคำเชิญ" +createInviteCode: "สร้างรหัสเชิญ" createWithOptions: "สร้างด้วยตัวเลือก" -createCount: "จำนวนการเชิญ" -inviteCodeCreated: "สร้างคำเชิญแล้ว" -inviteLimitExceeded: "คุณสร้างคำเชิญเกินถึงขีดจำกัดแล้วนะ" -createLimitRemaining: "ขีดจำกัดการเชิญ: {limit} ที่เหลืออยู่" -inviteLimitResetCycle: "ขีดจำกัดนี้จะถูกรีเซ็ตเป็น {limit} ที่ {time}." +createCount: "จำนวนรหัสเชิญ" +inviteCodeCreated: "สร้างรหัสเชิญแล้ว" +inviteLimitExceeded: "จำนวนรหัสเชิญที่สามารถสร้างได้ถึงขีดจำกัดแล้ว" +createLimitRemaining: "รหัสเชิญที่สามารถสร้างได้: เหลืออยู่ {limit} รหัส" +inviteLimitResetCycle: "สามารถสร้างรหัสเชิญได้อีกสูงสุด {limit} รหัส ภายใน {time}" expirationDate: "วันที่หมดอายุ" noExpirationDate: "ไม่มีหมดอายุ" -inviteCodeUsedAt: "รหัสคำเชิญใช้แล้วที่" -registeredUserUsingInviteCode: "ใช้คำเชิญแล้วโดย" +inviteCodeUsedAt: "วันเวลาที่ใช้รหัสเชิญ" +registeredUserUsingInviteCode: "ผู้ใช้ที่ใช้รหัสเชิญ" waitingForMailAuth: "กำลังรอการยืนยันอีเมล" -inviteCodeCreator: "สร้างการเชิญแล้วโดย" -usedAt: "ใช้แล้วที่" +inviteCodeCreator: "ผู้ใช้ที่สร้างรหัสเชิญ" +usedAt: "วันเวลาที่ถูกใช้" unused: "ยังไม่ได้ใช้" used: "ถูกใช้แล้ว" expired: "หมดอายุแล้ว" @@ -1162,7 +1163,7 @@ renotes: "รีโน้ต" loadReplies: "แสดงการตอบกลับ" loadConversation: "แสดงบทสนทนา" pinnedList: "รายชื่อที่ปักหมุดไว้" -keepScreenOn: "เปิดหน้าจอไว้" +keepScreenOn: "เปิดหน้าจออุปกรณ์ค้างไว้" verifiedLink: "ความเป็นเจ้าของลิงก์ได้รับการยืนยันแล้ว" notifyNotes: "แจ้งเตือนเกี่ยวกับโพสต์ใหม่" unnotifyNotes: "หยุดการแจ้งเตือนเกี่ยวกับโน้ตใหม่" @@ -1173,6 +1174,7 @@ showRenotes: "แสดงรีโน้ต" edited: "แก้ไขแล้ว" notificationRecieveConfig: "การตั้งค่าการแจ้งเตือน" mutualFollow: "ติดตามซึ่งกันและกัน" +followingOrFollower: "กำลังติดตามหรือผู้ติดตาม" fileAttachedOnly: "เฉพาะโน้ตที่มีไฟล์เท่านั้น" showRepliesToOthersInTimeline: "แสดงการตอบกลับผู้อื่นลงในไทม์ไลน์" hideRepliesToOthersInTimeline: "ไม่แสดงการตอบกลับผู้อื่นลงในไทม์ไลน์" @@ -1182,6 +1184,12 @@ confirmShowRepliesAll: "การดำเนินการนี้ไม่ confirmHideRepliesAll: "การดำเนินการนี้ไม่สามารถย้อนกลับได้ คุณต้องการซ่อนการตอบกลับผู้อื่นจากผู้ใช้ทุกคนที่คุณติดตามอยู่ในไทม์ไลน์ของคุณหรือไม่?" externalServices: "บริการภายนอก" sourceCode: "ซอร์สโค้ด" +sourceCodeIsNotYetProvided: "ซอร์สโค้ดยังไม่พร้อมใช้งาน โปรดติดต่อผู้ดูแลระบบของคุณเพื่อแก้ไขปัญหานี้" +repositoryUrl: "URL ของ repository" +repositoryUrlDescription: "หากมีที่เก็บซอร์สโค้ดที่เปิดเผยต่อสาธารณะ ให้ป้อน URL ที่เก็บซอร์สโค้ดนั้น แต่หากคุณใช้ CherryPick ตามต้นฉบับ (ไม่มีการเปลี่ยนแปลงซอร์สโค้ด) ให้ป้อน https://github.com/kokonect-link/cherrypick" +repositoryUrlOrTarballRequired: "หากคุณไม่มี repository สาธารณะ คุณจะต้องจัดเตรียม tarball แทน ดู .config/example.yml สำหรับรายละเอียด" +feedback: "ฟีดแบ็ก" +feedbackUrl: "URLของฟีดแบ็ก" impressum: "อิมเพรสชั่น" impressumUrl: "URL อิมเพรสชั่น" impressumDescription: "การติดป้ายกำกับ (Impressum) มีผลบังคับใช้ในบางประเทศและภูมิภาค เช่น ประเทศเยอรมนี" @@ -1193,7 +1201,7 @@ attach: "แนบ" detach: "นำออก" detachAll: "เอาออกทั้งหมด" angle: "แองเกิล" -flip: "ย้อนกลับ" +flip: "พลิก" showAvatarDecorations: "แสดงตกแต่งอวตาร" releaseToRefresh: "ปล่อยเพื่อรีเฟรช" refreshing: "กำลังรีเฟรช..." @@ -1217,15 +1225,29 @@ soundWillBePlayed: "จะมีการเล่นเอฟเฟกต์เ showReplay: "ดูรีเพลย์" replay: "รีเพลย์" replaying: "กำลังรีเพลย์" +endReplay: "ออกจากรีเพลย์" +copyReplayData: "คัดลอกข้อมูลรีเพลย์" ranking: "อันดับ" lastNDays: "ล่าสุด {n} วันที่แล้ว" backToTitle: "กลับไปหน้าไตเติ้ล" hemisphere: "พื้นที่ที่อาศัยอยู่" -withSensitive: "แสดงโน้ตที่มีไฟล์ที่ระบุว่ามีเนื้อหาละเอียดอ่อน" +withSensitive: "แสดงโน้ตที่มีไฟล์เนื้อหาละเอียดอ่อน" userSaysSomethingSensitive: "โพสต์ที่มีไฟล์เนื้อหาละเอียดอ่อนของ {name}" enableHorizontalSwipe: "ปัดเพื่อสลับแท็บ" +loading: "กำลังโหลด" +surrender: "ยอมแพ้" +gameRetry: "เริ่มเกมใหม่" _bubbleGame: howToPlay: "วิธีเล่น" + hold: "หยุดชั่วคราว" + _score: + score: "คะแนน" + scoreYen: "จำนวนเงินที่ได้รับ" + highScore: "คะแนนสูงสุด" + maxChain: "จำนวน chain สูงสุด" + yen: "{yen} เยน" + estimatedQty: "{qty} อัน" + scoreSweets: "โอนิงิริ {onigiriQtyWithUnit}" _howToPlay: section1: "ขยับตำแหน่งและวางวัตถุลงในกล่อง" section2: "เมื่อวัตถุประเภทเดียวกันมารวมกัน พวกมันจะกลายเป็นวัตถุใหม่และคุณจะได้รับคะแนน" @@ -1233,16 +1255,16 @@ _bubbleGame: _announcement: forExistingUsers: "ผู้ใช้งานที่มีอยู่เท่านั้น" forExistingUsersDescription: "การประกาศนี้จะแสดงต่อผู้ใช้ที่มีอยู่ ณ จุดที่เผยแพร่นั้นๆถ้าหากเปิดใช้งาน ถ้าหากปิดใช้งานผู้ที่กำลังสมัครใหม่หลังจากโพสต์แล้วนั้นก็จะเห็นเช่นกัน" - needConfirmationToRead: "จำเป็นต้องยืนยันเพื่อทำเครื่องหมายบอกว่าอ่านแล้ว" - needConfirmationToReadDescription: "ข้อความแจ้งแยก ถ้าหากต้องการเพื่อยืนยันว่ากำลังทำเครื่องหมายประกาศนี้ว่าอ่านแล้วจะแสดงขึ้นถ้าหากเปิดใช้งาน การประกาศนั้นจะไม่รวมอยู่ในฟังก์ชั่นว่า \"ทำเครื่องหมายทั้งหมดว่าอ่านแล้ว\"" + needConfirmationToRead: "จำเป็นต้องยืนยันว่าอ่านแล้ว" + needConfirmationToReadDescription: "กล่องโต้ตอบการยืนยันจะปรากฏขึ้นเมื่อจะทำเครื่องหมายว่าอ่านแล้ว นอกจากนี้ยังทำให้ประกาศนี้ยังไม่ถูกอ่านเมื่อใช้ฟังก์ชั่น “ทำเครื่องหมายฯ ทั้งหมดว่าอ่านแล้ว”" end: "เก็บประกาศ" tooManyActiveAnnouncementDescription: "การมีประกาศที่ใช้งานมากเกินไปนั้นอาจจะทำให้ประสบการณ์ของผู้ใช้งานนั้นดูแย่ลง โปรดกรุณาพิจารณาการเก็บประกาศที่ล้าสมัยด้วยนะค่ะ" - readConfirmTitle: "ทำเครื่องหมายบอกว่าอ่านแล้วเลยมั้ย?" - readConfirmText: "การดำเนินการนี้จะทำเครื่องหมายเนื้อหาของ \"{title}\" บอกว่าอ่านแล้วนะ" + readConfirmTitle: "ทำเครื่องหมายว่าอ่านแล้วเลยไหม?" + readConfirmText: "จะทำเครื่องหมายใส่ “{title}” ว่าอ่านแล้ว" shouldNotBeUsedToPresentPermanentInfo: "เราขอแนะนำให้ใช้ประกาศเพื่อโพสต์ข้อมูลแบบ flow มากกว่าข้อมูลแบบ stock เนื่องจากมีแนวโน้มที่จะส่งผลเสียต่อ UX โดยเฉพาะสำหรับผู้ใช้ใหม่" dialogAnnouncementUxWarn: "เราขอแนะนำให้ใช้ด้วยความระมัดระวัง เนื่องจากการแจ้งเตือนแบบกล่องโต้ตอบตั้งแต่ 2 รายการขึ้นไปพร้อมกันอาจส่งผลเสียต่อ UX ได้อย่างมาก" silence: "ไม่มีการแจ้งเตือน" - silenceDescription: "หากเปิดใช้งาน จะไม่ได้แจ้งเตือนประกาศนี้ และผู้ใช้จะไม่จำเป็นต้องอ่าน" + silenceDescription: "หากเปิดใช้งาน จะไม่มีการแจ้งเตือนประกาศนี้ และผู้ใช้จะไม่จำเป็นต้องทำเครื่องหมายว่าอ่านแล้ว" _initialAccountSetting: accountCreated: "คุณได้สร้างบัญชีของคุณสำเร็จเรียบร้อยแล้ว!" letsStartAccountSetup: "สำหรับผู้เริ่มต้นมาตั้งค่าโปรไฟล์ของคุณกันเถอะ" @@ -1329,7 +1351,7 @@ _timelineDescription: _serverRules: description: "ชุดของกฎที่จะแสดงก่อนการลงทะเบียนเราขอแนะนำให้ตั้งค่าสรุปข้อกำหนดในการให้บริการ" _serverSettings: - iconUrl: "ไอคอน URL" + iconUrl: "URL ไอคอน" appIconDescription: "ระบุไอคอนที่จะใช้เมื่อ {host} แสดงเป็นแอป" appIconUsageExample: "E.g. เป็น PWA หรือเมื่อแสดงผลเป็นบุ๊กมาร์กหน้าจอหลักบนโทรศัพท์" appIconStyleRecommendation: "เนื่องจากไอคอนอาจถูกครอบตัดเป็นสี่เหลี่ยมจัตุรัสหรือวงกลม จึงแนะนำให้ใช้ไอคอนที่มีขอบสีรอบๆ เนื้อหา" @@ -1620,7 +1642,7 @@ _role: assignTarget: "มอบหมาย" descriptionOfAssignTarget: "แบบปรับเอง เพิ่มถอนบทบาทนี้แก่ผู้ใช้ด้วยตัวเอง\nแบบมีเงื่อนไข เพิ่มถอนบทบาทนี้แก่ผู้ใช้โดยอัตโนมัติหากเข้าเงื่อนไขใดต่อไปนี้" manual: "ปรับเอง" - manualRoles: "บทบาทแบบทำเอง" + manualRoles: "บทบาทแบบทำมือ" conditional: "มีเงื่อนไข" conditionalRoles: "บทบาทแบบมีเงื่อนไข" condition: "เงื่อนไข" @@ -1632,13 +1654,13 @@ _role: baseRole: "เทมเพลตบทบาท" useBaseValue: "ใช้ตามเทมเพลตบทบาท" chooseRoleToAssign: "เลือกบทบาทที่ต้องการกำหนด" - iconUrl: "ไอคอน URL" + iconUrl: "URL ไอคอน" asBadge: "แสดงเป็นตรา" descriptionOfAsBadge: "เมื่อเปิดใช้งาน ไอคอนบทบาทจะปรากฏถัดจากชื่อผู้ใช้" isExplorable: "ค้นหาผู้ใช้ได้ง่ายขึ้นโดยดูจากบทบาท" descriptionOfIsExplorable: "เมื่อเปิดใช้งาน ไทมไลน์บทบาทนี้และสมาชิกที่มีบทบาทนี้จะเปิดเผยเป็นสาธารณะ" - displayOrder: "ตำแหน่ง" - descriptionOfDisplayOrder: "ยิ่งตัวเลขสูง ตำแหน่ง UI ก็ยิ่งสูงขึ้นนะ" + displayOrder: "ลำดับการแสดงผล" + descriptionOfDisplayOrder: "เลขที่สูงกว่าจะแสดงบน UI ก่อน" canEditMembersByModerator: "อนุญาตให้ผู้ควบคุมแก้ไขสมาชิก" descriptionOfCanEditMembersByModerator: "เมื่อเปิดใช้ นอกเหนือจากผู้ควบคุมและผู้ดูแลระบบแล้ว จะสามารถเพิ่มถอนบทบาทนี้แก่ผู้ใช้ได้ แต่เมื่อปิดใช้ จะมีเฉพาะผู้ดูแลระบบเท่านั้นที่จะสามารถดำเนินการได้" priority: "ลำดับความสำคัญ" @@ -1651,6 +1673,7 @@ _role: ltlAvailable: "การดูไทม์ไลน์ในท้องถิ่น" canPublicNote: "สามารถโพสต์แบบสาธารณะ" canEditNote: "กำลังแก้ไขโน้ต" + mentionMax: "จำนวนการกล่าวถึงสูงสุดต่อโน้ต" canInvite: "สร้างรหัสเชิญอินสแตนซ์" inviteLimit: "จำกัดการเชิญ" inviteLimitCycle: "คูลดาวน์ในการเชิญ" @@ -1674,6 +1697,7 @@ _role: canUseTranslator: "การใช้งานแปล" avatarDecorationLimit: "จำนวนการตกแต่งไอคอนสูงสุดที่สามารถติดตั้งได้" _condition: + roleAssignedTo: "มอบหมายให้มีบทบาทแบบทำมือ" isLocal: "ผู้ใช้ในพื้นที่" isRemote: "ผู้ใช้ระยะไกล" createdLessThan: "สร้างน้อยกว่า" @@ -1703,13 +1727,13 @@ _emailUnavailable: smtp: "เซิร์ฟเวอร์อีเมลนี้ไม่มีการตอบสนอง" banned: "คุณไม่สามารถลงทะเบียนด้วยที่อยู่อีเมลนี้ได้" _ffVisibility: - public: "เผยแพร่" + public: "สาธารณะ" followers: "ปรากฏให้แก่ผู้ติดตามเท่านั้น" private: "ส่วนตัว" _signup: almostThere: "เกือบจะเสร็จแล้ว" emailAddressInfo: "กรุณากรอกที่อยู่อีเมลที่คุณใช้ ที่อยู่อีเมลของคุณจะไม่ถูกเผยแพร่สู่สาธารณชน" - emailSent: "เราได้ส่งอีเมลยืนยันไปยังที่อยู่อีเมลของคุณแล้วนะ ({email}) โปรดคลิกลิงก์ที่รวมไว้เพื่อสร้างบัญชีให้เสร็จสิ้น" + emailSent: "อีเมลยืนยันได้ถูกส่งไปยังที่อยู่อีเมลที่คุณป้อน ({email}) แล้ว กรุณาติดตามลิงก์ในอีเมลเพื่อสร้างบัญชีให้เสร็จสมบูรณ์ ลิงก์ที่ให้ไว้จะหมดอายุใน 30 นาที" _accountDelete: accountDelete: "ลบบัญชีผู้ใช้" mayTakeTime: "เนื่องจากการลบบัญชีนี้จะเป็นกระบวนการที่ต้องใช้ทรัพยากรมาก จึงอาจจะต้องใช้เวลาสักครู่ถึงจะเสร็จสมบูรณ์ ทั้งนี้ขึ้นอยู่กับจำนวนเนื้อหาที่คุณสร้างและจำนวนไฟล์ที่คุณอัปโหลดนะ" @@ -1747,7 +1771,7 @@ _plugin: viewSource: "ดูต้นฉบับ" _preferencesBackups: list: "สร้างการสำรองข้อมูล" - saveNew: "บันทึกใหม่" + saveNew: "บันทึกข้อมูลสำรองใหม่" loadFile: "โหลดจากไฟล์" apply: "นำไปใช้กับอุปกรณ์นี้" save: "บันทึก" @@ -1757,8 +1781,8 @@ _preferencesBackups: applyConfirm: "คุณต้องการใช้ข้อมูลสำรอง \"{name}\" กับอุปกรณ์นี้อย่างงั้นจริงหรอ การตั้งค่าที่มีอยู่ของอุปกรณ์นี้จะถูกเขียนทับนะ" saveConfirm: "บันทึกข้อมูลสำรองเป็น {name} มั้ย?" deleteConfirm: "ลบข้อมูลสำรอง {name} มั้ย?" - renameConfirm: "เปลี่ยนชื่อข้อมูลสำรองนี้จาก \"{old}\" เป็น \"{new}\" หรือไม่?" - noBackups: "ไม่มีข้อมูลสำรองนะ คุณสามารถสำรองข้อมูลการตั้งค่าไคลเอนต์ของคุณบนเซิร์ฟเวอร์นี้โดยใช้ \"สร้างการสำรองข้อมูลใหม่\"ได้นะ" + renameConfirm: "ต้องการเปลี่ยนชื่อข้อมูลสำรองจาก “{old}” เป็น “{new}” ใช่ไหม?" + noBackups: "ไม่มีข้อมูลสำรอง สามารถบันทึกการตั้งค่าไคลเอนต์ปัจจุบันไปยังเซิร์ฟเวอร์ด้วย “บันทึกข้อมูลสำรองใหม่”" createdAt: "สร้างเมื่อ: {date} {time}" updatedAt: "อัปเดตเมื่อ: {date} {time}" cannotLoad: "การโหลดล้มเหลว" @@ -1774,14 +1798,16 @@ _aboutMisskey: contributors: "ผู้สนับสนุนหลัก" allContributors: "ผู้มีส่วนร่วมทั้งหมด" source: "ซอร์สโค้ด" + original: "ต้นฉบับ" + thisIsModifiedVersion: "{name} ใช้ CherryPick เวอร์ชันดัดแปลง" translation: "แปลภาษา Misskey" donate: "บริจาคให้กับ Misskey" - morePatrons: " ขอบคุณทุกท่านที่ร่วมกันช่วยเหลือตลอดมานะคะ 🥰" - patrons: "สมาชิกพันธมิตร" + morePatrons: "และอีกหลายท่านที่ไม่ได้เอ่ยนาม ขอบคุณที่ร่วมช่วยเหลือตลอดมานะคะ 🥰" + patrons: "ผู้อุปถัมภ์" projectMembers: "สมาชิกในโครงการ" _displayOfSensitiveMedia: - respect: "ซ่อนสื่อที่ทำเครื่องหมายว่ามีเนื้อหาละเอียดอ่อน" - ignore: "แสดงสื่อที่ทำเครื่องหมายว่ามีเนื้อหาละเอียดอ่อน" + respect: "ซ่อนสื่อที่มีเนื้อหาละเอียดอ่อน" + ignore: "แสดงสื่อที่มีเนื้อหาละเอียดอ่อน" force: "ซ่อนสื่อทั้งหมด" _mfm: cheatSheet: "โค้ด MFM Cheat Sheet" @@ -1915,8 +1941,8 @@ _theme: importInfo: "ถ้าหากต้องการป้อนโค้ดที่นี่ คุณยังสามารถนำเข้าไปยังโปรแกรมแก้ไขธีมได้" deleteConstantConfirm: "คุณต้องการลบค่าคงที่ {const} หรือป่าว?" keys: - accent: "เน้น" - bg: "ภาพพื้นหลัง" + accent: "สีหลัก" + bg: "พื้นหลัง" fg: "ข้อความ" focus: "โฟกัส" indicator: "ตัวบ่งชี้" @@ -1952,11 +1978,11 @@ _theme: wallpaperOverlay: "วอลล์เปเปอร์ซ้อนทับ" badge: "ตรา" messageBg: "พื้นหลังแชท" - accentDarken: "เน้น (มืด)" - accentLighten: "เน้น (สว่าง)" + accentDarken: "สีหลัก (มืด)" + accentLighten: "สีหลัก (สว่าง)" fgHighlighted: "ข้อความที่ไฮไลต์" _sfx: - note: "หมายเหตุ" + note: "โน้ต" noteMy: "โน้ตของตัวเอง" notification: "การเเจ้งเตือน" chat: "แชท" @@ -2045,7 +2071,7 @@ _permissions: "read:reactions": "ดูรีแอคชั่นของคุณ" "write:reactions": "แก้ไขรีแอคชั่นของคุณ" "write:votes": "โหวตบนสำรวจความคิดเห็น" - "read:pages": "ดหน้าเพจ" + "read:pages": "ดูหน้าเพจ" "write:pages": "แก้ไขหรือลบเพจของคุณ" "read:page-likes": "ดูรายการเพจที่ถูกใจไว้" "write:page-likes": "แก้ไขรายการเพจที่ถูกใจ" @@ -2057,8 +2083,8 @@ _permissions: "write:gallery": "แก้ไขแกลเลอรี่ของคุณ" "read:gallery-likes": "ดูรายการโพสต์แกลเลอรีที่ถูกใจไว้" "write:gallery-likes": "แก้ไขรายการโพสต์แกลเลอรีที่ถูกใจไว้" - "read:flash": "วิว เพลย์" - "write:flash": "แก้ไขเพลย์" + "read:flash": "ดู Play" + "write:flash": "แก้ไข Play" "read:flash-likes": "ดูรายการ play ที่ถูกใจไว้" "write:flash-likes": "แก้ไขรายการ play ที่ถูกใจไว้" "read:admin:abuse-user-reports": "ดูรายงานจากผู้ใช้" @@ -2085,8 +2111,8 @@ _permissions: "read:admin:roles": "ดูบทบาท" "write:admin:relays": "จัดการรีเลย์" "read:admin:relays": "ดูรีเลย์" - "write:admin:invite-codes": "จัดการคำเชิญ" - "read:admin:invite-codes": "ดูรหัสคำเชิญ" + "write:admin:invite-codes": "จัดการรหัสเชิญ" + "read:admin:invite-codes": "ดูรหัสเชิญ" "write:admin:announcements": "จัดการประกาศ" "read:admin:announcements": "ดูประกาศ" "write:admin:avatar-decorations": "จัดการการตกแต่งอวตาร" @@ -2104,7 +2130,7 @@ _permissions: "read:admin:stream": "ใช้ Websocket API สำหรับผู้ดูแลระบบ" "write:admin:ad": "จัดการโฆษณา" "read:admin:ad": "ดูโฆษณา" - "write:invite-codes": "สร้างรหัสคำเชิญ" + "write:invite-codes": "สร้างรหัสเชิญ" "read:invite-codes": "รับรหัสเชิญ" "write:clip-favorite": "ควบคุมการถูกใจของคลิป" "read:clip-favorite": "ดูการถูกใจของคลิป" @@ -2158,8 +2184,8 @@ _widgets: onlineUsers: "ผู้ใช้ที่ออนไลน์" jobQueue: "คิวงาน" serverMetric: "ตัวชี้วัดเซิร์ฟเวอร์" - aiscript: "AiScript คอนโซล" - aiscriptApp: "AiScript แอพ" + aiscript: " คอนโซล AiScript" + aiscriptApp: "แอป AiScript" aichan: "ไอ" userList: "รายชื่อผู้ใช้" _userList: @@ -2173,15 +2199,15 @@ _cw: files: "{count} ไฟล์" _poll: noOnlyOneChoice: "จำเป็นต้องมีอย่างน้อยสองตัวเลือก" - choiceN: "ตัวเลือก {n}" - noMore: "คุณไม่สามารถเพิ่มตัวเลือกอื่นได้" + choiceN: "ตัวเลือกที่ {n}" + noMore: "เพิ่มตัวเลือกอีกไม่ได้แล้ว" canMultipleVote: "สามารถตอบได้หลายคำตอบ" - expiration: "สิ้นสุดการสำรวจความคิดเห็น" - infinite: "ไม่ต้องเลย" - at: "จบที่..." - after: "สิ้นสุดหลัง..." + expiration: "สิ้นสุดโพล" + infinite: "ไม่กำหนดระยะเวลา" + at: "ระบุวันเวลา" + after: "ระบุระยะเวลา" deadlineDate: "วันสิ้นสุด" - deadlineTime: "ชั่วโมง" + deadlineTime: "เวลา" duration: "ระยะเวลา" votesCount: "{n} คะแนนเสียง" totalVotes: "{n} คะแนนเสียงทั้งหมด" @@ -2189,17 +2215,17 @@ _poll: showResult: "ดูผลลัพธ์" voted: "โหวตแล้ว" closed: "สิ้นสุดแล้ว" - remainingDays: "จะเสร็จสิ้นในอีก {d} วัน {h} ชั่วโมง" - remainingHours: "{h} ชั่วโมง(s) {m} นาที(s) ที่เหลืออยู่" - remainingMinutes: "{m} นาที(s) {s} วินาที(s) ที่เหลืออยู่" - remainingSeconds: "{s} นาที(s) ที่เหลืออยู่" + remainingDays: "เหลืออีก {d} วัน {h} ชั่วโมง" + remainingHours: "เหลืออีก {h} ชั่วโมง {m} นาที" + remainingMinutes: "เหลืออีก {m} นาที {s} วินาที" + remainingSeconds: "เหลืออีก {s} วินาที" _visibility: public: "สาธารณะ" publicDescription: "โน้ตของคุณจะปรากฏแก่ผู้ใช้ทุกคน" home: "หน้าแรก" homeDescription: "โพสลงไทม์ไลน์ที่บ้านเท่านั้น" followers: "ผู้ติดตาม" - followersDescription: "ทำให้ผู้ติดตามนั้นมองเห็นแค่คุณเท่านั้น" + followersDescription: "เฉพาะผู้ติดตามเท่านั้นที่มองเห็นได้" specified: "ไดเร็ค" specifiedDescription: "ทำให้มองเห็นได้เฉพาะผู้ใช้ที่ระบุเท่านั้น" disableFederation: "ไม่มีสหพันธ์" @@ -2209,11 +2235,11 @@ _postForm: quotePlaceholder: "อ้างโน้ตนี้..." channelPlaceholder: "โพสต์ลงช่อง..." _placeholders: - a: "คุณเป็นอะไรไปหรอ?" - b: "เกิดอะไรขึ้นรอบตัวคุณ?" - c: "คุณกำลังคิดอะไรอยู่?" - d: "คุณต้องการจะพูดอะไร?" - e: "เริ่มเขียน..." + a: "ตอนนี้เป็นยังไงบ้าง?" + b: "มีอะไรเกิดขึ้นหรือเปล่า?" + c: "กำลังคิดอะไรอยู่?" + d: "ต้องการจะพูดอะไรไหม?" + e: "มาเขียนกันเถอะ" f: "กำลังรอให้คุณเขียน..." _profile: name: "ชื่อ" @@ -2227,11 +2253,11 @@ _profile: metadataContent: "เนื้อหา" changeAvatar: "เปลี่ยนอวาตาร์" changeBanner: "เปลี่ยนแบนเนอร์" - verifiedLinkDescription: "โดยการป้อน URL ที่มีลิงก์ไปยังโปรไฟล์ของคุณตรงนี้ ส่วนไอคอนการยืนยันความเป็นเจ้าของนั้นก็สามารถแสดงถัดจากฟิลด์ได้นะ" + verifiedLinkDescription: "หากป้อน URL ที่มีลิงก์ไปยังโปรไฟล์ของคุณ ไอคอนการยืนยันความเป็นเจ้าของจะแสดงถัดจากฟิลด์นั้น ๆ" avatarDecorationMax: "คุณสามารถเพิ่มการตกแต่งได้สูงสุด {max}" _exportOrImport: allNotes: "โน้ตทั้งหมด" - favoritedNotes: "บันทึกที่ชื่นชอบ" + favoritedNotes: "โน้ตที่ถูกใจไว้" clips: "คลิป" followingList: "กำลังติดตาม" muteList: "ปิดเสียง" @@ -2315,7 +2341,7 @@ _pages: summary: "สรุปเพจ" alignCenter: "เซ็นเตอร์" hideTitleWhenPinned: "ซ่อนชื่อหน้าเพจเมื่อปักหมุดไว้ที่โปรไฟล์" - font: "ตัวอักษร" + font: "แบบอักษร" fontSerif: "Serif" fontSansSerif: "Sans Serif" eyeCatchingImageSet: "ตั้งค่าภาพขนาดย่อ" @@ -2341,28 +2367,29 @@ _relayStatus: accepted: "ได้รับการอนุมัติ" rejected: "ถูกปฏิเสธ" _notification: - fileUploaded: "ไฟล์ถูกอัพโหลดแล้วน่ะ" + fileUploaded: "ไฟล์ถูกอัปโหลดแล้ว" youGotMention: "{name} กล่าวถึงคุณ" youGotReply: "{name} ตอบกลับถึงคุณ" - youGotQuote: "{name} อ้างถึงคุณ" + youGotQuote: "{name} อ้างอิงคุณ" youRenoted: "รีโน้ตจาก {name}" youWereFollowed: "ได้ติดตามคุณ" - youReceivedFollowRequest: "คุณมีคำขอติดตามใหม่น่ะ" - yourFollowRequestAccepted: "คำขอติดตามของคุณได้รับการยอมรับแล้วน่ะ" + youReceivedFollowRequest: "ได้รับคำขอติดตาม" + yourFollowRequestAccepted: "คำขอติดตามได้รับการอนุมัติแล้ว" youWereInvitedToGroup: "{userName} ได้เชิญคุณเข้ากลุ่ม" - pollEnded: "โพลสำรวจความคิดเห็นผลลัพธ์มีพร้อมใช้งาน" + pollEnded: "ผลโพลออกมาแล้ว" newNote: "โพสต์ใหม่" unreadAntennaNote: "เสาอากาศ {name}" roleAssigned: "ได้รับบทบาท" - emptyPushNotificationMessage: "การแจ้งเตือนแบบพุชได้รับการอัพเดทแล้ว" + emptyPushNotificationMessage: "อัปเดตการแจ้งเตือนแบบพุชแล้ว" achievementEarned: "รับความสำเร็จ" testNotification: "ทดสอบการแจ้งเตือน" checkNotificationBehavior: "กดเพื่อดูลักษณะการแจ้งเตือน" sendTestNotification: "ส่งทดสอบการแจ้งเตือน" notificationWillBeDisplayedLikeThis: "การแจ้งเตือนมีลักษณะแบบนี้" reactedBySomeUsers: "ถูกรีแอคชั่นโดยผู้ใช้ {n} ราย" - renotedBySomeUsers: "Renote จากผู้ใช้จำนวน {n} ราย" + renotedBySomeUsers: "รีโน้ตจากผู้ใช้ {n} ราย" followedBySomeUsers: "มีผู้ติดตาม {n} ราย" + flushNotification: "ล้างประวัติการแจ้งเตือน" _types: all: "ทั้งหมด" note: "โน้ตใหม่" @@ -2372,9 +2399,9 @@ _notification: renote: "รีโน้ต" quote: "อ้างคำพูด" reaction: "รีแอคชั่น" - pollEnded: "โพลนี้สิ้นสุดลงแล้ว" - receiveFollowRequest: "ได้รับคำขอติดตาม\n" - followRequestAccepted: "ยอมรับคำขอติดตาม" + pollEnded: "โพลสิ้นสุดแล้ว" + receiveFollowRequest: "ได้รับคำร้องขอติดตาม" + followRequestAccepted: "อนุมัติให้ติดตามแล้ว" groupInvited: "ได้รับคำเชิญเข้ากลุ่ม" roleAssigned: "ให้บทบาท" achievementEarned: "ปลดล็อกความสำเร็จแล้ว" @@ -2412,7 +2439,7 @@ _deck: list: "รายการ" channel: "ช่อง" mentions: "พูดถึง" - direct: "ไดเร็ค" + direct: "ไดเร็กต์" roleTimeline: "บทบาทไทม์ไลน์" _dialog: charactersExceeded: "คุณกำลังมีตัวอักขระเกินขีดจำกัดสูงสุดแล้วนะ! ปัจจุบันอยู่ที่ {current} จาก {max}" @@ -2443,8 +2470,8 @@ _moderationLogTypes: updateRole: "อัปเดตบทบาทแล้ว" assignRole: "ได้รับมอบหมายบทบาท" unassignRole: "ถอดออกจากบทบาทแล้ว" - suspend: "ถูกระงับ" - unsuspend: "เลิกถูกระงับ" + suspend: "ระงับ" + unsuspend: "เลิกระงับ" addCustomEmoji: "เพิ่มเอโมจิที่กำหนดเองแล้ว" updateCustomEmoji: "อัปเดตเอโมจิที่กำหนดเองแล้ว" deleteCustomEmoji: "ลบเอโมจิที่กำหนดเองออกแล้ว" @@ -2459,12 +2486,13 @@ _moderationLogTypes: deleteGlobalAnnouncement: "ลบประกาศทั่วโลกออกแล้ว" deleteUserAnnouncement: "ลบประกาศผู้ใช้ออกแล้ว" resetPassword: "รีเซ็ตรหัสผ่าน" - suspendRemoteInstance: "อินสแตนซ์ระยะไกลถูกระงับ" - unsuspendRemoteInstance: "อินสแตนซ์ระยะไกลเลิกการระงับ" + suspendRemoteInstance: "ระงับอินสแตนซ์ระยะไกล" + unsuspendRemoteInstance: "เลิกระงับอินสแตนซ์ระยะไกล" + updateRemoteInstanceNote: "อัปเดตโน้ตการกลั่นกรองของอินสแตนซ์ระยะไกลแล้ว" markSensitiveDriveFile: "ทำเครื่องหมายไฟล์ว่ามีเนื้อหาละเอียดอ่อน" unmarkSensitiveDriveFile: "ยกเลิกทำเครื่องหมายไฟล์ว่ามีเนื้อหาละเอียดอ่อน" resolveAbuseReport: "รายงานได้รับการแก้ไขแล้ว" - createInvitation: "สร้างคำเชิญ" + createInvitation: "สร้างรหัสเชิญ" createAd: "สร้างโฆษณาแล้ว" deleteAd: "ลบโฆษณาออกแล้ว" updateAd: "อัปเดตโฆษณาแล้ว" @@ -2581,6 +2609,8 @@ _reversi: opponentHasSettingsChanged: "อีกฝ่ายเปลี่ยนการตั้งค่า" allowIrregularRules: "อนุญาตกฎที่ไม่ปรกติ (โหมดฟรีทุกอย่าง)" disallowIrregularRules: "ไม่อนุญาตกฎที่ไม่ปรกติ" + showBoardLabels: "แสดงหมายเลขแถว/คอลัมน์บนกระดาน" + useAvatarAsStone: "ใช้รูปอวตารเป็นหมาก" _offlineScreen: title: "ออฟไลน์ - ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ได้" header: "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ได้" diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index e50c3b7896..931c0cdd2f 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -1062,6 +1062,7 @@ verifiedLink: "Chúng tôi đã xác nhận bạn là chủ sở hữu của đ sourceCode: "Mã nguồn" flip: "Lật" lastNDays: "{n} ngày trước" +surrender: "Từ chối" _announcement: forExistingUsers: "Chỉ những người dùng đã tồn tại" forExistingUsersDescription: "Nếu được bật, thông báo này sẽ chỉ hiển thị với những người dùng đã tồn tại vào lúc thông báo được tạo. Nếu tắt đi, những tài khoản mới đăng ký sau khi thông báo được đăng lên cũng sẽ thấy nó." diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index e77cec3907..66ce859eea 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -336,7 +336,7 @@ displayOfSensitiveMedia: "显示敏感媒体" whenServerDisconnected: "与服务器连接中断时" disconnectedFromServer: "已和服务器断开连接" reload: "重新加载" -doNothing: "关闭弹窗" +doNothing: "关闭" reloadConfirm: "确定要重新加载吗?" watch: "关注" unwatch: "取消关注" @@ -1005,6 +1005,7 @@ neverShow: "不再显示" remindMeLater: "稍后提醒我" didYouLikeMisskey: "您喜欢 CherryPick 吗?" pleaseDonate: "CherryPick 是 {host} 所使用的免费软件。为了今后也能够维持 CherryPick 的开发,请在有余力的情况下进行捐助!" +correspondingSourceIsAvailable: "对应的源代码可在{anchor}找到" roles: "角色" role: "角色" noRole: "角色不存在" @@ -1056,6 +1057,7 @@ sensitiveWords: "敏感词" sensitiveWordsDescription: "将包含设置词的帖子的可见范围设置为首页。可以通过用换行符分隔来设置多个。" sensitiveWordsDescription2: "AND 条件用空格分隔,正则表达式用斜线包裹。" prohibitedWords: "禁用词" +prohibitedWordsDescription: "发布包含设定词汇的帖子时将出错。可用换行设定多个关键字" prohibitedWordsDescription2: "AND 条件用空格分隔,正则表达式用斜线包裹。" hiddenTags: "隐藏标签" hiddenTagsDescription: "设定的标签将不会在时间线上显示。可使用换行来设置多个标签。" @@ -1129,7 +1131,7 @@ branding: "品牌" enableServerMachineStats: "公开服务器硬件统计信息" enableIdenticonGeneration: "启用生成用户 Identicon" turnOffToImprovePerformance: "关闭该选项可以提高性能。" -createInviteCode: "发行邀请码" +createInviteCode: "生成邀请码" createWithOptions: "使用选项来创建" createCount: "发行数" inviteCodeCreated: "已创建邀请码" @@ -1141,7 +1143,7 @@ noExpirationDate: "不设置有效日期" inviteCodeUsedAt: "邀请码被使用的日期和时间" registeredUserUsingInviteCode: "使用了邀请码的用户" waitingForMailAuth: "等待验证电子邮件" -inviteCodeCreator: "发行邀请码的用户" +inviteCodeCreator: "生成邀请码的用户" usedAt: "使用时间" unused: "未使用" used: "已使用" @@ -1172,6 +1174,7 @@ showRenotes: "显示转帖" edited: "已编辑" notificationRecieveConfig: "通知接收设置" mutualFollow: "互相关注" +followingOrFollower: "关注中或关注者" fileAttachedOnly: "仅限媒体" showRepliesToOthersInTimeline: "在时间线中包含给别人的回复" hideRepliesToOthersInTimeline: "在时间线中隐藏给别人的回复" @@ -1181,6 +1184,12 @@ confirmShowRepliesAll: "此操作不可撤销。确认要在时间线中包含 confirmHideRepliesAll: "此操作不可撤销。确认要在时间线中隐藏现在关注的所有人的回复吗?" externalServices: "外部服务" sourceCode: "源代码" +sourceCodeIsNotYetProvided: "还未提供源代码。要解决此问题请联系管理员。" +repositoryUrl: "仓库地址" +repositoryUrlDescription: "若源代码所在的仓库是公开的,请填入对应的 URL。若是按原样使用 CherryPick(并未追加或者修改代码)的情况请填入 https://github.com/kokonect-link/cherrypick。" +repositoryUrlOrTarballRequired: "若仓库并未公开,则需要提供 tarball 作为替代。详情请看 .config/example.yml。" +feedback: "反馈" +feedbackUrl: "反馈地址" impressum: "运营商信息" impressumUrl: "运营商信息地址" impressumDescription: "德国等国家和地区有义务展示此类信息(Impressum)。" @@ -1210,11 +1219,14 @@ seasonalScreenEffect: "应景的画面效果" decorate: "装饰" addMfmFunction: "添加装饰" enableQuickAddMfmFunction: "显示高级 MFM 选择器" +bubbleGame: "泡泡游戏" sfx: "音效" soundWillBePlayed: "声音将会播放" -showReplay: "查看重播" +showReplay: "观看回放" replay: "重播" replaying: "重播中" +endReplay: "结束回放" +copyReplayData: "复制回放数据" ranking: "排行榜" lastNDays: "最近 {n} 天" backToTitle: "返回标题" @@ -1222,8 +1234,19 @@ hemisphere: "居住地区" withSensitive: "显示包含敏感媒体的帖子" userSaysSomethingSensitive: "含 {name} 敏感文件的帖子" enableHorizontalSwipe: "滑动切换标签页" +loading: "读取中" +surrender: "取消" +gameRetry: "重试" _bubbleGame: howToPlay: "游戏说明" + hold: "抓住" + _score: + score: "得分" + scoreYen: "赚到的钱" + highScore: "最高分" + maxChain: "最高连击数" + yen: "{yen} 日元" + estimatedQty: "约 {qty} 个" _howToPlay: section1: "对准位置将Emoji投入盒子。" section2: "相同的Emoji相互接触合成后会得到新的Emoji,以此获得分数。" @@ -1312,8 +1335,8 @@ _initialTutorial: description: "对于服务器方针所要求要求的,又或者不适合直接展示的附件,请添加「敏感」标记。\n" tryThisFile: "试试看,将附加到此窗口的图像标注为敏感!" _exampleNote: - note: "不该打开纳豆的盖子的……" - method: "要标注附件为敏感内容,请单击该文件以打开菜单,然后单击“设置为敏感”。" + note: "拆纳豆包装时出错了…" + method: "要标注附件为敏感内容,请单击该文件以打开菜单,然后单击“标记为敏感内容”。" sensitiveSucceeded: "附加文件时,请遵循服务器的条款来设置正确敏感设定。\n" doItToContinue: "将图像标记为敏感后才能够继续" _done: @@ -1648,8 +1671,9 @@ _role: ltlAvailable: "查看本地时间线" canPublicNote: "允许公开发帖" canEditNote: "编辑帖子" + mentionMax: "帖子内最多提及数" canInvite: "发放服务器邀请码" - inviteLimit: "可发行邀请码的数量" + inviteLimit: "可生成邀请码的数量" inviteLimitCycle: "邀请码的发行间隔" inviteExpirationTime: "邀请码的有效日期" canManageCustomEmojis: "管理自定义表情符号" @@ -1671,6 +1695,7 @@ _role: canUseTranslator: "使用翻译功能" avatarDecorationLimit: "可添加头像挂件的最大个数" _condition: + roleAssignedTo: "已分配给手动角色" isLocal: "是本地用户" isRemote: "是远程用户" createdLessThan: "账户创建时间少于" @@ -1771,6 +1796,8 @@ _aboutMisskey: contributors: "主要贡献者" allContributors: "全体贡献者" source: "源代码" + original: "原版" + thisIsModifiedVersion: "{name}正在使用修改后的 CherryPick。" translation: "翻译 Misskey" donate: "赞助 Misskey" morePatrons: "还有很多其它的人也在支持我们,非常感谢🥰" @@ -2101,7 +2128,7 @@ _permissions: "read:admin:stream": "使用管理员用的 Websocket API" "write:admin:ad": "编辑广告" "read:admin:ad": "查看广告" - "write:invite-codes": "发行邀请码" + "write:invite-codes": "生成邀请码" "read:invite-codes": "获取已发行的邀请码" "write:clip-favorite": "编辑便签的点赞" "read:clip-favorite": "查看便签的点赞" @@ -2360,6 +2387,7 @@ _notification: reactedBySomeUsers: "{n} 人回应了" renotedBySomeUsers: "{n} 人转发了" followedBySomeUsers: "被 {n} 人关注" + flushNotification: "重置通知历史" _types: all: "全部" note: "用户的新帖子" @@ -2458,10 +2486,11 @@ _moderationLogTypes: resetPassword: "重置密码" suspendRemoteInstance: "停止远程服务器" unsuspendRemoteInstance: "恢复远程服务器" + updateRemoteInstanceNote: "更新远程服务器的管理笔记" markSensitiveDriveFile: "标记网盘文件为敏感媒体" unmarkSensitiveDriveFile: "取消标记网盘文件为敏感媒体" resolveAbuseReport: "处理举报" - createInvitation: "发行邀请码" + createInvitation: "生成邀请码" createAd: "创建了广告" deleteAd: "删除了广告" updateAd: "更新了广告" @@ -2552,6 +2581,8 @@ _reversi: myTurn: "你的回合" turnOf: "{name}的回合" pastTurnOf: "{name}的回合" + surrender: "认输" + surrendered: "已认输" timeout: "超时" drawn: "平局" won: "{name}获胜" @@ -2573,6 +2604,8 @@ _reversi: opponentHasSettingsChanged: "对手更改了设定" allowIrregularRules: "允许非常规规则(完全自由)" disallowIrregularRules: "禁止非常规规则" + showBoardLabels: "显示行号和列号" + useAvatarAsStone: "用头像作为棋子" _offlineScreen: title: "离线——无法连接到服务器" header: "无法连接到服务器" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index a8ae2629fd..d6f0d51e97 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -1005,6 +1005,7 @@ neverShow: "不再顯示" remindMeLater: "以後再說" didYouLikeMisskey: "您喜歡 CherryPick 嗎?" pleaseDonate: "CherryPick 是由 {host} 使用的免費軟體。請贊助我們,讓開發得以持續!" +correspondingSourceIsAvailable: "對應的原始碼可以在 {anchor} 處找到。" roles: "角色" role: "角色" noRole: "沒有角色" @@ -1173,6 +1174,7 @@ showRenotes: "顯示其他人的轉發貼文" edited: "已編輯" notificationRecieveConfig: "接受通知的設定" mutualFollow: "互相追隨" +followingOrFollower: "追隨中或追隨者" fileAttachedOnly: "顯示包含附件的貼文" showRepliesToOthersInTimeline: "顯示給其他人的回覆" hideRepliesToOthersInTimeline: "在時間軸上隱藏給其他人的回覆" @@ -1182,6 +1184,12 @@ confirmShowRepliesAll: "進行此操作後無法復原。您真的希望時間 confirmHideRepliesAll: "進行此操作後無法復原。您真的希望時間軸「不包含」您目前追隨的所有人的回覆嗎?" externalServices: "外部服務" sourceCode: "原始碼" +sourceCodeIsNotYetProvided: "尚未提供原始碼,請洽詢管理員解決這個問題。" +repositoryUrl: "儲存庫 URL" +repositoryUrlDescription: "如果存在可公開取得原始碼的儲存庫,請輸入其 URL。 如果您按原樣使用 CherryPick(不對原始碼進行任何更改),請輸入 https://github.com/kokonect-link/cherrypick。" +repositoryUrlOrTarballRequired: "如果儲存庫不是公開的,則必須提供 tarball。 詳細資訊請參閱 .config/example.yml。" +feedback: "意見回饋" +feedbackUrl: "意見回饋 URL" impressum: "營運者資訊" impressumUrl: "營運者資訊網址" impressumDescription: "在德國與部份地區必須要明確顯示營運者資訊。" @@ -1217,6 +1225,8 @@ soundWillBePlayed: "將播放音效" showReplay: "觀看重播" replay: "重播" replaying: "重播中" +endReplay: "退出重播" +copyReplayData: "複製重播資料" ranking: "排行榜" lastNDays: "過去 {n} 天" backToTitle: "回到遊戲標題頁" @@ -1224,8 +1234,20 @@ hemisphere: "您居住的地區" withSensitive: "顯示包含敏感檔案的貼文" userSaysSomethingSensitive: "包含 {name} 敏感檔案的貼文" enableHorizontalSwipe: "滑動切換時間軸" +loading: "載入中" +surrender: "退出" +gameRetry: "再試一次" _bubbleGame: howToPlay: "玩法說明" + hold: "保留" + _score: + score: "分數" + scoreYen: "賺取的金額" + highScore: "最高分" + maxChain: "最大結合數" + yen: "{yen} 日圓" + estimatedQty: "{qty}個" + scoreSweets: "飯糰 {onigiriQtyWithUnit}" _howToPlay: section1: "調整位置並將物體放入盒子中。" section2: "當相同類型的物體黏在一起時,它們會變成不同的物體,您就會得到分數。" @@ -1632,7 +1654,7 @@ _role: baseRole: "基本角色" useBaseValue: "使用基本角色的值" chooseRoleToAssign: "選擇要指派的角色" - iconUrl: "圖示的URL" + iconUrl: "圖示的 URL" asBadge: "顯示為徽章" descriptionOfAsBadge: "開啟的話,角色圖示會顯示在使用者名稱旁邊。" isExplorable: "讓使用者更容易找到您" @@ -1651,6 +1673,7 @@ _role: ltlAvailable: "瀏覽本地時間軸" canPublicNote: "允許公開貼文" canEditNote: "允許編輯貼文" + mentionMax: "貼文內的最大提及數" canInvite: "發行伺服器邀請碼" inviteLimit: "可建立邀請碼的數量" inviteLimitCycle: "邀請碼的發放間隔" @@ -1674,6 +1697,7 @@ _role: canUseTranslator: "使用翻譯功能" avatarDecorationLimit: "頭像裝飾的最大設置量" _condition: + roleAssignedTo: "手動指派角色完成" isLocal: "本地使用者" isRemote: "遠端使用者" createdLessThan: "帳戶加入時間不超過" @@ -1774,6 +1798,8 @@ _aboutMisskey: contributors: "主要貢獻者" allContributors: "全體貢獻人員" source: "原始碼" + original: "原始" + thisIsModifiedVersion: "{name} 使用原始 CherryPick 的修改版本。" translation: "翻譯 Misskey" donate: "贊助 Misskey" morePatrons: "還有許許多多幫助我們的其他人,非常感謝你們。 🥰" @@ -2363,6 +2389,7 @@ _notification: reactedBySomeUsers: "{n}人做出了反應" renotedBySomeUsers: "{n}人做了轉發" followedBySomeUsers: "被{n}人追隨了" + flushNotification: "重置通知歷史紀錄" _types: all: "全部 " note: "使用者的最新貼文" @@ -2449,7 +2476,7 @@ _moderationLogTypes: updateCustomEmoji: "更新自訂表情符號" deleteCustomEmoji: "刪除自訂表情符號" updateServerSettings: "更新伺服器設定" - updateUserNote: "更新管理筆記" + updateUserNote: "更新了使用者的管理筆記" deleteDriveFile: "刪除檔案" deleteNote: "刪除貼文" createGlobalAnnouncement: "建立全網通知" @@ -2461,6 +2488,7 @@ _moderationLogTypes: resetPassword: "重設密碼" suspendRemoteInstance: "封鎖遠端伺服器" unsuspendRemoteInstance: "解除封鎖遠端伺服器" + updateRemoteInstanceNote: "更新了遠端伺服器的管理筆記" markSensitiveDriveFile: "標記為敏感檔案" unmarkSensitiveDriveFile: "撤銷標記為敏感檔案" resolveAbuseReport: "解決檢舉" @@ -2581,6 +2609,8 @@ _reversi: opponentHasSettingsChanged: "對手更改了設定" allowIrregularRules: "允許異常規則(完全自由)" disallowIrregularRules: "不允許異常規則" + showBoardLabels: "在棋盤上顯示行、列號" + useAvatarAsStone: "用大頭貼當作棋子" _offlineScreen: title: "離線-無法連接伺服器" header: "無法連接伺服器" diff --git a/package.json b/package.json index 0cc5936383..2257b4ea60 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "cherrypick", - "version": "4.7.0", - "basedMisskeyVersion": "2024.2.0", + "version": "4.8.0", + "basedMisskeyVersion": "2024.3.1", "codename": "nasubi", "repository": { "type": "git", "url": "https://github.com/kokonect-link/cherrypick.git" }, - "packageManager": "pnpm@8.15.1", + "packageManager": "pnpm@8.15.4", "workspaces": [ "packages/frontend", "packages/backend", @@ -52,22 +52,25 @@ "lodash": "4.17.21" }, "dependencies": { - "cssnano": "6.0.3", + "cssnano": "6.0.5", "execa": "8.0.1", "fast-glob": "3.3.2", "ignore-walk": "6.0.4", "js-yaml": "4.1.0", - "postcss": "8.4.33", + "postcss": "8.4.35", "tar": "6.2.0", - "terser": "5.27.0", - "typescript": "5.3.3" + "terser": "5.28.1", + "typescript": "5.3.3", + "esbuild": "0.19.11", + "glob": "10.3.10" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "6.18.1", - "@typescript-eslint/parser": "6.18.1", + "@types/node": "^20.11.28", + "@typescript-eslint/eslint-plugin": "7.1.0", + "@typescript-eslint/parser": "7.1.0", "cross-env": "7.0.3", - "cypress": "13.6.3", - "eslint": "8.56.0", + "cypress": "13.6.6", + "eslint": "8.57.0", "ncp": "2.0.0", "start-server-and-test": "2.0.3" }, diff --git a/packages/backend/.swcrc b/packages/backend/.swcrc index 0504a2d389..845190b5f4 100644 --- a/packages/backend/.swcrc +++ b/packages/backend/.swcrc @@ -19,5 +19,6 @@ }, "target": "es2022" }, - "minify": false + "minify": false, + "sourceMaps": "inline" } diff --git a/packages/backend/assets/redoc.html b/packages/backend/assets/redoc.html index e41d47f046..e525ebf5db 100644 --- a/packages/backend/assets/redoc.html +++ b/packages/backend/assets/redoc.html @@ -19,6 +19,6 @@ - + diff --git a/packages/backend/generate_api_json.js b/packages/backend/generate_api_json.js deleted file mode 100644 index 4079b3bb0a..0000000000 --- a/packages/backend/generate_api_json.js +++ /dev/null @@ -1,8 +0,0 @@ -import { loadConfig } from './built/config.js' -import { genOpenapiSpec } from './built/server/api/openapi/gen-spec.js' -import { writeFileSync } from "node:fs"; - -const config = loadConfig(); -const spec = genOpenapiSpec(config, true); - -writeFileSync('./built/api.json', JSON.stringify(spec), 'utf-8'); \ No newline at end of file diff --git a/packages/backend/migration/1629387925000-disableRightClick.js b/packages/backend/migration/1629387925000-disableRightClick.js index a4f9da6d52..6c821791fb 100644 --- a/packages/backend/migration/1629387925000-disableRightClick.js +++ b/packages/backend/migration/1629387925000-disableRightClick.js @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/migration/1684231006000-tweak-varchar-length2.js b/packages/backend/migration/1684231006000-tweak-varchar-length2.js index af58cf578a..1935b3293b 100644 --- a/packages/backend/migration/1684231006000-tweak-varchar-length2.js +++ b/packages/backend/migration/1684231006000-tweak-varchar-length2.js @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/migration/1689325027964-UserBlacklistAnntena.js b/packages/backend/migration/1689325027964-UserBlacklistAnntena.js index ce246b20f8..2dc7774493 100644 --- a/packages/backend/migration/1689325027964-UserBlacklistAnntena.js +++ b/packages/backend/migration/1689325027964-UserBlacklistAnntena.js @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export class UserBlacklistAnntena1689325027964 { name = 'UserBlacklistAnntena1689325027964' diff --git a/packages/backend/migration/1690417561185-fix-renote-muting.js b/packages/backend/migration/1690417561185-fix-renote-muting.js index 14150b0362..d9604ca26c 100644 --- a/packages/backend/migration/1690417561185-fix-renote-muting.js +++ b/packages/backend/migration/1690417561185-fix-renote-muting.js @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export class FixRenoteMuting1690417561185 { name = 'FixRenoteMuting1690417561185' diff --git a/packages/backend/migration/1690417561186-ChangeCacheRemoteFilesDefault.js b/packages/backend/migration/1690417561186-ChangeCacheRemoteFilesDefault.js index 7eda5debe5..9bccdb3bb5 100644 --- a/packages/backend/migration/1690417561186-ChangeCacheRemoteFilesDefault.js +++ b/packages/backend/migration/1690417561186-ChangeCacheRemoteFilesDefault.js @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export class ChangeCacheRemoteFilesDefault1690417561186 { name = 'ChangeCacheRemoteFilesDefault1690417561186' diff --git a/packages/backend/migration/1690417561187-Fix.js b/packages/backend/migration/1690417561187-Fix.js index e48e069f40..6275662c23 100644 --- a/packages/backend/migration/1690417561187-Fix.js +++ b/packages/backend/migration/1690417561187-Fix.js @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export class Fix1690417561187 { name = 'Fix1690417561187' diff --git a/packages/backend/migration/1690569881926-user-2fa-backup-codes.js b/packages/backend/migration/1690569881926-user-2fa-backup-codes.js index 2049df8ea2..a3ef8dcf08 100644 --- a/packages/backend/migration/1690569881926-user-2fa-backup-codes.js +++ b/packages/backend/migration/1690569881926-user-2fa-backup-codes.js @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export class User2faBackupCodes1690569881926 { name = 'User2faBackupCodes1690569881926' diff --git a/packages/backend/migration/1691649257651-refine-announcement.js b/packages/backend/migration/1691649257651-refine-announcement.js index d8d63f3103..ac621155d5 100644 --- a/packages/backend/migration/1691649257651-refine-announcement.js +++ b/packages/backend/migration/1691649257651-refine-announcement.js @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export class RefineAnnouncement1691649257651 { name = 'RefineAnnouncement1691649257651' diff --git a/packages/backend/migration/1691657412740-refine-announcement-2.js b/packages/backend/migration/1691657412740-refine-announcement-2.js index 8791f99f44..67edf19659 100644 --- a/packages/backend/migration/1691657412740-refine-announcement-2.js +++ b/packages/backend/migration/1691657412740-refine-announcement-2.js @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export class RefineAnnouncement21691657412740 { name = 'RefineAnnouncement21691657412740' diff --git a/packages/backend/migration/1695260774117-verified-links.js b/packages/backend/migration/1695260774117-verified-links.js index 18e0571d81..64c8a9ad8f 100644 --- a/packages/backend/migration/1695260774117-verified-links.js +++ b/packages/backend/migration/1695260774117-verified-links.js @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export class VerifiedLinks1695260774117 { name = 'VerifiedLinks1695260774117' diff --git a/packages/backend/migration/1695288787870-following-notify.js b/packages/backend/migration/1695288787870-following-notify.js index e7e2194b15..b3f78d5f2a 100644 --- a/packages/backend/migration/1695288787870-following-notify.js +++ b/packages/backend/migration/1695288787870-following-notify.js @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export class FollowingNotify1695288787870 { name = 'FollowingNotify1695288787870' diff --git a/packages/backend/migration/1695440131671-short-name.js b/packages/backend/migration/1695440131671-short-name.js index 2c37297fc1..fdc256caf8 100644 --- a/packages/backend/migration/1695440131671-short-name.js +++ b/packages/backend/migration/1695440131671-short-name.js @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export class ShortName1695440131671 { name = 'ShortName1695440131671' diff --git a/packages/backend/migration/1695605508898-mutingNotificationTypes.js b/packages/backend/migration/1695605508898-mutingNotificationTypes.js index 8c0e52a2f0..67d4243142 100644 --- a/packages/backend/migration/1695605508898-mutingNotificationTypes.js +++ b/packages/backend/migration/1695605508898-mutingNotificationTypes.js @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export class MutingNotificationTypes1695605508898 { name = 'MutingNotificationTypes1695605508898' diff --git a/packages/backend/migration/1695901659683-note-updated-at.js b/packages/backend/migration/1695901659683-note-updated-at.js index d8a151a1f7..e828fb1a6f 100644 --- a/packages/backend/migration/1695901659683-note-updated-at.js +++ b/packages/backend/migration/1695901659683-note-updated-at.js @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export class NoteUpdatedAt1695901659683 { name = 'NoteUpdatedAt1695901659683' diff --git a/packages/backend/migration/1696323464251-user-list-membership.js b/packages/backend/migration/1696323464251-user-list-membership.js index 7534040c4c..dc1d438dd7 100644 --- a/packages/backend/migration/1696323464251-user-list-membership.js +++ b/packages/backend/migration/1696323464251-user-list-membership.js @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export class UserListMembership1696323464251 { name = 'UserListMembership1696323464251' diff --git a/packages/backend/migration/1696331570827-hibernation.js b/packages/backend/migration/1696331570827-hibernation.js index 119d35913f..1487ece77c 100644 --- a/packages/backend/migration/1696331570827-hibernation.js +++ b/packages/backend/migration/1696331570827-hibernation.js @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export class Hibernation1696331570827 { name = 'Hibernation1696331570827' diff --git a/packages/backend/migration/1696332072038-clean.js b/packages/backend/migration/1696332072038-clean.js index 45b7511acc..23dca55fdc 100644 --- a/packages/backend/migration/1696332072038-clean.js +++ b/packages/backend/migration/1696332072038-clean.js @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export class Clean1696332072038 { name = 'Clean1696332072038' diff --git a/packages/backend/migration/1696402675000-add-meta-options.js b/packages/backend/migration/1696402675000-add-meta-options.js index 9941edc6ae..2dc55ef7db 100644 --- a/packages/backend/migration/1696402675000-add-meta-options.js +++ b/packages/backend/migration/1696402675000-add-meta-options.js @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/migration/1696417300000-add-meta-options.js b/packages/backend/migration/1696417300000-add-meta-options.js index d9b13e2179..c2660dd1b0 100644 --- a/packages/backend/migration/1696417300000-add-meta-options.js +++ b/packages/backend/migration/1696417300000-add-meta-options.js @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/migration/1696604572677-poll-vote-poll.js b/packages/backend/migration/1696604572677-poll-vote-poll.js index da52904565..32c2e867fb 100644 --- a/packages/backend/migration/1696604572677-poll-vote-poll.js +++ b/packages/backend/migration/1696604572677-poll-vote-poll.js @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export class PollVotePoll1696604572677 { name = 'PollVotePoll1696604572677'; diff --git a/packages/backend/migration/1697737204579-deleteCreatedAt.js b/packages/backend/migration/1697737204579-deleteCreatedAt.js index 8f801e7913..6ac568c681 100644 --- a/packages/backend/migration/1697737204579-deleteCreatedAt.js +++ b/packages/backend/migration/1697737204579-deleteCreatedAt.js @@ -1,3 +1,7 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project + * SPDX-License-Identifier: AGPL-3.0-only + */ export class DeleteCreatedAt1697737204579 { name = 'DeleteCreatedAt1697737204579' diff --git a/packages/backend/migration/1699432324194-remoteAvaterDecoration.js b/packages/backend/migration/1699432324194-remoteAvaterDecoration.js index 5b2762b476..c5e0dd53af 100644 --- a/packages/backend/migration/1699432324194-remoteAvaterDecoration.js +++ b/packages/backend/migration/1699432324194-remoteAvaterDecoration.js @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export class RemoteAvaterDecoration1699432324194 { name = 'RemoteAvaterDecoration1699432324194' diff --git a/packages/backend/migration/1700383825690-hard-mute.js b/packages/backend/migration/1700383825690-hard-mute.js index afd3247f5c..92c3ada4a1 100644 --- a/packages/backend/migration/1700383825690-hard-mute.js +++ b/packages/backend/migration/1700383825690-hard-mute.js @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export class HardMute1700383825690 { name = 'HardMute1700383825690' diff --git a/packages/backend/migration/1704185628000-note-updated-at2.js b/packages/backend/migration/1704185628000-note-updated-at2.js index b3fee061e4..1eca703214 100644 --- a/packages/backend/migration/1704185628000-note-updated-at2.js +++ b/packages/backend/migration/1704185628000-note-updated-at2.js @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export class NoteUpdatedAt1704185628000 { name = 'NoteUpdatedAt1704185628000' diff --git a/packages/backend/migration/1708399372194-per-instance-mod-note.js b/packages/backend/migration/1708399372194-per-instance-mod-note.js new file mode 100644 index 0000000000..339a4d7af9 --- /dev/null +++ b/packages/backend/migration/1708399372194-per-instance-mod-note.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class PerInstanceModNote1708399372194 { + name = 'PerInstanceModNote1708399372194' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "instance" ADD "moderationNote" character varying(16384) NOT NULL DEFAULT ''`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "moderationNote"`); + } +} diff --git a/packages/backend/migration/1710512074000-url-preview-meta.js b/packages/backend/migration/1710512074000-url-preview-meta.js new file mode 100644 index 0000000000..8af521bbf4 --- /dev/null +++ b/packages/backend/migration/1710512074000-url-preview-meta.js @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class UrlPreviewMeta1710512074000 { + name = 'UrlPreviewMeta1710512074000' + + async up(queryRunner) { + await queryRunner.query(` + alter table meta + rename column "summalyProxy" to "urlPreviewSummaryProxyUrl"; + alter table meta + add "urlPreviewEnabled" boolean default true not null; + alter table meta + add "urlPreviewTimeout" integer default 10000 not null; + alter table meta + add "urlPreviewMaximumContentLength" bigint default 10485760 not null; + alter table meta + add "urlPreviewRequireContentLength" boolean default false not null; + alter table meta + add "urlPreviewUserAgent" varchar(1024) default null; + `); + } + + async down(queryRunner) { + await queryRunner.query(` + alter table meta + rename column "urlPreviewSummaryProxyUrl" to "summalyProxy"; + alter table meta + drop column "urlPreviewEnabled"; + alter table meta + drop column "urlPreviewTimeout"; + alter table meta + drop column "urlPreviewMaximumContentLength"; + alter table meta + drop column "urlPreviewRequireContentLength"; + alter table meta + drop column "urlPreviewUserAgent"; + `); + } +} diff --git a/packages/backend/migration/1710919614510-antenna-exclude-bots.js b/packages/backend/migration/1710919614510-antenna-exclude-bots.js new file mode 100644 index 0000000000..fac84317cc --- /dev/null +++ b/packages/backend/migration/1710919614510-antenna-exclude-bots.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class AntennaExcludeBots1710919614510 { + name = 'AntennaExcludeBots1710919614510' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "antenna" ADD "excludeBots" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "excludeBots"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index ce3a134106..f6d26b55b4 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -11,14 +11,14 @@ "start:test": "cross-env NODE_ENV=test node ./built/boot/entry.js", "migrate": "pnpm typeorm migration:run -d ormconfig.js", "revert": "pnpm typeorm migration:revert -d ormconfig.js", - "check:connect": "node ./check_connect.js", + "check:connect": "node ./scripts/check_connect.js", "build": "swc src -d built -D", "build:test": "swc test-server -d built-test -D --config-file test-server/.swcrc", "watch:swc": "swc src -d built -D -w", "build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json", - "watch": "node watch.mjs", + "watch": "node ./scripts/watch.mjs", "restart": "pnpm build && pnpm start", - "dev": "nodemon -w src -e ts,js,mjs,cjs,json --exec \"cross-env NODE_ENV=development pnpm run restart\"", + "dev": "node ./scripts/dev.mjs", "typecheck": "tsc --noEmit", "eslint": "eslint --quiet \"src/**/*.ts\"", "lint": "pnpm typecheck && pnpm eslint", @@ -31,7 +31,7 @@ "test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e", "test-and-coverage": "pnpm jest-and-coverage", "test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e", - "generate-api-json": "pnpm build && node ./generate_api_json.js", + "generate-api-json": "pnpm build && node ./scripts/generate_api_json.js", "schema:sync": "pnpm typeorm schema:sync -d ormconfig.js" }, "optionalDependencies": { @@ -66,11 +66,11 @@ "utf-8-validate": "6.0.3" }, "dependencies": { - "@aws-sdk/client-s3": "3.412.0", - "@aws-sdk/lib-storage": "3.412.0", - "@bull-board/api": "5.14.0", - "@bull-board/fastify": "5.14.0", - "@bull-board/ui": "5.14.0", + "@aws-sdk/client-s3": "3.540.0", + "@aws-sdk/lib-storage": "3.413.0", + "@bull-board/api": "5.14.2", + "@bull-board/fastify": "5.14.2", + "@bull-board/ui": "5.14.2", "@discordapp/twemoji": "15.0.2", "@fastify/accepts": "4.3.0", "@fastify/cookie": "9.3.1", @@ -82,13 +82,13 @@ "@fastify/view": "8.2.0", "@google-cloud/logging": "^10.5.0", "@google-cloud/translate": "^7.2.1", - "@misskey-dev/sharp-read-bmp": "^1.1.1", - "@misskey-dev/summaly": "^5.0.3", - "@nestjs/common": "10.2.10", - "@nestjs/core": "10.2.10", - "@nestjs/testing": "10.2.10", + "@misskey-dev/sharp-read-bmp": "1.2.0", + "@misskey-dev/summaly": "5.1.0", + "@nestjs/common": "10.3.3", + "@nestjs/core": "10.3.3", + "@nestjs/testing": "10.3.3", "@peertube/http-signature": "1.7.0", - "@simplewebauthn/server": "9.0.2", + "@simplewebauthn/server": "9.0.3", "@sinonjs/fake-timers": "11.2.2", "@smithy/node-http-handler": "2.1.10", "@swc/cli": "0.1.63", @@ -102,7 +102,7 @@ "bcryptjs": "2.4.3", "blurhash": "2.0.5", "body-parser": "1.20.2", - "bullmq": "5.1.9", + "bullmq": "5.4.0", "cacheable-lookup": "7.0.0", "cbor": "9.0.2", "chalk": "5.3.0", @@ -121,10 +121,11 @@ "file-type": "19.0.0", "fluent-ffmpeg": "2.1.2", "form-data": "4.0.0", - "got": "14.1.0", + "got": "14.2.0", "happy-dom": "10.0.3", "hpagent": "1.2.0", - "http-link-header": "1.1.1", + "htmlescape": "1.1.1", + "http-link-header": "1.1.2", "ioredis": "5.3.2", "ip-cidr": "3.1.0", "ipaddr.js": "2.1.0", @@ -133,16 +134,16 @@ "jsdom": "23.2.0", "json5": "2.2.3", "jsonld": "8.3.2", - "jsrsasign": "11.0.0", + "jsrsasign": "11.1.0", "meilisearch": "0.37.0", "microformats-parser": "2.0.2", "mime-types": "2.1.35", "misskey-reversi": "workspace:*", "ms": "3.0.0-canary.1", - "nanoid": "5.0.4", + "nanoid": "5.0.6", "nested-property": "4.0.0", "node-fetch": "3.3.2", - "nodemailer": "6.9.8", + "nodemailer": "6.9.10", "nsfwjs": "2.4.2", "oauth": "0.10.0", "oauth2orize": "1.12.0", @@ -162,20 +163,20 @@ "ratelimiter": "3.4.1", "re2": "1.20.9", "redis-lock": "0.1.4", - "reflect-metadata": "0.1.14", + "reflect-metadata": "0.2.1", "rename": "1.0.4", "rss-parser": "3.13.0", "rxjs": "7.8.1", - "sanitize-html": "2.11.0", + "sanitize-html": "2.12.1", "secure-json-parse": "2.7.0", - "sharp": "0.32.6", + "sharp": "0.33.2", "slacc": "0.0.10", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", "strip-ansi": "^7.1.0", - "systeminformation": "5.21.24", + "systeminformation": "5.22.0", "tinycolor2": "1.6.0", - "tmp": "0.2.1", + "tmp": "0.2.2", "tsc-alias": "1.8.8", "tsconfig-paths": "4.2.0", "typeorm": "0.3.20", @@ -189,7 +190,7 @@ "devDependencies": { "@jest/globals": "29.7.0", "@misskey-dev/eslint-plugin": "1.0.0", - "@nestjs/platform-express": "10.3.1", + "@nestjs/platform-express": "10.3.3", "@simplewebauthn/types": "9.0.1", "@swc/jest": "0.2.31", "@types/accepts": "1.3.7", @@ -199,6 +200,7 @@ "@types/color-convert": "2.0.3", "@types/content-disposition": "0.5.8", "@types/fluent-ffmpeg": "2.1.24", + "@types/htmlescape": "^1.1.3", "@types/http-link-header": "1.0.5", "@types/jest": "29.5.11", "@types/js-yaml": "4.0.9", @@ -207,21 +209,21 @@ "@types/jsrsasign": "10.5.12", "@types/mime-types": "2.1.4", "@types/ms": "0.7.34", - "@types/node": "20.11.17", + "@types/node": "20.11.22", "@types/node-fetch": "3.0.3", "@types/nodemailer": "6.4.14", "@types/oauth": "0.9.4", "@types/oauth2orize": "1.11.3", "@types/oauth2orize-pkce": "0.1.2", - "@types/pg": "8.11.0", + "@types/pg": "8.11.2", "@types/pug": "2.0.10", - "@types/punycode": "2.1.3", + "@types/punycode": "2.1.4", "@types/qrcode": "1.5.5", "@types/random-seed": "0.3.5", "@types/ratelimiter": "3.4.6", "@types/rename": "1.0.7", - "@types/sanitize-html": "2.9.5", - "@types/semver": "7.5.6", + "@types/sanitize-html": "2.11.0", + "@types/semver": "7.5.8", "@types/simple-oauth2": "5.0.7", "@types/sinonjs__fake-timers": "8.1.5", "@types/tinycolor2": "1.4.6", @@ -229,17 +231,17 @@ "@types/vary": "1.1.3", "@types/web-push": "3.6.3", "@types/ws": "8.5.10", - "@typescript-eslint/eslint-plugin": "6.18.1", - "@typescript-eslint/parser": "6.18.1", + "@typescript-eslint/eslint-plugin": "7.1.0", + "@typescript-eslint/parser": "7.1.0", "aws-sdk-client-mock": "3.0.1", "cross-env": "7.0.3", - "eslint": "8.56.0", + "eslint": "8.57.0", "eslint-plugin-import": "2.29.1", "execa": "8.0.1", "fkill": "^9.0.0", "jest": "29.7.0", "jest-mock": "29.7.0", - "nodemon": "3.0.3", + "nodemon": "3.1.0", "pid-port": "1.0.0", "simple-oauth2": "5.0.0" } diff --git a/packages/backend/check_connect.js b/packages/backend/scripts/check_connect.js similarity index 85% rename from packages/backend/check_connect.js rename to packages/backend/scripts/check_connect.js index d88e649c09..ba25fd416c 100644 --- a/packages/backend/check_connect.js +++ b/packages/backend/scripts/check_connect.js @@ -4,7 +4,7 @@ */ import Redis from 'ioredis'; -import { loadConfig } from './built/config.js'; +import { loadConfig } from '../built/config.js'; const config = loadConfig(); const redis = new Redis(config.redis); diff --git a/packages/backend/scripts/dev.mjs b/packages/backend/scripts/dev.mjs new file mode 100644 index 0000000000..2d0de0f916 --- /dev/null +++ b/packages/backend/scripts/dev.mjs @@ -0,0 +1,61 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { execa, execaNode } from 'execa'; + +/** @type {import('execa').ExecaChildProcess | undefined} */ +let backendProcess; + +async function execBuildAssets() { + await execa('pnpm', ['run', 'build-assets'], { + cwd: '../../', + stdout: process.stdout, + stderr: process.stderr, + }) +} + +function execStart() { + // pnpm run start を呼び出したいが、windowsだとプロセスグループ単位でのkillが出来ずゾンビプロセス化するので + // 上記と同等の動きをするコマンドで子・孫プロセスを作らないようにしたい + backendProcess = execaNode('./built/boot/entry.js', [], { + stdout: process.stdout, + stderr: process.stderr, + env: { + 'NODE_ENV': 'development', + }, + }); +} + +async function killProc() { + if (backendProcess) { + backendProcess.kill(); + await new Promise(resolve => backendProcess.on('exit', resolve)); + backendProcess = undefined; + } +} + +(async () => { + execaNode( + './node_modules/nodemon/bin/nodemon.js', + [ + '-w', 'src', + '-e', 'ts,js,mjs,cjs,json', + '--exec', 'pnpm', 'run', 'build', + ], + { + stdio: [process.stdin, process.stdout, process.stderr, 'ipc'], + }) + .on('message', async (message) => { + if (message.type === 'exit') { + // かならずbuild->build-assetsの順番で呼び出したいので、 + // 少々トリッキーだがnodemonからのexitイベントを利用してbuild-assets->startを行う。 + // pnpm restartをbuildが終わる前にbuild-assetsが動いてしまうので、バラバラに呼び出す必要がある + + await killProc(); + await execBuildAssets(); + execStart(); + } + }) +})(); diff --git a/packages/backend/scripts/generate_api_json.js b/packages/backend/scripts/generate_api_json.js new file mode 100644 index 0000000000..b4769ef801 --- /dev/null +++ b/packages/backend/scripts/generate_api_json.js @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { loadConfig } from '../built/config.js' +import { genOpenapiSpec } from '../built/server/api/openapi/gen-spec.js' +import { writeFileSync } from "node:fs"; + +const config = loadConfig(); +const spec = genOpenapiSpec(config, true); + +writeFileSync('./built/api.json', JSON.stringify(spec), 'utf-8'); diff --git a/packages/backend/watch.mjs b/packages/backend/scripts/watch.mjs similarity index 100% rename from packages/backend/watch.mjs rename to packages/backend/scripts/watch.mjs diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts index b7796a5183..b6b591d240 100644 --- a/packages/backend/src/core/AccountMoveService.ts +++ b/packages/backend/src/core/AccountMoveService.ts @@ -20,7 +20,6 @@ import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { CacheService } from '@/core/CacheService.js'; import { ProxyAccountService } from '@/core/ProxyAccountService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { MetaService } from '@/core/MetaService.js'; @@ -60,7 +59,6 @@ export class AccountMoveService { private instanceChart: InstanceChart, private metaService: MetaService, private relayService: RelayService, - private cacheService: CacheService, private queueService: QueueService, ) { } @@ -84,7 +82,7 @@ export class AccountMoveService { Object.assign(src, update); // Update cache - this.cacheService.uriPersonCache.set(srcUri, src); + this.globalEventService.publishInternalEvent('localUserUpdated', src); const srcPerson = await this.apRendererService.renderPerson(src); const updateAct = this.apRendererService.addContext(this.apRendererService.renderUpdate(srcPerson, src)); @@ -307,7 +305,7 @@ export class AccountMoveService { let resultUser: MiLocalUser | MiRemoteUser | null = null; if (this.userEntityService.isRemoteUser(dst)) { - if ((new Date()).getTime() - (dst.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) { + if (Date.now() - (dst.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) { await this.apPersonService.updatePerson(dst.uri); } dst = await this.apPersonService.fetchPerson(dst.uri) ?? dst; @@ -323,7 +321,7 @@ export class AccountMoveService { if (!src) continue; // oldAccountを探してもこのサーバーに存在しない場合はフォロー関係もないということなのでスルー if (this.userEntityService.isRemoteUser(dst)) { - if ((new Date()).getTime() - (src.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) { + if (Date.now() - (src.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) { await this.apPersonService.updatePerson(srcUri); } diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index 03c8741dc7..d9b40c1734 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -17,6 +17,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; import type { GlobalEvents } from '@/core/GlobalEventService.js'; import { FanoutTimelineService } from '@/core/FanoutTimelineService.js'; +import { deserializeAntenna } from './deserializeAntenna.js'; import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() @@ -58,30 +59,14 @@ export class AntennaService implements OnApplicationShutdown { const { type, body } = obj.message as GlobalEvents['internal']['payload']; switch (type) { case 'antennaCreated': - this.antennas.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい - ...body, - lastUsedAt: new Date(body.lastUsedAt), - user: null, // joinなカラムは通常取ってこないので - userList: null, // joinなカラムは通常取ってこないので - }); + this.antennas.push(deserializeAntenna(body)); break; case 'antennaUpdated': { const idx = this.antennas.findIndex(a => a.id === body.id); if (idx >= 0) { - this.antennas[idx] = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい - ...body, - lastUsedAt: new Date(body.lastUsedAt), - user: null, // joinなカラムは通常取ってこないので - userList: null, // joinなカラムは通常取ってこないので - }; + this.antennas[idx] = deserializeAntenna(body); } else { - // サーバ起動時にactiveじゃなかった場合、リストに持っていないので追加する必要あり - this.antennas.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい - ...body, - lastUsedAt: new Date(body.lastUsedAt), - user: null, // joinなカラムは通常取ってこないので - userList: null, // joinなカラムは通常取ってこないので - }); + this.antennas.push(deserializeAntenna(body)); } } break; @@ -95,7 +80,7 @@ export class AntennaService implements OnApplicationShutdown { } @bindThis - public async addNoteToAntennas(note: MiNote, noteUser: { id: MiUser['id']; username: string; host: string | null; }): Promise { + public async addNoteToAntennas(note: MiNote, noteUser: { id: MiUser['id']; username: string; host: string | null; isBot: boolean; }): Promise { const antennas = await this.getAntennas(); const antennasWithMatchResult = await Promise.all(antennas.map(antenna => this.checkHitAntenna(antenna, note, noteUser).then(hit => [antenna, hit] as const))); const matchedAntennas = antennasWithMatchResult.filter(([, hit]) => hit).map(([antenna]) => antenna); @@ -113,10 +98,12 @@ export class AntennaService implements OnApplicationShutdown { // NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている @bindThis - public async checkHitAntenna(antenna: MiAntenna, note: (MiNote | Packed<'Note'>), noteUser: { id: MiUser['id']; username: string; host: string | null; }): Promise { + public async checkHitAntenna(antenna: MiAntenna, note: (MiNote | Packed<'Note'>), noteUser: { id: MiUser['id']; username: string; host: string | null; isBot: boolean; }): Promise { if (note.visibility === 'specified') return false; if (note.visibility === 'followers') return false; + if (antenna.excludeBots && noteUser.isBot) return false; + if (antenna.localOnly && noteUser.host != null) return false; if (!antenna.withReplies && note.replyId != null) return false; diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts index 2bafb8ffe2..9d6074f929 100644 --- a/packages/backend/src/core/CacheService.ts +++ b/packages/backend/src/core/CacheService.ts @@ -137,10 +137,13 @@ export class CacheService implements OnApplicationShutdown { const { type, body } = obj.message as GlobalEvents['internal']['payload']; switch (type) { case 'userChangeSuspendedState': - case 'remoteUserUpdated': { + case 'userChangeDeletedState': + case 'remoteUserUpdated': + case 'localUserUpdated': { const user = await this.usersRepository.findOneBy({ id: body.id }); if (user == null) { this.userByIdCache.delete(body.id); + this.localUserByIdCache.delete(body.id); for (const [k, v] of this.uriPersonCache.cache.entries()) { if (v.value?.id === body.id) { this.uriPersonCache.delete(k); diff --git a/packages/backend/src/core/ChannelFollowingService.ts b/packages/backend/src/core/ChannelFollowingService.ts index 75843b9773..12251595e2 100644 --- a/packages/backend/src/core/ChannelFollowingService.ts +++ b/packages/backend/src/core/ChannelFollowingService.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; import Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index cda1b641d1..e7edac5add 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -121,6 +121,7 @@ import { FlashEntityService } from './entities/FlashEntityService.js'; import { FlashLikeEntityService } from './entities/FlashLikeEntityService.js'; import { RoleEntityService } from './entities/RoleEntityService.js'; import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js'; +import { MetaEntityService } from './entities/MetaEntityService.js'; import { ApAudienceService } from './activitypub/ApAudienceService.js'; import { ApDbResolverService } from './activitypub/ApDbResolverService.js'; @@ -265,6 +266,7 @@ const $FlashEntityService: Provider = { provide: 'FlashEntityService', useExisti const $FlashLikeEntityService: Provider = { provide: 'FlashLikeEntityService', useExisting: FlashLikeEntityService }; const $RoleEntityService: Provider = { provide: 'RoleEntityService', useExisting: RoleEntityService }; const $ReversiGameEntityService: Provider = { provide: 'ReversiGameEntityService', useExisting: ReversiGameEntityService }; +const $MetaEntityService: Provider = { provide: 'MetaEntityService', useExisting: MetaEntityService }; const $ApAudienceService: Provider = { provide: 'ApAudienceService', useExisting: ApAudienceService }; const $ApDbResolverService: Provider = { provide: 'ApDbResolverService', useExisting: ApDbResolverService }; @@ -410,6 +412,7 @@ const $ApEventService: Provider = { provide: 'ApEventService', useExisting: ApEv FlashLikeEntityService, RoleEntityService, ReversiGameEntityService, + MetaEntityService, ApAudienceService, ApDbResolverService, @@ -551,6 +554,7 @@ const $ApEventService: Provider = { provide: 'ApEventService', useExisting: ApEv $FlashLikeEntityService, $RoleEntityService, $ReversiGameEntityService, + $MetaEntityService, $ApAudienceService, $ApDbResolverService, @@ -692,6 +696,7 @@ const $ApEventService: Provider = { provide: 'ApEventService', useExisting: ApEv FlashLikeEntityService, RoleEntityService, ReversiGameEntityService, + MetaEntityService, ApAudienceService, ApDbResolverService, @@ -832,6 +837,7 @@ const $ApEventService: Provider = { provide: 'ApEventService', useExisting: ApEv $FlashLikeEntityService, $RoleEntityService, $ReversiGameEntityService, + $MetaEntityService, $ApAudienceService, $ApDbResolverService, diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index c0a7d9a445..bf9458e5f7 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -396,6 +396,11 @@ export class CustomEmojiService implements OnApplicationShutdown { return this.emojisRepository.findOneBy({ id }); } + @bindThis + public getEmojiByName(name: string): Promise { + return this.emojisRepository.findOneBy({ name, host: IsNull() }); + } + @bindThis public dispose(): void { this.cache.dispose(); diff --git a/packages/backend/src/core/DeleteAccountService.ts b/packages/backend/src/core/DeleteAccountService.ts index fc5d217ae0..79b614edba 100644 --- a/packages/backend/src/core/DeleteAccountService.ts +++ b/packages/backend/src/core/DeleteAccountService.ts @@ -9,6 +9,7 @@ import { QueueService } from '@/core/QueueService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; @Injectable() export class DeleteAccountService { @@ -18,6 +19,7 @@ export class DeleteAccountService { private userSuspendService: UserSuspendService, private queueService: QueueService, + private globalEventService: GlobalEventService, ) { } @@ -39,5 +41,7 @@ export class DeleteAccountService { await this.usersRepository.update(user.id, { isDeleted: true, }); + + this.globalEventService.publishInternalEvent('userChangeDeletedState', { id: user.id, isDeleted: true }); } } diff --git a/packages/backend/src/core/FanoutTimelineEndpointService.ts b/packages/backend/src/core/FanoutTimelineEndpointService.ts index 8a88b2a34b..e813abbc86 100644 --- a/packages/backend/src/core/FanoutTimelineEndpointService.ts +++ b/packages/backend/src/core/FanoutTimelineEndpointService.ts @@ -13,7 +13,7 @@ import type { NotesRepository } from '@/models/_.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { FanoutTimelineName, FanoutTimelineService } from '@/core/FanoutTimelineService.js'; import { isUserRelated } from '@/misc/is-user-related.js'; -import { isPureRenote } from '@/misc/is-pure-renote.js'; +import { isQuote, isRenote } from '@/misc/is-renote.js'; import { CacheService } from '@/core/CacheService.js'; import { isReply } from '@/misc/is-reply.js'; import { isInstanceMuted } from '@/misc/is-instance-muted.js'; @@ -96,7 +96,7 @@ export class FanoutTimelineEndpointService { if (ps.excludePureRenotes) { const parentFilter = filter; - filter = (note) => !isPureRenote(note) && parentFilter(note); + filter = (note) => (!isRenote(note) || isQuote(note)) && parentFilter(note); } if (ps.withCats) { @@ -122,7 +122,7 @@ export class FanoutTimelineEndpointService { filter = (note) => { if (isUserRelated(note, userIdsWhoBlockingMe, ps.ignoreAuthorFromBlock)) return false; if (isUserRelated(note, userIdsWhoMeMuting, ps.ignoreAuthorFromMute)) return false; - if (isPureRenote(note) && isUserRelated(note, userIdsWhoMeMutingRenotes, ps.ignoreAuthorFromMute)) return false; + if (isRenote(note) && !isQuote(note) && isUserRelated(note, userIdsWhoMeMutingRenotes, ps.ignoreAuthorFromMute)) return false; if (isInstanceMuted(note, userMutedInstances)) return false; return parentFilter(note); diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index 4336e2a619..62ae9a9a6c 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -52,21 +52,35 @@ export class FetchInstanceMetadataService { } @bindThis - public async tryLock(host: string): Promise { - const mutex = await this.redisClient.set(`fetchInstanceMetadata:mutex:${host}`, '1', 'GET'); - return mutex !== '1'; + // public for test + public async tryLock(host: string): Promise { + // TODO: マイグレーションなのであとで消す (2024.3.1) + this.redisClient.del(`fetchInstanceMetadata:mutex:${host}`); + + return await this.redisClient.set( + `fetchInstanceMetadata:mutex:v2:${host}`, '1', + 'EX', 30, // 30秒したら自動でロック解除 https://github.com/misskey-dev/misskey/issues/13506#issuecomment-1975375395 + 'GET', // 古い値を返す(なかったらnull) + ); } @bindThis - public unlock(host: string): Promise<'OK'> { - return this.redisClient.set(`fetchInstanceMetadata:mutex:${host}`, '0'); + // public for test + public unlock(host: string): Promise { + return this.redisClient.del(`fetchInstanceMetadata:mutex:v2:${host}`); } @bindThis public async fetchInstanceMetadata(instance: MiInstance, force = false): Promise { const host = instance.host; - // Acquire mutex to ensure no parallel runs - if (!await this.tryLock(host)) return; + + // finallyでunlockされてしまうのでtry内でロックチェックをしない + // (returnであってもfinallyは実行される) + if (!force && await this.tryLock(host) === '1') { + // 1が返ってきていたらロックされているという意味なので、何もしない + return; + } + try { if (!force) { const _instance = await this.federatedInstanceService.fetch(host); diff --git a/packages/backend/src/core/FileInfoService.ts b/packages/backend/src/core/FileInfoService.ts index b177367a16..169285f033 100644 --- a/packages/backend/src/core/FileInfoService.ts +++ b/packages/backend/src/core/FileInfoService.ts @@ -14,10 +14,12 @@ import FFmpeg from 'fluent-ffmpeg'; import isSvg from 'is-svg'; import probeImageSize from 'probe-image-size'; import { type predictionType } from 'nsfwjs'; -import sharp from 'sharp'; +import { sharpBmp } from '@misskey-dev/sharp-read-bmp'; import { encode } from 'blurhash'; import { createTempDir } from '@/misc/create-temp.js'; import { AiService } from '@/core/AiService.js'; +import { LoggerService } from '@/core/LoggerService.js'; +import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; export type FileInfo = { @@ -48,9 +50,13 @@ const TYPE_SVG = { @Injectable() export class FileInfoService { + private logger: Logger; + constructor( private aiService: AiService, + private loggerService: LoggerService, ) { + this.logger = this.loggerService.getLogger('file-info'); } /** @@ -122,7 +128,7 @@ export class FileInfoService { 'image/avif', 'image/svg+xml', ].includes(type.mime)) { - blurhash = await this.getBlurhash(path).catch(e => { + blurhash = await this.getBlurhash(path, type.mime).catch(e => { warnings.push(`getBlurhash failed: ${e}`); return undefined; }); @@ -316,6 +322,34 @@ export class FileInfoService { return mime; } + /** + * ビデオファイルにビデオトラックがあるかどうかチェック + * (ない場合:m4a, webmなど) + * + * @param path ファイルパス + * @returns ビデオトラックがあるかどうか(エラー発生時は常に`true`を返す) + */ + @bindThis + private hasVideoTrackOnVideoFile(path: string): Promise { + const sublogger = this.logger.createSubLogger('ffprobe'); + sublogger.info(`Checking the video file. File path: ${path}`); + return new Promise((resolve) => { + try { + FFmpeg.ffprobe(path, (err, metadata) => { + if (err) { + sublogger.warn(`Could not check the video file. Returns true. File path: ${path}`, err); + resolve(true); + return; + } + resolve(metadata.streams.some((stream) => stream.codec_type === 'video')); + }); + } catch (err) { + sublogger.warn(`Could not check the video file. Returns true. File path: ${path}`, err as Error); + resolve(true); + } + }); + } + /** * Detect MIME Type and extension */ @@ -338,6 +372,20 @@ export class FileInfoService { return TYPE_SVG; } + if ((type.mime.startsWith('video') || type.mime === 'application/ogg') && !(await this.hasVideoTrackOnVideoFile(path))) { + const newMime = `audio/${type.mime.split('/')[1]}`; + if (newMime === 'audio/mp4') { + return { + mime: 'audio/mp4', + ext: 'm4a', + }; + } + return { + mime: newMime, + ext: type.ext, + }; + } + return { mime: this.fixMime(type.mime), ext: type.ext, @@ -407,9 +455,9 @@ export class FileInfoService { * Calculate average color of image */ @bindThis - private getBlurhash(path: string): Promise { - return new Promise((resolve, reject) => { - sharp(path) + private getBlurhash(path: string, type: string): Promise { + return new Promise(async (resolve, reject) => { + (await sharpBmp(path, type)) .raw() .ensureAlpha() .resize(64, 64, { fit: 'inside' }) diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index 2454c0fec2..bcbf782933 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -71,6 +71,7 @@ export interface MainEventTypes { file: Packed<'DriveFile'>; }; readAllNotifications: undefined; + notificationFlushed: undefined; unreadNotification: Packed<'Notification'>; unreadMention: MiNote['id']; readAllUnreadMentions: undefined; @@ -240,8 +241,10 @@ type SerializedAll = { export interface InternalEventTypes { userChangeSuspendedState: { id: MiUser['id']; isSuspended: MiUser['isSuspended']; }; + userChangeDeletedState: { id: MiUser['id']; isDeleted: MiUser['isDeleted']; }; userTokenRegenerated: { id: MiUser['id']; oldToken: string; newToken: string; }; remoteUserUpdated: { id: MiUser['id']; }; + localUserUpdated: { id: MiUser['id']; }; follow: { followerId: MiUser['id']; followeeId: MiUser['id']; }; unfollow: { followerId: MiUser['id']; followeeId: MiUser['id']; }; blockingCreated: { blockerId: MiUser['id']; blockeeId: MiUser['id']; }; diff --git a/packages/backend/src/core/MessagingService.ts b/packages/backend/src/core/MessagingService.ts index 5891f2c8ea..d316cd611b 100644 --- a/packages/backend/src/core/MessagingService.ts +++ b/packages/backend/src/core/MessagingService.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ @@ -137,7 +137,7 @@ export class MessagingService { userId: message.userId, visibility: 'specified', emojis: [{}], - tags: [{}], + tags: [], mentions: [recipientUser].map(u => u.id), mentionedRemoteUsers: JSON.stringify([recipientUser].map(u => ({ uri: u.uri, @@ -146,7 +146,7 @@ export class MessagingService { host: u.host, } as IMentionedRemoteUsers[0] ))), - } as MiNote; + } as unknown as MiNote; const activity = this.apRendererService.addContext(this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false, true), note)); diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index c3122ca42d..e7ee811e49 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -61,6 +61,8 @@ import { UtilityService } from '@/core/UtilityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; import { isReply } from '@/misc/is-reply.js'; import { trackPromise } from '@/misc/promise-tracker.js'; +import { isNotNull } from '@/misc/is-not-null.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; @@ -156,8 +158,6 @@ type Option = { export class NoteCreateService implements OnApplicationShutdown { #shutdownController = new AbortController(); - public static ContainsProhibitedWordsError = class extends Error {}; - constructor( @Inject(DI.config) private config: Config, @@ -269,8 +269,14 @@ export class NoteCreateService implements OnApplicationShutdown { } } - if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', meta.prohibitedWords)) { - throw new NoteCreateService.ContainsProhibitedWordsError(); + const hasProhibitedWords = await this.checkProhibitedWordsContain({ + cw: data.cw, + text: data.text, + pollChoices: data.poll?.choices, + }, meta.prohibitedWords); + + if (hasProhibitedWords) { + throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words'); } const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host); @@ -306,7 +312,7 @@ export class NoteCreateService implements OnApplicationShutdown { } // Check blocking - if (data.renote && !this.isQuote(data)) { + if (this.isRenote(data) && !this.isQuote(data)) { if (data.renote.userHost === null) { if (data.renote.userId !== user.id) { const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id); @@ -385,6 +391,10 @@ export class NoteCreateService implements OnApplicationShutdown { } } + if (mentionedUsers.length > 0 && mentionedUsers.length > (await this.roleService.getUserPolicies(user.id)).mentionLimit) { + throw new IdentifiableError('9f466dab-c856-48cd-9e65-ff90ff750580', 'Note contains too many mentions'); + } + const note = await this.insertNote(user, data, tags, emojis, mentionedUsers); setImmediate('post created', { signal: this.#shutdownController.signal }).then( @@ -656,7 +666,7 @@ export class NoteCreateService implements OnApplicationShutdown { } // If it is renote - if (data.renote) { + if (this.isRenote(data)) { const type = this.isQuote(data) ? 'quote' : 'renote'; // Notify @@ -740,9 +750,20 @@ export class NoteCreateService implements OnApplicationShutdown { } @bindThis - private isQuote(note: Option): note is Option & { renote: MiNote } { - // sync with misc/is-quote.ts - return !!note.renote && (!!note.text || !!note.cw || (!!note.files && !!note.files.length) || !!note.poll); + private isRenote(note: Option): note is Option & { renote: MiNote } { + return note.renote != null; + } + + @bindThis + private isQuote(note: Option & { renote: MiNote }): note is Option & { renote: MiNote } & ( + { text: string } | { cw: string } | { reply: MiNote } | { poll: IPoll } | { files: MiDriveFile[] } + ) { + // NOTE: SYNC WITH misc/is-quote.ts + return note.text != null || + note.reply != null || + note.cw != null || + note.poll != null || + (note.files != null && note.files.length > 0); } @bindThis @@ -810,7 +831,7 @@ export class NoteCreateService implements OnApplicationShutdown { private async renderNoteOrRenoteActivity(data: Option, note: MiNote) { if (data.localOnly) return null; - const content = data.renote && !this.isQuote(data) + const content = this.isRenote(data) && !this.isQuote(data) ? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note) : this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note); @@ -842,7 +863,7 @@ export class NoteCreateService implements OnApplicationShutdown { const mentions = extractMentions(tokens); let mentionedUsers = (await Promise.all(mentions.map(m => this.remoteUserResolveService.resolveUser(m.username, m.host ?? user.host).catch(() => null), - ))).filter(x => x != null) as MiUser[]; + ))).filter(isNotNull); // Drop duplicate users mentionedUsers = mentionedUsers.filter((u, i, self) => @@ -1016,6 +1037,23 @@ export class NoteCreateService implements OnApplicationShutdown { } } + public async checkProhibitedWordsContain(content: Parameters[0], prohibitedWords?: string[]) { + if (prohibitedWords == null) { + prohibitedWords = (await this.metaService.fetch()).prohibitedWords; + } + + if ( + this.utilityService.isKeyWordIncluded( + this.utilityService.concatNoteContentsForKeyWordCheck(content), + prohibitedWords, + ) + ) { + return true; + } + + return false; + } + @bindThis public dispose(): void { this.#shutdownController.abort(); diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index fdf843c3e8..801ed02e00 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -24,7 +24,7 @@ import { bindThis } from '@/decorators.js'; import { MetaService } from '@/core/MetaService.js'; import { SearchService } from '@/core/SearchService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; -import { isPureRenote } from '@/misc/is-pure-renote.js'; +import { isQuote, isRenote } from '@/misc/is-renote.js'; @Injectable() export class NoteDeleteService { @@ -79,7 +79,7 @@ export class NoteDeleteService { let renote: MiNote | null = null; // if deleted note is renote - if (isPureRenote(note)) { + if (isRenote(note) && !isQuote(note)) { renote = await this.notesRepository.findOneBy({ id: note.renoteId, }); diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts index feef024602..181c9f7649 100644 --- a/packages/backend/src/core/NoteReadService.ts +++ b/packages/backend/src/core/NoteReadService.ts @@ -88,46 +88,47 @@ export class NoteReadService implements OnApplicationShutdown { userId: MiUser['id'], notes: (MiNote | Packed<'Note'>)[], ): Promise { - const readMentions: (MiNote | Packed<'Note'>)[] = []; - const readSpecifiedNotes: (MiNote | Packed<'Note'>)[] = []; + if (notes.length === 0) return; + + const noteIds = new Set(); for (const note of notes) { if (note.mentions && note.mentions.includes(userId)) { - readMentions.push(note); + noteIds.add(note.id); } else if (note.visibleUserIds && note.visibleUserIds.includes(userId)) { - readSpecifiedNotes.push(note); + noteIds.add(note.id); } } - if ((readMentions.length > 0) || (readSpecifiedNotes.length > 0)) { - // Remove the record - await this.noteUnreadsRepository.delete({ - userId: userId, - noteId: In([...readMentions.map(n => n.id), ...readSpecifiedNotes.map(n => n.id)]), - }); + if (noteIds.size === 0) return; - // TODO: ↓まとめてクエリしたい + // Remove the record + await this.noteUnreadsRepository.delete({ + userId: userId, + noteId: In(Array.from(noteIds)), + }); - trackPromise(this.noteUnreadsRepository.countBy({ - userId: userId, - isMentioned: true, - }).then(mentionsCount => { - if (mentionsCount === 0) { - // 全て既読になったイベントを発行 - this.globalEventService.publishMainStream(userId, 'readAllUnreadMentions'); - } - })); - - trackPromise(this.noteUnreadsRepository.countBy({ - userId: userId, - isSpecified: true, - }).then(specifiedCount => { - if (specifiedCount === 0) { - // 全て既読になったイベントを発行 - this.globalEventService.publishMainStream(userId, 'readAllUnreadSpecifiedNotes'); - } - })); - } + // TODO: ↓まとめてクエリしたい + + trackPromise(this.noteUnreadsRepository.countBy({ + userId: userId, + isMentioned: true, + }).then(mentionsCount => { + if (mentionsCount === 0) { + // 全て既読になったイベントを発行 + this.globalEventService.publishMainStream(userId, 'readAllUnreadMentions'); + } + })); + + trackPromise(this.noteUnreadsRepository.countBy({ + userId: userId, + isSpecified: true, + }).then(specifiedCount => { + if (specifiedCount === 0) { + // 全て既読になったイベントを発行 + this.globalEventService.publishMainStream(userId, 'readAllUnreadSpecifiedNotes'); + } + })); } @bindThis diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index ee16193579..68ad92f396 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -122,6 +122,14 @@ export class NotificationService implements OnApplicationShutdown { return null; } } else if (recieveConfig?.type === 'mutualFollow') { + const [isFollowing, isFollower] = await Promise.all([ + this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => Object.hasOwn(followings, notifierId)), + this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => Object.hasOwn(followings, notifieeId)), + ]); + if (!(isFollowing && isFollower)) { + return null; + } + } else if (recieveConfig?.type === 'followingOrFollower') { const [isFollowing, isFollower] = await Promise.all([ this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => Object.hasOwn(followings, notifierId)), this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => Object.hasOwn(followings, notifieeId)), @@ -155,6 +163,8 @@ export class NotificationService implements OnApplicationShutdown { const packed = await this.notificationEntityService.pack(notification, notifieeId, {}); + if (packed == null) return null; + // Publish notification event this.globalEventService.publishMainStream(notifieeId, 'notification', packed); @@ -204,6 +214,15 @@ export class NotificationService implements OnApplicationShutdown { */ } + @bindThis + public async flushAllNotifications(userId: MiUser['id']) { + await Promise.all([ + this.redisClient.del(`notificationTimeline:${userId}`), + this.redisClient.del(`latestReadNotification:${userId}`), + ]); + this.globalEventService.publishMainStream(userId, 'notificationFlushed'); + } + @bindThis public dispose(): void { this.#shutdownController.abort(); diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index 6ccf56e630..239124b484 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -106,7 +106,7 @@ export class PushNotificationService implements OnApplicationShutdown { type, body: (type === 'notification' || type === 'unreadAntennaNote') ? truncateBody(type, body) : body, userId, - dateTime: (new Date()).getTime(), + dateTime: Date.now(), }), { proxy: this.config.proxy, }).catch((err: any) => { @@ -120,12 +120,19 @@ export class PushNotificationService implements OnApplicationShutdown { endpoint: subscription.endpoint, auth: subscription.auth, publickey: subscription.publickey, + }).then(() => { + this.refreshCache(userId); }); } }); } } + @bindThis + public refreshCache(userId: string): void { + this.subscriptionsCache.refresh(userId); + } + @bindThis public dispose(): void { this.subscriptionsCache.dispose(); diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index 5014156a5c..cb0b079df0 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -322,35 +322,36 @@ export class ReactionService { //#endregion } + /** + * 文字列タイプのレガシーな形式のリアクションを現在の形式に変換しつつ、 + * データベース上には存在する「0個のリアクションがついている」という情報を削除する。 + */ @bindThis - public convertLegacyReactions(reactions: Record) { - const _reactions = {} as Record; + public convertLegacyReactions(reactions: MiNote['reactions']): MiNote['reactions'] { + return Object.entries(reactions) + .filter(([, count]) => { + // `ReactionService.prototype.delete`ではリアクション削除時に、 + // `MiNote['reactions']`のエントリの値をデクリメントしているが、 + // デクリメントしているだけなのでエントリ自体は0を値として持つ形で残り続ける。 + // そのため、この処理がなければ、「0個のリアクションがついている」ということになってしまう。 + return count > 0; + }) + .map(([reaction, count]) => { + // unchecked indexed access + const convertedReaction = legacies[reaction] as string | undefined; - for (const reaction of Object.keys(reactions)) { - if (reactions[reaction] <= 0) continue; + const key = this.decodeReaction(convertedReaction ?? reaction).reaction; - if (Object.keys(legacies).includes(reaction)) { - if (_reactions[legacies[reaction]]) { - _reactions[legacies[reaction]] += reactions[reaction]; - } else { - _reactions[legacies[reaction]] = reactions[reaction]; - } - } else { - if (_reactions[reaction]) { - _reactions[reaction] += reactions[reaction]; - } else { - _reactions[reaction] = reactions[reaction]; - } - } - } - - const _reactions2 = {} as Record; + return [key, count] as const; + }) + .reduce((acc, [key, count]) => { + // unchecked indexed access + const prevCount = acc[key] as number | undefined; - for (const reaction of Object.keys(_reactions)) { - _reactions2[this.decodeReaction(reaction).reaction] = _reactions[reaction]; - } + acc[key] = (prevCount ?? 0) + count; - return _reactions2; + return acc; + }, {}); } @bindThis diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 813a715996..9181dafaf3 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -36,6 +36,7 @@ export type RolePolicies = { ltlAvailable: boolean; canPublicNote: boolean; canEditNote: boolean; + mentionLimit: number; canInvite: boolean; inviteLimit: number; inviteLimitCycle: number; @@ -64,6 +65,7 @@ export const DEFAULT_POLICIES: RolePolicies = { ltlAvailable: true, canPublicNote: true, canEditNote: true, + mentionLimit: 20, canInvite: false, inviteLimit: 0, inviteLimitCycle: 60 * 24 * 7, @@ -202,17 +204,20 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { } @bindThis - private evalCond(user: MiUser, value: RoleCondFormulaValue): boolean { + private evalCond(user: MiUser, roles: MiRole[], value: RoleCondFormulaValue): boolean { try { switch (value.type) { case 'and': { - return value.values.every(v => this.evalCond(user, v)); + return value.values.every(v => this.evalCond(user, roles, v)); } case 'or': { - return value.values.some(v => this.evalCond(user, v)); + return value.values.some(v => this.evalCond(user, roles, v)); } case 'not': { - return !this.evalCond(user, value.value); + return !this.evalCond(user, roles, value.value); + } + case 'roleAssignedTo': { + return roles.some(r => r.id === value.roleId); } case 'isLocal': { return this.userEntityService.isLocalUser(user); @@ -274,7 +279,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { const assigns = await this.getUserAssigns(userId); const assignedRoles = roles.filter(r => assigns.map(x => x.roleId).includes(r.id)); const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null; - const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, r.condFormula)); + const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, assignedRoles, r.condFormula)); return [...assignedRoles, ...matchedCondRoles]; } @@ -287,13 +292,13 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { let assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId })); // 期限切れのロールを除外 assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now)); - const assignedRoleIds = assigns.map(x => x.roleId); const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({})); - const assignedBadgeRoles = roles.filter(r => r.asBadge && assignedRoleIds.includes(r.id)); + const assignedRoles = roles.filter(r => assigns.map(x => x.roleId).includes(r.id)); + const assignedBadgeRoles = assignedRoles.filter(r => r.asBadge); const badgeCondRoles = roles.filter(r => r.asBadge && (r.target === 'conditional')); if (badgeCondRoles.length > 0) { const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null; - const matchedBadgeCondRoles = badgeCondRoles.filter(r => this.evalCond(user!, r.condFormula)); + const matchedBadgeCondRoles = badgeCondRoles.filter(r => this.evalCond(user!, assignedRoles, r.condFormula)); return [...assignedBadgeRoles, ...matchedBadgeCondRoles]; } else { return assignedBadgeRoles; @@ -328,6 +333,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)), canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)), canEditNote: calc('canEditNote', vs => vs.some(v => v === true)), + mentionLimit: calc('mentionLimit', vs => Math.max(...vs)), canInvite: calc('canInvite', vs => vs.some(v => v === true)), inviteLimit: calc('inviteLimit', vs => Math.max(...vs)), inviteLimitCycle: calc('inviteLimitCycle', vs => Math.max(...vs)), diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index 8ad85391c6..deeecdeb1f 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -30,6 +30,7 @@ import type { Config } from '@/config.js'; import { AccountMoveService } from '@/core/AccountMoveService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { FanoutTimelineService } from '@/core/FanoutTimelineService.js'; +import type { ThinUser } from '@/queue/types.js'; import Logger from '../logger.js'; const logger = new Logger('following/create'); @@ -94,21 +95,35 @@ export class UserFollowingService implements OnModuleInit { this.userBlockingService = this.moduleRef.get('UserBlockingService'); } + @bindThis + public async deliverAccept(follower: MiRemoteUser, followee: MiPartialLocalUser, requestId?: string) { + const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, requestId), followee)); + this.queueService.deliver(followee, content, follower.inbox, false); + } + @bindThis public async follow( - _follower: { id: MiUser['id'] }, - _followee: { id: MiUser['id'] }, + _follower: ThinUser, + _followee: ThinUser, { requestId, silent = false, withReplies }: { requestId?: string, silent?: boolean, withReplies?: boolean, } = {}, ): Promise { + /** + * 必ず最新のユーザー情報を取得する + */ const [follower, followee] = await Promise.all([ this.usersRepository.findOneByOrFail({ id: _follower.id }), this.usersRepository.findOneByOrFail({ id: _followee.id }), ]) as [MiLocalUser | MiRemoteUser, MiLocalUser | MiRemoteUser]; + if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isRemoteUser(followee)) { + // What? + throw new Error('Remote user cannot follow remote user.'); + } + // check blocking const [blocking, blocked] = await Promise.all([ this.userBlockingService.checkBlocked(follower.id, followee.id), @@ -129,6 +144,24 @@ export class UserFollowingService implements OnModuleInit { if (blocked) throw new IdentifiableError('3338392a-f764-498d-8855-db939dcf8c48', 'blocked'); } + if (await this.followingsRepository.exists({ + where: { + followerId: follower.id, + followeeId: followee.id, + }, + })) { + // すでにフォロー関係が存在している場合 + if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { + // リモート → ローカル: acceptを送り返しておしまい + this.deliverAccept(follower, followee, requestId); + return; + } + if (this.userEntityService.isLocalUser(follower)) { + // ローカル → リモート/ローカル: 例外 + throw new IdentifiableError('ec3f65c0-a9d1-47d9-8791-b2e7b9dcdced', 'already following'); + } + } + const followeeProfile = await this.userProfilesRepository.findOneByOrFail({ userId: followee.id }); // フォロー対象が鍵アカウントである or // フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or @@ -189,8 +222,7 @@ export class UserFollowingService implements OnModuleInit { await this.insertFollowingDoc(followee, follower, silent, withReplies); if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { - const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, requestId), followee)); - this.queueService.deliver(followee, content, follower.inbox, false); + this.deliverAccept(follower, followee, requestId); } } @@ -479,6 +511,12 @@ export class UserFollowingService implements OnModuleInit { if (blocking) throw new Error('blocking'); if (blocked) throw new Error('blocked'); + // Remove old follow requests before creating a new one. + await this.followRequestsRepository.delete({ + followeeId: followee.id, + followerId: follower.id, + }); + const followRequest = await this.followRequestsRepository.insert({ id: this.idService.gen(), followerId: follower.id, @@ -571,8 +609,7 @@ export class UserFollowingService implements OnModuleInit { await this.insertFollowingDoc(followee, follower, false, request.withReplies); if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { - const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee as MiPartialLocalUser, request.requestId!), followee)); - this.queueService.deliver(followee, content, follower.inbox, false); + this.deliverAccept(follower, followee as MiPartialLocalUser, request.requestId ?? undefined); } this.userEntityService.pack(followee.id, followee, { diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts index 638a0c019e..652e8f7449 100644 --- a/packages/backend/src/core/UtilityService.ts +++ b/packages/backend/src/core/UtilityService.ts @@ -42,6 +42,20 @@ export class UtilityService { return silencedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`)); } + @bindThis + public concatNoteContentsForKeyWordCheck(content: { + cw?: string | null; + text?: string | null; + pollChoices?: string[] | null; + others?: string[] | null; + }): string { + /** + * ノートの内容を結合してキーワードチェック用の文字列を生成する + * cwとtextは内容が繋がっているかもしれないので間に何も入れずにチェックする + */ + return `${content.cw ?? ''}${content.text ?? ''}\n${(content.pollChoices ?? []).join('\n')}\n${(content.others ?? []).join('\n')}`; + } + @bindThis public isKeyWordIncluded(text: string, keyWords: string[]): boolean { if (keyWords.length === 0) return false; diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts index 4d11865906..42fbed2110 100644 --- a/packages/backend/src/core/WebAuthnService.ts +++ b/packages/backend/src/core/WebAuthnService.ts @@ -191,7 +191,7 @@ export class WebAuthnService { if (cert[0] === 0x04) { // 前の実装ではいつも 0x04 で始まっていた const halfLength = (cert.length - 1) / 2; - const cborMap = new Map(); + const cborMap = new Map(); cborMap.set(1, 2); // kty, EC2 cborMap.set(3, -7); // alg, ES256 cborMap.set(-1, 1); // crv, P256 diff --git a/packages/backend/src/core/activitypub/ApAudienceService.ts b/packages/backend/src/core/activitypub/ApAudienceService.ts index d47be79441..0fccc7b950 100644 --- a/packages/backend/src/core/activitypub/ApAudienceService.ts +++ b/packages/backend/src/core/activitypub/ApAudienceService.ts @@ -8,6 +8,7 @@ import promiseLimit from 'promise-limit'; import type { MiRemoteUser, MiUser } from '@/models/User.js'; import { concat, unique } from '@/misc/prelude/array.js'; import { bindThis } from '@/decorators.js'; +import { isNotNull } from '@/misc/is-not-null.js'; import { getApIds } from './type.js'; import { ApPersonService } from './models/ApPersonService.js'; import type { ApObject } from './type.js'; @@ -40,7 +41,7 @@ export class ApAudienceService { const limit = promiseLimit(2); const mentionedUsers = (await Promise.all( others.map(id => limit(() => this.apPersonService.resolvePerson(id, resolver).catch(() => null))), - )).filter((x): x is MiUser => x != null); + )).filter(isNotNull); if (toGroups.public.length > 0) { return { diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index 40b204e9f9..95e003c1f4 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -29,6 +29,8 @@ import { MessagingService } from '@/core/MessagingService.js'; import type { UsersRepository, NotesRepository, FollowingsRepository, MessagingMessagesRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import type { MiRemoteUser } from '@/models/User.js'; +import { isNotNull } from '@/misc/is-not-null.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isRead, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; import { ApNoteService } from './models/ApNoteService.js'; import { ApLoggerService } from './ApLoggerService.js'; @@ -37,8 +39,6 @@ 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, IRead, IReject, IRemove, IUndo, IUpdate, IMove } from './type.js'; @@ -90,7 +90,6 @@ export class ApInboxService { private apPersonService: ApPersonService, private apQuestionService: ApQuestionService, private queueService: QueueService, - private cacheService: CacheService, private globalEventService: GlobalEventService, private messagingService: MessagingService, ) { @@ -571,7 +570,7 @@ export class ApInboxService { const userIds = uris .filter(uri => uri.startsWith(this.config.url + '/users/')) .map(uri => uri.split('/').at(-1)) - .filter((userId): userId is string => userId !== undefined); + .filter(isNotNull); const users = await this.usersRepository.findBy({ id: In(userIds), }); diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index a53e880586..75d5a47c4c 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -320,7 +320,7 @@ export class ApRendererService { const getPromisedFiles = async (ids: string[]): Promise => { if (ids.length === 0) return []; const items = await this.driveFilesRepository.findBy({ id: In(ids) }); - return ids.map(id => items.find(item => item.id === id)).filter((item): item is MiDriveFile => item != null); + return ids.map(id => items.find(item => item.id === id)).filter(isNotNull); }; let inReplyTo; diff --git a/packages/backend/src/core/activitypub/models/ApMentionService.ts b/packages/backend/src/core/activitypub/models/ApMentionService.ts index 73eea1edf0..0ced7e88af 100644 --- a/packages/backend/src/core/activitypub/models/ApMentionService.ts +++ b/packages/backend/src/core/activitypub/models/ApMentionService.ts @@ -8,6 +8,7 @@ import promiseLimit from 'promise-limit'; import type { MiUser } from '@/models/_.js'; import { toArray, unique } from '@/misc/prelude/array.js'; import { bindThis } from '@/decorators.js'; +import { isNotNull } from '@/misc/is-not-null.js'; import { isMention } from '../type.js'; import { Resolver } from '../ApResolverService.js'; import { ApPersonService } from './ApPersonService.js'; @@ -27,7 +28,7 @@ export class ApMentionService { const limit = promiseLimit(2); const mentionedUsers = (await Promise.all( hrefs.map(x => limit(() => this.apPersonService.resolvePerson(x, resolver).catch(() => null))), - )).filter((x): x is MiUser => x != null); + )).filter(isNotNull); return mentionedUsers; } diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index 15eb16dd33..6351528cd2 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -26,7 +26,9 @@ import { MessagingService } from '@/core/MessagingService.js'; import { bindThis } from '@/decorators.js'; import { checkHttps } from '@/misc/check-https.js'; import { NoteUpdateService } from '@/core/NoteUpdateService.js'; -import { getApId, getApType, getOneApHrefNullable, getOneApId, isEmoji, validPost } from '../type.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { isNotNull } from '@/misc/is-not-null.js'; +import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js'; import { ApLoggerService } from '../ApLoggerService.js'; import { ApMfmService } from '../ApMfmService.js'; import { ApDbResolverService } from '../ApDbResolverService.js'; @@ -139,7 +141,7 @@ export class ApNoteService { value, object, }); - throw new Error('invalid note'); + throw err; } const note = object as IPost; @@ -163,11 +165,47 @@ export class ApNoteService { throw new Error('invalid note.attributedTo: ' + note.attributedTo); } - const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo), resolver) as MiRemoteUser; + const uri = getOneApId(note.attributedTo); - // 投稿者が凍結されていたらスキップ + // ローカルで投稿者を検索し、もし凍結されていたらスキップ + const cachedActor = await this.apPersonService.fetchPerson(uri) as MiRemoteUser; + if (cachedActor && cachedActor.isSuspended) { + throw new IdentifiableError('85ab9bd7-3a41-4530-959d-f07073900109', 'actor has been suspended'); + } + + const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver); + const apHashtags = extractApHashtags(note.tag); + + const cw = note.summary === '' ? null : note.summary; + + // テキストのパース + let text: string | null = null; + if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') { + text = note.source.content; + } else if (typeof note._misskey_content !== 'undefined') { + text = note._misskey_content; + } else if (typeof note.content === 'string') { + text = this.apMfmService.htmlToMfm(note.content, note.tag); + } + + const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined); + + //#region Contents Check + // 添付ファイルとユーザーをこのサーバーで登録する前に内容をチェックする + /** + * 禁止ワードチェック + */ + const hasProhibitedWords = await this.noteCreateService.checkProhibitedWordsContain({ cw, text, pollChoices: poll?.choices }); + if (hasProhibitedWords) { + throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words'); + } + //#endregion + + const actor = cachedActor ?? await this.apPersonService.resolvePerson(uri, resolver) as MiRemoteUser; + + // 解決した投稿者が凍結されていたらスキップ if (actor.isSuspended) { - throw new Error('actor has been suspended'); + throw new IdentifiableError('85ab9bd7-3a41-4530-959d-f07073900109', 'actor has been suspended'); } const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver); @@ -184,9 +222,6 @@ export class ApNoteService { let isMessaging = note._misskey_talk && visibility === 'specified'; - const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver); - const apHashtags = extractApHashtags(note.tag); - // 添付ファイル // TODO: attachmentは必ずしもImageではない // TODO: attachmentは必ずしも配列ではない @@ -246,7 +281,7 @@ export class ApNoteService { } }; - const uris = unique([note._misskey_quote, note.quoteUrl].filter((x): x is string => typeof x === 'string')); + const uris = unique([note._misskey_quote, note.quoteUrl].filter(isNotNull)); const results = await Promise.all(uris.map(tryResolveNote)); quote = results.filter((x): x is { status: 'ok', res: MiNote } => x.status === 'ok').map(x => x.res).at(0); @@ -257,18 +292,6 @@ export class ApNoteService { } } - const cw = note.summary === '' ? null : note.summary; - - // テキストのパース - let text: string | null = null; - if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') { - text = note.source.content; - } else if (typeof note._misskey_content !== 'undefined') { - text = note._misskey_content; - } else if (typeof note.content === 'string') { - text = this.apMfmService.htmlToMfm(note.content, note.tag); - } - // vote if (reply && reply.hasPoll) { const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id }); @@ -298,7 +321,6 @@ export class ApNoteService { const apEmojis = emojis.map(emoji => emoji.name); - const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined); const event = await this.apEventService.extractEventFromNote(note, resolver).catch(() => undefined); if (isMessaging) { diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index f3cda78038..ea0654e9ad 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -39,6 +39,7 @@ import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.j import type { AccountMoveService } from '@/core/AccountMoveService.js'; import { checkHttps } from '@/misc/check-https.js'; import { AvatarDecorationService } from '@/core/AvatarDecorationService.js'; +import { isNotNull } from '@/misc/is-not-null.js'; import { getApId, getApType, getOneApHrefNullable, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js'; import { extractApHashtags } from './tag.js'; import type { OnModuleInit } from '@nestjs/common'; @@ -796,7 +797,7 @@ export class ApPersonService implements OnModuleInit { // とりあえずidを別の時間で生成して順番を維持 let td = 0; - for (const note of featuredNotes.filter((note): note is MiNote => note != null)) { + for (const note of featuredNotes.filter(isNotNull)) { td -= 1000; transactionalEntityManager.insert(MiUserNotePining, { id: this.idService.gen(Date.now() + td), diff --git a/packages/backend/src/core/activitypub/models/ApQuestionService.ts b/packages/backend/src/core/activitypub/models/ApQuestionService.ts index e78b3a3599..d1936cfe1d 100644 --- a/packages/backend/src/core/activitypub/models/ApQuestionService.ts +++ b/packages/backend/src/core/activitypub/models/ApQuestionService.ts @@ -10,6 +10,7 @@ import type { Config } from '@/config.js'; import type { IPoll } from '@/models/Poll.js'; import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; +import { isNotNull } from '@/misc/is-not-null.js'; import { isQuestion } from '../type.js'; import { ApLoggerService } from '../ApLoggerService.js'; import { ApResolverService } from '../ApResolverService.js'; @@ -51,7 +52,7 @@ export class ApQuestionService { const choices = question[multiple ? 'anyOf' : 'oneOf'] ?.map((x) => x.name) - .filter((x): x is string => typeof x === 'string') + .filter(isNotNull) ?? []; const votes = question[multiple ? 'anyOf' : 'oneOf']?.map((x) => x.replies?.totalItems ?? x._misskey_votes ?? 0); diff --git a/packages/backend/src/core/activitypub/models/tag.ts b/packages/backend/src/core/activitypub/models/tag.ts index ced101b764..e7ceec3262 100644 --- a/packages/backend/src/core/activitypub/models/tag.ts +++ b/packages/backend/src/core/activitypub/models/tag.ts @@ -4,6 +4,7 @@ */ import { toArray } from '@/misc/prelude/array.js'; +import { isNotNull } from '@/misc/is-not-null.js'; import { isHashtag } from '../type.js'; import type { IObject, IApHashtag } from '../type.js'; @@ -15,7 +16,7 @@ export function extractApHashtags(tags: IObject | IObject[] | null | undefined): return hashtags.map(tag => { const m = tag.name.match(/^#(.+)/); return m ? m[1] : null; - }).filter((x): x is string => x != null); + }).filter(isNotNull); } export function extractApHashtagObjects(tags: IObject | IObject[] | null | undefined): IApHashtag[] { diff --git a/packages/backend/src/core/chart/core.ts b/packages/backend/src/core/chart/core.ts index aa0cb9dc2b..f10e30ef10 100644 --- a/packages/backend/src/core/chart/core.ts +++ b/packages/backend/src/core/chart/core.ts @@ -459,13 +459,15 @@ export default abstract class Chart { } } - // bake unique count + // bake cardinality for (const [k, v] of Object.entries(finalDiffs)) { if (this.schema[k].uniqueIncrement) { const name = COLUMN_PREFIX + k.replaceAll('.', COLUMN_DELIMITER) as keyof Columns; const tempColumnName = UNIQUE_TEMP_COLUMN_PREFIX + k.replaceAll('.', COLUMN_DELIMITER) as keyof TempColumnsForUnique; - queryForHour[name] = new Set([...(v as string[]), ...(logHour[tempColumnName] as unknown as string[])]).size; - queryForDay[name] = new Set([...(v as string[]), ...(logDay[tempColumnName] as unknown as string[])]).size; + const cardinalityOfHour = new Set([...(v as string[]), ...(logHour[tempColumnName] as unknown as string[])]).size; + const cardinalityOfDay = new Set([...(v as string[]), ...(logDay[tempColumnName] as unknown as string[])]).size; + queryForHour[name] = cardinalityOfHour; + queryForDay[name] = cardinalityOfDay; } } @@ -637,7 +639,7 @@ export default abstract class Chart { // 要求された範囲にログがひとつもなかったら if (logs.length === 0) { // もっとも新しいログを持ってくる - // (すくなくともひとつログが無いと隙間埋めできないため) + // (すくなくともひとつログが無いと補間できないため) const recentLog = await repository.findOne({ where: group ? { group: group, @@ -654,7 +656,7 @@ export default abstract class Chart { // 要求された範囲の最も古い箇所に位置するログが存在しなかったら } else if (!isTimeSame(new Date(logs.at(-1)!.date * 1000), gt)) { // 要求された範囲の最も古い箇所時点での最も新しいログを持ってきて末尾に追加する - // (隙間埋めできないため) + // (補間できないため) const outdatedLog = await repository.findOne({ where: { date: LessThan(Chart.dateToTimestamp(gt)), @@ -683,7 +685,7 @@ export default abstract class Chart { if (log) { chart.unshift(this.convertRawRecord(log)); } else { - // 隙間埋め + // 補間 const latest = logs.find(l => isTimeBefore(new Date(l.date * 1000), current)); const data = latest ? this.convertRawRecord(latest) : null; chart.unshift(this.getNewLog(data)); diff --git a/packages/backend/src/core/deserializeAntenna.ts b/packages/backend/src/core/deserializeAntenna.ts new file mode 100644 index 0000000000..1d0fbbdc86 --- /dev/null +++ b/packages/backend/src/core/deserializeAntenna.ts @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: noridev and cherrypick-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { MiAntenna } from '@/models/Antenna.js'; + +export function deserializeAntenna(body: any): MiAntenna { + return { + ...body, + lastUsedAt: new Date(body.lastUsedAt), + user: null, + userList: null, + }; +} diff --git a/packages/backend/src/core/entities/AntennaEntityService.ts b/packages/backend/src/core/entities/AntennaEntityService.ts index 95d563f3c9..9ea1df2e70 100644 --- a/packages/backend/src/core/entities/AntennaEntityService.ts +++ b/packages/backend/src/core/entities/AntennaEntityService.ts @@ -45,6 +45,7 @@ export class AntennaEntityService { caseSensitive: antenna.caseSensitive, localOnly: antenna.localOnly, notify: antenna.notify, + excludeBots: antenna.excludeBots, withReplies: antenna.withReplies, withFile: antenna.withFile, isActive: antenna.isActive, diff --git a/packages/backend/src/core/entities/ClipEntityService.ts b/packages/backend/src/core/entities/ClipEntityService.ts index 26fcd6714d..ce49c3458c 100644 --- a/packages/backend/src/core/entities/ClipEntityService.ts +++ b/packages/backend/src/core/entities/ClipEntityService.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { ClipFavoritesRepository, ClipsRepository, MiUser } from '@/models/_.js'; +import type { ClipNotesRepository, ClipFavoritesRepository, ClipsRepository, MiUser } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Packed } from '@/misc/json-schema.js'; import type { } from '@/models/Blocking.js'; @@ -20,6 +20,9 @@ export class ClipEntityService { @Inject(DI.clipsRepository) private clipsRepository: ClipsRepository, + @Inject(DI.clipNotesRepository) + private clipNotesRepository: ClipNotesRepository, + @Inject(DI.clipFavoritesRepository) private clipFavoritesRepository: ClipFavoritesRepository, @@ -47,6 +50,7 @@ export class ClipEntityService { isPublic: clip.isPublic, favoritedCount: await this.clipFavoritesRepository.countBy({ clipId: clip.id }), isFavorited: meId ? await this.clipFavoritesRepository.exists({ where: { clipId: clip.id, userId: meId } }) : undefined, + notesCount: meId ? await this.clipNotesRepository.countBy({ clipId: clip.id }) : undefined, }); } diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index 15782b9298..4a3ed1fc4d 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -258,7 +258,7 @@ export class DriveFileEntityService { folder: opts.detail && file.folderId ? this.driveFolderEntityService.pack(file.folderId, { detail: true, }) : null, - userId: opts.withUser ? file.userId : null, + userId: file.userId, user: (opts.withUser && file.userId) ? this.userEntityService.pack(file.userId) : null, }); } @@ -269,7 +269,7 @@ export class DriveFileEntityService { options?: PackOptions, ): Promise[]> { const items = await Promise.all(files.map(f => this.packNullable(f, options))); - return items.filter((x): x is Packed<'DriveFile'> => x != null); + return items.filter(isNotNull); } @bindThis diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts index 9287c98003..e46bd8b963 100644 --- a/packages/backend/src/core/entities/InstanceEntityService.ts +++ b/packages/backend/src/core/entities/InstanceEntityService.ts @@ -8,12 +8,15 @@ import type { Packed } from '@/misc/json-schema.js'; import type { MiInstance } from '@/models/Instance.js'; import { MetaService } from '@/core/MetaService.js'; import { bindThis } from '@/decorators.js'; -import { UtilityService } from '../UtilityService.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { RoleService } from '@/core/RoleService.js'; +import { MiUser } from '@/models/User.js'; @Injectable() export class InstanceEntityService { constructor( private metaService: MetaService, + private roleService: RoleService, private utilityService: UtilityService, ) { @@ -22,8 +25,11 @@ export class InstanceEntityService { @bindThis public async pack( instance: MiInstance, + me?: { id: MiUser['id']; } | null | undefined, ): Promise> { const meta = await this.metaService.fetch(); + const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false; + return { id: instance.id, firstRetrievedAt: instance.firstRetrievedAt.toISOString(), @@ -48,6 +54,7 @@ export class InstanceEntityService { themeColor: instance.themeColor, infoUpdatedAt: instance.infoUpdatedAt ? instance.infoUpdatedAt.toISOString() : null, latestRequestReceivedAt: instance.latestRequestReceivedAt ? instance.latestRequestReceivedAt.toISOString() : null, + moderationNote: iAmModerator ? instance.moderationNote : null, }; } diff --git a/packages/backend/src/core/entities/MessagingMessageEntityService.ts b/packages/backend/src/core/entities/MessagingMessageEntityService.ts index 5974e984f8..40d961bacb 100644 --- a/packages/backend/src/core/entities/MessagingMessageEntityService.ts +++ b/packages/backend/src/core/entities/MessagingMessageEntityService.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts new file mode 100644 index 0000000000..7c4cec1e4d --- /dev/null +++ b/packages/backend/src/core/entities/MetaEntityService.ts @@ -0,0 +1,156 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Brackets } from 'typeorm'; +import { Inject, Injectable } from '@nestjs/common'; +import JSON5 from 'json5'; +import type { Packed } from '@/misc/json-schema.js'; +import type { MiMeta } from '@/models/Meta.js'; +import type { AdsRepository } from '@/models/_.js'; +import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; +import { MetaService } from '@/core/MetaService.js'; +import { bindThis } from '@/decorators.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { InstanceActorService } from '@/core/InstanceActorService.js'; +import type { Config } from '@/config.js'; +import { DI } from '@/di-symbols.js'; +import { DEFAULT_POLICIES } from '@/core/RoleService.js'; + +@Injectable() +export class MetaEntityService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.adsRepository) + private adsRepository: AdsRepository, + + private userEntityService: UserEntityService, + private metaService: MetaService, + private instanceActorService: InstanceActorService, + ) { } + + @bindThis + public async pack(meta?: MiMeta): Promise> { + let instance = meta; + + if (!instance) { + instance = await this.metaService.fetch(); + } + + const ads = await this.adsRepository.createQueryBuilder('ads') + .where('ads.expiresAt > :now', { now: new Date() }) + .andWhere('ads.startsAt <= :now', { now: new Date() }) + .andWhere(new Brackets(qb => { + // 曜日のビットフラグを確認する + qb.where('ads.dayOfWeek & :dayOfWeek > 0', { dayOfWeek: 1 << new Date().getDay() }) + .orWhere('ads.dayOfWeek = 0'); + })) + .getMany(); + + const packed: Packed<'MetaLite'> = { + maintainerName: instance.maintainerName, + maintainerEmail: instance.maintainerEmail, + + version: this.config.version, + basedMisskeyVersion: this.config.basedMisskeyVersion, + providesTarball: this.config.publishTarballInsteadOfProvideRepositoryUrl, + + name: instance.name, + shortName: instance.shortName, + uri: this.config.url, + description: instance.description, + langs: instance.langs, + tosUrl: instance.termsOfServiceUrl, + repositoryUrl: instance.repositoryUrl, + feedbackUrl: instance.feedbackUrl, + impressumUrl: instance.impressumUrl, + privacyPolicyUrl: instance.privacyPolicyUrl, + disableRegistration: instance.disableRegistration, + emailRequiredForSignup: instance.emailRequiredForSignup, + enableHcaptcha: instance.enableHcaptcha, + hcaptchaSiteKey: instance.hcaptchaSiteKey, + enableMcaptcha: instance.enableMcaptcha, + mcaptchaSiteKey: instance.mcaptchaSitekey, + mcaptchaInstanceUrl: instance.mcaptchaInstanceUrl, + enableRecaptcha: instance.enableRecaptcha, + recaptchaSiteKey: instance.recaptchaSiteKey, + enableTurnstile: instance.enableTurnstile, + turnstileSiteKey: instance.turnstileSiteKey, + swPublickey: instance.swPublicKey, + themeColor: instance.themeColor, + mascotImageUrl: instance.mascotImageUrl ?? '/assets/ai.png', + bannerUrl: instance.bannerUrl, + infoImageUrl: instance.infoImageUrl, + serverErrorImageUrl: instance.serverErrorImageUrl, + notFoundImageUrl: instance.notFoundImageUrl, + iconUrl: instance.iconUrl, + backgroundImageUrl: instance.backgroundImageUrl, + logoImageUrl: instance.logoImageUrl, + maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, + // クライアントの手間を減らすためあらかじめJSONに変換しておく + defaultLightTheme: instance.defaultLightTheme ? JSON.stringify(JSON5.parse(instance.defaultLightTheme)) : null, + defaultDarkTheme: instance.defaultDarkTheme ? JSON.stringify(JSON5.parse(instance.defaultDarkTheme)) : null, + ads: ads.map(ad => ({ + id: ad.id, + url: ad.url, + place: ad.place, + ratio: ad.ratio, + imageUrl: ad.imageUrl, + dayOfWeek: ad.dayOfWeek, + })), + notesPerOneAd: instance.notesPerOneAd, + enableEmail: instance.enableEmail, + enableServiceWorker: instance.enableServiceWorker, + + translatorAvailable: instance.deeplAuthKey != null, + + serverRules: instance.serverRules, + + policies: { ...DEFAULT_POLICIES, ...instance.policies }, + + mediaProxy: this.config.mediaProxy, + enableUrlPreview: instance.urlPreviewEnabled, + }; + + return packed; + } + + @bindThis + public async packDetailed(meta?: MiMeta): Promise> { + let instance = meta; + + if (!instance) { + instance = await this.metaService.fetch(); + } + + const packed = await this.pack(instance); + + const proxyAccount = instance.proxyAccountId ? await this.userEntityService.pack(instance.proxyAccountId).catch(() => null) : null; + + const packDetailed: Packed<'MetaDetailed'> = { + ...packed, + cacheRemoteFiles: instance.cacheRemoteFiles, + cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles, + requireSetup: !await this.instanceActorService.realLocalUsersPresent(), + proxyAccountName: proxyAccount ? proxyAccount.username : null, + features: { + localTimeline: instance.policies.ltlAvailable, + globalTimeline: instance.policies.gtlAvailable, + registration: !instance.disableRegistration, + emailRequiredForSignup: instance.emailRequiredForSignup, + hcaptcha: instance.enableHcaptcha, + recaptcha: instance.enableRecaptcha, + turnstile: instance.enableTurnstile, + objectStorage: instance.useObjectStorage, + serviceWorker: instance.enableServiceWorker, + miauth: true, + }, + }; + + return packDetailed; + } +} + diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 5fda736b15..43cc3fc010 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -352,6 +352,7 @@ export class NoteEntityService implements OnModuleInit { disableRightClick: note.disableRightClick || undefined, renoteCount: note.renoteCount, repliesCount: note.repliesCount, + reactionCount: Object.values(note.reactions).reduce((a, b) => a + b, 0), reactions: this.reactionService.convertLegacyReactions(note.reactions), reactionEmojis: this.customEmojiService.populateEmojis(reactionEmojiNames, host), reactionAndUserPairCache: opts.withReactionAndUserPairCache ? note.reactionAndUserPairCache : undefined, diff --git a/packages/backend/src/core/entities/NoteReactionEntityService.ts b/packages/backend/src/core/entities/NoteReactionEntityService.ts index 2799f58992..3f4fa3cf96 100644 --- a/packages/backend/src/core/entities/NoteReactionEntityService.ts +++ b/packages/backend/src/core/entities/NoteReactionEntityService.ts @@ -69,4 +69,19 @@ export class NoteReactionEntityService implements OnModuleInit { } : {}), }; } + + @bindThis + public async packMany( + reactions: MiNoteReaction[], + me?: { id: MiUser['id'] } | null | undefined, + options?: { + withNote: boolean; + }, + ): Promise[]> { + const opts = Object.assign({ + withNote: false, + }, options); + + return Promise.all(reactions.map(reaction => this.pack(reaction, me, opts))); + } } diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts index 9a66f37b66..f0c2e7f6fa 100644 --- a/packages/backend/src/core/entities/NotificationEntityService.ts +++ b/packages/backend/src/core/entities/NotificationEntityService.ts @@ -14,15 +14,15 @@ import type { MiNote } from '@/models/Note.js'; import type { Packed } from '@/misc/json-schema.js'; import { bindThis } from '@/decorators.js'; import { isNotNull } from '@/misc/is-not-null.js'; -import { FilterUnionByProperty, notificationTypes } from '@/types.js'; +import { FilterUnionByProperty, groupedNotificationTypes } from '@/types.js'; +import { CacheService } from '@/core/CacheService.js'; import { RoleEntityService } from './RoleEntityService.js'; import type { OnModuleInit } from '@nestjs/common'; import type { UserEntityService } from './UserEntityService.js'; import type { NoteEntityService } from './NoteEntityService.js'; import type { UserGroupInvitationEntityService } from './UserGroupInvitationEntityService.js'; -const NOTE_REQUIRED_NOTIFICATION_TYPES = new Set(['note', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded'] as (typeof notificationTypes[number])[]); -const NOTE_REQUIRED_GROUPED_NOTIFICATION_TYPES = new Set(['note', 'mention', 'reply', 'renote', 'renote:grouped', 'quote', 'reaction', 'reaction:grouped', 'pollEnded']); +const NOTE_REQUIRED_NOTIFICATION_TYPES = new Set(['note', 'mention', 'reply', 'renote', 'renote:grouped', 'quote', 'reaction', 'reaction:grouped', 'pollEnded'] as (typeof groupedNotificationTypes[number])[]); @Injectable() export class NotificationEntityService implements OnModuleInit { @@ -46,6 +46,8 @@ export class NotificationEntityService implements OnModuleInit { @Inject(DI.userGroupInvitationsRepository) private userGroupInvitationsRepository: UserGroupInvitationsRepository, + private cacheService: CacheService, + //private userEntityService: UserEntityService, //private noteEntityService: NoteEntityService, //private userGroupInvitationEntityService: UserGroupInvitationEntityService, @@ -59,147 +61,48 @@ export class NotificationEntityService implements OnModuleInit { this.userGroupInvitationEntityService = this.moduleRef.get('UserGroupInvitationEntityService'); } - @bindThis - public async pack( - src: MiNotification, + /** + * 通知をパックする共通処理 + */ + async #packInternal ( + src: T, meId: MiUser['id'], // eslint-disable-next-line @typescript-eslint/ban-types options: { - + checkValidNotifier?: boolean; }, hint?: { packedNotes: Map>; packedUsers: Map>; }, - ): Promise> { + ): Promise | null> { const notification = src; - const noteIfNeed = NOTE_REQUIRED_NOTIFICATION_TYPES.has(notification.type) && 'noteId' in notification ? ( - hint?.packedNotes != null - ? hint.packedNotes.get(notification.noteId) - : this.noteEntityService.pack(notification.noteId, { id: meId }, { - detail: true, - }) - ) : undefined; - const userIfNeed = 'notifierId' in notification ? ( - hint?.packedUsers != null - ? hint.packedUsers.get(notification.notifierId) - : this.userEntityService.pack(notification.notifierId, { id: meId }) - ) : undefined; - const role = notification.type === 'roleAssigned' ? await this.roleEntityService.pack(notification.roleId) : undefined; - - return await awaitAll({ - id: notification.id, - createdAt: new Date(notification.createdAt).toISOString(), - type: notification.type, - userId: 'notifierId' in notification ? notification.notifierId : undefined, - ...(userIfNeed != null ? { user: userIfNeed } : {}), - ...(noteIfNeed != null ? { note: noteIfNeed } : {}), - ...(notification.type === 'reaction' ? { - reaction: notification.reaction, - } : {}), - ...(notification.type === 'roleAssigned' ? { - role: role, - } : {}), - // ...(notification.type === 'pollEnded' ? { - // note: this.noteEntityService.pack(notification.note ?? notification.noteId!, { id: notification.notifieeId }, { - // detail: true, - // _hint_: options._hintForEachNotes_, - // }), - // } : {}), - ...(notification.type === 'groupInvited' ? { - invitation: this.userGroupInvitationEntityService.pack(notification.userGroupInvitationId), - } : {}), - ...(notification.type === 'achievementEarned' ? { - achievement: notification.achievement, - } : {}), - ...(notification.type === 'app' ? { - body: notification.customBody, - header: notification.customHeader, - icon: notification.customIcon, - } : {}), - }); - } - - @bindThis - public async packMany( - notifications: MiNotification[], - meId: MiUser['id'], - ) { - if (notifications.length === 0) return []; - - let validNotifications = notifications; - - const noteIds = validNotifications.map(x => 'noteId' in x ? x.noteId : null).filter(isNotNull); - const notes = noteIds.length > 0 ? await this.notesRepository.find({ - where: { id: In(noteIds) }, - relations: ['user', 'reply', 'reply.user', 'renote', 'renote.user'], - }) : []; - const packedNotesArray = await this.noteEntityService.packMany(notes, { id: meId }, { - detail: true, - }); - const packedNotes = new Map(packedNotesArray.map(p => [p.id, p])); - - validNotifications = validNotifications.filter(x => !('noteId' in x) || packedNotes.has(x.noteId)); - - const userIds = validNotifications.map(x => 'notifierId' in x ? x.notifierId : null).filter(isNotNull); - const users = userIds.length > 0 ? await this.usersRepository.find({ - where: { id: In(userIds) }, - }) : []; - const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }); - const packedUsers = new Map(packedUsersArray.map(p => [p.id, p])); - - // 既に解決されたフォローリクエストの通知を除外 - const followRequestNotifications = validNotifications.filter((x): x is FilterUnionByProperty => x.type === 'receiveFollowRequest'); - if (followRequestNotifications.length > 0) { - const reqs = await this.followRequestsRepository.find({ - where: { followerId: In(followRequestNotifications.map(x => x.notifierId)) }, - }); - validNotifications = validNotifications.filter(x => (x.type !== 'receiveFollowRequest') || reqs.some(r => r.followerId === x.notifierId)); - } - - const groupInvitedNotifications = validNotifications.filter((x): x is FilterUnionByProperty => x.type === 'groupInvited'); - if (groupInvitedNotifications.length > 0) { - const existingInvitationIds = await this.userGroupInvitationsRepository.find({ - where: { id: In(groupInvitedNotifications.map(x => x.userGroupInvitationId)) }, - }); - validNotifications = validNotifications.filter(x => (x.type !== 'groupInvited') || existingInvitationIds.some(r => r.id === x.userGroupInvitationId)); - } - return await Promise.all(validNotifications.map(x => this.pack(x, meId, {}, { - packedNotes, - packedUsers, - }))); - } + if (options.checkValidNotifier !== false && !(await this.#isValidNotifier(notification, meId))) return null; - @bindThis - public async packGrouped( - src: MiGroupedNotification, - meId: MiUser['id'], - // eslint-disable-next-line @typescript-eslint/ban-types - options: { - - }, - hint?: { - packedNotes: Map>; - packedUsers: Map>; - }, - ): Promise> { - const notification = src; - const noteIfNeed = NOTE_REQUIRED_GROUPED_NOTIFICATION_TYPES.has(notification.type) && 'noteId' in notification ? ( + const needsNote = NOTE_REQUIRED_NOTIFICATION_TYPES.has(notification.type) && 'noteId' in notification; + const noteIfNeed = needsNote ? ( hint?.packedNotes != null ? hint.packedNotes.get(notification.noteId) : this.noteEntityService.pack(notification.noteId, { id: meId }, { detail: true, }) ) : undefined; - const userIfNeed = 'notifierId' in notification ? ( + // if the note has been deleted, don't show this notification + if (needsNote && !noteIfNeed) return null; + + const needsUser = 'notifierId' in notification; + const userIfNeed = needsUser ? ( hint?.packedUsers != null ? hint.packedUsers.get(notification.notifierId) : this.userEntityService.pack(notification.notifierId, { id: meId }) ) : undefined; + // if the user has been deleted, don't show this notification + if (needsUser && !userIfNeed) return null; + // #region Grouped notifications if (notification.type === 'reaction:grouped') { - const reactions = await Promise.all(notification.reactions.map(async reaction => { + const reactions = (await Promise.all(notification.reactions.map(async reaction => { const user = hint?.packedUsers != null ? hint.packedUsers.get(reaction.userId)! : await this.userEntityService.pack(reaction.userId, { id: meId }); @@ -207,7 +110,12 @@ export class NotificationEntityService implements OnModuleInit { user, reaction: reaction.reaction, }; - })); + }))).filter(r => isNotNull(r.user)); + // if all users have been deleted, don't show this notification + if (reactions.length === 0) { + return null; + } + return await awaitAll({ id: notification.id, createdAt: new Date(notification.createdAt).toISOString(), @@ -216,14 +124,19 @@ export class NotificationEntityService implements OnModuleInit { reactions, }); } else if (notification.type === 'renote:grouped') { - const users = await Promise.all(notification.userIds.map(userId => { + const users = (await Promise.all(notification.userIds.map(userId => { const packedUser = hint?.packedUsers != null ? hint.packedUsers.get(userId) : null; if (packedUser) { return packedUser; } return this.userEntityService.pack(userId, { id: meId }); - })); + }))).filter(isNotNull); + // if all users have been deleted, don't show this notification + if (users.length === 0) { + return null; + } + return await awaitAll({ id: notification.id, createdAt: new Date(notification.createdAt).toISOString(), @@ -232,8 +145,14 @@ export class NotificationEntityService implements OnModuleInit { users, }); } + // #endregion - const role = notification.type === 'roleAssigned' ? await this.roleEntityService.pack(notification.roleId) : undefined; + const needsRole = notification.type === 'roleAssigned'; + const role = needsRole ? await this.roleEntityService.pack(notification.roleId) : undefined; + // if the role has been deleted, don't show this notification + if (needsRole && !role) { + return null; + } return await awaitAll({ id: notification.id, @@ -245,12 +164,12 @@ export class NotificationEntityService implements OnModuleInit { ...(notification.type === 'reaction' ? { reaction: notification.reaction, } : {}), - ...(notification.type === 'roleAssigned' ? { - role: role, - } : {}), ...(notification.type === 'groupInvited' ? { invitation: this.userGroupInvitationEntityService.pack(notification.userGroupInvitationId), } : {}), + ...(notification.type === 'roleAssigned' ? { + role: role, + } : {}), ...(notification.type === 'achievementEarned' ? { achievement: notification.achievement, } : {}), @@ -262,15 +181,16 @@ export class NotificationEntityService implements OnModuleInit { }); } - @bindThis - public async packGroupedMany( - notifications: MiGroupedNotification[], + async #packManyInternal ( + notifications: T[], meId: MiUser['id'], - ) { + ): Promise { if (notifications.length === 0) return []; let validNotifications = notifications; + validNotifications = await this.#filterValidNotifier(validNotifications, meId); + const noteIds = validNotifications.map(x => 'noteId' in x ? x.noteId : null).filter(isNotNull); const notes = noteIds.length > 0 ? await this.notesRepository.find({ where: { id: In(noteIds) }, @@ -296,7 +216,7 @@ export class NotificationEntityService implements OnModuleInit { const packedUsers = new Map(packedUsersArray.map(p => [p.id, p])); // 既に解決されたフォローリクエストの通知を除外 - const followRequestNotifications = validNotifications.filter((x): x is FilterUnionByProperty => x.type === 'receiveFollowRequest'); + const followRequestNotifications = validNotifications.filter((x): x is FilterUnionByProperty => x.type === 'receiveFollowRequest'); if (followRequestNotifications.length > 0) { const reqs = await this.followRequestsRepository.find({ where: { followerId: In(followRequestNotifications.map(x => x.notifierId)) }, @@ -304,17 +224,107 @@ export class NotificationEntityService implements OnModuleInit { validNotifications = validNotifications.filter(x => (x.type !== 'receiveFollowRequest') || reqs.some(r => r.followerId === x.notifierId)); } - const groupInvitedNotifications = validNotifications.filter((x): x is FilterUnionByProperty => x.type === 'groupInvited'); - if (groupInvitedNotifications.length > 0) { - const existingInvitationIds = await this.userGroupInvitationsRepository.find({ - where: { id: In(groupInvitedNotifications.map(x => x.userGroupInvitationId)) }, - }); - validNotifications = validNotifications.filter(x => (x.type !== 'groupInvited') || existingInvitationIds.some(r => r.id === x.userGroupInvitationId)); - } + const packPromises = validNotifications.map(x => { + return this.pack( + x, + meId, + { checkValidNotifier: false }, + { packedNotes, packedUsers }, + ); + }); + + return (await Promise.all(packPromises)).filter(isNotNull); + } + + @bindThis + public async pack( + src: MiNotification | MiGroupedNotification, + meId: MiUser['id'], + // eslint-disable-next-line @typescript-eslint/ban-types + options: { + checkValidNotifier?: boolean; + }, + hint?: { + packedNotes: Map>; + packedUsers: Map>; + }, + ): Promise | null> { + return await this.#packInternal(src, meId, options, hint); + } + + @bindThis + public async packMany( + notifications: MiNotification[], + meId: MiUser['id'], + ): Promise { + return await this.#packManyInternal(notifications, meId); + } + + @bindThis + public async packGroupedMany( + notifications: MiGroupedNotification[], + meId: MiUser['id'], + ): Promise { + return await this.#packManyInternal(notifications, meId); + } + + /** + * notifierが存在するか、ミュートされていないか、サスペンドされていないかを確認するvalidator + */ + #validateNotifier ( + notification: T, + userIdsWhoMeMuting: Set, + userMutedInstances: Set, + notifiers: MiUser[], + ): boolean { + if (!('notifierId' in notification)) return true; + if (userIdsWhoMeMuting.has(notification.notifierId)) return false; + + const notifier = notifiers.find(x => x.id === notification.notifierId) ?? null; + + if (notifier == null) return false; + if (notifier.host && userMutedInstances.has(notifier.host)) return false; + + if (notifier.isSuspended) return false; + + return true; + } + + /** + * notifierが存在するか、ミュートされていないか、サスペンドされていないかを実際に確認する + */ + async #isValidNotifier( + notification: MiNotification | MiGroupedNotification, + meId: MiUser['id'], + ): Promise { + return (await this.#filterValidNotifier([notification], meId)).length === 1; + } + + /** + * notifierが存在するか、ミュートされていないか、サスペンドされていないかを実際に複数確認する + */ + async #filterValidNotifier ( + notifications: T[], + meId: MiUser['id'], + ): Promise { + const [ + userIdsWhoMeMuting, + userMutedInstances, + ] = await Promise.all([ + this.cacheService.userMutingsCache.fetch(meId), + this.cacheService.userProfileCache.fetch(meId).then(p => new Set(p.mutedInstances)), + ]); + + const notifierIds = notifications.map(notification => 'notifierId' in notification ? notification.notifierId : null).filter(isNotNull); + const notifiers = notifierIds.length > 0 ? await this.usersRepository.find({ + where: { id: In(notifierIds) }, + }) : []; + + const filteredNotifications = ((await Promise.all(notifications.map(async (notification) => { + const isValid = this.#validateNotifier(notification, userIdsWhoMeMuting, userMutedInstances, notifiers); + return isValid ? notification : null; + }))) as [T | null] ).filter(isNotNull); - return await Promise.all(validNotifications.map(x => this.packGrouped(x, meId, {}, { - packedNotes, - packedUsers, - }))); + return filteredNotifications; } } diff --git a/packages/backend/src/core/entities/PageEntityService.ts b/packages/backend/src/core/entities/PageEntityService.ts index fe7b137bd2..65c69a49a7 100644 --- a/packages/backend/src/core/entities/PageEntityService.ts +++ b/packages/backend/src/core/entities/PageEntityService.ts @@ -14,6 +14,7 @@ import type { MiPage } from '@/models/Page.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; +import { isNotNull } from '@/misc/is-not-null.js'; import { UserEntityService } from './UserEntityService.js'; import { DriveFileEntityService } from './DriveFileEntityService.js'; @@ -102,7 +103,7 @@ export class PageEntityService { script: page.script, eyeCatchingImageId: page.eyeCatchingImageId, eyeCatchingImage: page.eyeCatchingImageId ? await this.driveFileEntityService.pack(page.eyeCatchingImageId) : null, - attachedFiles: this.driveFileEntityService.packMany((await Promise.all(attachedFiles)).filter((x): x is MiDriveFile => x != null)), + attachedFiles: this.driveFileEntityService.packMany((await Promise.all(attachedFiles)).filter(isNotNull)), likedCount: page.likedCount, isLiked: meId ? await this.pageLikesRepository.exists({ where: { pageId: page.id, userId: meId } }) : undefined, }); diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 0e0023568b..34718570de 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -15,8 +15,32 @@ import type { Promiseable } from '@/misc/prelude/await-all.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js'; import type { MiLocalUser, MiPartialLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js'; -import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/User.js'; -import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, UserNotePiningsRepository, UserProfilesRepository, AnnouncementReadsRepository, AnnouncementsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository, MiUserProfile, RenoteMutingsRepository, UserMemoRepository } from '@/models/_.js'; +import { + birthdaySchema, + descriptionSchema, + localUsernameSchema, + locationSchema, + nameSchema, + passwordSchema, +} from '@/models/User.js'; +import type { + BlockingsRepository, + FollowingsRepository, + FollowRequestsRepository, + MessagingMessagesRepository, + MiFollowing, + MiUserNotePining, + MiUserProfile, + MutingsRepository, + NoteUnreadsRepository, + RenoteMutingsRepository, + UserGroupJoiningsRepository, + UserMemoRepository, + UserNotePiningsRepository, + UserProfilesRepository, + UserSecurityKeysRepository, + UsersRepository, +} from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; @@ -25,6 +49,7 @@ import { IdService } from '@/core/IdService.js'; import type { AnnouncementService } from '@/core/AnnouncementService.js'; import type { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { AvatarDecorationService } from '@/core/AvatarDecorationService.js'; +import { isNotNull } from '@/misc/is-not-null.js'; import type { OnModuleInit } from '@nestjs/common'; import type { NoteEntityService } from './NoteEntityService.js'; import type { DriveFileEntityService } from './DriveFileEntityService.js'; @@ -45,11 +70,23 @@ function isRemoteUser(user: MiUser | { host: MiUser['host'] }): boolean { return !isLocalUser(user); } +export type UserRelation = { + id: MiUser['id'] + following: MiFollowing | null, + isFollowing: boolean + isFollowed: boolean + hasPendingFollowRequestFromYou: boolean + hasPendingFollowRequestToYou: boolean + isBlocking: boolean + isBlocked: boolean + isMuted: boolean + isRenoteMuted: boolean +} + @Injectable() export class UserEntityService implements OnModuleInit { private apPersonService: ApPersonService; private noteEntityService: NoteEntityService; - private driveFileEntityService: DriveFileEntityService; private pageEntityService: PageEntityService; private customEmojiService: CustomEmojiService; private announcementService: AnnouncementService; @@ -88,9 +125,6 @@ export class UserEntityService implements OnModuleInit { @Inject(DI.renoteMutingsRepository) private renoteMutingsRepository: RenoteMutingsRepository, - @Inject(DI.driveFilesRepository) - private driveFilesRepository: DriveFilesRepository, - @Inject(DI.noteUnreadsRepository) private noteUnreadsRepository: NoteUnreadsRepository, @@ -106,12 +140,6 @@ export class UserEntityService implements OnModuleInit { @Inject(DI.userGroupJoiningsRepository) private userGroupJoiningsRepository: UserGroupJoiningsRepository, - @Inject(DI.announcementReadsRepository) - private announcementReadsRepository: AnnouncementReadsRepository, - - @Inject(DI.announcementsRepository) - private announcementsRepository: AnnouncementsRepository, - @Inject(DI.userMemosRepository) private userMemosRepository: UserMemoRepository, ) { @@ -120,7 +148,6 @@ export class UserEntityService implements OnModuleInit { onModuleInit() { this.apPersonService = this.moduleRef.get('ApPersonService'); this.noteEntityService = this.moduleRef.get('NoteEntityService'); - this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService'); this.pageEntityService = this.moduleRef.get('PageEntityService'); this.customEmojiService = this.moduleRef.get('CustomEmojiService'); this.announcementService = this.moduleRef.get('AnnouncementService'); @@ -143,7 +170,7 @@ export class UserEntityService implements OnModuleInit { public isRemoteUser = isRemoteUser; @bindThis - public async getRelation(me: MiUser['id'], target: MiUser['id']) { + public async getRelation(me: MiUser['id'], target: MiUser['id']): Promise { const [ following, isFollowed, @@ -216,6 +243,59 @@ export class UserEntityService implements OnModuleInit { }; } + @bindThis + public async getRelations(me: MiUser['id'], targets: MiUser['id'][]): Promise> { + const [ + followers, + followees, + followersRequests, + followeesRequests, + blockers, + blockees, + muters, + renoteMuters, + ] = await Promise.all([ + this.followingsRepository.findBy({ followerId: me }) + .then(f => new Map(f.map(it => [it.followeeId, it]))), + this.followingsRepository.findBy({ followeeId: me }) + .then(it => it.map(it => it.followerId)), + this.followRequestsRepository.findBy({ followerId: me }) + .then(it => it.map(it => it.followeeId)), + this.followRequestsRepository.findBy({ followeeId: me }) + .then(it => it.map(it => it.followerId)), + this.blockingsRepository.findBy({ blockerId: me }) + .then(it => it.map(it => it.blockeeId)), + this.blockingsRepository.findBy({ blockeeId: me }) + .then(it => it.map(it => it.blockerId)), + this.mutingsRepository.findBy({ muterId: me }) + .then(it => it.map(it => it.muteeId)), + this.renoteMutingsRepository.findBy({ muterId: me }) + .then(it => it.map(it => it.muteeId)), + ]); + + return new Map( + targets.map(target => { + const following = followers.get(target) ?? null; + + return [ + target, + { + id: target, + following: following, + isFollowing: following != null, + isFollowed: followees.includes(target), + hasPendingFollowRequestFromYou: followersRequests.includes(target), + hasPendingFollowRequestToYou: followeesRequests.includes(target), + isBlocking: blockers.includes(target), + isBlocked: blockees.includes(target), + isMuted: muters.includes(target), + isRenoteMuted: renoteMuters.includes(target), + }, + ]; + }), + ); + } + @bindThis public async getHasUnreadMessagingMessage(userId: MiUser['id']): Promise { const mute = await this.mutingsRepository.findBy({ @@ -338,6 +418,9 @@ export class UserEntityService implements OnModuleInit { schema?: S, includeSecrets?: boolean, userProfile?: MiUserProfile, + userRelations?: Map, + userMemos?: Map, + pinNotes?: Map, }, ): Promise> { const opts = Object.assign({ @@ -352,13 +435,41 @@ export class UserEntityService implements OnModuleInit { const isMe = meId === user.id; const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false; - const relation = meId && !isMe && isDetailed ? await this.getRelation(meId, user.id) : null; - const pins = isDetailed ? await this.userNotePiningsRepository.createQueryBuilder('pin') - .where('pin.userId = :userId', { userId: user.id }) - .innerJoinAndSelect('pin.note', 'note') - .orderBy('pin.id', 'DESC') - .getMany() : []; - const profile = isDetailed ? (opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id })) : null; + const profile = isDetailed + ? (opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id })) + : null; + + let relation: UserRelation | null = null; + if (meId && !isMe && isDetailed) { + if (opts.userRelations) { + relation = opts.userRelations.get(user.id) ?? null; + } else { + relation = await this.getRelation(meId, user.id); + } + } + + let memo: string | null = null; + if (isDetailed && meId) { + if (opts.userMemos) { + memo = opts.userMemos.get(user.id) ?? null; + } else { + memo = await this.userMemosRepository.findOneBy({ userId: meId, targetUserId: user.id }) + .then(row => row?.memo ?? null); + } + } + + let pins: MiUserNotePining[] = []; + if (isDetailed) { + if (opts.pinNotes) { + pins = opts.pinNotes.get(user.id) ?? []; + } else { + pins = await this.userNotePiningsRepository.createQueryBuilder('pin') + .where('pin.userId = :userId', { userId: user.id }) + .innerJoinAndSelect('pin.note', 'note') + .orderBy('pin.id', 'DESC') + .getMany(); + } + } const followingCount = profile == null ? null : (profile.followingVisibility === 'public') || isMe ? user.followingCount : @@ -422,7 +533,7 @@ export class UserEntityService implements OnModuleInit { movedTo: user.movedToUri ? this.apPersonService.resolvePerson(user.movedToUri).then(user => user.id).catch(() => null) : null, alsoKnownAs: user.alsoKnownAs ? Promise.all(user.alsoKnownAs.map(uri => this.apPersonService.fetchPerson(uri).then(user => user?.id).catch(() => null))) - .then(xs => xs.length === 0 ? null : xs.filter(x => x != null) as string[]) + .then(xs => xs.length === 0 ? null : xs.filter(isNotNull)) : null, createdAt: this.idService.parse(user.id).date.toISOString(), updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null, @@ -453,9 +564,7 @@ export class UserEntityService implements OnModuleInit { twoFactorEnabled: profile!.twoFactorEnabled, usePasswordLessLogin: profile!.usePasswordLessLogin, securityKeys: profile!.twoFactorEnabled - ? this.userSecurityKeysRepository.countBy({ - userId: user.id, - }).then(result => result >= 1) + ? this.userSecurityKeysRepository.countBy({ userId: user.id }).then(result => result >= 1) : false, roles: this.roleService.getUserRoles(user.id).then(roles => roles.filter(role => role.isPublic).sort((a, b) => b.displayOrder - a.displayOrder).map(role => ({ id: role.id, @@ -467,10 +576,7 @@ export class UserEntityService implements OnModuleInit { isAdministrator: role.isAdministrator, displayOrder: role.displayOrder, }))), - memo: meId == null ? null : await this.userMemosRepository.findOneBy({ - userId: meId, - targetUserId: user.id, - }).then(row => row?.memo ?? null), + memo: memo, moderationNote: iAmModerator ? (profile!.moderationNote ?? '') : undefined, } : {}), @@ -552,7 +658,7 @@ export class UserEntityService implements OnModuleInit { return await awaitAll(packed); } - public packMany( + public async packMany( users: (MiUser['id'] | MiUser)[], me?: { id: MiUser['id'] } | null | undefined, options?: { @@ -560,6 +666,70 @@ export class UserEntityService implements OnModuleInit { includeSecrets?: boolean, }, ): Promise[]> { - return Promise.all(users.map(u => this.pack(u, me, options))); + // -- IDのみの要素を補完して完全なエンティティ一覧を作る + + const _users = users.filter((user): user is MiUser => typeof user !== 'string'); + if (_users.length !== users.length) { + _users.push( + ...await this.usersRepository.findBy({ + id: In(users.filter((user): user is string => typeof user === 'string')), + }), + ); + } + const _userIds = _users.map(u => u.id); + + // -- 特に前提条件のない値群を取得 + + const profilesMap = await this.userProfilesRepository.findBy({ userId: In(_userIds) }) + .then(profiles => new Map(profiles.map(p => [p.userId, p]))); + + // -- 実行者の有無や指定スキーマの種別によって要否が異なる値群を取得 + + let userRelations: Map = new Map(); + let userMemos: Map = new Map(); + let pinNotes: Map = new Map(); + + if (options?.schema !== 'UserLite') { + const meId = me ? me.id : null; + if (meId) { + userMemos = await this.userMemosRepository.findBy({ userId: meId }) + .then(memos => new Map(memos.map(memo => [memo.targetUserId, memo.memo]))); + + if (_userIds.length > 0) { + userRelations = await this.getRelations(meId, _userIds); + pinNotes = await this.userNotePiningsRepository.createQueryBuilder('pin') + .where('pin.userId IN (:...userIds)', { userIds: _userIds }) + .innerJoinAndSelect('pin.note', 'note') + .getMany() + .then(pinsNotes => { + const map = new Map(); + for (const note of pinsNotes) { + const notes = map.get(note.userId) ?? []; + notes.push(note); + map.set(note.userId, notes); + } + for (const [, notes] of map.entries()) { + // pack側ではDESCで取得しているので、それに合わせて降順に並び替えておく + notes.sort((a, b) => b.id.localeCompare(a.id)); + } + return map; + }); + } + } + } + + return Promise.all( + _users.map(u => this.pack( + u, + me, + { + ...options, + userProfile: profilesMap.get(u.id), + userRelations: userRelations, + userMemos: userMemos, + pinNotes: pinNotes, + }, + )), + ); } } diff --git a/packages/backend/src/core/entities/UserGroupEntityService.ts b/packages/backend/src/core/entities/UserGroupEntityService.ts index 72de937062..402d27923c 100644 --- a/packages/backend/src/core/entities/UserGroupEntityService.ts +++ b/packages/backend/src/core/entities/UserGroupEntityService.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/core/entities/UserGroupInvitationEntityService.ts b/packages/backend/src/core/entities/UserGroupInvitationEntityService.ts index ba1f30ae9d..9500d6c95f 100644 --- a/packages/backend/src/core/entities/UserGroupInvitationEntityService.ts +++ b/packages/backend/src/core/entities/UserGroupInvitationEntityService.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/misc/FileWriterStream.ts b/packages/backend/src/misc/FileWriterStream.ts new file mode 100644 index 0000000000..367a8eb560 --- /dev/null +++ b/packages/backend/src/misc/FileWriterStream.ts @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as fs from 'node:fs/promises'; +import type { PathLike } from 'node:fs'; + +/** + * `fs.createWriteStream()`相当のことを行う`WritableStream` (Web標準) + */ +export class FileWriterStream extends WritableStream { + constructor(path: PathLike) { + let file: fs.FileHandle | null = null; + + super({ + start: async () => { + file = await fs.open(path, 'a'); + }, + write: async (chunk, controller) => { + if (file === null) { + controller.error(); + throw new Error(); + } + + await file.write(chunk); + }, + close: async () => { + await file?.close(); + }, + abort: async () => { + await file?.close(); + }, + }); + } +} diff --git a/packages/backend/src/misc/JsonArrayStream.ts b/packages/backend/src/misc/JsonArrayStream.ts new file mode 100644 index 0000000000..754938989d --- /dev/null +++ b/packages/backend/src/misc/JsonArrayStream.ts @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { TransformStream } from 'node:stream/web'; + +/** + * ストリームに流れてきた各データについて`JSON.stringify()`した上で、それらを一つの配列にまとめる + */ +export class JsonArrayStream extends TransformStream { + constructor() { + /** 最初の要素かどうかを変数に記録 */ + let isFirst = true; + + super({ + start(controller) { + controller.enqueue('['); + }, + flush(controller) { + controller.enqueue(']'); + }, + transform(chunk, controller) { + if (isFirst) { + isFirst = false; + } else { + // 妥当なJSON配列にするためには最初以外の要素の前に`,`を挿入しなければならない + controller.enqueue(',\n'); + } + + controller.enqueue(JSON.stringify(chunk)); + }, + }); + } +} diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts index b5ffe76fda..71f7b838a8 100644 --- a/packages/backend/src/misc/cache.ts +++ b/packages/backend/src/misc/cache.ts @@ -193,6 +193,10 @@ export class RedisSingleCache { // TODO: メモリ節約のためあまり参照されないキーを定期的に削除できるようにする? export class MemoryKVCache { + /** + * データを持つマップ + * @deprecated これを直接操作するべきではない + */ public cache: Map; private lifetime: number; private gcIntervalHandle: NodeJS.Timeout; @@ -207,6 +211,10 @@ export class MemoryKVCache { } @bindThis + /** + * Mapにキャッシュをセットします + * @deprecated これを直接呼び出すべきではない。InternalEventなどで変更を全てのプロセス/マシンに通知するべき + */ public set(key: string, value: T): void { this.cache.set(key, { date: Date.now(), diff --git a/packages/backend/src/misc/fastify-hook-handlers.ts b/packages/backend/src/misc/fastify-hook-handlers.ts index 49a48f6a6b..3e1c099e00 100644 --- a/packages/backend/src/misc/fastify-hook-handlers.ts +++ b/packages/backend/src/misc/fastify-hook-handlers.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + import type { onRequestHookHandler } from 'fastify'; export const handleRequestRedirectToOmitSearch: onRequestHookHandler = (request, reply, done) => { diff --git a/packages/backend/src/misc/is-not-null.ts b/packages/backend/src/misc/is-not-null.ts index 584a09d35a..8d9dc8bb39 100644 --- a/packages/backend/src/misc/is-not-null.ts +++ b/packages/backend/src/misc/is-not-null.ts @@ -3,8 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -// we are using {} as "any non-nullish value" as expected -// eslint-disable-next-line @typescript-eslint/ban-types -export function isNotNull(input: T | undefined | null): input is T { +export function isNotNull>(input: T | undefined | null): input is T { return input != null; } diff --git a/packages/backend/src/misc/is-pure-renote.ts b/packages/backend/src/misc/is-pure-renote.ts deleted file mode 100644 index 994d981522..0000000000 --- a/packages/backend/src/misc/is-pure-renote.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { MiNote } from '@/models/Note.js'; - -export function isPureRenote(note: MiNote): note is MiNote & { renoteId: NonNullable } { - if (!note.renoteId) return false; - - if (note.text) return false; // it's quoted with text - if (note.fileIds.length !== 0) return false; // it's quoted with files - if (note.hasPoll) return false; // it's quoted with poll - return true; -} diff --git a/packages/backend/src/misc/is-quote.ts b/packages/backend/src/misc/is-quote.ts deleted file mode 100644 index 75b29f63f4..0000000000 --- a/packages/backend/src/misc/is-quote.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import type { MiNote } from '@/models/Note.js'; - -// eslint-disable-next-line import/no-default-export -export default function(note: MiNote): boolean { - // sync with NoteCreateService.isQuote - return note.renoteId != null && (note.text != null || note.hasPoll || (note.fileIds != null && note.fileIds.length > 0)); -} diff --git a/packages/backend/src/misc/is-renote.ts b/packages/backend/src/misc/is-renote.ts new file mode 100644 index 0000000000..5d48aba360 --- /dev/null +++ b/packages/backend/src/misc/is-renote.ts @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { MiNote } from '@/models/Note.js'; + +type Renote = + MiNote & { + renoteId: NonNullable + }; + +type Quote = + Renote & ({ + text: NonNullable + } | { + cw: NonNullable + } | { + replyId: NonNullable + reply: NonNullable + } | { + hasPoll: true + }); + +export function isRenote(note: MiNote): note is Renote { + return note.renoteId != null; +} + +export function isQuote(note: Renote): note is Quote { + // NOTE: SYNC WITH NoteCreateService.isQuote + return note.text != null || + note.cw != null || + note.replyId != null || + note.hasPoll || + note.fileIds.length > 0; +} diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts index 8881985a63..5ccbcf330c 100644 --- a/packages/backend/src/misc/json-schema.ts +++ b/packages/backend/src/misc/json-schema.ts @@ -46,12 +46,18 @@ import { packedRoleCondFormulaLogicsSchema, packedRoleCondFormulaValueNot, packedRoleCondFormulaValueIsLocalOrRemoteSchema, + packedRoleCondFormulaValueAssignedRoleSchema, 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'; +import { + packedMetaLiteSchema, + packedMetaDetailedOnlySchema, + packedMetaDetailedSchema, +} from '@/models/json-schema/meta.js'; export const refs = { UserLite: packedUserLiteSchema, @@ -95,6 +101,7 @@ export const refs = { RoleCondFormulaLogics: packedRoleCondFormulaLogicsSchema, RoleCondFormulaValueNot: packedRoleCondFormulaValueNot, RoleCondFormulaValueIsLocalOrRemote: packedRoleCondFormulaValueIsLocalOrRemoteSchema, + RoleCondFormulaValueAssignedRole: packedRoleCondFormulaValueAssignedRoleSchema, RoleCondFormulaValueCreated: packedRoleCondFormulaValueCreatedSchema, RoleCondFormulaFollowersOrFollowingOrNotes: packedRoleCondFormulaFollowersOrFollowingOrNotesSchema, RoleCondFormulaValue: packedRoleCondFormulaValueSchema, @@ -103,6 +110,9 @@ export const refs = { RolePolicies: packedRolePoliciesSchema, ReversiGameLite: packedReversiGameLiteSchema, ReversiGameDetailed: packedReversiGameDetailedSchema, + MetaLite: packedMetaLiteSchema, + MetaDetailedOnly: packedMetaDetailedOnlySchema, + MetaDetailed: packedMetaDetailedSchema, }; export type Packed = SchemaType; diff --git a/packages/backend/src/misc/loader.ts b/packages/backend/src/misc/loader.ts index 25f7b54d31..7f29b9db10 100644 --- a/packages/backend/src/misc/loader.ts +++ b/packages/backend/src/misc/loader.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export type FetchFunction = (key: K) => Promise; type ResolveReject = Parameters>[0]>; diff --git a/packages/backend/src/models/Antenna.ts b/packages/backend/src/models/Antenna.ts index 2704d61bad..3261d2e6a4 100644 --- a/packages/backend/src/models/Antenna.ts +++ b/packages/backend/src/models/Antenna.ts @@ -85,6 +85,11 @@ export class MiAntenna { }) public caseSensitive: boolean; + @Column('boolean', { + default: false, + }) + public excludeBots: boolean; + @Column('boolean', { default: false, }) diff --git a/packages/backend/src/models/Instance.ts b/packages/backend/src/models/Instance.ts index 0632ef525b..9863c9d75d 100644 --- a/packages/backend/src/models/Instance.ts +++ b/packages/backend/src/models/Instance.ts @@ -144,4 +144,9 @@ export class MiInstance { nullable: true, }) public infoUpdatedAt: Date | null; + + @Column('varchar', { + length: 16384, default: '', + }) + public moderationNote: string; } diff --git a/packages/backend/src/models/MessagingMessage.ts b/packages/backend/src/models/MessagingMessage.ts index 80b3c2c4c2..1357438679 100644 --- a/packages/backend/src/models/MessagingMessage.ts +++ b/packages/backend/src/models/MessagingMessage.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 174a6459b5..91e16f05de 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -277,12 +277,6 @@ export class MiMeta { }) public enableSensitiveMediaDetectionForVideos: boolean; - @Column('varchar', { - length: 1024, - nullable: true, - }) - public summalyProxy: string | null; - @Column('boolean', { default: false, }) @@ -697,6 +691,38 @@ export class MiMeta { }) public notesPerOneAd: number; + @Column('boolean', { + default: true, + }) + public urlPreviewEnabled: boolean; + + @Column('integer', { + default: 10000, + }) + public urlPreviewTimeout: number; + + @Column('bigint', { + default: 1024 * 1024 * 10, + }) + public urlPreviewMaximumContentLength: number; + + @Column('boolean', { + default: true, + }) + public urlPreviewRequireContentLength: boolean; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public urlPreviewSummaryProxyUrl: string | null; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public urlPreviewUserAgent: string | null; + @Column('boolean', { default: false, }) diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts index 4eace566d7..68ece6b947 100644 --- a/packages/backend/src/models/Notification.ts +++ b/packages/backend/src/models/Notification.ts @@ -68,17 +68,17 @@ export type MiNotification = { id: string; createdAt: string; notifierId: MiUser['id']; -} | { - type: 'roleAssigned'; - id: string; - createdAt: string; - roleId: MiRole['id']; } | { type: 'groupInvited'; id: string; createdAt: string; notifierId: MiUser['id']; userGroupInvitationId: MiUserGroupInvitation['id']; +} | { + type: 'roleAssigned'; + id: string; + createdAt: string; + roleId: MiRole['id']; } | { type: 'achievementEarned'; id: string; diff --git a/packages/backend/src/models/ReversiGame.ts b/packages/backend/src/models/ReversiGame.ts index c03335dd63..6b29a0ce8c 100644 --- a/packages/backend/src/models/ReversiGame.ts +++ b/packages/backend/src/models/ReversiGame.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; import { id } from './util/id.js'; import { MiUser } from './User.js'; diff --git a/packages/backend/src/models/Role.ts b/packages/backend/src/models/Role.ts index fa05ea8637..058abe3118 100644 --- a/packages/backend/src/models/Role.ts +++ b/packages/backend/src/models/Role.ts @@ -29,6 +29,11 @@ type CondFormulaValueIsRemote = { type: 'isRemote'; }; +type CondFormulaValueRoleAssignedTo = { + type: 'roleAssignedTo'; + roleId: string; +}; + type CondFormulaValueCreatedLessThan = { type: 'createdLessThan'; sec: number; @@ -75,6 +80,7 @@ export type RoleCondFormulaValue = { id: string } & ( CondFormulaValueNot | CondFormulaValueIsLocal | CondFormulaValueIsRemote | + CondFormulaValueRoleAssignedTo | CondFormulaValueCreatedLessThan | CondFormulaValueCreatedMoreThan | CondFormulaValueFollowersLessThanOrEq | diff --git a/packages/backend/src/models/UserGroup.ts b/packages/backend/src/models/UserGroup.ts index 96005898dd..69567c53c6 100644 --- a/packages/backend/src/models/UserGroup.ts +++ b/packages/backend/src/models/UserGroup.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/models/UserGroupInvitation.ts b/packages/backend/src/models/UserGroupInvitation.ts index 59b1aee163..8d53d63794 100644 --- a/packages/backend/src/models/UserGroupInvitation.ts +++ b/packages/backend/src/models/UserGroupInvitation.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/models/UserGroupJoining.ts b/packages/backend/src/models/UserGroupJoining.ts index 7cfb6dacb1..aa2c26a03d 100644 --- a/packages/backend/src/models/UserGroupJoining.ts +++ b/packages/backend/src/models/UserGroupJoining.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/models/UserProfile.ts b/packages/backend/src/models/UserProfile.ts index d8105784b3..dddd434c03 100644 --- a/packages/backend/src/models/UserProfile.ts +++ b/packages/backend/src/models/UserProfile.ts @@ -249,6 +249,8 @@ export class MiUserProfile { type: 'follower'; } | { type: 'mutualFollow'; + } | { + type: 'followingOrFollower'; } | { type: 'list'; userListId: MiUserList['id']; diff --git a/packages/backend/src/models/json-schema/antenna.ts b/packages/backend/src/models/json-schema/antenna.ts index 3997f858c9..6a1b0a782f 100644 --- a/packages/backend/src/models/json-schema/antenna.ts +++ b/packages/backend/src/models/json-schema/antenna.ts @@ -81,6 +81,11 @@ export const packedAntennaSchema = { type: 'boolean', optional: false, nullable: false, }, + excludeBots: { + type: 'boolean', + optional: false, nullable: false, + default: false, + }, withReplies: { type: 'boolean', optional: false, nullable: false, diff --git a/packages/backend/src/models/json-schema/clip.ts b/packages/backend/src/models/json-schema/clip.ts index ca4886c978..c4e7055cd8 100644 --- a/packages/backend/src/models/json-schema/clip.ts +++ b/packages/backend/src/models/json-schema/clip.ts @@ -52,5 +52,9 @@ export const packedClipSchema = { type: 'boolean', optional: true, nullable: false, }, + notesCount: { + type: 'integer', + optional: true, nullable: false, + }, }, } as const; diff --git a/packages/backend/src/models/json-schema/federation-instance.ts b/packages/backend/src/models/json-schema/federation-instance.ts index 0940d9e940..9aca997960 100644 --- a/packages/backend/src/models/json-schema/federation-instance.ts +++ b/packages/backend/src/models/json-schema/federation-instance.ts @@ -107,5 +107,9 @@ export const packedFederationInstanceSchema = { optional: false, nullable: true, format: 'date-time', }, + moderationNote: { + type: 'string', + optional: true, nullable: true, + }, }, } as const; diff --git a/packages/backend/src/models/json-schema/messaging-message.ts b/packages/backend/src/models/json-schema/messaging-message.ts index 74caa0ed76..b8cf33e3bd 100644 --- a/packages/backend/src/models/json-schema/messaging-message.ts +++ b/packages/backend/src/models/json-schema/messaging-message.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts new file mode 100644 index 0000000000..a4cb3ee99a --- /dev/null +++ b/packages/backend/src/models/json-schema/meta.ts @@ -0,0 +1,336 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export const packedMetaLiteSchema = { + type: 'object', + optional: false, nullable: false, + properties: { + maintainerName: { + type: 'string', + optional: false, nullable: true, + }, + maintainerEmail: { + type: 'string', + optional: false, nullable: true, + }, + version: { + type: 'string', + optional: false, nullable: false, + }, + basedMisskeyVersion: { + type: 'string', + optional: false, nullable: false, + }, + providesTarball: { + type: 'boolean', + optional: false, nullable: false, + }, + name: { + type: 'string', + optional: false, nullable: true, + }, + shortName: { + type: 'string', + optional: false, nullable: true, + }, + uri: { + type: 'string', + optional: false, nullable: false, + format: 'url', + example: 'https://cherrypick.example.com', + }, + description: { + type: 'string', + optional: false, nullable: true, + }, + langs: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + tosUrl: { + type: 'string', + optional: false, nullable: true, + }, + repositoryUrl: { + type: 'string', + optional: false, nullable: true, + default: 'https://github.com/kokonect-link/cherrypick', + }, + feedbackUrl: { + type: 'string', + optional: false, nullable: true, + default: 'https://github.com/kokonect-link/cherrypick/issues/new', + }, + defaultDarkTheme: { + type: 'string', + optional: false, nullable: true, + }, + defaultLightTheme: { + type: 'string', + optional: false, nullable: true, + }, + disableRegistration: { + type: 'boolean', + optional: false, nullable: false, + }, + emailRequiredForSignup: { + type: 'boolean', + optional: false, nullable: false, + }, + enableHcaptcha: { + type: 'boolean', + optional: false, nullable: false, + }, + hcaptchaSiteKey: { + type: 'string', + optional: false, nullable: true, + }, + enableMcaptcha: { + type: 'boolean', + optional: false, nullable: false, + }, + mcaptchaSiteKey: { + type: 'string', + optional: false, nullable: true, + }, + mcaptchaInstanceUrl: { + type: 'string', + optional: false, nullable: true, + }, + enableRecaptcha: { + type: 'boolean', + optional: false, nullable: false, + }, + recaptchaSiteKey: { + type: 'string', + optional: false, nullable: true, + }, + enableTurnstile: { + type: 'boolean', + optional: false, nullable: false, + }, + turnstileSiteKey: { + type: 'string', + optional: false, nullable: true, + }, + swPublickey: { + type: 'string', + optional: false, nullable: true, + }, + mascotImageUrl: { + type: 'string', + optional: false, nullable: false, + default: '/assets/ai.png', + }, + bannerUrl: { + type: 'string', + optional: false, nullable: true, + }, + serverErrorImageUrl: { + type: 'string', + optional: false, nullable: true, + }, + infoImageUrl: { + type: 'string', + optional: false, nullable: true, + }, + notFoundImageUrl: { + type: 'string', + optional: false, nullable: true, + }, + iconUrl: { + type: 'string', + optional: false, nullable: true, + }, + maxNoteTextLength: { + type: 'number', + optional: false, nullable: false, + }, + ads: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + url: { + type: 'string', + optional: false, nullable: false, + format: 'url', + }, + place: { + type: 'string', + optional: false, nullable: false, + }, + ratio: { + type: 'number', + optional: false, nullable: false, + }, + imageUrl: { + type: 'string', + optional: false, nullable: false, + format: 'url', + }, + dayOfWeek: { + type: 'integer', + optional: false, nullable: false, + }, + }, + }, + }, + notesPerOneAd: { + type: 'number', + optional: false, nullable: false, + default: 0, + }, + enableEmail: { + type: 'boolean', + optional: false, nullable: false, + }, + enableServiceWorker: { + type: 'boolean', + optional: false, nullable: false, + }, + translatorAvailable: { + type: 'boolean', + optional: false, nullable: false, + }, + mediaProxy: { + type: 'string', + optional: false, nullable: false, + }, + enableUrlPreview: { + type: 'boolean', + optional: false, nullable: false, + }, + backgroundImageUrl: { + type: 'string', + optional: false, nullable: true, + }, + impressumUrl: { + type: 'string', + optional: false, nullable: true, + }, + logoImageUrl: { + type: 'string', + optional: false, nullable: true, + }, + privacyPolicyUrl: { + type: 'string', + optional: false, nullable: true, + }, + serverRules: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + }, + }, + themeColor: { + type: 'string', + optional: false, nullable: true, + }, + policies: { + type: 'object', + optional: false, nullable: false, + ref: 'RolePolicies', + }, + }, +} as const; + +export const packedMetaDetailedOnlySchema = { + type: 'object', + optional: false, nullable: false, + properties: { + features: { + type: 'object', + optional: true, nullable: false, + properties: { + registration: { + type: 'boolean', + optional: false, nullable: false, + }, + emailRequiredForSignup: { + type: 'boolean', + optional: false, nullable: false, + }, + localTimeline: { + type: 'boolean', + optional: false, nullable: false, + }, + globalTimeline: { + type: 'boolean', + optional: false, nullable: false, + }, + hcaptcha: { + type: 'boolean', + optional: false, nullable: false, + }, + turnstile: { + type: 'boolean', + optional: false, nullable: false, + }, + recaptcha: { + type: 'boolean', + optional: false, nullable: false, + }, + objectStorage: { + type: 'boolean', + optional: false, nullable: false, + }, + serviceWorker: { + type: 'boolean', + optional: false, nullable: false, + }, + miauth: { + type: 'boolean', + optional: true, nullable: false, + default: true, + }, + }, + }, + proxyAccountName: { + type: 'string', + optional: false, nullable: true, + }, + requireSetup: { + type: 'boolean', + optional: false, nullable: false, + example: false, + }, + cacheRemoteFiles: { + type: 'boolean', + optional: false, nullable: false, + }, + cacheRemoteSensitiveFiles: { + type: 'boolean', + optional: false, nullable: false, + }, + }, +} as const; + +export const packedMetaDetailedSchema = { + type: 'object', + allOf: [ + { + type: 'object', + ref: 'MetaLite', + }, + { + type: 'object', + ref: 'MetaDetailedOnly', + }, + ], +} as const; diff --git a/packages/backend/src/models/json-schema/note.ts b/packages/backend/src/models/json-schema/note.ts index 00c2eac310..4fe44ce0b6 100644 --- a/packages/backend/src/models/json-schema/note.ts +++ b/packages/backend/src/models/json-schema/note.ts @@ -249,6 +249,10 @@ export const packedNoteSchema = { }], }, }, + reactionCount: { + type: 'number', + optional: false, nullable: false, + }, renoteCount: { type: 'number', optional: false, nullable: false, diff --git a/packages/backend/src/models/json-schema/notification.ts b/packages/backend/src/models/json-schema/notification.ts index b4c4442758..052eb84bf7 100644 --- a/packages/backend/src/models/json-schema/notification.ts +++ b/packages/backend/src/models/json-schema/notification.ts @@ -386,5 +386,20 @@ export const packedNotificationSchema = { enum: ['test'], }, }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['groupInvited'], + }, + invitation: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, }], } as const; diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts index ef6b279bee..dfa941b29b 100644 --- a/packages/backend/src/models/json-schema/role.ts +++ b/packages/backend/src/models/json-schema/role.ts @@ -57,6 +57,26 @@ export const packedRoleCondFormulaValueIsLocalOrRemoteSchema = { }, } as const; +export const packedRoleCondFormulaValueAssignedRoleSchema = { + type: 'object', + properties: { + id: { + type: 'string', optional: false, + }, + type: { + type: 'string', + nullable: false, optional: false, + enum: ['roleAssignedTo'], + }, + roleId: { + type: 'string', + nullable: false, optional: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + }, +} as const; + export const packedRoleCondFormulaValueCreatedSchema = { type: 'object', properties: { @@ -115,6 +135,9 @@ export const packedRoleCondFormulaValueSchema = { { ref: 'RoleCondFormulaValueIsLocalOrRemote', }, + { + ref: 'RoleCondFormulaValueAssignedRole', + }, { ref: 'RoleCondFormulaValueCreated', }, @@ -140,6 +163,10 @@ export const packedRolePoliciesSchema = { type: 'boolean', optional: false, nullable: false, }, + mentionLimit: { + type: 'integer', + optional: false, nullable: false, + }, canInvite: { type: 'boolean', optional: false, nullable: false, @@ -224,6 +251,10 @@ export const packedRolePoliciesSchema = { type: 'integer', optional: false, nullable: false, }, + canEditNote: { + type: 'boolean', + optional: false, nullable: false, + }, }, } as const; diff --git a/packages/backend/src/models/json-schema/signin.ts b/packages/backend/src/models/json-schema/signin.ts index d27d2490c5..45732a742b 100644 --- a/packages/backend/src/models/json-schema/signin.ts +++ b/packages/backend/src/models/json-schema/signin.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + export const packedSigninSchema = { type: 'object', properties: { diff --git a/packages/backend/src/models/json-schema/user-group.ts b/packages/backend/src/models/json-schema/user-group.ts index 63eeeb59d3..f0e81928db 100644 --- a/packages/backend/src/models/json-schema/user-group.ts +++ b/packages/backend/src/models/json-schema/user-group.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 85d111b23f..b8efacd1a8 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -13,7 +13,7 @@ export const notificationRecieveConfig = { type: { type: 'string', nullable: false, - enum: ['all', 'following', 'follower', 'mutualFollow', 'never'], + enum: ['all', 'following', 'follower', 'mutualFollow', 'followingOrFollower', 'never'], }, }, required: ['type'], @@ -156,6 +156,9 @@ export const packedUserLiteSchema = { emojis: { type: 'object', nullable: false, optional: false, + additionalProperties: { + type: 'string', + }, }, onlineStatus: { type: 'string', diff --git a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts index 936c7d4e32..1c1739a7ba 100644 --- a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts @@ -63,7 +63,7 @@ export class CleanRemoteFilesProcessorService { isLink: false, }); - job.updateProgress(deletedCount / total); + job.updateProgress(100 / total * deletedCount); } this.logger.succ('All cached remote files has been deleted.'); diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts index af48bad417..1d8e90f367 100644 --- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts @@ -81,6 +81,7 @@ export class ExportAntennasProcessorService { }) : null, caseSensitive: antenna.caseSensitive, localOnly: antenna.localOnly, + excludeBots: antenna.excludeBots, withReplies: antenna.withReplies, withFile: antenna.withFile, notify: antenna.notify, diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts index f2ae0ce4b4..7a10ea3a50 100644 --- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import * as fs from 'node:fs'; +import { ReadableStream, TextEncoderStream } from 'node:stream/web'; import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { format as dateFormat } from 'date-fns'; @@ -18,10 +18,85 @@ import { bindThis } from '@/decorators.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { Packed } from '@/misc/json-schema.js'; import { IdService } from '@/core/IdService.js'; +import { JsonArrayStream } from '@/misc/JsonArrayStream.js'; +import { FileWriterStream } from '@/misc/FileWriterStream.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; +class NoteStream extends ReadableStream> { + constructor( + job: Bull.Job, + notesRepository: NotesRepository, + pollsRepository: PollsRepository, + driveFileEntityService: DriveFileEntityService, + idService: IdService, + userId: string, + ) { + let exportedNotesCount = 0; + let cursor: MiNote['id'] | null = null; + + const serialize = ( + note: MiNote, + poll: MiPoll | null, + files: Packed<'DriveFile'>[], + ): Record => { + return { + id: note.id, + text: note.text, + createdAt: idService.parse(note.id).date.toISOString(), + updatedAt: note.updatedAt?.toISOString(), + updatedAtHistory: note.updatedAtHistory?.map(x => x.toISOString()), + noteEditHistory: note.noteEditHistory, + fileIds: note.fileIds, + files: files, + replyId: note.replyId, + renoteId: note.renoteId, + poll: poll, + cw: note.cw, + visibility: note.visibility, + visibleUserIds: note.visibleUserIds, + localOnly: note.localOnly, + reactionAcceptance: note.reactionAcceptance, + }; + }; + + super({ + async pull(controller): Promise { + const notes = await notesRepository.find({ + where: { + userId, + ...(cursor !== null ? { id: MoreThan(cursor) } : {}), + }, + take: 100, // 100件ずつ取得 + order: { id: 1 }, + }); + + if (notes.length === 0) { + job.updateProgress(100); + controller.close(); + } + + cursor = notes.at(-1)?.id ?? null; + + for (const note of notes) { + const poll = note.hasPoll + ? await pollsRepository.findOneByOrFail({ noteId: note.id }) // N+1 + : null; + const files = await driveFileEntityService.packManyByIds(note.fileIds); // N+1 + const content = serialize(note, poll, files); + + controller.enqueue(content); + exportedNotesCount++; + } + + const total = await notesRepository.countBy({ userId }); + job.updateProgress(exportedNotesCount / total); + }, + }); + } +} + @Injectable() export class ExportNotesProcessorService { private logger: Logger; @@ -59,67 +134,19 @@ export class ExportNotesProcessorService { this.logger.info(`Temp file is ${path}`); try { - const stream = fs.createWriteStream(path, { flags: 'a' }); - - const write = (text: string): Promise => { - return new Promise((res, rej) => { - stream.write(text, err => { - if (err) { - this.logger.error(err); - rej(err); - } else { - res(); - } - }); - }); - }; - - await write('['); - - let exportedNotesCount = 0; - let cursor: MiNote['id'] | null = null; - - while (true) { - const notes = await this.notesRepository.find({ - where: { - userId: user.id, - ...(cursor ? { id: MoreThan(cursor) } : {}), - }, - take: 100, - order: { - id: 1, - }, - }) as MiNote[]; - - if (notes.length === 0) { - job.updateProgress(100); - break; - } - - cursor = notes.at(-1)?.id ?? null; + // メモリが足りなくならないようにストリームで処理する + await new NoteStream( + job, + this.notesRepository, + this.pollsRepository, + this.driveFileEntityService, + this.idService, + user.id, + ) + .pipeThrough(new JsonArrayStream()) + .pipeThrough(new TextEncoderStream()) + .pipeTo(new FileWriterStream(path)); - for (const note of notes) { - let poll: MiPoll | undefined; - if (note.hasPoll) { - poll = await this.pollsRepository.findOneByOrFail({ noteId: note.id }); - } - const files = await this.driveFileEntityService.packManyByIds(note.fileIds); - const content = JSON.stringify(this.serialize(note, poll, files)); - const isFirst = exportedNotesCount === 0; - await write(isFirst ? content : ',\n' + content); - exportedNotesCount++; - } - - const total = await this.notesRepository.countBy({ - userId: user.id, - }); - - job.updateProgress(exportedNotesCount / total); - } - - await write(']'); - - stream.end(); this.logger.succ(`Exported to: ${path}`); const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json'; @@ -130,22 +157,4 @@ export class ExportNotesProcessorService { cleanup(); } } - - private serialize(note: MiNote, poll: MiPoll | null = null, files: Packed<'DriveFile'>[]): Record { - return { - id: note.id, - text: note.text, - createdAt: this.idService.parse(note.id).date.toISOString(), - fileIds: note.fileIds, - files: files, - replyId: note.replyId, - renoteId: note.renoteId, - poll: poll, - cw: note.cw, - visibility: note.visibility, - visibleUserIds: note.visibleUserIds, - localOnly: note.localOnly, - reactionAcceptance: note.reactionAcceptance, - }; - } } diff --git a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts index 951b560597..ff1c04de06 100644 --- a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts @@ -44,6 +44,7 @@ const validate = new Ajv().compile({ } }, caseSensitive: { type: 'boolean' }, localOnly: { type: 'boolean' }, + excludeBots: { type: 'boolean' }, withReplies: { type: 'boolean' }, withFile: { type: 'boolean' }, notify: { type: 'boolean' }, @@ -88,6 +89,7 @@ export class ImportAntennasProcessorService { users: (antenna.src === 'list' && antenna.userListAccts !== null ? antenna.userListAccts : antenna.users).filter(Boolean), caseSensitive: antenna.caseSensitive, localOnly: antenna.localOnly, + excludeBots: antenna.excludeBots, withReplies: antenna.withReplies, withFile: antenna.withFile, notify: antenna.notify, diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 7adadd799b..3addead058 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -24,6 +24,7 @@ import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; import { LdSignatureService } from '@/core/activitypub/LdSignatureService.js'; import { ApInboxService } from '@/core/activitypub/ApInboxService.js'; import { bindThis } from '@/decorators.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type { InboxJobData } from '../types.js'; @@ -180,7 +181,17 @@ export class InboxProcessorService { }); // アクティビティを処理 - await this.apInboxService.performActivity(authUser.user, activity); + try { + await this.apInboxService.performActivity(authUser.user, activity); + } catch (e) { + if (e instanceof IdentifiableError) { + if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') { + return 'blocked notes with prohibited words'; + } + if (e.id === '85ab9bd7-3a41-4530-959d-f07073900109') return 'actor has been suspended'; + } + throw e; + } return 'ok'; } } diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index c7a4483a6c..365207b34d 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -28,7 +28,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; import { IActivity } from '@/core/activitypub/type.js'; -import { isPureRenote } from '@/misc/is-pure-renote.js'; +import { isQuote, isRenote } from '@/misc/is-renote.js'; import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify'; import type { FindOptionsWhere } from 'typeorm'; @@ -91,7 +91,7 @@ export class ActivityPubServerService { */ @bindThis private async packActivity(note: MiNote): Promise { - if (isPureRenote(note)) { + if (isRenote(note) && !isQuote(note)) { const renote = await this.notesRepository.findOneByOrFail({ id: note.renoteId }); return this.apRendererService.renderAnnounce(renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`, note); } diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index f51d7aebca..9db3aa1bfb 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -194,6 +194,7 @@ export class FileServerService { reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`); reply.header('Accept-Ranges', 'bytes'); reply.header('Content-Length', chunksize); + reply.code(206); } else { image = { data: fs.createReadStream(file.path), @@ -213,6 +214,8 @@ export class FileServerService { } reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream'); + reply.header('Content-Length', file.file.size); + reply.header('Cache-Control', 'max-age=31536000, immutable'); reply.header('Content-Disposition', contentDisposition( 'inline', @@ -255,6 +258,7 @@ export class FileServerService { return fs.createReadStream(file.path); } else { reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.file.type) ? file.file.type : 'application/octet-stream'); + reply.header('Content-Length', file.file.size); reply.header('Cache-Control', 'max-age=31536000, immutable'); reply.header('Content-Disposition', contentDisposition('inline', file.filename)); @@ -263,7 +267,6 @@ export class FileServerService { const parts = range.replace(/bytes=/, '').split('-'); const start = parseInt(parts[0], 10); let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1; - console.log(end); if (end > file.file.size) { end = file.file.size - 1; } @@ -433,6 +436,7 @@ export class FileServerService { reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`); reply.header('Accept-Ranges', 'bytes'); reply.header('Content-Length', chunksize); + reply.code(206); } else { image = { data: fs.createReadStream(file.path), @@ -529,6 +533,7 @@ export class FileServerService { if (!file.storedInternal) { if (!(file.isLink && file.uri)) return '204'; const result = await this.downloadAndDetectTypeFromUrl(file.uri); + file.size = (await fs.promises.stat(result.path)).size; // DB file.sizeは正確とは限らないので return { ...result, url: file.uri, diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 9bc3506135..7a3da56a2c 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -310,6 +310,7 @@ import * as ep___notes_translate from './endpoints/notes/translate.js'; import * as ep___notes_unrenote from './endpoints/notes/unrenote.js'; import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js'; import * as ep___notifications_create from './endpoints/notifications/create.js'; +import * as ep___notifications_flush from './endpoints/notifications/flush.js'; import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js'; import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js'; import * as ep___pagePush from './endpoints/page-push.js'; @@ -712,6 +713,7 @@ const $notes_translate: Provider = { provide: 'ep:notes/translate', useClass: ep const $notes_unrenote: Provider = { provide: 'ep:notes/unrenote', useClass: ep___notes_unrenote.default }; const $notes_userListTimeline: Provider = { provide: 'ep:notes/user-list-timeline', useClass: ep___notes_userListTimeline.default }; const $notifications_create: Provider = { provide: 'ep:notifications/create', useClass: ep___notifications_create.default }; +const $notifications_flush: Provider = { provide: 'ep:notifications/flush', useClass: ep___notifications_flush.default }; const $notifications_markAllAsRead: Provider = { provide: 'ep:notifications/mark-all-as-read', useClass: ep___notifications_markAllAsRead.default }; const $notifications_testNotification: Provider = { provide: 'ep:notifications/test-notification', useClass: ep___notifications_testNotification.default }; const $pagePush: Provider = { provide: 'ep:page-push', useClass: ep___pagePush.default }; @@ -1119,6 +1121,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $notes_unrenote, $notes_userListTimeline, $notifications_create, + $notifications_flush, $notifications_markAllAsRead, $notifications_testNotification, $pagePush, @@ -1518,7 +1521,9 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $notes_unrenote, $notes_userListTimeline, $notifications_create, + $notifications_flush, $notifications_markAllAsRead, + $notifications_testNotification, $pagePush, $pages_create, $pages_delete, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index ff75ec81b9..101ae67f5e 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -309,6 +309,7 @@ import * as ep___notes_translate from './endpoints/notes/translate.js'; import * as ep___notes_unrenote from './endpoints/notes/unrenote.js'; import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js'; import * as ep___notifications_create from './endpoints/notifications/create.js'; +import * as ep___notifications_flush from './endpoints/notifications/flush.js'; import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js'; import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js'; import * as ep___pagePush from './endpoints/page-push.js'; @@ -709,6 +710,7 @@ const eps = [ ['notes/unrenote', ep___notes_unrenote], ['notes/user-list-timeline', ep___notes_userListTimeline], ['notifications/create', ep___notifications_create], + ['notifications/flush', ep___notifications_flush], ['notifications/mark-all-as-read', ep___notifications_markAllAsRead], ['notifications/test-notification', ep___notifications_testNotification], ['page-push', ep___pagePush], diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/create.ts b/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/create.ts index 8530d69480..d0d398838c 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/create.ts @@ -13,41 +13,45 @@ import { IdService } from '@/core/IdService.js'; export const meta = { tags: ['admin'], - requireCredential: true, - + secure: true, requireAdmin: true, - + kind: 'arr-create', // ここにkindプロパティを追加 res: { type: 'object', properties: { name: { type: 'string', - nullable: false, optional: false, + nullable: false, + optional: false, }, targetUserPattern: { type: 'string', - nullable: true, optional: false, + nullable: true, + optional: false, }, reporterPattern: { type: 'string', - nullable: true, optional: false, + nullable: true, + optional: false, }, reportContentPattern: { type: 'string', - nullable: true, optional: false, + nullable: true, + optional: false, }, expiresAt: { type: 'string', - nullable: false, optional: false, + nullable: false, + optional: false, }, forward: { type: 'boolean', - nullable: false, optional: false, + nullable: false, + optional: false, }, }, }, - errors: { invalidRegularExpressionForTargetUser: { message: 'Invalid regular expression for target user.', diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/delete.ts b/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/delete.ts index eedd2cfe1c..ea32124320 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/delete.ts @@ -11,8 +11,9 @@ import { ApiError } from '../../../error.js'; export const meta = { requireCrendential: true, - + kind: 'arr-delete', // ここにkindプロパティを追加 requireAdmin: true, + secure: true, errors: { resolverNotFound: { diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/list.ts b/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/list.ts index defe1319d8..a5a6f3c54c 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/list.ts @@ -12,7 +12,8 @@ import type { AbuseReportResolversRepository } from '@/models/_.js'; export const meta = { requireCredential: true, - + kind: 'arr-list', // ここにkindプロパティを追加 + secure: true, requireAdmin: true, res: { diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/update.ts b/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/update.ts index 355e688dfe..d0cd055444 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/update.ts @@ -12,7 +12,8 @@ import { ApiError } from '../../../error.js'; export const meta = { requireCredential: true, - + kind: 'arr-update', // ここにkindプロパティを追加 + secure: true, requireAdmin: true, errors: { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts index e32a19120d..796f273330 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -31,7 +31,10 @@ export const meta = { }, }, - ref: 'EmojiDetailed', + res: { + type: 'object', + ref: 'EmojiDetailed', + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/adds.ts b/packages/backend/src/server/api/endpoints/admin/emoji/adds.ts index b7e38e3ff1..4ca78e16d9 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/adds.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/adds.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ @@ -32,7 +32,10 @@ export const meta = { }, }, - ref: 'EmojiDetailed', + res: { + type: 'object', + ref: 'EmojiDetailed', + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts index a9ff4236d2..22609a16a3 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -57,7 +57,10 @@ export const paramDef = { type: 'string', } }, }, - required: ['id', 'name', 'aliases'], + anyOf: [ + { required: ['id'] }, + { required: ['name'] }, + ], } as const; @Injectable() @@ -70,27 +73,33 @@ export default class extends Endpoint { // eslint- ) { super(meta, paramDef, async (ps, me) => { let driveFile; - if (ps.fileId) { driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); if (driveFile == null) throw new ApiError(meta.errors.noSuchFile); } - const emoji = await this.customEmojiService.getEmojiById(ps.id); - if (emoji != null) { - if (ps.name !== emoji.name) { + + let emojiId; + if (ps.id) { + emojiId = ps.id; + const emoji = await this.customEmojiService.getEmojiById(ps.id); + if (!emoji) throw new ApiError(meta.errors.noSuchEmoji); + if (ps.name && (ps.name !== emoji.name)) { const isDuplicate = await this.customEmojiService.checkDuplicate(ps.name); if (isDuplicate) throw new ApiError(meta.errors.sameNameEmojiExists); } } else { - throw new ApiError(meta.errors.noSuchEmoji); + if (!ps.name) throw new Error('Invalid Params unexpectedly passed. This is a BUG. Please report it to the development team.'); + const emoji = await this.customEmojiService.getEmojiByName(ps.name); + if (!emoji) throw new ApiError(meta.errors.noSuchEmoji); + emojiId = emoji.id; } - await this.customEmojiService.update(ps.id, { + await this.customEmojiService.update(emojiId, { driveFile, name: ps.name, - category: ps.category ?? null, + category: ps.category, aliases: ps.aliases, - license: ps.license ?? null, + license: ps.license, isSensitive: ps.isSensitive, localOnly: ps.localOnly, roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction, diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts index b989b99e47..0bcdc2a4b8 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts @@ -24,8 +24,9 @@ export const paramDef = { properties: { host: { type: 'string' }, isSuspended: { type: 'boolean' }, + moderationNote: { type: 'string' }, }, - required: ['host', 'isSuspended'], + required: ['host'], } as const; @Injectable() @@ -47,9 +48,10 @@ export default class extends Endpoint { // eslint- await this.federatedInstanceService.update(instance.id, { isSuspended: ps.isSuspended, + moderationNote: ps.moderationNote, }); - if (instance.isSuspended !== ps.isSuspended) { + if (ps.isSuspended != null && instance.isSuspended !== ps.isSuspended) { if (ps.isSuspended) { this.moderationLogService.log(me, 'suspendRemoteInstance', { id: instance.id, @@ -62,6 +64,15 @@ export default class extends Endpoint { // eslint- }); } } + + if (ps.moderationNote != null && instance.moderationNote !== ps.moderationNote) { + this.moderationLogService.log(me, 'updateRemoteInstanceNote', { + id: instance.id, + host: instance.host, + before: instance.moderationNote, + after: ps.moderationNote, + }); + } }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/invite/revoke.ts b/packages/backend/src/server/api/endpoints/admin/invite/revoke.ts index 83ee1d11bf..c7db1eae65 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite/revoke.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite/revoke.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 33959ecd2a..aaddc5775d 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -490,6 +490,8 @@ export const meta = { summalyProxy: { type: 'string', optional: false, nullable: true, + deprecated: true, + description: '[Deprecated] Use "urlPreviewSummaryProxyUrl" instead.', }, themeColor: { type: 'string', @@ -507,6 +509,30 @@ export const meta = { type: 'string', optional: false, nullable: false, }, + urlPreviewEnabled: { + type: 'boolean', + optional: false, nullable: false, + }, + urlPreviewTimeout: { + type: 'number', + optional: false, nullable: false, + }, + urlPreviewMaximumContentLength: { + type: 'number', + optional: false, nullable: false, + }, + urlPreviewRequireContentLength: { + type: 'boolean', + optional: false, nullable: false, + }, + urlPreviewUserAgent: { + type: 'string', + optional: false, nullable: true, + }, + urlPreviewSummaryProxyUrl: { + type: 'string', + optional: false, nullable: true, + }, doNotSendNotificationEmailsForAbuseReport: { type: 'boolean', optional: false, nullable: false, @@ -614,7 +640,6 @@ export default class extends Endpoint { // eslint- setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically, enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos, proxyAccountId: instance.proxyAccountId, - summalyProxy: instance.summalyProxy, email: instance.email, smtpSecure: instance.smtpSecure, smtpHost: instance.smtpHost, @@ -676,6 +701,13 @@ export default class extends Endpoint { // eslint- perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax, perUserListTimelineCacheMax: instance.perUserListTimelineCacheMax, notesPerOneAd: instance.notesPerOneAd, + summalyProxy: instance.urlPreviewSummaryProxyUrl, + urlPreviewEnabled: instance.urlPreviewEnabled, + urlPreviewTimeout: instance.urlPreviewTimeout, + urlPreviewMaximumContentLength: instance.urlPreviewMaximumContentLength, + urlPreviewRequireContentLength: instance.urlPreviewRequireContentLength, + urlPreviewUserAgent: instance.urlPreviewUserAgent, + urlPreviewSummaryProxyUrl: instance.urlPreviewSummaryProxyUrl, doNotSendNotificationEmailsForAbuseReport: instance.doNotSendNotificationEmailsForAbuseReport, emailToReceiveAbuseReport: instance.emailToReceiveAbuseReport, enableReceivePrerelease: instance.enableReceivePrerelease, 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 5a1c05f41a..292fc2e436 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -101,6 +101,7 @@ export const meta = { pollEnded: { optional: true, ...notificationRecieveConfig }, receiveFollowRequest: { optional: true, ...notificationRecieveConfig }, followRequestAccepted: { optional: true, ...notificationRecieveConfig }, + groupInvited: { optional: true, ...notificationRecieveConfig }, roleAssigned: { optional: true, ...notificationRecieveConfig }, achievementEarned: { optional: true, ...notificationRecieveConfig }, app: { optional: true, ...notificationRecieveConfig }, 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 ce173ea47d..5d36710017 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -92,7 +92,6 @@ export const paramDef = { type: 'string', }, }, - summalyProxy: { type: 'string', nullable: true }, translatorType: { type: 'string', nullable: true }, deeplAuthKey: { type: 'string', nullable: true }, deeplIsPro: { type: 'boolean' }, @@ -171,6 +170,16 @@ export const paramDef = { type: 'string', }, }, + summalyProxy: { + type: 'string', nullable: true, + description: '[Deprecated] Use "urlPreviewSummaryProxyUrl" instead.', + }, + urlPreviewEnabled: { type: 'boolean' }, + urlPreviewTimeout: { type: 'integer' }, + urlPreviewMaximumContentLength: { type: 'integer' }, + urlPreviewRequireContentLength: { type: 'boolean' }, + urlPreviewUserAgent: { type: 'string', nullable: true }, + urlPreviewSummaryProxyUrl: { type: 'string', nullable: true }, doNotSendNotificationEmailsForAbuseReport: { type: 'boolean' }, emailToReceiveAbuseReport: { type: 'string', nullable: true }, enableReceivePrerelease: { type: 'boolean' }, @@ -380,10 +389,6 @@ export default class extends Endpoint { // eslint- set.langs = ps.langs.filter(Boolean); } - if (ps.summalyProxy !== undefined) { - set.summalyProxy = ps.summalyProxy; - } - if (ps.enableEmail !== undefined) { set.enableEmail = ps.enableEmail; } @@ -688,6 +693,32 @@ export default class extends Endpoint { // eslint- set.bannedEmailDomains = ps.bannedEmailDomains; } + if (ps.urlPreviewEnabled !== undefined) { + set.urlPreviewEnabled = ps.urlPreviewEnabled; + } + + if (ps.urlPreviewTimeout !== undefined) { + set.urlPreviewTimeout = ps.urlPreviewTimeout; + } + + if (ps.urlPreviewMaximumContentLength !== undefined) { + set.urlPreviewMaximumContentLength = ps.urlPreviewMaximumContentLength; + } + + if (ps.urlPreviewRequireContentLength !== undefined) { + set.urlPreviewRequireContentLength = ps.urlPreviewRequireContentLength; + } + + if (ps.urlPreviewUserAgent !== undefined) { + const value = (ps.urlPreviewUserAgent ?? '').trim(); + set.urlPreviewUserAgent = value === '' ? null : ps.urlPreviewUserAgent; + } + + if (ps.summalyProxy !== undefined || ps.urlPreviewSummaryProxyUrl !== undefined) { + const value = ((ps.urlPreviewSummaryProxyUrl ?? ps.summalyProxy) ?? '').trim(); + set.urlPreviewSummaryProxyUrl = value === '' ? null : value; + } + if (ps.doNotSendNotificationEmailsForAbuseReport !== undefined) { set.doNotSendNotificationEmailsForAbuseReport = ps.doNotSendNotificationEmailsForAbuseReport; } diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index 3669619833..6002e4e26b 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -71,6 +71,7 @@ export const paramDef = { } }, caseSensitive: { type: 'boolean' }, localOnly: { type: 'boolean' }, + excludeBots: { type: 'boolean' }, withReplies: { type: 'boolean' }, withFile: { type: 'boolean' }, notify: { type: 'boolean' }, @@ -145,6 +146,7 @@ export default class extends Endpoint { // eslint- users: ps.users, caseSensitive: ps.caseSensitive, localOnly: ps.localOnly, + excludeBots: ps.excludeBots, withReplies: ps.withReplies, withFile: ps.withFile, notify: ps.notify, diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index 39f3fab21e..f4dfe1ecc4 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -124,9 +124,7 @@ export default class extends Endpoint { // eslint- notes.sort((a, b) => a.id > b.id ? -1 : 1); } - if (notes.length > 0) { - this.noteReadService.read(me.id, notes); - } + this.noteReadService.read(me.id, notes); return await this.noteEntityService.packMany(notes, me); }); diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index 66140d3d23..40ba4d38d7 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -70,11 +70,12 @@ export const paramDef = { } }, caseSensitive: { type: 'boolean' }, localOnly: { type: 'boolean' }, + excludeBots: { type: 'boolean' }, withReplies: { type: 'boolean' }, withFile: { type: 'boolean' }, notify: { type: 'boolean' }, }, - required: ['antennaId', 'name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile', 'notify'], + required: ['antennaId'], } as const; @Injectable() @@ -93,8 +94,10 @@ export default class extends Endpoint { // eslint- private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps, me) => { - if (ps.keywords.flat().every(x => x === '') && ps.excludeKeywords.flat().every(x => x === '')) { - throw new Error('either keywords or excludeKeywords is required.'); + if (ps.keywords && ps.excludeKeywords) { + if (ps.keywords.flat().every(x => x === '') && ps.excludeKeywords.flat().every(x => x === '')) { + throw new Error('either keywords or excludeKeywords is required.'); + } } // Fetch the antenna const antenna = await this.antennasRepository.findOneBy({ @@ -109,7 +112,7 @@ export default class extends Endpoint { // eslint- let userList; let userGroupJoining; - if (ps.src === 'list' && ps.userListId) { + if ((ps.src === 'list' || antenna.src === 'list') && ps.userListId) { userList = await this.userListsRepository.findOneBy({ id: ps.userListId, userId: me.id, @@ -132,13 +135,14 @@ export default class extends Endpoint { // eslint- await this.antennasRepository.update(antenna.id, { name: ps.name, src: ps.src, - userListId: userList ? userList.id : null, + userListId: ps.userListId !== undefined ? userList ? userList.id : null : undefined, userGroupJoiningId: userGroupJoining ? userGroupJoining.id : null, keywords: ps.keywords, excludeKeywords: ps.excludeKeywords, users: ps.users, caseSensitive: ps.caseSensitive, localOnly: ps.localOnly, + excludeBots: ps.excludeBots, withReplies: ps.withReplies, withFile: ps.withFile, notify: ps.notify, diff --git a/packages/backend/src/server/api/endpoints/federation/show-instance.ts b/packages/backend/src/server/api/endpoints/federation/show-instance.ts index e3c598d110..2972861a4b 100644 --- a/packages/backend/src/server/api/endpoints/federation/show-instance.ts +++ b/packages/backend/src/server/api/endpoints/federation/show-instance.ts @@ -43,7 +43,7 @@ export default class extends Endpoint { // eslint- const instance = await this.instancesRepository .findOneBy({ host: this.utilityService.toPuny(ps.host) }); - return instance ? await this.instanceEntityService.pack(instance) : null; + return instance ? await this.instanceEntityService.pack(instance, me) : null; }); } } diff --git a/packages/backend/src/server/api/endpoints/flash/create.ts b/packages/backend/src/server/api/endpoints/flash/create.ts index 584d167a29..361496e17e 100644 --- a/packages/backend/src/server/api/endpoints/flash/create.ts +++ b/packages/backend/src/server/api/endpoints/flash/create.ts @@ -44,6 +44,7 @@ export const paramDef = { permissions: { type: 'array', items: { type: 'string', } }, + visibility: { type: 'string', enum: ['public', 'private'], default: 'public' }, }, required: ['title', 'summary', 'script', 'permissions'], } as const; @@ -66,6 +67,7 @@ export default class extends Endpoint { // eslint- summary: ps.summary, script: ps.script, permissions: ps.permissions, + visibility: ps.visibility, }).then(x => this.flashsRepository.findOneByOrFail(x.identifiers[0])); return await this.flashEntityService.pack(flash); diff --git a/packages/backend/src/server/api/endpoints/flash/update.ts b/packages/backend/src/server/api/endpoints/flash/update.ts index 7d7633daa5..8696c6f6e8 100644 --- a/packages/backend/src/server/api/endpoints/flash/update.ts +++ b/packages/backend/src/server/api/endpoints/flash/update.ts @@ -51,7 +51,7 @@ export const paramDef = { } }, visibility: { type: 'string', enum: ['public', 'private'] }, }, - required: ['flashId', 'title', 'summary', 'script', 'permissions'], + required: ['flashId'], } as const; @Injectable() @@ -71,11 +71,11 @@ export default class extends Endpoint { // eslint- await this.flashsRepository.update(flash.id, { updatedAt: new Date(), - title: ps.title, - summary: ps.summary, - script: ps.script, - permissions: ps.permissions, - visibility: ps.visibility, + ...Object.fromEntries( + Object.entries(ps).filter( + ([key, val]) => (key !== 'flashId') && Object.hasOwn(paramDef.properties, key), + ), + ), }); }); } diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index 590fab013f..db320e7129 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -100,22 +100,11 @@ export default class extends Endpoint { // eslint- throw err; }); - // Check if already following - const exist = await this.followingsRepository.exists({ - where: { - followerId: follower.id, - followeeId: followee.id, - }, - }); - - if (exist) { - throw new ApiError(meta.errors.alreadyFollowing); - } - try { await this.userFollowingService.follow(follower, followee, { withReplies: ps.withReplies }); } catch (e) { if (e instanceof IdentifiableError) { + if (e.id === 'ec3f65c0-a9d1-47d9-8791-b2e7b9dcdced') throw new ApiError(meta.errors.alreadyFollowing); if (e.id === '710e8fb0-b8c3-4922-be49-d5d93d8e6a6e') throw new ApiError(meta.errors.blocking); if (e.id === '3338392a-f764-498d-8855-db939dcf8c48') throw new ApiError(meta.errors.blocked); } diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts index 784ae5088f..b07cdf1ed9 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -12,6 +12,7 @@ import type { MiDriveFile } from '@/models/DriveFile.js'; import { IdService } from '@/core/IdService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; +import { isNotNull } from '@/misc/is-not-null.js'; export const meta = { tags: ['gallery'], @@ -69,7 +70,7 @@ export default class extends Endpoint { // eslint- id: fileId, userId: me.id, }), - ))).filter((file): file is MiDriveFile => file != null); + ))).filter(isNotNull); if (files.length === 0) { throw new Error(); diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts index 8872b261dd..8bd83ff5ba 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts @@ -10,6 +10,7 @@ import type { DriveFilesRepository, GalleryPostsRepository } from '@/models/_.js import type { MiDriveFile } from '@/models/DriveFile.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; +import { isNotNull } from '@/misc/is-not-null.js'; export const meta = { tags: ['gallery'], @@ -67,7 +68,7 @@ export default class extends Endpoint { // eslint- id: fileId, userId: me.id, }), - ))).filter((file): file is MiDriveFile => file != null); + ))).filter(isNotNull); if (files.length === 0) { throw new Error(); diff --git a/packages/backend/src/server/api/endpoints/hashtags/search.ts b/packages/backend/src/server/api/endpoints/hashtags/search.ts index 12d47fa512..d4eb851054 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/search.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/search.ts @@ -43,7 +43,7 @@ export default class extends Endpoint { // eslint- super(meta, paramDef, async (ps, me) => { const hashtags = await this.hashtagsRepository.createQueryBuilder('tag') .where('tag.name like :q', { q: sqlLikeEscape(ps.query.toLowerCase()) + '%' }) - .orderBy('tag.count', 'DESC') + .orderBy('tag.mentionedLocalUsersCount', 'DESC') .groupBy('tag.id') .limit(ps.limit) .offset(ps.offset) diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts index 8ddbe5663e..2606108539 100644 --- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts @@ -75,7 +75,7 @@ export default class extends Endpoint { // eslint- const checkMoving = await this.accountMoveService.validateAlsoKnownAs( me, - (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(), + (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > Date.now(), true, ); if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts index 390dd9cd71..d5e824df27 100644 --- a/packages/backend/src/server/api/endpoints/i/import-following.ts +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -75,7 +75,7 @@ export default class extends Endpoint { // eslint- const checkMoving = await this.accountMoveService.validateAlsoKnownAs( me, - (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(), + (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > Date.now(), true, ); if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts index 51a9cdf5a5..0f5800404e 100644 --- a/packages/backend/src/server/api/endpoints/i/import-muting.ts +++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts @@ -75,7 +75,7 @@ export default class extends Endpoint { // eslint- const checkMoving = await this.accountMoveService.validateAlsoKnownAs( me, - (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(), + (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > Date.now(), true, ); if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts index a3b67301a7..bacdd5c88f 100644 --- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts @@ -74,7 +74,7 @@ export default class extends Endpoint { // eslint- const checkMoving = await this.accountMoveService.validateAlsoKnownAs( me, - (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(), + (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > Date.now(), true, ); if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); diff --git a/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts b/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts index 703808d279..dc6ffd3e02 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts @@ -3,11 +3,11 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Brackets, In } from 'typeorm'; +import { In } from 'typeorm'; import * as Redis from 'ioredis'; import { Inject, Injectable } from '@nestjs/common'; import type { NotesRepository } from '@/models/_.js'; -import { obsoleteNotificationTypes, notificationTypes, FilterUnionByProperty } from '@/types.js'; +import { obsoleteNotificationTypes, groupedNotificationTypes, FilterUnionByProperty } from '@/types.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteReadService } from '@/core/NoteReadService.js'; import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js'; @@ -48,10 +48,10 @@ export const paramDef = { markAsRead: { type: 'boolean', default: true }, // 後方互換のため、廃止された通知タイプも受け付ける includeTypes: { type: 'array', items: { - type: 'string', enum: [...notificationTypes, ...obsoleteNotificationTypes], + type: 'string', enum: [...groupedNotificationTypes, ...obsoleteNotificationTypes], } }, excludeTypes: { type: 'array', items: { - type: 'string', enum: [...notificationTypes, ...obsoleteNotificationTypes], + type: 'string', enum: [...groupedNotificationTypes, ...obsoleteNotificationTypes], } }, }, required: [], @@ -79,12 +79,12 @@ export default class extends Endpoint { // eslint- return []; } // excludeTypes に全指定されている場合はクエリしない - if (notificationTypes.every(type => ps.excludeTypes?.includes(type))) { + if (groupedNotificationTypes.every(type => ps.excludeTypes?.includes(type))) { return []; } - const includeTypes = ps.includeTypes && ps.includeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][]; - const excludeTypes = ps.excludeTypes && ps.excludeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][]; + const includeTypes = ps.includeTypes && ps.includeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof groupedNotificationTypes[number][]; + const excludeTypes = ps.excludeTypes && ps.excludeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof groupedNotificationTypes[number][]; const limit = (ps.limit + EXTRA_LIMIT) + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 const notificationsRes = await this.redisClient.xrevrange( @@ -162,7 +162,6 @@ export default class extends Endpoint { // eslint- } groupedNotifications = groupedNotifications.slice(0, ps.limit); - const noteIds = groupedNotifications .filter((notification): notification is FilterUnionByProperty => ['mention', 'reply', 'quote'].includes(notification.type)) .map(notification => notification.noteId!); diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts index 52b6749e3f..320d9fdb00 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Brackets, In } from 'typeorm'; +import { In } from 'typeorm'; import * as Redis from 'ioredis'; import { Inject, Injectable } from '@nestjs/common'; import type { NotesRepository } from '@/models/_.js'; diff --git a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts index ede0957234..510c14a9b7 100644 --- a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts +++ b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index 3868278690..eea657ebbd 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -15,6 +15,7 @@ import { DI } from '@/di-symbols.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js'; import { UserAuthService } from '@/core/UserAuthService.js'; +import { MetaService } from '@/core/MetaService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -39,6 +40,12 @@ export const meta = { code: 'UNAVAILABLE', id: 'a2defefb-f220-8849-0af6-17f816099323', }, + + emailRequired: { + message: 'Email address is required.', + code: 'EMAIL_REQUIRED', + id: '324c7a88-59f2-492f-903f-89134f93e47e', + }, }, res: { @@ -66,6 +73,7 @@ export default class extends Endpoint { // eslint- @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, + private metaService: MetaService, private userEntityService: UserEntityService, private emailService: EmailService, private userAuthService: UserAuthService, @@ -97,6 +105,8 @@ export default class extends Endpoint { // eslint- if (!res.available) { throw new ApiError(meta.errors.unavailable); } + } else if ((await this.metaService.fetch()).emailRequiredForSignup) { + throw new ApiError(meta.errors.emailRequired); } await this.userProfilesRepository.update(me.id, { diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index c7be6a8211..d742901fc4 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -201,6 +201,7 @@ export const paramDef = { pollEnded: notificationRecieveConfig, receiveFollowRequest: notificationRecieveConfig, followRequestAccepted: notificationRecieveConfig, + groupInvited: notificationRecieveConfig, roleAssigned: notificationRecieveConfig, achievementEarned: notificationRecieveConfig, app: notificationRecieveConfig, @@ -460,9 +461,9 @@ export default class extends Endpoint { // eslint- this.hashtagService.updateUsertags(user, tags); //#endregion - if (Object.keys(updates).length > 0) await this.usersRepository.update(user.id, updates); - if (Object.keys(updates).includes('alsoKnownAs')) { - this.cacheService.uriPersonCache.set(this.userEntityService.genLocalUserUri(user.id), { ...user, ...updates }); + if (Object.keys(updates).length > 0) { + await this.usersRepository.update(user.id, updates); + this.globalEventService.publishInternalEvent('localUserUpdated', { id: user.id }); } await this.userProfilesRepository.update(user.id, { diff --git a/packages/backend/src/server/api/endpoints/i/user-group-invites.ts b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts index 0de6c2327a..414aff59be 100644 --- a/packages/backend/src/server/api/endpoints/i/user-group-invites.ts +++ b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/server/api/endpoints/messaging/history.ts b/packages/backend/src/server/api/endpoints/messaging/history.ts index c314ba46c7..cf8f037638 100644 --- a/packages/backend/src/server/api/endpoints/messaging/history.ts +++ b/packages/backend/src/server/api/endpoints/messaging/history.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/server/api/endpoints/messaging/messages.ts b/packages/backend/src/server/api/endpoints/messaging/messages.ts index 718cd65d23..a97e9c57ee 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ @@ -128,7 +128,7 @@ export default class extends Endpoint { // eslint- } } - return await Promise.all(messages.map(message => this.messagingMessageEntityService.pack(message, me, { + return Promise.all(messages.map(message => this.messagingMessageEntityService.pack(message, me, { populateRecipient: false, }))); } else if (ps.groupId != null) { @@ -160,13 +160,16 @@ export default class extends Endpoint { // eslint- // Mark all as read if (ps.markAsRead) { - this.messagingService.readGroupMessagingMessage(me.id, recipientGroup.id, messages.map(x => x.id)); + await this.messagingService.readGroupMessagingMessage(me.id, recipientGroup.id, messages.map(x => x.id)); } - return await Promise.all(messages.map(message => this.messagingMessageEntityService.pack(message, me, { + return Promise.all(messages.map(message => this.messagingMessageEntityService.pack(message, me, { populateGroup: false, }))); } + + // 必要に応じて適切な戻り値を提供する + return []; // デフォルトの戻り値を返す }); } } diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts index 46268f86b2..173497abfc 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts index aeebe1202b..c780684637 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts index 3a8fc15965..e7c1849a52 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index e3592dfd7a..5460635e1d 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -3,18 +3,9 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { IsNull, LessThanOrEqual, MoreThan, Brackets } from 'typeorm'; -import { Inject, Injectable } from '@nestjs/common'; -import JSON5 from 'json5'; -import type { AdsRepository } from '@/models/_.js'; -import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; +import { Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { MetaService } from '@/core/MetaService.js'; -import { InstanceActorService } from '@/core/InstanceActorService.js'; -import type { Config } from '@/config.js'; -import { DI } from '@/di-symbols.js'; -import { DEFAULT_POLICIES } from '@/core/RoleService.js'; +import { MetaEntityService } from '@/core/entities/MetaEntityService.js'; export const meta = { tags: ['meta'], @@ -23,301 +14,10 @@ export const meta = { res: { type: 'object', - optional: false, nullable: false, - properties: { - maintainerName: { - type: 'string', - optional: false, nullable: true, - }, - maintainerEmail: { - type: 'string', - optional: false, nullable: true, - }, - version: { - type: 'string', - optional: false, nullable: false, - }, - basedMisskeyVersion: { - type: 'string', - optional: false, nullable: false, - }, - providesTarball: { - type: 'boolean', - optional: false, nullable: false, - }, - name: { - type: 'string', - optional: false, nullable: false, - }, - shortName: { - type: 'string', - optional: false, nullable: true, - }, - uri: { - type: 'string', - optional: false, nullable: false, - format: 'url', - example: 'https://cherrypick.example.com', - }, - description: { - type: 'string', - optional: false, nullable: true, - }, - langs: { - type: 'array', - optional: false, nullable: false, - items: { - type: 'string', - optional: false, nullable: false, - }, - }, - tosUrl: { - type: 'string', - optional: false, nullable: true, - }, - repositoryUrl: { - type: 'string', - optional: false, nullable: true, - default: 'https://github.com/kokonect-link/cherrypick', - }, - feedbackUrl: { - type: 'string', - optional: false, nullable: true, - default: 'https://github.com/kokonect-link/cherrypick/issues/new', - }, - defaultDarkTheme: { - type: 'string', - optional: false, nullable: true, - }, - defaultLightTheme: { - type: 'string', - optional: false, nullable: true, - }, - disableRegistration: { - type: 'boolean', - optional: false, nullable: false, - }, - cacheRemoteFiles: { - type: 'boolean', - optional: false, nullable: false, - }, - cacheRemoteSensitiveFiles: { - type: 'boolean', - optional: false, nullable: false, - }, - emailRequiredForSignup: { - type: 'boolean', - optional: false, nullable: false, - }, - enableHcaptcha: { - type: 'boolean', - optional: false, nullable: false, - }, - hcaptchaSiteKey: { - type: 'string', - optional: false, nullable: true, - }, - enableMcaptcha: { - type: 'boolean', - optional: false, nullable: false, - }, - mcaptchaSiteKey: { - type: 'string', - optional: false, nullable: true, - }, - mcaptchaInstanceUrl: { - type: 'string', - optional: false, nullable: true, - }, - enableRecaptcha: { - type: 'boolean', - optional: false, nullable: false, - }, - recaptchaSiteKey: { - type: 'string', - optional: false, nullable: true, - }, - enableTurnstile: { - type: 'boolean', - optional: false, nullable: false, - }, - turnstileSiteKey: { - type: 'string', - optional: false, nullable: true, - }, - swPublickey: { - type: 'string', - optional: false, nullable: true, - }, - mascotImageUrl: { - type: 'string', - optional: false, nullable: false, - default: '/assets/ai.png', - }, - bannerUrl: { - type: 'string', - optional: false, nullable: false, - }, - serverErrorImageUrl: { - type: 'string', - optional: false, nullable: true, - }, - infoImageUrl: { - type: 'string', - optional: false, nullable: true, - }, - notFoundImageUrl: { - type: 'string', - optional: false, nullable: true, - }, - iconUrl: { - type: 'string', - optional: false, nullable: true, - }, - maxNoteTextLength: { - type: 'number', - optional: false, nullable: false, - }, - ads: { - type: 'array', - optional: false, nullable: false, - items: { - type: 'object', - optional: false, nullable: false, - properties: { - id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', - }, - url: { - type: 'string', - optional: false, nullable: false, - format: 'url', - }, - place: { - type: 'string', - optional: false, nullable: false, - }, - ratio: { - type: 'number', - optional: false, nullable: false, - }, - imageUrl: { - type: 'string', - optional: false, nullable: false, - format: 'url', - }, - dayOfWeek: { - type: 'integer', - optional: false, nullable: false, - }, - }, - }, - }, - notesPerOneAd: { - type: 'number', - optional: false, nullable: false, - default: 0, - }, - requireSetup: { - type: 'boolean', - optional: false, nullable: false, - example: false, - }, - enableEmail: { - type: 'boolean', - optional: false, nullable: false, - }, - enableServiceWorker: { - type: 'boolean', - optional: false, nullable: false, - }, - translatorAvailable: { - type: 'boolean', - optional: false, nullable: false, - }, - proxyAccountName: { - type: 'string', - optional: false, nullable: true, - }, - mediaProxy: { - type: 'string', - optional: false, nullable: false, - }, - features: { - type: 'object', - optional: true, nullable: false, - properties: { - registration: { - type: 'boolean', - optional: false, nullable: false, - }, - localTimeline: { - type: 'boolean', - optional: false, nullable: false, - }, - globalTimeline: { - type: 'boolean', - optional: false, nullable: false, - }, - hcaptcha: { - type: 'boolean', - optional: false, nullable: false, - }, - recaptcha: { - type: 'boolean', - optional: false, nullable: false, - }, - objectStorage: { - type: 'boolean', - optional: false, nullable: false, - }, - serviceWorker: { - type: 'boolean', - optional: false, nullable: false, - }, - miauth: { - type: 'boolean', - optional: true, nullable: false, - default: true, - }, - }, - }, - backgroundImageUrl: { - type: 'string', - optional: false, nullable: true, - }, - impressumUrl: { - type: 'string', - optional: false, nullable: true, - }, - logoImageUrl: { - type: 'string', - optional: false, nullable: true, - }, - privacyPolicyUrl: { - type: 'string', - optional: false, nullable: true, - }, - serverRules: { - type: 'array', - optional: false, nullable: false, - items: { - type: 'string', - }, - }, - themeColor: { - type: 'string', - optional: false, nullable: true, - }, - policies: { - type: 'object', - optional: false, nullable: false, - ref: 'RolePolicies', - }, - }, + oneOf: [ + { type: 'object', ref: 'MetaLite' }, + { type: 'object', ref: 'MetaDetailed' }, + ], }, } as const; @@ -332,117 +32,10 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.adsRepository) - private adsRepository: AdsRepository, - - private userEntityService: UserEntityService, - private metaService: MetaService, - private instanceActorService: InstanceActorService, + private metaEntityService: MetaEntityService, ) { super(meta, paramDef, async (ps, me) => { - const instance = await this.metaService.fetch(true); - - const ads = await this.adsRepository.createQueryBuilder('ads') - .where('ads.expiresAt > :now', { now: new Date() }) - .andWhere('ads.startsAt <= :now', { now: new Date() }) - .andWhere(new Brackets(qb => { - // 曜日のビットフラグを確認する - qb.where('ads.dayOfWeek & :dayOfWeek > 0', { dayOfWeek: 1 << new Date().getDay() }) - .orWhere('ads.dayOfWeek = 0'); - })) - .getMany(); - - const response: any = { - maintainerName: instance.maintainerName, - maintainerEmail: instance.maintainerEmail, - - version: this.config.version, - basedMisskeyVersion: this.config.basedMisskeyVersion, - providesTarball: this.config.publishTarballInsteadOfProvideRepositoryUrl, - - name: instance.name, - shortName: instance.shortName, - uri: this.config.url, - description: instance.description, - langs: instance.langs, - tosUrl: instance.termsOfServiceUrl, - repositoryUrl: instance.repositoryUrl, - feedbackUrl: instance.feedbackUrl, - impressumUrl: instance.impressumUrl, - privacyPolicyUrl: instance.privacyPolicyUrl, - disableRegistration: instance.disableRegistration, - emailRequiredForSignup: instance.emailRequiredForSignup, - enableHcaptcha: instance.enableHcaptcha, - hcaptchaSiteKey: instance.hcaptchaSiteKey, - enableMcaptcha: instance.enableMcaptcha, - mcaptchaSiteKey: instance.mcaptchaSitekey, - mcaptchaInstanceUrl: instance.mcaptchaInstanceUrl, - enableRecaptcha: instance.enableRecaptcha, - recaptchaSiteKey: instance.recaptchaSiteKey, - enableTurnstile: instance.enableTurnstile, - turnstileSiteKey: instance.turnstileSiteKey, - swPublickey: instance.swPublicKey, - themeColor: instance.themeColor, - mascotImageUrl: instance.mascotImageUrl, - bannerUrl: instance.bannerUrl, - infoImageUrl: instance.infoImageUrl, - serverErrorImageUrl: instance.serverErrorImageUrl, - notFoundImageUrl: instance.notFoundImageUrl, - iconUrl: instance.iconUrl, - backgroundImageUrl: instance.backgroundImageUrl, - logoImageUrl: instance.logoImageUrl, - maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, - // クライアントの手間を減らすためあらかじめJSONに変換しておく - defaultLightTheme: instance.defaultLightTheme ? JSON.stringify(JSON5.parse(instance.defaultLightTheme)) : null, - defaultDarkTheme: instance.defaultDarkTheme ? JSON.stringify(JSON5.parse(instance.defaultDarkTheme)) : null, - ads: ads.map(ad => ({ - id: ad.id, - url: ad.url, - place: ad.place, - ratio: ad.ratio, - imageUrl: ad.imageUrl, - dayOfWeek: ad.dayOfWeek, - })), - notesPerOneAd: instance.notesPerOneAd, - enableEmail: instance.enableEmail, - enableServiceWorker: instance.enableServiceWorker, - - // translatorAvailable: instance.deeplAuthKey != null, - translatorAvailable: instance.translatorType != null, - - serverRules: instance.serverRules, - - policies: { ...DEFAULT_POLICIES, ...instance.policies }, - - mediaProxy: this.config.mediaProxy, - - ...(ps.detail ? { - cacheRemoteFiles: instance.cacheRemoteFiles, - cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles, - requireSetup: !await this.instanceActorService.realLocalUsersPresent(), - } : {}), - }; - - if (ps.detail) { - const proxyAccount = instance.proxyAccountId ? await this.userEntityService.pack(instance.proxyAccountId).catch(() => null) : null; - - response.proxyAccountName = proxyAccount ? proxyAccount.username : null; - response.features = { - registration: !instance.disableRegistration, - emailRequiredForSignup: instance.emailRequiredForSignup, - hcaptcha: instance.enableHcaptcha, - recaptcha: instance.enableRecaptcha, - turnstile: instance.enableTurnstile, - objectStorage: instance.useObjectStorage, - serviceWorker: instance.enableServiceWorker, - miauth: true, - }; - } - - return response; + return ps.detail ? await this.metaEntityService.packDetailed() : await this.metaEntityService.pack(); }); } } diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 07d328724f..fe5dfbd1e2 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -16,9 +16,10 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { NoteCreateService } from '@/core/NoteCreateService.js'; import { DI } from '@/di-symbols.js'; -import { isPureRenote } from '@/misc/is-pure-renote.js'; +import { isQuote, isRenote } from '@/misc/is-renote.js'; import { MetaService } from '@/core/MetaService.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -84,6 +85,12 @@ export const meta = { id: '3ac74a84-8fd5-4bb0-870f-01804f82ce15', }, + cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility: { + message: 'You cannot reply to a specified visibility note with extended visibility.', + code: 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY', + id: 'ed940410-535c-4d5e-bfa3-af798671e93c', + }, + cannotCreateAlreadyExpiredPoll: { message: 'Poll is already expired.', code: 'CANNOT_CREATE_ALREADY_EXPIRED_POLL', @@ -119,6 +126,12 @@ export const meta = { code: 'CONTAINS_PROHIBITED_WORDS', id: 'aa6e01d3-a85c-669d-758a-76aab43af334', }, + + containsTooManyMentions: { + message: 'Cannot post because it exceeds the allowed number of mentions.', + code: 'CONTAINS_TOO_MANY_MENTIONS', + id: '4de0363a-3046-481b-9b0f-feff3e211025', + }, }, } as const; @@ -273,7 +286,7 @@ export default class extends Endpoint { // eslint- if (renote == null) { throw new ApiError(meta.errors.noSuchRenoteTarget); - } else if (isPureRenote(renote)) { + } else if (isRenote(renote) && !isQuote(renote)) { throw new ApiError(meta.errors.cannotReRenote); } @@ -319,10 +332,12 @@ export default class extends Endpoint { // eslint- if (reply == null) { throw new ApiError(meta.errors.noSuchReplyTarget); - } else if (isPureRenote(reply)) { + } else if (isRenote(reply) && !isQuote(reply)) { throw new ApiError(meta.errors.cannotReplyToPureRenote); } else if (!await this.noteEntityService.isVisibleForMe(reply, me.id)) { throw new ApiError(meta.errors.cannotReplyToInvisibleNote); + } else if (reply.visibility === 'specified' && ps.visibility !== 'specified') { + throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility); } // Check blocking @@ -394,10 +409,13 @@ export default class extends Endpoint { // eslint- }; } catch (e) { // TODO: 他のErrorもここでキャッチしてエラーメッセージを当てるようにしたい - if (e instanceof NoteCreateService.ContainsProhibitedWordsError) { - throw new ApiError(meta.errors.containsProhibitedWords); + if (e instanceof IdentifiableError) { + if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') { + throw new ApiError(meta.errors.containsProhibitedWords); + } else if (e.id === '9f466dab-c856-48cd-9e65-ff90ff750580') { + throw new ApiError(meta.errors.containsTooManyMentions); + } } - throw e; } }); diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index 3588cad3b9..a1a135601e 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -25,7 +25,7 @@ export const meta = { res: { type: 'object', - optional: false, nullable: false, + optional: true, nullable: false, properties: { sourceLang: { type: 'string' }, text: { type: 'string' }, @@ -43,6 +43,11 @@ export const meta = { code: 'NO_SUCH_NOTE', id: 'bea9b03f-36e0-49c5-a4db-627a029f8971', }, + cannotTranslateInvisibleNote: { + message: 'Cannot translate invisible note.', + code: 'CANNOT_TRANSLATE_INVISIBLE_NOTE', + id: 'ea29f2ca-c368-43b3-aaf1-5ac3e74bbe5d', + }, noTranslateService: { message: 'Translate service is not available.', code: 'NO_TRANSLATE_SERVICE', @@ -81,11 +86,11 @@ export default class extends Endpoint { // eslint- }); if (!(await this.noteEntityService.isVisibleForMe(note, me.id))) { - return 204; // TODO: 良い感じのエラー返す + throw new ApiError(meta.errors.cannotTranslateInvisibleNote); } if (note.text == null) { - return 204; + return; } const instance = await this.metaService.fetch(); @@ -106,7 +111,7 @@ export default class extends Endpoint { // eslint- let translationResult; if (instance.translatorType === 'deepl') { if (instance.deeplAuthKey == null) { - return 204; // TODO: 良い感じのエラー返す + throw new ApiError(meta.errors.unavailable); } translationResult = await this.translateDeepL((note.cw ? note.cw + '\n' : '') + note.text, targetLang, instance.deeplAuthKey, instance.deeplIsPro, instance.translatorType); } else if (instance.translatorType === 'google_no_api') { @@ -121,9 +126,9 @@ export default class extends Endpoint { // eslint- translator: translatorServices, }; } else if (instance.translatorType === 'ctav3') { - if (instance.ctav3SaKey == null) return 204; - else if (instance.ctav3ProjectId == null) return 204; - else if (instance.ctav3Location == null) return 204; + if (instance.ctav3SaKey == null) return Promise.resolve(204); + else if (instance.ctav3ProjectId == null) return Promise.resolve(204); + else if (instance.ctav3Location == null) return Promise.resolve(204); translationResult = await this.apiCloudTranslationAdvanced( (note.cw ? note.cw + '\n' : '') + note.text, targetLang, instance.ctav3SaKey, instance.ctav3ProjectId, instance.ctav3Location, instance.ctav3Model, instance.ctav3Glossary, instance.translatorType, ); @@ -131,11 +136,11 @@ export default class extends Endpoint { // eslint- throw new Error('Unsupported translator type'); } - return { - sourceLang: translationResult.sourceLang, - text: translationResult.text, - translator: translationResult.translator, - }; + return Promise.resolve({ + sourceLang: translationResult.sourceLang || '', + text: translationResult.text || '', + translator: translationResult.translator || [], + }); }); } @@ -157,11 +162,11 @@ export default class extends Endpoint { // eslint- }); const json = (await res.json()) as { - translations: { - detected_source_language: string; - text: string; - }[]; - }; + translations: { + detected_source_language: string; + text: string; + }[]; + }; return { sourceLang: json.translations[0].detected_source_language, diff --git a/packages/backend/src/server/api/endpoints/notes/update.ts b/packages/backend/src/server/api/endpoints/notes/update.ts index 282f1fe537..fecd0baf21 100644 --- a/packages/backend/src/server/api/endpoints/notes/update.ts +++ b/packages/backend/src/server/api/endpoints/notes/update.ts @@ -95,7 +95,7 @@ export const paramDef = { } as const; @Injectable() -export default class extends Endpoint { // eslint-disable-line import/no-default-export +export default class extends Endpoint { constructor( @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, @@ -104,7 +104,10 @@ export default class extends Endpoint { // eslint- private noteEntityService: NoteEntityService, private noteUpdateService: NoteUpdateService, ) { - super(meta, paramDef, async (ps, me) => { + super({ + ...meta, + requireRolePolicy: 'canEditNote', // 修正された部分 + }, paramDef, async (ps, me) => { const note = await this.getterService.getNote(ps.noteId).catch(err => { if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); throw err; diff --git a/packages/backend/src/server/api/endpoints/notifications/flush.ts b/packages/backend/src/server/api/endpoints/notifications/flush.ts new file mode 100644 index 0000000000..47c0642fd1 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/notifications/flush.ts @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NotificationService } from '@/core/NotificationService.js'; + +export const meta = { + tags: ['notifications', 'account'], + + requireCredential: true, + + kind: 'write:notifications', +} as const; + +export const paramDef = { + type: 'object', + properties: {}, + required: [], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private notificationService: NotificationService, + ) { + super(meta, paramDef, async (ps, me) => { + this.notificationService.flushAllNotifications(me.id); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 1f4509764f..784766bcb5 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -12,6 +12,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { MetaService } from '@/core/MetaService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; +import { isNotNull } from '@/misc/is-not-null.js'; export const meta = { tags: ['users'], @@ -52,7 +53,7 @@ export default class extends Endpoint { // eslint- host: acct.host ?? IsNull(), }))); - return await this.userEntityService.packMany(users.filter(x => x !== null) as MiUser[], me, { schema: 'UserDetailed' }); + return await this.userEntityService.packMany(users.filter(isNotNull), me, { schema: 'UserDetailed' }); }); } } diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index 06c04b3f9a..a9a33149f9 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -9,6 +9,7 @@ import type { SwSubscriptionsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { MetaService } from '@/core/MetaService.js'; import { DI } from '@/di-symbols.js'; +import { PushNotificationService } from '@/core/PushNotificationService.js'; export const meta = { tags: ['account'], @@ -66,6 +67,7 @@ export default class extends Endpoint { // eslint- private idService: IdService, private metaService: MetaService, + private pushNotificationService: PushNotificationService, ) { super(meta, paramDef, async (ps, me) => { // if already subscribed @@ -97,6 +99,8 @@ export default class extends Endpoint { // eslint- sendReadMessage: ps.sendReadMessage, }); + this.pushNotificationService.refreshCache(me.id); + return { state: 'subscribed' as const, key: instance.swPublicKey, diff --git a/packages/backend/src/server/api/endpoints/sw/unregister.ts b/packages/backend/src/server/api/endpoints/sw/unregister.ts index 2bc91c7278..2edf7fab1b 100644 --- a/packages/backend/src/server/api/endpoints/sw/unregister.ts +++ b/packages/backend/src/server/api/endpoints/sw/unregister.ts @@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { SwSubscriptionsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; +import { PushNotificationService } from '@/core/PushNotificationService.js'; export const meta = { tags: ['account'], @@ -29,12 +30,18 @@ export default class extends Endpoint { // eslint- constructor( @Inject(DI.swSubscriptionsRepository) private swSubscriptionsRepository: SwSubscriptionsRepository, + + private pushNotificationService: PushNotificationService, ) { super(meta, paramDef, async (ps, me) => { await this.swSubscriptionsRepository.delete({ ...(me ? { userId: me.id } : {}), endpoint: ps.endpoint, }); + + if (me) { + this.pushNotificationService.refreshCache(me.id); + } }); } } diff --git a/packages/backend/src/server/api/endpoints/sw/update-registration.ts b/packages/backend/src/server/api/endpoints/sw/update-registration.ts index b56b07fd00..839a07c770 100644 --- a/packages/backend/src/server/api/endpoints/sw/update-registration.ts +++ b/packages/backend/src/server/api/endpoints/sw/update-registration.ts @@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { SwSubscriptionsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; +import { PushNotificationService } from '@/core/PushNotificationService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -58,6 +59,8 @@ export default class extends Endpoint { // eslint- constructor( @Inject(DI.swSubscriptionsRepository) private swSubscriptionsRepository: SwSubscriptionsRepository, + + private pushNotificationService: PushNotificationService, ) { super(meta, paramDef, async (ps, me) => { const swSubscription = await this.swSubscriptionsRepository.findOneBy({ @@ -77,6 +80,8 @@ export default class extends Endpoint { // eslint- sendReadMessage: swSubscription.sendReadMessage, }); + this.pushNotificationService.refreshCache(me.id); + return { userId: swSubscription.userId, endpoint: swSubscription.endpoint, diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index 5d52ebba76..6b3389f0b2 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -6,6 +6,7 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import type { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/_.js'; +import { birthdaySchema } from '@/models/User.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; @@ -66,7 +67,7 @@ export const paramDef = { description: 'The local host is represented with `null`.', }, - birthday: { type: 'string', nullable: true }, + birthday: { ...birthdaySchema, nullable: true }, }, anyOf: [ { required: ['userId'] }, @@ -127,9 +128,7 @@ export default class extends Endpoint { // eslint- if (ps.birthday) { try { - const d = new Date(ps.birthday); - d.setHours(0, 0, 0, 0); - const birthday = `${(d.getMonth() + 1).toString().padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`; + const birthday = ps.birthday.substring(5, 10); const birthdayUserQuery = this.userProfilesRepository.createQueryBuilder('user_profile'); birthdayUserQuery.select('user_profile.userId') .where(`SUBSTR(user_profile.birthday, 6, 5) = '${birthday}'`); diff --git a/packages/backend/src/server/api/endpoints/users/groups/create.ts b/packages/backend/src/server/api/endpoints/users/groups/create.ts index 0bcc4bf7be..aab9d38081 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/create.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/create.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/server/api/endpoints/users/groups/delete.ts b/packages/backend/src/server/api/endpoints/users/groups/delete.ts index 3ed21b8e3b..9a8112ffef 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/delete.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts index e790d000bc..968691d552 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts index 0740845ecf..fa7d40e28c 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/server/api/endpoints/users/groups/invite.ts b/packages/backend/src/server/api/endpoints/users/groups/invite.ts index aa5488e271..f4d990c18c 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invite.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invite.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/server/api/endpoints/users/groups/joined.ts b/packages/backend/src/server/api/endpoints/users/groups/joined.ts index 42bd0e4b6e..e34e8d3d38 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/joined.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/joined.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/server/api/endpoints/users/groups/leave.ts b/packages/backend/src/server/api/endpoints/users/groups/leave.ts index d2f9d79caf..ed105fc4b6 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/leave.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/leave.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/server/api/endpoints/users/groups/owned.ts b/packages/backend/src/server/api/endpoints/users/groups/owned.ts index 7ae5893840..1c456e8686 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/owned.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/owned.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/server/api/endpoints/users/groups/pull.ts b/packages/backend/src/server/api/endpoints/users/groups/pull.ts index df429d7f3b..41838540e4 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/pull.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/server/api/endpoints/users/groups/show.ts b/packages/backend/src/server/api/endpoints/users/groups/show.ts index e6dcd53bde..898b967d01 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/show.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/show.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts index 83f3a93d9b..cb9ff1ebcd 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/server/api/endpoints/users/groups/update.ts b/packages/backend/src/server/api/endpoints/users/groups/update.ts index 08e6bf1c38..ce887c1647 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/update.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/update.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index 9dbee87b82..23b3a517c2 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -109,7 +109,7 @@ export default class extends Endpoint { // eslint- .limit(ps.limit) .getRawMany(); - return await Promise.all(reactions.map(reaction => this.noteReactionEntityService.pack(reaction, me, { withNote: true }))); + return await this.noteReactionEntityService.packMany(reactions, me, { withNote: true }); }); } } diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts index 6a5b2262fa..1d75437b81 100644 --- a/packages/backend/src/server/api/endpoints/users/relation.ts +++ b/packages/backend/src/server/api/endpoints/users/relation.ts @@ -132,11 +132,9 @@ export default class extends Endpoint { // eslint- private userEntityService: UserEntityService, ) { super(meta, paramDef, async (ps, me) => { - const ids = Array.isArray(ps.userId) ? ps.userId : [ps.userId]; - - const relations = await Promise.all(ids.map(id => this.userEntityService.getRelation(me.id, id))); - - return Array.isArray(ps.userId) ? relations : relations[0]; + return Array.isArray(ps.userId) + ? await this.userEntityService.getRelations(me.id, ps.userId).then(it => [...it.values()]) + : await this.userEntityService.getRelation(me.id, ps.userId).then(it => [it]); }); } } diff --git a/packages/backend/src/server/api/endpoints/users/translate.ts b/packages/backend/src/server/api/endpoints/users/translate.ts index 2848711790..8a6b7b14fe 100644 --- a/packages/backend/src/server/api/endpoints/users/translate.ts +++ b/packages/backend/src/server/api/endpoints/users/translate.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ @@ -24,7 +24,7 @@ export const meta = { res: { type: 'object', - optional: false, nullable: false, + optional: true, nullable: false, properties: { sourceLang: { type: 'string' }, text: { type: 'string' }, @@ -79,7 +79,7 @@ export default class extends Endpoint { // eslint- }); if (target.description == null) { - return 204; + return; } const instance = await this.metaService.fetch(); @@ -91,7 +91,7 @@ export default class extends Endpoint { // eslint- ]; if (instance.translatorType == null || !translatorServices.includes(instance.translatorType)) { - throw new ApiError(meta.errors.noTranslateService); + return Promise.resolve(204); // Promise.resolveで204をラップする } let targetLang = ps.targetLang; @@ -100,7 +100,7 @@ export default class extends Endpoint { // eslint- let translationResult; if (instance.translatorType === 'deepl') { if (instance.deeplAuthKey == null) { - return 204; // TODO: 良い感じのエラー返す + throw new ApiError(meta.errors.unavailable); } translationResult = await this.translateDeepL(target.description, targetLang, instance.deeplAuthKey, instance.deeplIsPro, instance.translatorType); } else if (instance.translatorType === 'google_no_api') { @@ -112,12 +112,12 @@ export default class extends Endpoint { // eslint- return { sourceLang: raw.src, text: text, - translator: translatorServices, + translator: instance.translatorType, // 修正点: 配列ではなく単一の文字列 }; } else if (instance.translatorType === 'ctav3') { - if (instance.ctav3SaKey == null) return 204; - else if (instance.ctav3ProjectId == null) return 204; - else if (instance.ctav3Location == null) return 204; + if (instance.ctav3SaKey == null) return Promise.resolve(204); + else if (instance.ctav3ProjectId == null) return Promise.resolve(204); + else if (instance.ctav3Location == null) return Promise.resolve(204); translationResult = await this.apiCloudTranslationAdvanced( target.description, targetLang, instance.ctav3SaKey, instance.ctav3ProjectId, instance.ctav3Location, instance.ctav3Model, instance.ctav3Glossary, instance.translatorType, ); @@ -125,11 +125,11 @@ export default class extends Endpoint { // eslint- throw new Error('Unsupported translator type'); } - return { - sourceLang: translationResult.sourceLang, - text: translationResult.text, - translator: translationResult.translator, - }; + return Promise.resolve({ + sourceLang: translationResult.sourceLang || '', + text: translationResult.text || '', + translator: translationResult.translator || [], + }); }); } diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts index a7f9bce2f0..b80320a854 100644 --- a/packages/backend/src/server/api/openapi/gen-spec.ts +++ b/packages/backend/src/server/api/openapi/gen-spec.ts @@ -94,7 +94,7 @@ export function genOpenapiSpec(config: Config, includeSelfRef = false) { const hasBody = (schema.type === 'object' && schema.properties && Object.keys(schema.properties).length >= 1); const info = { - operationId: endpoint.name, + operationId: endpoint.name.replaceAll('/', '___'), // NOTE: スラッシュは使えない summary: endpoint.name, description: desc, externalDocs: { diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index 20a6f3e39d..e8fc2ebd0c 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -87,7 +87,7 @@ class ChannelChannel extends Channel { if (now.getTime() - date.getTime() > 5000) delete this.typers[userId]; } - const users = await this.userEntityService.packMany(Object.keys(this.typers), null, { detail: false }); + const users = await this.userEntityService.packMany(Object.keys(this.typers), null, { schema: 'UserLite' }); this.send({ type: 'typers', diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index ce9d7f5647..f45bf8622e 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -71,7 +71,15 @@ class HomeTimelineChannel extends Channel { } } - if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return; + // 純粋なリノート(引用リノートでないリノート)の場合 + if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && note.poll == null) { + if (!this.withRenotes) return; + if (note.renote.reply) { + const reply = note.renote.reply; + // 自分のフォローしていないユーザーの visibility: followers な投稿への返信のリノートは弾く + if (reply.visibility === 'followers' && !Object.hasOwn(this.following, reply.userId)) return; + } + } // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isUserRelated(note, this.userIdsWhoMeMuting)) return; diff --git a/packages/backend/src/server/api/stream/channels/messaging-index.ts b/packages/backend/src/server/api/stream/channels/messaging-index.ts index 7f26b37353..970c67c9f9 100644 --- a/packages/backend/src/server/api/stream/channels/messaging-index.ts +++ b/packages/backend/src/server/api/stream/channels/messaging-index.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ @@ -25,10 +25,9 @@ class MessagingIndexChannel extends Channel { export class MessagingIndexChannelService { public readonly shouldShare = MessagingIndexChannel.shouldShare; public readonly requireCredential = MessagingIndexChannel.requireCredential; + public readonly kind: string = 'messagingIndex'; // kind の型を string に変更し、適切な値を代入する - constructor( - ) { - } + constructor() {} @bindThis public create(id: string, connection: Channel['connection']): MessagingIndexChannel { diff --git a/packages/backend/src/server/api/stream/channels/messaging.ts b/packages/backend/src/server/api/stream/channels/messaging.ts index e198aec4e6..7c7cb3b253 100644 --- a/packages/backend/src/server/api/stream/channels/messaging.ts +++ b/packages/backend/src/server/api/stream/channels/messaging.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors + * SPDX-FileCopyrightText: syuilo and misskey-project & noridev and cherrypick-project * SPDX-License-Identifier: AGPL-3.0-only */ @@ -113,7 +113,7 @@ class MessagingChannel extends Channel { if (now.getTime() - date.getTime() > 5000) delete this.typers[userId]; } - const users = await this.userEntityService.packMany(Object.keys(this.typers), null, { detail: false }); + const users = await this.userEntityService.packMany(Object.keys(this.typers), null, { schema: 'UserLite' }); this.send({ type: 'typers', @@ -133,6 +133,7 @@ class MessagingChannel extends Channel { export class MessagingChannelService { public readonly shouldShare = MessagingChannel.shouldShare; public readonly requireCredential = MessagingChannel.requireCredential; + public readonly kind: string = 'messaging'; // kind の型を string に変更し、適切な値に設定する constructor( @Inject(DI.usersRepository) diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 345a26cd62..cca3c4e096 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -19,6 +19,7 @@ import fastifyView from '@fastify/view'; import fastifyCookie from '@fastify/cookie'; import fastifyProxy from '@fastify/http-proxy'; import vary from 'vary'; +import htmlSafeJsonStringify from 'htmlescape'; import type { Config } from '@/config.js'; import { getNoteSummary } from '@/misc/get-note-summary.js'; import { DI } from '@/di-symbols.js'; @@ -28,12 +29,12 @@ import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, Obj import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; +import { MetaEntityService } from '@/core/entities/MetaEntityService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, MiMeta, NotesRepository, PagesRepository, ReversiGamesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; -import { deepClone } from '@/misc/clone.js'; import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js'; import { bindThis } from '@/decorators.js'; import { FlashEntityService } from '@/core/entities/FlashEntityService.js'; @@ -93,6 +94,7 @@ export class ClientServerService { private userEntityService: UserEntityService, private noteEntityService: NoteEntityService, private pageEntityService: PageEntityService, + private metaEntityService: MetaEntityService, private galleryPostEntityService: GalleryPostEntityService, private clipEntityService: ClipEntityService, private channelEntityService: ChannelEntityService, @@ -173,7 +175,7 @@ export class ClientServerService { } @bindThis - private generateCommonPugData(meta: MiMeta) { + private async generateCommonPugData(meta: MiMeta) { return { instanceName: meta.name ?? 'CherryPick', icon: meta.iconUrl, @@ -183,6 +185,8 @@ export class ClientServerService { infoImageUrl: meta.infoImageUrl ?? 'https://xn--931a.moe/assets/info.jpg', notFoundImageUrl: meta.notFoundImageUrl ?? 'https://xn--931a.moe/assets/not-found.jpg', instanceUrl: this.config.url, + metaJson: htmlSafeJsonStringify(await this.metaEntityService.packDetailed(meta)), + now: Date.now(), }; } @@ -434,7 +438,7 @@ export class ClientServerService { url: this.config.url, title: meta.name ?? 'CherryPick', desc: meta.description, - ...this.generateCommonPugData(meta), + ...await this.generateCommonPugData(meta), }); }; @@ -521,7 +525,7 @@ export class ClientServerService { user, profile, me, avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user), sub: request.params.sub, - ...this.generateCommonPugData(meta), + ...await this.generateCommonPugData(meta), }); } else { // リモートユーザーなので @@ -571,7 +575,7 @@ export class ClientServerService { avatarUrl: _note.user.avatarUrl, // TODO: Let locale changeable by instance setting summary: getNoteSummary(_note), - ...this.generateCommonPugData(meta), + ...await this.generateCommonPugData(meta), }); } else { return await renderBase(reply); @@ -610,7 +614,7 @@ export class ClientServerService { page: _page, profile, avatarUrl: _page.user.avatarUrl, - ...this.generateCommonPugData(meta), + ...await this.generateCommonPugData(meta), }); } else { return await renderBase(reply); @@ -636,7 +640,7 @@ export class ClientServerService { flash: _flash, profile, avatarUrl: _flash.user.avatarUrl, - ...this.generateCommonPugData(meta), + ...await this.generateCommonPugData(meta), }); } else { return await renderBase(reply); @@ -662,7 +666,7 @@ export class ClientServerService { clip: _clip, profile, avatarUrl: _clip.user.avatarUrl, - ...this.generateCommonPugData(meta), + ...await this.generateCommonPugData(meta), }); } else { return await renderBase(reply); @@ -686,7 +690,7 @@ export class ClientServerService { post: _post, profile, avatarUrl: _post.user.avatarUrl, - ...this.generateCommonPugData(meta), + ...await this.generateCommonPugData(meta), }); } else { return await renderBase(reply); @@ -705,7 +709,7 @@ export class ClientServerService { reply.header('Cache-Control', 'public, max-age=15'); return await reply.view('channel', { channel: _channel, - ...this.generateCommonPugData(meta), + ...await this.generateCommonPugData(meta), }); } else { return await renderBase(reply); @@ -724,7 +728,7 @@ export class ClientServerService { reply.header('Cache-Control', 'public, max-age=3600'); return await reply.view('reversi-game', { game: _game, - ...this.generateCommonPugData(meta), + ...await this.generateCommonPugData(meta), }); } else { return await renderBase(reply); diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index c6a96e94cb..8f8f08a305 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -5,6 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { summaly } from '@misskey-dev/summaly'; +import { SummalyResult } from '@misskey-dev/summaly/built/summary.js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; @@ -14,6 +15,7 @@ import { query } from '@/misc/prelude/url.js'; import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; import { ApiError } from '@/server/api/error.js'; +import { MiMeta } from '@/models/Meta.js'; import type { FastifyRequest, FastifyReply } from 'fastify'; @Injectable() @@ -62,24 +64,25 @@ export class UrlPreviewService { const meta = await this.metaService.fetch(); - this.logger.info(meta.summalyProxy + if (!meta.urlPreviewEnabled) { + reply.code(403); + return { + error: new ApiError({ + message: 'URL preview is disabled', + code: 'URL_PREVIEW_DISABLED', + id: '58b36e13-d2f5-0323-b0c6-76aa9dabefb8', + }), + }; + } + + this.logger.info(meta.urlPreviewSummaryProxyUrl ? `(Proxy) Getting preview of ${url}@${lang} ...` : `Getting preview of ${url}@${lang} ...`); + try { - const summary = meta.summalyProxy ? - await this.httpRequestService.getJson>(`${meta.summalyProxy}?${query({ - url: url, - lang: lang ?? 'ja-JP', - })}`) - : - await summaly(url, { - followRedirects: false, - lang: lang ?? 'ja-JP', - agent: this.config.proxy ? { - http: this.httpRequestService.httpAgent, - https: this.httpRequestService.httpsAgent, - } : undefined, - }); + const summary = meta.urlPreviewSummaryProxyUrl + ? await this.fetchSummaryFromProxy(url, meta, lang) + : await this.fetchSummary(url, meta, lang); this.logger.succ(`Got preview of ${url}: ${summary.title}`); @@ -100,6 +103,7 @@ export class UrlPreviewService { return summary; } catch (err) { this.logger.warn(`Failed to get preview of ${url}: ${err}`); + reply.code(422); reply.header('Cache-Control', 'max-age=86400, immutable'); return { @@ -111,4 +115,37 @@ export class UrlPreviewService { }; } } + + private fetchSummary(url: string, meta: MiMeta, lang?: string): Promise { + const agent = this.config.proxy + ? { + http: this.httpRequestService.httpAgent, + https: this.httpRequestService.httpsAgent, + } + : undefined; + + return summaly(url, { + followRedirects: false, + lang: lang ?? 'ja-JP', + agent: agent, + userAgent: meta.urlPreviewUserAgent ?? undefined, + operationTimeout: meta.urlPreviewTimeout, + contentLengthLimit: meta.urlPreviewMaximumContentLength, + contentLengthRequired: meta.urlPreviewRequireContentLength, + }); + } + + private fetchSummaryFromProxy(url: string, meta: MiMeta, lang?: string): Promise { + const proxy = meta.urlPreviewSummaryProxyUrl!; + const queryStr = query({ + url: url, + lang: lang ?? 'ja-JP', + userAgent: meta.urlPreviewUserAgent ?? undefined, + operationTimeout: meta.urlPreviewTimeout, + contentLengthLimit: meta.urlPreviewMaximumContentLength, + contentLengthRequired: meta.urlPreviewRequireContentLength, + }); + + return this.httpRequestService.getJson(`${proxy}?${queryStr}`); + } } diff --git a/packages/backend/src/server/web/bios.css b/packages/backend/src/server/web/bios.css index 06f46cdb7d..88cc5aef27 100644 --- a/packages/backend/src/server/web/bios.css +++ b/packages/backend/src/server/web/bios.css @@ -4,38 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -@font-face { - font-family: 'Pretendard JP'; - font-weight: 400; - font-display: swap; - src: local('Pretendard JP Regular'), - url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/packages/pretendard-jp/dist/web/static/woff2/PretendardJP-Regular.woff2') format('woff2'), - url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/packages/pretendard-jp/dist/web/static/woff/PretendardJP-Regular.woff') format('woff'); -} -@font-face { - font-family: 'Pretendard JP'; - font-weight: 700; - font-display: swap; - src: local('Pretendard JP Bold'), - url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/packages/pretendard-jp/dist/web/static/woff2/PretendardJP-Bold.woff2') format('woff2'), - url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/packages/pretendard-jp/dist/web/static/woff/PretendardJP-Bold.woff') format('woff'); -} -@font-face { - font-family: 'JetBrains Mono'; - font-style: normal; - font-weight: 400; - src: local("JetBrains Mono Regular"), local("JetBrainsMono-Regular"), - url("https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono@master/fonts/webfonts/JetBrainsMono-Regular.woff2") format("woff2"); - font-display: swap; -} -@font-face { - font-family: 'JetBrains Mono'; - font-style: normal; - font-weight: 700; - src: local("JetBrains Mono Bold"), local("JetBrainsMono-Bold"), - url("https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono@master/fonts/webfonts/JetBrainsMono-Bold.woff2") format("woff2"); - font-display: swap; -} +@import url("https://cdn.jsdelivr.net/npm/jetbrains-mono@1.0.6/css/jetbrains-mono.min.css"); +@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css"); +@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard-jp.min.css"); * { font-family: "JetBrains Mono", "Pretendard JP", Fira code, Fira Mono, Consolas, Menlo, Courier, monospace; diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index 290431c2e5..f6218992f7 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -91,8 +91,8 @@ //#endregion //#region Script - function importAppScript() { - import(`/vite/${CLIENT_ENTRY}`) + async function importAppScript() { + await import(`/vite/${CLIENT_ENTRY}`) .catch(async e => { console.error(e); renderError('APP_IMPORT', e); diff --git a/packages/backend/src/server/web/cli.css b/packages/backend/src/server/web/cli.css index baa7d1dea7..dec2365e1c 100644 --- a/packages/backend/src/server/web/cli.css +++ b/packages/backend/src/server/web/cli.css @@ -4,38 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -@font-face { - font-family: 'Pretendard JP'; - font-weight: 400; - font-display: swap; - src: local('Pretendard JP Regular'), - url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/packages/pretendard-jp/dist/web/static/woff2/PretendardJP-Regular.woff2') format('woff2'), - url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/packages/pretendard-jp/dist/web/static/woff/PretendardJP-Regular.woff') format('woff'); -} -@font-face { - font-family: 'Pretendard JP'; - font-weight: 700; - font-display: swap; - src: local('Pretendard JP Bold'), - url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/packages/pretendard-jp/dist/web/static/woff2/PretendardJP-Bold.woff2') format('woff2'), - url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/packages/pretendard-jp/dist/web/static/woff/PretendardJP-Bold.woff') format('woff'); -} -@font-face { - font-family: 'JetBrains Mono'; - font-style: normal; - font-weight: 400; - src: local("JetBrains Mono Regular"), local("JetBrainsMono-Regular"), - url("https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono@master/fonts/webfonts/JetBrainsMono-Regular.woff2") format("woff2"); - font-display: swap; -} -@font-face { - font-family: 'JetBrains Mono'; - font-style: normal; - font-weight: 700; - src: local("JetBrains Mono Bold"), local("JetBrainsMono-Bold"), - url("https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono@master/fonts/webfonts/JetBrainsMono-Bold.woff2") format("woff2"); - font-display: swap; -} +@import url("https://cdn.jsdelivr.net/npm/jetbrains-mono@1.0.6/css/jetbrains-mono.min.css"); +@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css"); +@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard-jp.min.css"); * { font-family: "JetBrains Mono", "Pretendard JP", Fira code, Fira Mono, Consolas, Menlo, Courier, monospace; diff --git a/packages/backend/src/server/web/error.css b/packages/backend/src/server/web/error.css index 5234f2d5de..046faf7e52 100644 --- a/packages/backend/src/server/web/error.css +++ b/packages/backend/src/server/web/error.css @@ -4,38 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -@font-face { - font-family: 'Pretendard JP'; - font-weight: 400; - font-display: swap; - src: local('Pretendard JP Regular'), - url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/packages/pretendard-jp/dist/web/static/woff2/PretendardJP-Regular.woff2') format('woff2'), - url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/packages/pretendard-jp/dist/web/static/woff/PretendardJP-Regular.woff') format('woff'); -} -@font-face { - font-family: 'Pretendard JP'; - font-weight: 700; - font-display: swap; - src: local('Pretendard JP Bold'), - url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/packages/pretendard-jp/dist/web/static/woff2/PretendardJP-Bold.woff2') format('woff2'), - url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/packages/pretendard-jp/dist/web/static/woff/PretendardJP-Bold.woff') format('woff'); -} -@font-face { - font-family: 'JetBrains Mono'; - font-style: normal; - font-weight: 400; - src: local("JetBrains Mono Regular"), local("JetBrainsMono-Regular"), - url("https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono@master/fonts/webfonts/JetBrainsMono-Regular.woff2") format("woff2"); - font-display: swap; -} -@font-face { - font-family: 'JetBrains Mono'; - font-style: normal; - font-weight: 700; - src: local("JetBrains Mono Bold"), local("JetBrainsMono-Bold"), - url("https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono@master/fonts/webfonts/JetBrainsMono-Bold.woff2") format("woff2"); - font-display: swap; -} +@import url("https://cdn.jsdelivr.net/npm/jetbrains-mono@1.0.6/css/jetbrains-mono.min.css"); +@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css"); +@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard-jp.min.css"); * { font-family: "Pretendard JP", "JetBrains Mono", BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif; diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index 1423d39b8a..dd06651a09 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -68,6 +68,9 @@ html var VERSION = "#{version}"; var CLIENT_ENTRY = "#{clientEntry.file}"; + script(type='application/json' id='misskey_meta' data-generated-at=now) + != metaJson + script include ../boot.js diff --git a/packages/backend/src/server/web/views/note.pug b/packages/backend/src/server/web/views/note.pug index 9bc652b6a1..fb659ce171 100644 --- a/packages/backend/src/server/web/views/note.pug +++ b/packages/backend/src/server/web/views/note.pug @@ -2,7 +2,7 @@ extends ./base block vars - const user = note.user; - - const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`; + - const title = user.name ? `${user.name} (@${user.username}${user.host ? `@${user.host}` : ''})` : `@${user.username}${user.host ? `@${user.host}` : ''}`; - const url = `${config.url}/notes/${note.id}`; - const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null; - const images = (note.files || []).filter(file => file.type.startsWith('image/') && !file.isSensitive) @@ -28,7 +28,7 @@ block og // FIXME: add embed player for Twitter if images.length meta(property='twitter:card' content='summary_large_image') - each image in images + each image in images meta(property='og:image' content= image.url) else meta(property='twitter:card' content='summary') diff --git a/packages/backend/src/server/web/views/user.pug b/packages/backend/src/server/web/views/user.pug index 83d57349a6..2b0a7bab5c 100644 --- a/packages/backend/src/server/web/views/user.pug +++ b/packages/backend/src/server/web/views/user.pug @@ -1,7 +1,7 @@ extends ./base block vars - - const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`; + - const title = user.name ? `${user.name} (@${user.username}${user.host ? `@${user.host}` : ''})` : `@${user.username}${user.host ? `@${user.host}` : ''}`; - const url = `${config.url}/@${(user.host ? `${user.username}@${user.host}` : user.username)}`; block title diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 24510d7f69..20d83cb137 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -14,8 +14,8 @@ * pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した * receiveFollowRequest - フォローリクエストされた * followRequestAccepted - 自分の送ったフォローリクエストが承認された - * roleAssigned - ロールが付与された * groupInvited - グループに招待された + * roleAssigned - ロールが付与された * achievementEarned - 実績を獲得 * app - アプリ通知 * test - テスト通知(サーバー側) @@ -31,11 +31,19 @@ export const notificationTypes = [ 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', - 'roleAssigned', 'groupInvited', + 'roleAssigned', 'achievementEarned', 'app', - 'test'] as const; + 'test', +] as const; + +export const groupedNotificationTypes = [ + ...notificationTypes, + 'reaction:grouped', + 'renote:grouped', +] as const; + export const obsoleteNotificationTypes = ['pollVote'/*, 'groupInvited'*/] as const; export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const; @@ -71,6 +79,7 @@ export const moderationLogTypes = [ 'resetPassword', 'suspendRemoteInstance', 'unsuspendRemoteInstance', + 'updateRemoteInstanceNote', 'markSensitiveDriveFile', 'unmarkSensitiveDriveFile', 'resolveAbuseReport', @@ -211,6 +220,12 @@ export type ModerationLogPayloads = { id: string; host: string; }; + updateRemoteInstanceNote: { + id: string; + host: string; + before: string | null; + after: string | null; + }; markSensitiveDriveFile: { fileId: string; fileUserId: string | null; diff --git a/packages/backend/test/e2e/2fa.ts b/packages/backend/test/e2e/2fa.ts index 8084081a55..1a7a89734b 100644 --- a/packages/backend/test/e2e/2fa.ts +++ b/packages/backend/test/e2e/2fa.ts @@ -187,7 +187,7 @@ describe('2要素認証', () => { }, 1000 * 60 * 2); test('が設定でき、OTPでログインできる。', async () => { - const registerResponse = await api('/i/2fa/register', { + const registerResponse = await api('i/2fa/register', { password, }, alice); assert.strictEqual(registerResponse.status, 200); @@ -197,18 +197,18 @@ describe('2要素認証', () => { assert.strictEqual(registerResponse.body.label, username); assert.strictEqual(registerResponse.body.issuer, config.host); - const doneResponse = await api('/i/2fa/done', { + const doneResponse = await api('i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); assert.strictEqual(doneResponse.status, 200); - const usersShowResponse = await api('/users/show', { + const usersShowResponse = await api('users/show', { username, }, alice); assert.strictEqual(usersShowResponse.status, 200); assert.strictEqual(usersShowResponse.body.twoFactorEnabled, true); - const signinResponse = await api('/signin', { + const signinResponse = await api('signin', { ...signinParam(), token: otpToken(registerResponse.body.secret), }); @@ -216,24 +216,24 @@ describe('2要素認証', () => { assert.notEqual(signinResponse.body.i, undefined); // 後片付け - await api('/i/2fa/unregister', { + await api('i/2fa/unregister', { password, token: otpToken(registerResponse.body.secret), }, alice); }); test('が設定でき、セキュリティキーでログインできる。', async () => { - const registerResponse = await api('/i/2fa/register', { + const registerResponse = await api('i/2fa/register', { password, }, alice); assert.strictEqual(registerResponse.status, 200); - const doneResponse = await api('/i/2fa/done', { + const doneResponse = await api('i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); assert.strictEqual(doneResponse.status, 200); - const registerKeyResponse = await api('/i/2fa/register-key', { + const registerKeyResponse = await api('i/2fa/register-key', { password, token: otpToken(registerResponse.body.secret), }, alice); @@ -243,23 +243,23 @@ describe('2要素認証', () => { const keyName = 'example-key'; const credentialId = crypto.randomBytes(0x41); - const keyDoneResponse = await api('/i/2fa/key-done', keyDoneParam({ + const keyDoneResponse = await api('i/2fa/key-done', keyDoneParam({ token: otpToken(registerResponse.body.secret), keyName, credentialId, creationOptions: registerKeyResponse.body, - }), alice); + }) as any, alice); assert.strictEqual(keyDoneResponse.status, 200); assert.strictEqual(keyDoneResponse.body.id, credentialId.toString('base64url')); assert.strictEqual(keyDoneResponse.body.name, keyName); - const usersShowResponse = await api('/users/show', { + const usersShowResponse = await api('users/show', { username, }); assert.strictEqual(usersShowResponse.status, 200); assert.strictEqual(usersShowResponse.body.securityKeys, true); - const signinResponse = await api('/signin', { + const signinResponse = await api('signin', { ...signinParam(), }); assert.strictEqual(signinResponse.status, 200); @@ -268,7 +268,7 @@ describe('2要素認証', () => { assert.notEqual(signinResponse.body.allowCredentials, undefined); assert.strictEqual(signinResponse.body.allowCredentials[0].id, credentialId.toString('base64url')); - const signinResponse2 = await api('/signin', signinWithSecurityKeyParam({ + const signinResponse2 = await api('signin', signinWithSecurityKeyParam({ keyName, credentialId, requestOptions: signinResponse.body, @@ -277,24 +277,24 @@ describe('2要素認証', () => { assert.notEqual(signinResponse2.body.i, undefined); // 後片付け - await api('/i/2fa/unregister', { + await api('i/2fa/unregister', { password, token: otpToken(registerResponse.body.secret), }, alice); }); test('が設定でき、セキュリティキーでパスワードレスログインできる。', async () => { - const registerResponse = await api('/i/2fa/register', { + const registerResponse = await api('i/2fa/register', { password, }, alice); assert.strictEqual(registerResponse.status, 200); - const doneResponse = await api('/i/2fa/done', { + const doneResponse = await api('i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); assert.strictEqual(doneResponse.status, 200); - const registerKeyResponse = await api('/i/2fa/register-key', { + const registerKeyResponse = await api('i/2fa/register-key', { token: otpToken(registerResponse.body.secret), password, }, alice); @@ -302,33 +302,33 @@ describe('2要素認証', () => { const keyName = 'example-key'; const credentialId = crypto.randomBytes(0x41); - const keyDoneResponse = await api('/i/2fa/key-done', keyDoneParam({ + const keyDoneResponse = await api('i/2fa/key-done', keyDoneParam({ token: otpToken(registerResponse.body.secret), keyName, credentialId, creationOptions: registerKeyResponse.body, - }), alice); + }) as any, alice); assert.strictEqual(keyDoneResponse.status, 200); - const passwordLessResponse = await api('/i/2fa/password-less', { + const passwordLessResponse = await api('i/2fa/password-less', { value: true, }, alice); assert.strictEqual(passwordLessResponse.status, 204); - const usersShowResponse = await api('/users/show', { + const usersShowResponse = await api('users/show', { username, }); assert.strictEqual(usersShowResponse.status, 200); assert.strictEqual(usersShowResponse.body.usePasswordLessLogin, true); - const signinResponse = await api('/signin', { + const signinResponse = await api('signin', { ...signinParam(), password: '', }); assert.strictEqual(signinResponse.status, 200); assert.strictEqual(signinResponse.body.i, undefined); - const signinResponse2 = await api('/signin', { + const signinResponse2 = await api('signin', { ...signinWithSecurityKeyParam({ keyName, credentialId, @@ -340,24 +340,24 @@ describe('2要素認証', () => { assert.notEqual(signinResponse2.body.i, undefined); // 後片付け - await api('/i/2fa/unregister', { + await api('i/2fa/unregister', { password, token: otpToken(registerResponse.body.secret), }, alice); }); test('が設定でき、設定したセキュリティキーの名前を変更できる。', async () => { - const registerResponse = await api('/i/2fa/register', { + const registerResponse = await api('i/2fa/register', { password, }, alice); assert.strictEqual(registerResponse.status, 200); - const doneResponse = await api('/i/2fa/done', { + const doneResponse = await api('i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); assert.strictEqual(doneResponse.status, 200); - const registerKeyResponse = await api('/i/2fa/register-key', { + const registerKeyResponse = await api('i/2fa/register-key', { token: otpToken(registerResponse.body.secret), password, }, alice); @@ -365,22 +365,22 @@ describe('2要素認証', () => { const keyName = 'example-key'; const credentialId = crypto.randomBytes(0x41); - const keyDoneResponse = await api('/i/2fa/key-done', keyDoneParam({ + const keyDoneResponse = await api('i/2fa/key-done', keyDoneParam({ token: otpToken(registerResponse.body.secret), keyName, credentialId, creationOptions: registerKeyResponse.body, - }), alice); + }) as any, alice); assert.strictEqual(keyDoneResponse.status, 200); const renamedKey = 'other-key'; - const updateKeyResponse = await api('/i/2fa/update-key', { + const updateKeyResponse = await api('i/2fa/update-key', { name: renamedKey, credentialId: credentialId.toString('base64url'), }, alice); assert.strictEqual(updateKeyResponse.status, 200); - const iResponse = await api('/i', { + const iResponse = await api('i', { }, alice); assert.strictEqual(iResponse.status, 200); const securityKeys = iResponse.body.securityKeysList.filter((s: { id: string; }) => s.id === credentialId.toString('base64url')); @@ -389,24 +389,24 @@ describe('2要素認証', () => { assert.notEqual(securityKeys[0].lastUsed, undefined); // 後片付け - await api('/i/2fa/unregister', { + await api('i/2fa/unregister', { password, token: otpToken(registerResponse.body.secret), }, alice); }); test('が設定でき、設定したセキュリティキーを削除できる。', async () => { - const registerResponse = await api('/i/2fa/register', { + const registerResponse = await api('i/2fa/register', { password, }, alice); assert.strictEqual(registerResponse.status, 200); - const doneResponse = await api('/i/2fa/done', { + const doneResponse = await api('i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); assert.strictEqual(doneResponse.status, 200); - const registerKeyResponse = await api('/i/2fa/register-key', { + const registerKeyResponse = await api('i/2fa/register-key', { token: otpToken(registerResponse.body.secret), password, }, alice); @@ -414,20 +414,20 @@ describe('2要素認証', () => { const keyName = 'example-key'; const credentialId = crypto.randomBytes(0x41); - const keyDoneResponse = await api('/i/2fa/key-done', keyDoneParam({ + const keyDoneResponse = await api('i/2fa/key-done', keyDoneParam({ token: otpToken(registerResponse.body.secret), keyName, credentialId, creationOptions: registerKeyResponse.body, - }), alice); + }) as any, alice); assert.strictEqual(keyDoneResponse.status, 200); // テストの実行順によっては複数残ってるので全部消す - const iResponse = await api('/i', { + const iResponse = await api('i', { }, alice); assert.strictEqual(iResponse.status, 200); for (const key of iResponse.body.securityKeysList) { - const removeKeyResponse = await api('/i/2fa/remove-key', { + const removeKeyResponse = await api('i/2fa/remove-key', { token: otpToken(registerResponse.body.secret), password, credentialId: key.id, @@ -435,13 +435,13 @@ describe('2要素認証', () => { assert.strictEqual(removeKeyResponse.status, 200); } - const usersShowResponse = await api('/users/show', { + const usersShowResponse = await api('users/show', { username, }); assert.strictEqual(usersShowResponse.status, 200); assert.strictEqual(usersShowResponse.body.securityKeys, false); - const signinResponse = await api('/signin', { + const signinResponse = await api('signin', { ...signinParam(), token: otpToken(registerResponse.body.secret), }); @@ -449,43 +449,43 @@ describe('2要素認証', () => { assert.notEqual(signinResponse.body.i, undefined); // 後片付け - await api('/i/2fa/unregister', { + await api('i/2fa/unregister', { password, token: otpToken(registerResponse.body.secret), }, alice); }); test('が設定でき、設定解除できる。(パスワードのみでログインできる。)', async () => { - const registerResponse = await api('/i/2fa/register', { + const registerResponse = await api('i/2fa/register', { password, }, alice); assert.strictEqual(registerResponse.status, 200); - const doneResponse = await api('/i/2fa/done', { + const doneResponse = await api('i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); assert.strictEqual(doneResponse.status, 200); - const usersShowResponse = await api('/users/show', { + const usersShowResponse = await api('users/show', { username, }); assert.strictEqual(usersShowResponse.status, 200); assert.strictEqual(usersShowResponse.body.twoFactorEnabled, true); - const unregisterResponse = await api('/i/2fa/unregister', { + const unregisterResponse = await api('i/2fa/unregister', { token: otpToken(registerResponse.body.secret), password, }, alice); assert.strictEqual(unregisterResponse.status, 204); - const signinResponse = await api('/signin', { + const signinResponse = await api('signin', { ...signinParam(), }); assert.strictEqual(signinResponse.status, 200); assert.notEqual(signinResponse.body.i, undefined); // 後片付け - await api('/i/2fa/unregister', { + await api('i/2fa/unregister', { password, token: otpToken(registerResponse.body.secret), }, alice); diff --git a/packages/backend/test/e2e/antennas.ts b/packages/backend/test/e2e/antennas.ts index 5c3bbaf1da..006daae07b 100644 --- a/packages/backend/test/e2e/antennas.ts +++ b/packages/backend/test/e2e/antennas.ts @@ -7,7 +7,6 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import { DEFAULT_POLICIES } from '@/core/RoleService.js'; -import type { Packed } from '@/misc/json-schema.js'; import { api, failedApiCall, @@ -29,10 +28,7 @@ describe('アンテナ', () => { // エンティティとしてのアンテナを主眼においたテストを記述する // (Antennaを返すエンドポイント、Antennaエンティティを書き換えるエンドポイント、Antennaからノートを取得するエンドポイントをテストする) - // BUG cherrypick-jsとjson-schemaが一致していない。 - // - srcのenumにgroupが残っている - // - userGroupIdが残っている, isActiveがない - type Antenna = misskey.entities.Antenna | Packed<'Antenna'>; + type Antenna = misskey.entities.Antenna; type User = misskey.entities.SignupResponse; type Note = misskey.entities.Note; @@ -49,6 +45,7 @@ describe('アンテナ', () => { users: [''], withFile: false, withReplies: false, + excludeBots: false, }; let root: User; @@ -81,7 +78,7 @@ describe('アンテナ', () => { aliceList = await userList(alice, {}); bob = await signup({ username: 'bob' }); aliceList = await userList(alice, {}); - bobFile = (await uploadFile(bob)).body; + bobFile = (await uploadFile(bob)).body!; bobList = await userList(bob); carol = await signup({ username: 'carol' }); await api('users/lists/push', { listId: aliceList.id, userId: bob.id }, alice); @@ -130,9 +127,9 @@ describe('アンテナ', () => { beforeEach(async () => { // テスト間で影響し合わないように毎回全部消す。 for (const user of [alice, bob]) { - const list = await api('/antennas/list', {}, user); + const list = await api('antennas/list', {}, user); for (const antenna of list.body) { - await api('/antennas/delete', { antennaId: antenna.id }, user); + await api('antennas/delete', { antennaId: antenna.id }, user); } } }); @@ -142,11 +139,11 @@ describe('アンテナ', () => { test('が作成できること、キーが過不足なく入っていること。', async () => { const response = await successfulApiCall({ endpoint: 'antennas/create', - parameters: { ...defaultParam }, + parameters: defaultParam, user: alice, }); assert.match(response.id, /[0-9a-z]{10}/); - const expected = { + const expected: Antenna = { id: response.id, caseSensitive: false, createdAt: new Date(response.createdAt).toISOString(), @@ -162,8 +159,9 @@ describe('アンテナ', () => { users: [''], withFile: false, withReplies: false, + excludeBots: false, localOnly: false, - } as Antenna; + }; assert.deepStrictEqual(response, expected); }); @@ -204,28 +202,28 @@ describe('アンテナ', () => { }); const antennaParamPattern = [ - { parameters: (): object => ({ name: 'x'.repeat(100) }) }, - { parameters: (): object => ({ name: 'x' }) }, - { parameters: (): object => ({ src: 'home' }) }, - { parameters: (): object => ({ src: 'all' }) }, - { parameters: (): object => ({ src: 'users' }) }, - { parameters: (): object => ({ src: 'list' }) }, - { parameters: (): object => ({ userGroupId: null }) }, - { parameters: (): object => ({ userListId: null }) }, - { parameters: (): object => ({ src: 'list', userListId: aliceList.id }) }, - { parameters: (): object => ({ keywords: [['x']] }) }, - { parameters: (): object => ({ keywords: [['a', 'b', 'c'], ['x'], ['y'], ['z']] }) }, - { parameters: (): object => ({ excludeKeywords: [['a', 'b', 'c'], ['x'], ['y'], ['z']] }) }, - { parameters: (): object => ({ users: [alice.username] }) }, - { parameters: (): object => ({ users: [alice.username, bob.username, carol.username] }) }, - { parameters: (): object => ({ caseSensitive: false }) }, - { parameters: (): object => ({ caseSensitive: true }) }, - { parameters: (): object => ({ withReplies: false }) }, - { parameters: (): object => ({ withReplies: true }) }, - { parameters: (): object => ({ withFile: false }) }, - { parameters: (): object => ({ withFile: true }) }, - { parameters: (): object => ({ notify: false }) }, - { parameters: (): object => ({ notify: true }) }, + { parameters: () => ({ name: 'x'.repeat(100) }) }, + { parameters: () => ({ name: 'x' }) }, + { parameters: () => ({ src: 'home' as const }) }, + { parameters: () => ({ src: 'all' as const }) }, + { parameters: () => ({ src: 'users' as const }) }, + { parameters: () => ({ src: 'list' as const }) }, + { parameters: () => ({ userGroupId: null }) }, + { parameters: () => ({ userListId: null }) }, + { parameters: () => ({ src: 'list' as const, userListId: aliceList.id }) }, + { parameters: () => ({ keywords: [['x']] }) }, + { parameters: () => ({ keywords: [['a', 'b', 'c'], ['x'], ['y'], ['z']] }) }, + { parameters: () => ({ excludeKeywords: [['a', 'b', 'c'], ['x'], ['y'], ['z']] }) }, + { parameters: () => ({ users: [alice.username] }) }, + { parameters: () => ({ users: [alice.username, bob.username, carol.username] }) }, + { parameters: () => ({ caseSensitive: false }) }, + { parameters: () => ({ caseSensitive: true }) }, + { parameters: () => ({ withReplies: false }) }, + { parameters: () => ({ withReplies: true }) }, + { parameters: () => ({ withFile: false }) }, + { parameters: () => ({ withFile: true }) }, + { parameters: () => ({ notify: false }) }, + { parameters: () => ({ notify: true }) }, ]; test.each(antennaParamPattern)('を作成できること($#)', async ({ parameters }) => { const response = await successfulApiCall({ @@ -338,7 +336,7 @@ describe('アンテナ', () => { test.each([ { label: '全体から', - parameters: (): object => ({ src: 'all' }), + parameters: () => ({ src: 'all' }), posts: [ { note: (): Promise => post(alice, { text: `${keyword}` }), included: true }, { note: (): Promise => post(userFollowedByAlice, { text: `${keyword}` }), included: true }, @@ -349,7 +347,7 @@ describe('アンテナ', () => { { // BUG e4144a1 以降home指定は壊れている(allと同じ) label: 'ホーム指定はallと同じ', - parameters: (): object => ({ src: 'home' }), + parameters: () => ({ src: 'home' }), posts: [ { note: (): Promise => post(alice, { text: `${keyword}` }), included: true }, { note: (): Promise => post(userFollowedByAlice, { text: `${keyword}` }), included: true }, @@ -360,7 +358,7 @@ describe('アンテナ', () => { { // https://github.com/misskey-dev/misskey/issues/9025 label: 'ただし、フォロワー限定投稿とDM投稿を含まない。フォロワーであっても。', - parameters: (): object => ({}), + parameters: () => ({}), posts: [ { note: (): Promise => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'public' }), included: true }, { note: (): Promise => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'home' }), included: true }, @@ -370,56 +368,56 @@ describe('アンテナ', () => { }, { label: 'ブロックしているユーザーのノートは含む', - parameters: (): object => ({}), + parameters: () => ({}), posts: [ { note: (): Promise => post(userBlockedByAlice, { text: `${keyword}` }), included: true }, ], }, { label: 'ブロックされているユーザーのノートは含まない', - parameters: (): object => ({}), + parameters: () => ({}), posts: [ { note: (): Promise => post(userBlockingAlice, { text: `${keyword}` }) }, ], }, { label: 'ミュートしているユーザーのノートは含まない', - parameters: (): object => ({}), + parameters: () => ({}), posts: [ { note: (): Promise => post(userMutedByAlice, { text: `${keyword}` }) }, ], }, { label: 'ミュートされているユーザーのノートは含む', - parameters: (): object => ({}), + parameters: () => ({}), posts: [ { note: (): Promise => post(userMutingAlice, { text: `${keyword}` }), included: true }, ], }, { label: '「見つけやすくする」がOFFのユーザーのノートも含まれる', - parameters: (): object => ({}), + parameters: () => ({}), posts: [ { note: (): Promise => post(userNotExplorable, { text: `${keyword}` }), included: true }, ], }, { label: '鍵付きユーザーのノートも含まれる', - parameters: (): object => ({}), + parameters: () => ({}), posts: [ { note: (): Promise => post(userLocking, { text: `${keyword}` }), included: true }, ], }, { label: 'サイレンスのノートも含まれる', - parameters: (): object => ({}), + parameters: () => ({}), posts: [ { note: (): Promise => post(userSilenced, { text: `${keyword}` }), included: true }, ], }, { label: '削除ユーザーのノートも含まれる', - parameters: (): object => ({}), + parameters: () => ({}), posts: [ { note: (): Promise => post(userDeletedBySelf, { text: `${keyword}` }), included: true }, { note: (): Promise => post(userDeletedByAdmin, { text: `${keyword}` }), included: true }, @@ -427,7 +425,7 @@ describe('アンテナ', () => { }, { label: 'ユーザー指定で', - parameters: (): object => ({ src: 'users', users: [`@${bob.username}`, `@${carol.username}`] }), + parameters: () => ({ src: 'users', users: [`@${bob.username}`, `@${carol.username}`] }), posts: [ { note: (): Promise => post(alice, { text: `test ${keyword}` }) }, { note: (): Promise => post(bob, { text: `test ${keyword}` }), included: true }, @@ -436,7 +434,7 @@ describe('アンテナ', () => { }, { label: 'リスト指定で', - parameters: (): object => ({ src: 'list', userListId: aliceList.id }), + parameters: () => ({ src: 'list', userListId: aliceList.id }), posts: [ { note: (): Promise => post(alice, { text: `test ${keyword}` }) }, { note: (): Promise => post(bob, { text: `test ${keyword}` }), included: true }, @@ -445,14 +443,14 @@ describe('アンテナ', () => { }, { label: 'CWにもマッチする', - parameters: (): object => ({ keywords: [[keyword]] }), + parameters: () => ({ keywords: [[keyword]] }), posts: [ { note: (): Promise => post(bob, { text: 'test', cw: `cw ${keyword}` }), included: true }, ], }, { label: 'キーワード1つ', - parameters: (): object => ({ keywords: [[keyword]] }), + parameters: () => ({ keywords: [[keyword]] }), posts: [ { note: (): Promise => post(alice, { text: 'test' }) }, { note: (): Promise => post(bob, { text: `test ${keyword}` }), included: true }, @@ -461,7 +459,7 @@ describe('アンテナ', () => { }, { label: 'キーワード3つ(AND)', - parameters: (): object => ({ keywords: [['A', 'B', 'C']] }), + parameters: () => ({ keywords: [['A', 'B', 'C']] }), posts: [ { note: (): Promise => post(bob, { text: 'test A' }) }, { note: (): Promise => post(bob, { text: 'test A B' }) }, @@ -472,7 +470,7 @@ describe('アンテナ', () => { }, { label: 'キーワード3つ(OR)', - parameters: (): object => ({ keywords: [['A'], ['B'], ['C']] }), + parameters: () => ({ keywords: [['A'], ['B'], ['C']] }), posts: [ { note: (): Promise => post(bob, { text: 'test' }) }, { note: (): Promise => post(bob, { text: 'test A' }), included: true }, @@ -485,7 +483,7 @@ describe('アンテナ', () => { }, { label: '除外ワード3つ(AND)', - parameters: (): object => ({ excludeKeywords: [['A', 'B', 'C']] }), + parameters: () => ({ excludeKeywords: [['A', 'B', 'C']] }), posts: [ { note: (): Promise => post(bob, { text: `test ${keyword}` }), included: true }, { note: (): Promise => post(bob, { text: `test ${keyword} A` }), included: true }, @@ -498,7 +496,7 @@ describe('アンテナ', () => { }, { label: '除外ワード3つ(OR)', - parameters: (): object => ({ excludeKeywords: [['A'], ['B'], ['C']] }), + parameters: () => ({ excludeKeywords: [['A'], ['B'], ['C']] }), posts: [ { note: (): Promise => post(bob, { text: `test ${keyword}` }), included: true }, { note: (): Promise => post(bob, { text: `test ${keyword} A` }) }, @@ -511,7 +509,7 @@ describe('アンテナ', () => { }, { label: 'キーワード1つ(大文字小文字区別する)', - parameters: (): object => ({ keywords: [['KEYWORD']], caseSensitive: true }), + parameters: () => ({ keywords: [['KEYWORD']], caseSensitive: true }), posts: [ { note: (): Promise => post(bob, { text: 'keyword' }) }, { note: (): Promise => post(bob, { text: 'kEyWoRd' }) }, @@ -520,7 +518,7 @@ describe('アンテナ', () => { }, { label: 'キーワード1つ(大文字小文字区別しない)', - parameters: (): object => ({ keywords: [['KEYWORD']], caseSensitive: false }), + parameters: () => ({ keywords: [['KEYWORD']], caseSensitive: false }), posts: [ { note: (): Promise => post(bob, { text: 'keyword' }), included: true }, { note: (): Promise => post(bob, { text: 'kEyWoRd' }), included: true }, @@ -529,7 +527,7 @@ describe('アンテナ', () => { }, { label: '除外ワード1つ(大文字小文字区別する)', - parameters: (): object => ({ excludeKeywords: [['KEYWORD']], caseSensitive: true }), + parameters: () => ({ excludeKeywords: [['KEYWORD']], caseSensitive: true }), posts: [ { note: (): Promise => post(bob, { text: `${keyword}` }), included: true }, { note: (): Promise => post(bob, { text: `${keyword} keyword` }), included: true }, @@ -539,7 +537,7 @@ describe('アンテナ', () => { }, { label: '除外ワード1つ(大文字小文字区別しない)', - parameters: (): object => ({ excludeKeywords: [['KEYWORD']], caseSensitive: false }), + parameters: () => ({ excludeKeywords: [['KEYWORD']], caseSensitive: false }), posts: [ { note: (): Promise => post(bob, { text: `${keyword}` }), included: true }, { note: (): Promise => post(bob, { text: `${keyword} keyword` }) }, @@ -549,7 +547,7 @@ describe('アンテナ', () => { }, { label: '添付ファイルを問わない', - parameters: (): object => ({ withFile: false }), + parameters: () => ({ withFile: false }), posts: [ { note: (): Promise => post(bob, { text: `${keyword}`, fileIds: [bobFile.id] }), included: true }, { note: (): Promise => post(bob, { text: `${keyword}` }), included: true }, @@ -557,7 +555,7 @@ describe('アンテナ', () => { }, { label: '添付ファイル付きのみ', - parameters: (): object => ({ withFile: true }), + parameters: () => ({ withFile: true }), posts: [ { note: (): Promise => post(bob, { text: `${keyword}`, fileIds: [bobFile.id] }), included: true }, { note: (): Promise => post(bob, { text: `${keyword}` }) }, @@ -565,7 +563,7 @@ describe('アンテナ', () => { }, { label: 'リプライ以外', - parameters: (): object => ({ withReplies: false }), + parameters: () => ({ withReplies: false }), posts: [ { note: (): Promise => post(bob, { text: `${keyword}`, replyId: alicePost.id }) }, { note: (): Promise => post(bob, { text: `${keyword}` }), included: true }, @@ -573,7 +571,7 @@ describe('アンテナ', () => { }, { label: 'リプライも含む', - parameters: (): object => ({ withReplies: true }), + parameters: () => ({ withReplies: true }), posts: [ { note: (): Promise => post(bob, { text: `${keyword}`, replyId: alicePost.id }), included: true }, { note: (): Promise => post(bob, { text: `${keyword}` }), included: true }, @@ -636,7 +634,7 @@ describe('アンテナ', () => { endpoint: 'antennas/notes', parameters: { antennaId: antenna.id, ...paginationParam }, user: alice, - }) as any as Note[]; + }); }, offsetBy, 'desc'); }); diff --git a/packages/backend/test/e2e/api-visibility.ts b/packages/backend/test/e2e/api-visibility.ts index e3c69106e4..0c38d49ae1 100644 --- a/packages/backend/test/e2e/api-visibility.ts +++ b/packages/backend/test/e2e/api-visibility.ts @@ -6,7 +6,7 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { api, post, signup } from '../utils.js'; +import { UserToken, api, post, signup } from '../utils.js'; import type * as misskey from 'cherrypick-js'; describe('API visibility', () => { @@ -24,38 +24,38 @@ describe('API visibility', () => { let target2: misskey.entities.SignupResponse; /** public-post */ - let pub: any; + let pub: misskey.entities.Note; /** home-post */ - let home: any; + let home: misskey.entities.Note; /** followers-post */ - let fol: any; + let fol: misskey.entities.Note; /** specified-post */ - let spe: any; + let spe: misskey.entities.Note; /** public-reply to target's post */ - let pubR: any; + let pubR: misskey.entities.Note; /** home-reply to target's post */ - let homeR: any; + let homeR: misskey.entities.Note; /** followers-reply to target's post */ - let folR: any; + let folR: misskey.entities.Note; /** specified-reply to target's post */ - let speR: any; + let speR: misskey.entities.Note; /** public-mention to target */ - let pubM: any; + let pubM: misskey.entities.Note; /** home-mention to target */ - let homeM: any; + let homeM: misskey.entities.Note; /** followers-mention to target */ - let folM: any; + let folM: misskey.entities.Note; /** specified-mention to target */ - let speM: any; + let speM: misskey.entities.Note; /** reply target post */ - let tgt: any; + let tgt: misskey.entities.Note; //#endregion - const show = async (noteId: any, by: any) => { - return await api('/notes/show', { + const show = async (noteId: misskey.entities.Note['id'], by?: UserToken) => { + return await api('notes/show', { noteId, }, by); }; @@ -70,7 +70,7 @@ describe('API visibility', () => { target2 = await signup({ username: 'target2' }); // follow alice <= follower - await api('/following/create', { userId: alice.id }, follower); + await api('following/create', { userId: alice.id }, follower); // normal posts pub = await post(alice, { text: 'x', visibility: 'public' }); @@ -111,7 +111,7 @@ describe('API visibility', () => { }); test('[show] public-postを未認証が見れる', async () => { - const res = await show(pub.id, null); + const res = await show(pub.id); assert.strictEqual(res.body.text, 'x'); }); @@ -132,7 +132,7 @@ describe('API visibility', () => { }); test('[show] home-postを未認証が見れる', async () => { - const res = await show(home.id, null); + const res = await show(home.id); assert.strictEqual(res.body.text, 'x'); }); @@ -153,7 +153,7 @@ describe('API visibility', () => { }); test('[show] followers-postを未認証が見れない', async () => { - const res = await show(fol.id, null); + const res = await show(fol.id); assert.strictEqual(res.body.isHidden, true); }); @@ -179,7 +179,7 @@ describe('API visibility', () => { }); test('[show] specified-postを未認証が見れない', async () => { - const res = await show(spe.id, null); + const res = await show(spe.id); assert.strictEqual(res.body.isHidden, true); }); //#endregion @@ -207,7 +207,7 @@ describe('API visibility', () => { }); test('[show] public-replyを未認証が見れる', async () => { - const res = await show(pubR.id, null); + const res = await show(pubR.id); assert.strictEqual(res.body.text, 'x'); }); @@ -233,7 +233,7 @@ describe('API visibility', () => { }); test('[show] home-replyを未認証が見れる', async () => { - const res = await show(homeR.id, null); + const res = await show(homeR.id); assert.strictEqual(res.body.text, 'x'); }); @@ -259,7 +259,7 @@ describe('API visibility', () => { }); test('[show] followers-replyを未認証が見れない', async () => { - const res = await show(folR.id, null); + const res = await show(folR.id); assert.strictEqual(res.body.isHidden, true); }); @@ -290,7 +290,7 @@ describe('API visibility', () => { }); test('[show] specified-replyを未認証が見れない', async () => { - const res = await show(speR.id, null); + const res = await show(speR.id); assert.strictEqual(res.body.isHidden, true); }); //#endregion @@ -318,7 +318,7 @@ describe('API visibility', () => { }); test('[show] public-mentionを未認証が見れる', async () => { - const res = await show(pubM.id, null); + const res = await show(pubM.id); assert.strictEqual(res.body.text, '@target x'); }); @@ -344,7 +344,7 @@ describe('API visibility', () => { }); test('[show] home-mentionを未認証が見れる', async () => { - const res = await show(homeM.id, null); + const res = await show(homeM.id); assert.strictEqual(res.body.text, '@target x'); }); @@ -370,7 +370,7 @@ describe('API visibility', () => { }); test('[show] followers-mentionを未認証が見れない', async () => { - const res = await show(folM.id, null); + const res = await show(folM.id); assert.strictEqual(res.body.isHidden, true); }); @@ -401,28 +401,28 @@ describe('API visibility', () => { }); test('[show] specified-mentionを未認証が見れない', async () => { - const res = await show(speM.id, null); + const res = await show(speM.id); assert.strictEqual(res.body.isHidden, true); }); //#endregion //#region HTL test('[HTL] public-post が 自分が見れる', async () => { - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id === pub.id); assert.strictEqual(notes[0].text, 'x'); }); test('[HTL] public-post が 非フォロワーから見れない', async () => { - const res = await api('/notes/timeline', { limit: 100 }, other); + const res = await api('notes/timeline', { limit: 100 }, other); assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id === pub.id); assert.strictEqual(notes.length, 0); }); test('[HTL] followers-post が フォロワーから見れる', async () => { - const res = await api('/notes/timeline', { limit: 100 }, follower); + const res = await api('notes/timeline', { limit: 100 }, follower); assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id === fol.id); assert.strictEqual(notes[0].text, 'x'); @@ -431,21 +431,21 @@ describe('API visibility', () => { //#region RTL test('[replies] followers-reply が フォロワーから見れる', async () => { - const res = await api('/notes/replies', { noteId: tgt.id, limit: 100 }, follower); + const res = await api('notes/replies', { noteId: tgt.id, limit: 100 }, follower); assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id === folR.id); assert.strictEqual(notes[0].text, 'x'); }); test('[replies] followers-reply が 非フォロワー (リプライ先ではない) から見れない', async () => { - const res = await api('/notes/replies', { noteId: tgt.id, limit: 100 }, other); + const res = await api('notes/replies', { noteId: tgt.id, limit: 100 }, other); assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id === folR.id); assert.strictEqual(notes.length, 0); }); test('[replies] followers-reply が 非フォロワー (リプライ先である) から見れる', async () => { - const res = await api('/notes/replies', { noteId: tgt.id, limit: 100 }, target); + const res = await api('notes/replies', { noteId: tgt.id, limit: 100 }, target); assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id === folR.id); assert.strictEqual(notes[0].text, 'x'); @@ -454,14 +454,14 @@ describe('API visibility', () => { //#region MTL test('[mentions] followers-reply が 非フォロワー (リプライ先である) から見れる', async () => { - const res = await api('/notes/mentions', { limit: 100 }, target); + const res = await api('notes/mentions', { limit: 100 }, target); assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id === folR.id); assert.strictEqual(notes[0].text, 'x'); }); test('[mentions] followers-mention が 非フォロワー (メンション先である) から見れる', async () => { - const res = await api('/notes/mentions', { limit: 100 }, target); + const res = await api('notes/mentions', { limit: 100 }, target); assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id === folM.id); assert.strictEqual(notes[0].text, '@target x'); diff --git a/packages/backend/test/e2e/api.ts b/packages/backend/test/e2e/api.ts index e5fccca16e..bc6e46421a 100644 --- a/packages/backend/test/e2e/api.ts +++ b/packages/backend/test/e2e/api.ts @@ -23,32 +23,32 @@ import type * as misskey from 'cherrypick-js'; describe('API', () => { let alice: misskey.entities.SignupResponse; let bob: misskey.entities.SignupResponse; - let carol: misskey.entities.SignupResponse; beforeAll(async () => { alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); - carol = await signup({ username: 'carol' }); }, 1000 * 60 * 2); describe('General validation', () => { test('wrong type', async () => { - const res = await api('/test', { + const res = await api('test', { required: true, + // @ts-expect-error string must be string string: 42, }); assert.strictEqual(res.status, 400); }); test('missing require param', async () => { - const res = await api('/test', { + // @ts-expect-error required is required + const res = await api('test', { string: 'a', }); assert.strictEqual(res.status, 400); }); test('invalid misskey:id (empty string)', async () => { - const res = await api('/test', { + const res = await api('test', { required: true, id: '', }); @@ -56,7 +56,7 @@ describe('API', () => { }); test('valid misskey:id', async () => { - const res = await api('/test', { + const res = await api('test', { required: true, id: '8wvhjghbxu', }); @@ -64,7 +64,7 @@ describe('API', () => { }); test('default value', async () => { - const res = await api('/test', { + const res = await api('test', { required: true, string: 'a', }); @@ -73,7 +73,7 @@ describe('API', () => { }); test('can set null even if it has default value', async () => { - const res = await api('/test', { + const res = await api('test', { required: true, nullableDefault: null, }); @@ -82,7 +82,7 @@ describe('API', () => { }); test('cannot set undefined if it has default value', async () => { - const res = await api('/test', { + const res = await api('test', { required: true, nullableDefault: undefined, }); @@ -99,14 +99,14 @@ describe('API', () => { // aliceは管理者、APIを使える await successfulApiCall({ - endpoint: '/admin/get-index-stats', + endpoint: 'admin/get-index-stats', parameters: {}, user: alice, }); // bobは一般ユーザーだからダメ await failedApiCall({ - endpoint: '/admin/get-index-stats', + endpoint: 'admin/get-index-stats', parameters: {}, user: bob, }, { @@ -117,7 +117,7 @@ describe('API', () => { // publicアクセスももちろんダメ await failedApiCall({ - endpoint: '/admin/get-index-stats', + endpoint: 'admin/get-index-stats', parameters: {}, user: undefined, }, { @@ -128,7 +128,7 @@ describe('API', () => { // ごまがしもダメ await failedApiCall({ - endpoint: '/admin/get-index-stats', + endpoint: 'admin/get-index-stats', parameters: {}, user: { token: 'tsukawasete' }, }, { @@ -138,13 +138,13 @@ describe('API', () => { }); await successfulApiCall({ - endpoint: '/admin/get-index-stats', + endpoint: 'admin/get-index-stats', parameters: {}, user: { token: application2 }, }); await failedApiCall({ - endpoint: '/admin/get-index-stats', + endpoint: 'admin/get-index-stats', parameters: {}, user: { token: application }, }, { @@ -154,7 +154,7 @@ describe('API', () => { }); await failedApiCall({ - endpoint: '/admin/get-index-stats', + endpoint: 'admin/get-index-stats', parameters: {}, user: { token: application3 }, }, { @@ -164,7 +164,7 @@ describe('API', () => { }); await failedApiCall({ - endpoint: '/admin/get-index-stats', + endpoint: 'admin/get-index-stats', parameters: {}, user: { token: application4 }, }, { @@ -177,7 +177,7 @@ describe('API', () => { describe('Authentication header', () => { test('一般リクエスト', async () => { await successfulApiCall({ - endpoint: '/admin/get-index-stats', + endpoint: 'admin/get-index-stats', parameters: {}, user: { token: alice.token, @@ -211,7 +211,7 @@ describe('API', () => { describe('tokenエラー応答でWWW-Authenticate headerを送る', () => { describe('invalid_token', () => { test('一般リクエスト', async () => { - const result = await api('/admin/get-index-stats', {}, { + const result = await api('admin/get-index-stats', {}, { token: 'noridev', bearer: true, }); @@ -246,7 +246,7 @@ describe('API', () => { describe('tokenがないとrealmだけおくる', () => { test('一般リクエスト', async () => { - const result = await api('/admin/get-index-stats', {}); + const result = await api('admin/get-index-stats', {}); assert.strictEqual(result.status, 401); assert.strictEqual(result.headers.get('WWW-Authenticate'), 'Bearer realm="CherryPick"'); }); @@ -259,7 +259,8 @@ describe('API', () => { }); test('invalid_request', async () => { - const result = await api('/notes/create', { text: true }, { + // @ts-expect-error text must be string + const result = await api('notes/create', { text: true }, { token: alice.token, bearer: true, }); diff --git a/packages/backend/test/e2e/block.ts b/packages/backend/test/e2e/block.ts index 2f2ae0380f..9244effda2 100644 --- a/packages/backend/test/e2e/block.ts +++ b/packages/backend/test/e2e/block.ts @@ -22,7 +22,7 @@ describe('Block', () => { }, 1000 * 60 * 2); test('Block作成', async () => { - const res = await api('/blocking/create', { + const res = await api('blocking/create', { userId: bob.id, }, alice); @@ -30,7 +30,7 @@ describe('Block', () => { }); test('ブロックされているユーザーをフォローできない', async () => { - const res = await api('/following/create', { userId: alice.id }, bob); + const res = await api('following/create', { userId: alice.id }, bob); assert.strictEqual(res.status, 400); assert.strictEqual(res.body.error.id, 'c4ab57cc-4e41-45e9-bfd9-584f61e35ce0'); @@ -39,7 +39,7 @@ describe('Block', () => { test('ブロックされているユーザーにリアクションできない', async () => { const note = await post(alice, { text: 'hello' }); - const res = await api('/notes/reactions/create', { noteId: note.id, reaction: '👍' }, bob); + const res = await api('notes/reactions/create', { noteId: note.id, reaction: '👍' }, bob); assert.strictEqual(res.status, 400); assert.strictEqual(res.body.error.id, '20ef5475-9f38-4e4c-bd33-de6d979498ec'); @@ -48,7 +48,7 @@ describe('Block', () => { test('ブロックされているユーザーに返信できない', async () => { const note = await post(alice, { text: 'hello' }); - const res = await api('/notes/create', { replyId: note.id, text: 'yo' }, bob); + const res = await api('notes/create', { replyId: note.id, text: 'yo' }, bob); assert.strictEqual(res.status, 400); assert.strictEqual(res.body.error.id, 'b390d7e1-8a5e-46ed-b625-06271cafd3d3'); @@ -57,7 +57,7 @@ describe('Block', () => { test('ブロックされているユーザーのノートをRenoteできない', async () => { const note = await post(alice, { text: 'hello' }); - const res = await api('/notes/create', { renoteId: note.id, text: 'yo' }, bob); + const res = await api('notes/create', { renoteId: note.id, text: 'yo' }, bob); assert.strictEqual(res.status, 400); assert.strictEqual(res.body.error.id, 'b390d7e1-8a5e-46ed-b625-06271cafd3d3'); @@ -72,12 +72,13 @@ describe('Block', () => { const bobNote = await post(bob, { text: 'hi' }); const carolNote = await post(carol, { text: 'hi' }); - const res = await api('/notes/local-timeline', {}, bob); + const res = await api('notes/local-timeline', {}, bob); + const body = res.body as misskey.entities.Note[]; assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); - assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), false); - assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); - assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true); + assert.strictEqual(body.some(note => note.id === aliceNote.id), false); + assert.strictEqual(body.some(note => note.id === bobNote.id), true); + assert.strictEqual(body.some(note => note.id === carolNote.id), true); }); }); diff --git a/packages/backend/test/e2e/clips.ts b/packages/backend/test/e2e/clips.ts index 476cbc6fdf..8144a85540 100644 --- a/packages/backend/test/e2e/clips.ts +++ b/packages/backend/test/e2e/clips.ts @@ -6,47 +6,34 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { JTDDataType } from 'ajv/dist/jtd'; import { DEFAULT_POLICIES } from '@/core/RoleService.js'; -import type { Packed } from '@/misc/json-schema.js'; -import { paramDef as CreateParamDef } from '@/server/api/endpoints/clips/create.js'; -import { paramDef as UpdateParamDef } from '@/server/api/endpoints/clips/update.js'; -import { paramDef as DeleteParamDef } from '@/server/api/endpoints/clips/delete.js'; -import { paramDef as ShowParamDef } from '@/server/api/endpoints/clips/show.js'; -import { paramDef as FavoriteParamDef } from '@/server/api/endpoints/clips/favorite.js'; -import { paramDef as UnfavoriteParamDef } from '@/server/api/endpoints/clips/unfavorite.js'; -import { paramDef as AddNoteParamDef } from '@/server/api/endpoints/clips/add-note.js'; -import { paramDef as RemoveNoteParamDef } from '@/server/api/endpoints/clips/remove-note.js'; -import { paramDef as NotesParamDef } from '@/server/api/endpoints/clips/notes.js'; import { api, ApiRequest, failedApiCall, hiddenNote, post, signup, successfulApiCall } from '../utils.js'; +import type * as Misskey from 'cherrypick-js'; + +type Optional = Pick, K> & Omit; describe('クリップ', () => { - type User = Packed<'User'>; - type Note = Packed<'Note'>; - type Clip = Packed<'Clip'>; - - let alice: User; - let bob: User; - let aliceNote: Note; - let aliceHomeNote: Note; - let aliceFollowersNote: Note; - let aliceSpecifiedNote: Note; - let bobNote: Note; - let bobHomeNote: Note; - let bobFollowersNote: Note; - let bobSpecifiedNote: Note; + let alice: Misskey.entities.SignupResponse; + let bob: Misskey.entities.SignupResponse; + let aliceNote: Misskey.entities.Note; + let aliceHomeNote: Misskey.entities.Note; + let aliceFollowersNote: Misskey.entities.Note; + let aliceSpecifiedNote: Misskey.entities.Note; + let bobNote: Misskey.entities.Note; + let bobHomeNote: Misskey.entities.Note; + let bobFollowersNote: Misskey.entities.Note; + let bobSpecifiedNote: Misskey.entities.Note; const compareBy = (selector: (s: T) => string = (s: T): string => s.id) => (a: T, b: T): number => { return selector(a).localeCompare(selector(b)); }; - type CreateParam = JTDDataType; - const defaultCreate = (): Partial => ({ + const defaultCreate = (): Pick => ({ name: 'test', }); - const create = async (parameters: Partial = {}, request: Partial = {}): Promise => { - const clip = await successfulApiCall({ - endpoint: '/clips/create', + const create = async (parameters: Partial = {}, request: Partial> = {}): Promise => { + const clip = await successfulApiCall({ + endpoint: 'clips/create', parameters: { ...defaultCreate(), ...parameters, @@ -64,17 +51,16 @@ describe('クリップ', () => { return clip; }; - const createMany = async (parameters: Partial, count = 10, user = alice): Promise => { + const createMany = async (parameters: Partial, count = 10, user = alice): Promise => { return await Promise.all([...Array(count)].map((_, i) => create({ name: `test${i}`, ...parameters, }, { user }))); }; - type UpdateParam = JTDDataType; - const update = async (parameters: Partial, request: Partial = {}): Promise => { - const clip = await successfulApiCall({ - endpoint: '/clips/update', + const update = async (parameters: Optional, request: Partial> = {}): Promise => { + const clip = await successfulApiCall({ + endpoint: 'clips/update', parameters: { name: 'updated', ...parameters, @@ -92,41 +78,39 @@ describe('クリップ', () => { return clip; }; - type DeleteParam = JTDDataType; - const deleteClip = async (parameters: DeleteParam, request: Partial = {}): Promise => { - return await successfulApiCall({ - endpoint: '/clips/delete', + const deleteClip = async (parameters: Misskey.entities.ClipsDeleteRequest, request: Partial> = {}): Promise => { + return await successfulApiCall({ + endpoint: 'clips/delete', parameters, user: alice, ...request, }, { status: 204, - }); + }) as any as void; }; - type ShowParam = JTDDataType; - const show = async (parameters: ShowParam, request: Partial = {}): Promise => { - return await successfulApiCall({ - endpoint: '/clips/show', + const show = async (parameters: Misskey.entities.ClipsShowRequest, request: Partial> = {}): Promise => { + return await successfulApiCall({ + endpoint: 'clips/show', parameters, user: alice, ...request, }); }; - const list = async (request: Partial): Promise => { - return successfulApiCall({ - endpoint: '/clips/list', + const list = async (request: Partial>): Promise => { + return successfulApiCall({ + endpoint: 'clips/list', parameters: {}, user: alice, ...request, }); }; - const usersClips = async (request: Partial): Promise => { - return await successfulApiCall({ - endpoint: '/users/clips', - parameters: {}, + const usersClips = async (parameters: Misskey.entities.UsersClipsRequest, request: Partial> = {}): Promise => { + return await successfulApiCall({ + endpoint: 'users/clips', + parameters, user: alice, ...request, }); @@ -136,23 +120,22 @@ describe('クリップ', () => { alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); - // FIXME: cherrypick-jsのNoteはoutdatedなので直接変換できない - aliceNote = await post(alice, { text: 'test' }) as any; - aliceHomeNote = await post(alice, { text: 'home only', visibility: 'home' }) as any; - aliceFollowersNote = await post(alice, { text: 'followers only', visibility: 'followers' }) as any; - aliceSpecifiedNote = await post(alice, { text: 'specified only', visibility: 'specified' }) as any; - bobNote = await post(bob, { text: 'test' }) as any; - bobHomeNote = await post(bob, { text: 'home only', visibility: 'home' }) as any; - bobFollowersNote = await post(bob, { text: 'followers only', visibility: 'followers' }) as any; - bobSpecifiedNote = await post(bob, { text: 'specified only', visibility: 'specified' }) as any; + aliceNote = await post(alice, { text: 'test' }); + aliceHomeNote = await post(alice, { text: 'home only', visibility: 'home' }); + aliceFollowersNote = await post(alice, { text: 'followers only', visibility: 'followers' }); + aliceSpecifiedNote = await post(alice, { text: 'specified only', visibility: 'specified' }); + bobNote = await post(bob, { text: 'test' }); + bobHomeNote = await post(bob, { text: 'home only', visibility: 'home' }); + bobFollowersNote = await post(bob, { text: 'followers only', visibility: 'followers' }); + bobSpecifiedNote = await post(bob, { text: 'specified only', visibility: 'specified' }); }, 1000 * 60 * 2); afterEach(async () => { // テスト間で影響し合わないように毎回全部消す。 for (const user of [alice, bob]) { - const list = await api('/clips/list', { limit: 11 }, user); + const list = await api('clips/list', { limit: 11 }, user); for (const clip of list.body) { - await api('/clips/delete', { clipId: clip.id }, user); + await api('clips/delete', { clipId: clip.id }, user); } } }); @@ -177,7 +160,7 @@ describe('クリップ', () => { } await failedApiCall({ - endpoint: '/clips/create', + endpoint: 'clips/create', parameters: defaultCreate(), user: alice, }, { @@ -204,7 +187,8 @@ describe('クリップ', () => { { label: 'descriptionが最大長+1', parameters: { description: 'a'.repeat(2049) } }, ]; test.each(createClipDenyPattern)('の作成は$labelならできない', async ({ parameters }) => failedApiCall({ - endpoint: '/clips/create', + endpoint: 'clips/create', + // @ts-expect-error invalid params parameters: { ...defaultCreate(), ...parameters, @@ -246,15 +230,15 @@ describe('クリップ', () => { code: 'NO_SUCH_CLIP', id: 'b4d92d70-b216-46fa-9a3f-a8c811699257', } }, - { label: '他人のクリップ', user: (): User => bob, assertion: { + { label: '他人のクリップ', user: () => bob, assertion: { code: 'NO_SUCH_CLIP', id: 'b4d92d70-b216-46fa-9a3f-a8c811699257', } }, ...createClipDenyPattern as any, ])('の更新は$labelならできない', async ({ parameters, user, assertion }) => failedApiCall({ - endpoint: '/clips/update', + endpoint: 'clips/update', parameters: { - clipId: (await create({}, { user: (user ?? ((): User => alice))() })).id, + clipId: (await create({}, { user: (user ?? (() => alice))() })).id, name: 'updated', ...parameters, }, @@ -279,14 +263,15 @@ describe('クリップ', () => { code: 'NO_SUCH_CLIP', id: '70ca08ba-6865-4630-b6fb-8494759aa754', } }, - { label: '他人のクリップ', user: (): User => bob, assertion: { + { label: '他人のクリップ', user: () => bob, assertion: { code: 'NO_SUCH_CLIP', id: '70ca08ba-6865-4630-b6fb-8494759aa754', } }, ])('の削除は$labelならできない', async ({ parameters, user, assertion }) => failedApiCall({ - endpoint: '/clips/delete', + endpoint: 'clips/delete', parameters: { - clipId: (await create({}, { user: (user ?? ((): User => alice))() })).id, + // @ts-expect-error clipId must not be null + clipId: (await create({}, { user: (user ?? (() => alice))() })).id, ...parameters, }, user: alice, @@ -306,7 +291,7 @@ describe('クリップ', () => { test('のID指定取得は他人のPrivateなクリップは取得できない', async () => { const clip = await create({ isPublic: false }, { user: bob } ); failedApiCall({ - endpoint: '/clips/show', + endpoint: 'clips/show', parameters: { clipId: clip.id }, user: alice, }, { @@ -323,7 +308,8 @@ describe('クリップ', () => { id: 'c3c5fe33-d62c-44d2-9ea5-d997703f5c20', } }, ])('のID指定取得は$labelならできない', async ({ parameters, assetion }) => failedApiCall({ - endpoint: '/clips/show', + endpoint: 'clips/show', + // @ts-expect-error clipId must not be undefined parameters: { ...parameters, }, @@ -356,27 +342,23 @@ describe('クリップ', () => { test('の一覧が取得できる(空)', async () => { const res = await usersClips({ - parameters: { - userId: alice.id, - }, + userId: alice.id, }); assert.deepStrictEqual(res, []); }); test.each([ { label: '' }, - { label: '他人アカウントから', user: (): User => bob }, + { label: '他人アカウントから', user: () => bob }, ])('の一覧が$label取得できる', async () => { const clips = await createMany({ isPublic: true }); const res = await usersClips({ - parameters: { - userId: alice.id, - }, + userId: alice.id, }); // 返ってくる配列には順序保障がないのでidでソートして厳密比較 assert.deepStrictEqual( - res.sort(compareBy(s => s.id)), + res.sort(compareBy(s => s.id)), clips.sort(compareBy(s => s.id))); // 認証状態で見たときだけisFavoritedが入っている @@ -386,17 +368,16 @@ describe('クリップ', () => { }); test.each([ - { label: '未認証', user: (): undefined => undefined }, + { label: '未認証', user: () => undefined }, { label: '存在しないユーザーのもの', parameters: { userId: 'xxxxxxx' } }, ])('の一覧は$labelでも取得できる', async ({ parameters, user }) => { const clips = await createMany({ isPublic: true }); const res = await usersClips({ - parameters: { - userId: alice.id, - limit: clips.length, - ...parameters, - }, - user: (user ?? ((): User => alice))(), + userId: alice.id, + limit: clips.length, + ...parameters, + }, { + user: (user ?? (() => alice))(), }); // 未認証で見たときはisFavoritedは入らない @@ -409,10 +390,8 @@ describe('クリップ', () => { await create({ isPublic: false }); const aliceClip = await create({ isPublic: true }); const res = await usersClips({ - parameters: { - userId: alice.id, - limit: 2, - }, + userId: alice.id, + limit: 2, }); assert.deepStrictEqual(res, [aliceClip]); }); @@ -421,17 +400,15 @@ describe('クリップ', () => { const clips = await createMany({ isPublic: true }, 7); clips.sort(compareBy(s => s.id)); const res = await usersClips({ - parameters: { - userId: alice.id, - sinceId: clips[1].id, - untilId: clips[5].id, - limit: 4, - }, + userId: alice.id, + sinceId: clips[1].id, + untilId: clips[5].id, + limit: 4, }); // Promise.allで返ってくる配列には順序保障がないのでidでソートして厳密比較 assert.deepStrictEqual( - res.sort(compareBy(s => s.id)), + res.sort(compareBy(s => s.id)), [clips[2], clips[3], clips[4]], // sinceIdとuntilId自体は結果に含まれない clips[1].id + ' ... ' + clips[3].id + ' with ' + clips.map(s => s.id) + ' vs. ' + res.map(s => s.id)); }); @@ -441,8 +418,9 @@ describe('クリップ', () => { { label: 'limitゼロ', parameters: { limit: 0 } }, { label: 'limit最大+1', parameters: { limit: 101 } }, ])('の一覧は$labelだと取得できない', async ({ parameters }) => failedApiCall({ - endpoint: '/users/clips', + endpoint: 'users/clips', parameters: { + // @ts-expect-error userId must not be undefined userId: alice.id, ...parameters, }, @@ -454,15 +432,15 @@ describe('クリップ', () => { })); test.each([ - { label: '作成', endpoint: '/clips/create' }, - { label: '更新', endpoint: '/clips/update' }, - { label: '削除', endpoint: '/clips/delete' }, - { label: '取得', endpoint: '/clips/list' }, - { label: 'お気に入り設定', endpoint: '/clips/favorite' }, - { label: 'お気に入り解除', endpoint: '/clips/unfavorite' }, - { label: 'お気に入り取得', endpoint: '/clips/my-favorites' }, - { label: 'ノート追加', endpoint: '/clips/add-note' }, - { label: 'ノート削除', endpoint: '/clips/remove-note' }, + { label: '作成', endpoint: 'clips/create' as const }, + { label: '更新', endpoint: 'clips/update' as const }, + { label: '削除', endpoint: 'clips/delete' as const }, + { label: '取得', endpoint: 'clips/list' as const }, + { label: 'お気に入り設定', endpoint: 'clips/favorite' as const }, + { label: 'お気に入り解除', endpoint: 'clips/unfavorite' as const }, + { label: 'お気に入り取得', endpoint: 'clips/my-favorites' as const }, + { label: 'ノート追加', endpoint: 'clips/add-note' as const }, + { label: 'ノート削除', endpoint: 'clips/remove-note' as const }, ])('の$labelは未認証ではできない', async ({ endpoint }) => await failedApiCall({ endpoint: endpoint, parameters: {}, @@ -474,35 +452,33 @@ describe('クリップ', () => { })); describe('のお気に入り', () => { - let aliceClip: Clip; + let aliceClip: Misskey.entities.Clip; - type FavoriteParam = JTDDataType; - const favorite = async (parameters: FavoriteParam, request: Partial = {}): Promise => { - return successfulApiCall({ - endpoint: '/clips/favorite', + const favorite = async (parameters: Misskey.entities.ClipsFavoriteRequest, request: Partial> = {}): Promise => { + return successfulApiCall({ + endpoint: 'clips/favorite', parameters, user: alice, ...request, }, { status: 204, - }); + }) as any as void; }; - type UnfavoriteParam = JTDDataType; - const unfavorite = async (parameters: UnfavoriteParam, request: Partial = {}): Promise => { - return successfulApiCall({ - endpoint: '/clips/unfavorite', + const unfavorite = async (parameters: Misskey.entities.ClipsUnfavoriteRequest, request: Partial> = {}): Promise => { + return successfulApiCall({ + endpoint: 'clips/unfavorite', parameters, user: alice, ...request, }, { status: 204, - }); + }) as any as void; }; - const myFavorites = async (request: Partial = {}): Promise => { - return successfulApiCall({ - endpoint: '/clips/my-favorites', + const myFavorites = async (request: Partial> = {}): Promise => { + return successfulApiCall({ + endpoint: 'clips/my-favorites', parameters: {}, user: alice, ...request, @@ -568,7 +544,7 @@ describe('クリップ', () => { test('は同じクリップに対して二回設定できない。', async () => { await favorite({ clipId: aliceClip.id }); await failedApiCall({ - endpoint: '/clips/favorite', + endpoint: 'clips/favorite', parameters: { clipId: aliceClip.id, }, @@ -586,14 +562,15 @@ describe('クリップ', () => { code: 'NO_SUCH_CLIP', id: '4c2aaeae-80d8-4250-9606-26cb1fdb77a5', } }, - { label: '他人のクリップ', user: (): User => bob, assertion: { + { label: '他人のクリップ', user: () => bob, assertion: { code: 'NO_SUCH_CLIP', id: '4c2aaeae-80d8-4250-9606-26cb1fdb77a5', } }, ])('の設定は$labelならできない', async ({ parameters, user, assertion }) => failedApiCall({ - endpoint: '/clips/favorite', + endpoint: 'clips/favorite', parameters: { - clipId: (await create({}, { user: (user ?? ((): User => alice))() })).id, + // @ts-expect-error clipId must not be null + clipId: (await create({}, { user: (user ?? (() => alice))() })).id, ...parameters, }, user: alice, @@ -619,7 +596,7 @@ describe('クリップ', () => { code: 'NO_SUCH_CLIP', id: '2603966e-b865-426c-94a7-af4a01241dc1', } }, - { label: '他人のクリップ', user: (): User => bob, assertion: { + { label: '他人のクリップ', user: () => bob, assertion: { code: 'NOT_FAVORITED', id: '90c3a9e8-b321-4dae-bf57-2bf79bbcc187', } }, @@ -628,9 +605,10 @@ describe('クリップ', () => { id: '90c3a9e8-b321-4dae-bf57-2bf79bbcc187', } }, ])('の設定解除は$labelならできない', async ({ parameters, user, assertion }) => failedApiCall({ - endpoint: '/clips/unfavorite', + endpoint: 'clips/unfavorite', parameters: { - clipId: (await create({}, { user: (user ?? ((): User => alice))() })).id, + // @ts-expect-error clipId must not be null + clipId: (await create({}, { user: (user ?? (() => alice))() })).id, ...parameters, }, user: alice, @@ -655,41 +633,38 @@ describe('クリップ', () => { }); describe('に紐づくノート', () => { - let aliceClip: Clip; + let aliceClip: Misskey.entities.Clip; - const sampleNotes = (): Note[] => [ + const sampleNotes = (): Misskey.entities.Note[] => [ aliceNote, aliceHomeNote, aliceFollowersNote, aliceSpecifiedNote, bobNote, bobHomeNote, bobFollowersNote, bobSpecifiedNote, ]; - type AddNoteParam = JTDDataType; - const addNote = async (parameters: AddNoteParam, request: Partial = {}): Promise => { - return successfulApiCall({ - endpoint: '/clips/add-note', + const addNote = async (parameters: Misskey.entities.ClipsAddNoteRequest, request: Partial> = {}): Promise => { + return successfulApiCall({ + endpoint: 'clips/add-note', parameters, user: alice, ...request, }, { status: 204, - }); + }) as any as void; }; - type RemoveNoteParam = JTDDataType; - const removeNote = async (parameters: RemoveNoteParam, request: Partial = {}): Promise => { - return successfulApiCall({ - endpoint: '/clips/remove-note', + const removeNote = async (parameters: Misskey.entities.ClipsRemoveNoteRequest, request: Partial> = {}): Promise => { + return successfulApiCall({ + endpoint: 'clips/remove-note', parameters, user: alice, ...request, }, { status: 204, - }); + }) as any as void; }; - type NotesParam = JTDDataType; - const notes = async (parameters: Partial, request: Partial = {}): Promise => { - return successfulApiCall({ - endpoint: '/clips/notes', + const notes = async (parameters: Misskey.entities.ClipsNotesRequest, request: Partial> = {}): Promise => { + return successfulApiCall({ + endpoint: 'clips/notes', parameters, user: alice, ...request, @@ -715,7 +690,7 @@ describe('クリップ', () => { test('として同じノートを二回紐づけることはできない', async () => { await addNote({ clipId: aliceClip.id, noteId: aliceNote.id }); await failedApiCall({ - endpoint: '/clips/add-note', + endpoint: 'clips/add-note', parameters: { clipId: aliceClip.id, noteId: aliceNote.id, @@ -733,11 +708,11 @@ describe('クリップ', () => { const noteLimit = DEFAULT_POLICIES.noteEachClipsLimit + 1; const noteList = await Promise.all([...Array(noteLimit)].map((_, i) => post(alice, { text: `test ${i}`, - }) as unknown)) as Note[]; + }) as unknown)) as Misskey.entities.Note[]; await Promise.all(noteList.map(s => addNote({ clipId: aliceClip.id, noteId: s.id }))); await failedApiCall({ - endpoint: '/clips/add-note', + endpoint: 'clips/add-note', parameters: { clipId: aliceClip.id, noteId: aliceNote.id, @@ -751,7 +726,7 @@ describe('クリップ', () => { }); test('は他人のクリップへ追加できない。', async () => await failedApiCall({ - endpoint: '/clips/add-note', + endpoint: 'clips/add-note', parameters: { clipId: aliceClip.id, noteId: aliceNote.id, @@ -774,18 +749,20 @@ describe('クリップ', () => { code: 'NO_SUCH_NOTE', id: 'fc8c0b49-c7a3-4664-a0a6-b418d386bb8b', } }, - { label: '他人のクリップ', user: (): object => bob, assetion: { + { label: '他人のクリップ', user: () => bob, assetion: { code: 'NO_SUCH_CLIP', id: 'd6e76cc0-a1b5-4c7c-a287-73fa9c716dcf', } }, ])('の追加は$labelだとできない', async ({ parameters, user, assetion }) => failedApiCall({ - endpoint: '/clips/add-note', + endpoint: 'clips/add-note', parameters: { + // @ts-expect-error clipId must not be undefined clipId: aliceClip.id, + // @ts-expect-error noteId must not be undefined noteId: aliceNote.id, ...parameters, }, - user: (user ?? ((): User => alice))(), + user: (user ?? (() => alice))(), }, { status: 400, code: 'INVALID_PARAM', @@ -810,18 +787,20 @@ describe('クリップ', () => { code: 'NO_SUCH_NOTE', id: 'aff017de-190e-434b-893e-33a9ff5049d8', // add-noteと異なる } }, - { label: '他人のクリップ', user: (): object => bob, assetion: { + { label: '他人のクリップ', user: () => bob, assetion: { code: 'NO_SUCH_CLIP', id: 'b80525c6-97f7-49d7-a42d-ebccd49cfd52', // add-noteと異なる } }, ])('の削除は$labelだとできない', async ({ parameters, user, assetion }) => failedApiCall({ - endpoint: '/clips/remove-note', + endpoint: 'clips/remove-note', parameters: { + // @ts-expect-error clipId must not be undefined clipId: aliceClip.id, + // @ts-expect-error noteId must not be undefined noteId: aliceNote.id, ...parameters, }, - user: (user ?? ((): User => alice))(), + user: (user ?? (() => alice))(), }, { status: 400, code: 'INVALID_PARAM', @@ -925,21 +904,22 @@ describe('クリップ', () => { code: 'NO_SUCH_CLIP', id: '1d7645e6-2b6d-4635-b0fe-fe22b0e72e00', } }, - { label: '他人のPrivateなクリップから', user: (): object => bob, assertion: { + { label: '他人のPrivateなクリップから', user: () => bob, assertion: { code: 'NO_SUCH_CLIP', id: '1d7645e6-2b6d-4635-b0fe-fe22b0e72e00', } }, - { label: '未認証でPrivateなクリップから', user: (): undefined => undefined, assertion: { + { label: '未認証でPrivateなクリップから', user: () => undefined, assertion: { code: 'NO_SUCH_CLIP', id: '1d7645e6-2b6d-4635-b0fe-fe22b0e72e00', } }, ])('は$labelだと取得できない', async ({ parameters, user, assertion }) => failedApiCall({ - endpoint: '/clips/notes', + endpoint: 'clips/notes', parameters: { + // @ts-expect-error clipId must not be undefined clipId: aliceClip.id, ...parameters, }, - user: (user ?? ((): User => alice))(), + user: (user ?? (() => alice))(), }, { status: 400, code: 'INVALID_PARAM', diff --git a/packages/backend/test/e2e/drive.ts b/packages/backend/test/e2e/drive.ts index 74dc5db829..dedc318fe1 100644 --- a/packages/backend/test/e2e/drive.ts +++ b/packages/backend/test/e2e/drive.ts @@ -6,21 +6,14 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { MiNote } from '@/models/Note.js'; -import type { Packed } from '@/misc/json-schema.js'; -import { api, initTestDb, makeStreamCatcher, post, signup, uploadFile } from '../utils.js'; +import { api, makeStreamCatcher, post, signup, uploadFile } from '../utils.js'; import type * as misskey from 'cherrypick-js'; -import type{ Repository } from 'typeorm'; describe('Drive', () => { - let Notes: Repository; - let alice: misskey.entities.SignupResponse; let bob: misskey.entities.SignupResponse; beforeAll(async () => { - const connection = await initTestDb(true); - Notes = connection.getRepository(MiNote); alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); }, 1000 * 60 * 2); @@ -36,7 +29,7 @@ describe('Drive', () => { alice, 'main', (msg) => msg.type === 'urlUploadFinished' && msg.body.marker === marker, - (msg) => msg.body.file as Packed<'DriveFile'>, + (msg) => msg.body.file, 10 * 1000); const res = await api('drive/files/upload-from-url', { @@ -90,4 +83,3 @@ describe('Drive', () => { assert.strictEqual('error' in res.body, true); }); }); - diff --git a/packages/backend/test/e2e/endpoints.ts b/packages/backend/test/e2e/endpoints.ts index 0a81fb9a6a..5d3f230b36 100644 --- a/packages/backend/test/e2e/endpoints.ts +++ b/packages/backend/test/e2e/endpoints.ts @@ -79,6 +79,7 @@ describe('Endpoints', () => { test('クエリをインジェクションできない', async () => { const res = await api('signin', { username: 'test1', + // @ts-expect-error password must be string password: { $gt: '', }, @@ -103,7 +104,7 @@ describe('Endpoints', () => { const myLocation = '七森中'; const myBirthday = '2000-09-07'; - const res = await api('/i/update', { + const res = await api('i/update', { name: myName, location: myLocation, birthday: myBirthday, @@ -117,7 +118,7 @@ describe('Endpoints', () => { }); test('名前を空白にできる', async () => { - const res = await api('/i/update', { + const res = await api('i/update', { name: ' ', }, alice); assert.strictEqual(res.status, 200); @@ -125,11 +126,11 @@ describe('Endpoints', () => { }); test('誕生日の設定を削除できる', async () => { - await api('/i/update', { + await api('i/update', { birthday: '2000-09-07', }, alice); - const res = await api('/i/update', { + const res = await api('i/update', { birthday: null, }, alice); @@ -139,7 +140,7 @@ describe('Endpoints', () => { }); test('不正な誕生日の形式で怒られる', async () => { - const res = await api('/i/update', { + const res = await api('i/update', { birthday: '2000/09/07', }, alice); assert.strictEqual(res.status, 400); @@ -148,7 +149,7 @@ describe('Endpoints', () => { describe('users/show', () => { test('ユーザーが取得できる', async () => { - const res = await api('/users/show', { + const res = await api('users/show', { userId: alice.id, }, alice); @@ -158,14 +159,14 @@ describe('Endpoints', () => { }); test('ユーザーが存在しなかったら怒る', async () => { - const res = await api('/users/show', { + const res = await api('users/show', { userId: '000000000000000000000000', }); assert.strictEqual(res.status, 404); }); test('間違ったIDで怒られる', async () => { - const res = await api('/users/show', { + const res = await api('users/show', { userId: 'kyoppie', }); assert.strictEqual(res.status, 404); @@ -178,7 +179,7 @@ describe('Endpoints', () => { text: 'test', }); - const res = await api('/notes/show', { + const res = await api('notes/show', { noteId: myPost.id, }, alice); @@ -189,14 +190,14 @@ describe('Endpoints', () => { }); test('投稿が存在しなかったら怒る', async () => { - const res = await api('/notes/show', { + const res = await api('notes/show', { noteId: '000000000000000000000000', }); assert.strictEqual(res.status, 400); }); test('間違ったIDで怒られる', async () => { - const res = await api('/notes/show', { + const res = await api('notes/show', { noteId: 'kyoppie', }); assert.strictEqual(res.status, 400); @@ -207,14 +208,14 @@ describe('Endpoints', () => { test('リアクションできる', async () => { const bobPost = await post(bob, { text: 'hi' }); - const res = await api('/notes/reactions/create', { + const res = await api('notes/reactions/create', { noteId: bobPost.id, reaction: '🚀', }, alice); assert.strictEqual(res.status, 204); - const resNote = await api('/notes/show', { + const resNote = await api('notes/show', { noteId: bobPost.id, }, alice); @@ -225,7 +226,7 @@ describe('Endpoints', () => { test('自分の投稿にもリアクションできる', async () => { const myPost = await post(alice, { text: 'hi' }); - const res = await api('/notes/reactions/create', { + const res = await api('notes/reactions/create', { noteId: myPost.id, reaction: '🚀', }, alice); @@ -236,19 +237,19 @@ describe('Endpoints', () => { test('二重にリアクションすると上書きされる', async () => { const bobPost = await post(bob, { text: 'hi' }); - await api('/notes/reactions/create', { + await api('notes/reactions/create', { noteId: bobPost.id, reaction: '🥰', }, alice); - const res = await api('/notes/reactions/create', { + const res = await api('notes/reactions/create', { noteId: bobPost.id, reaction: '🚀', }, alice); assert.strictEqual(res.status, 204); - const resNote = await api('/notes/show', { + const resNote = await api('notes/show', { noteId: bobPost.id, }, alice); @@ -257,7 +258,7 @@ describe('Endpoints', () => { }); test('存在しない投稿にはリアクションできない', async () => { - const res = await api('/notes/reactions/create', { + const res = await api('notes/reactions/create', { noteId: '000000000000000000000000', reaction: '🚀', }, alice); @@ -266,13 +267,14 @@ describe('Endpoints', () => { }); test('空のパラメータで怒られる', async () => { - const res = await api('/notes/reactions/create', {}, alice); + // @ts-expect-error param must not be empty + const res = await api('notes/reactions/create', {}, alice); assert.strictEqual(res.status, 400); }); test('間違ったIDで怒られる', async () => { - const res = await api('/notes/reactions/create', { + const res = await api('notes/reactions/create', { noteId: 'kyoppie', reaction: '🚀', }, alice); @@ -283,7 +285,7 @@ describe('Endpoints', () => { describe('following/create', () => { test('フォローできる', async () => { - const res = await api('/following/create', { + const res = await api('following/create', { userId: alice.id, }, bob); @@ -301,7 +303,7 @@ describe('Endpoints', () => { }); test('既にフォローしている場合は怒る', async () => { - const res = await api('/following/create', { + const res = await api('following/create', { userId: alice.id, }, bob); @@ -309,7 +311,7 @@ describe('Endpoints', () => { }); test('存在しないユーザーはフォローできない', async () => { - const res = await api('/following/create', { + const res = await api('following/create', { userId: '000000000000000000000000', }, alice); @@ -317,7 +319,7 @@ describe('Endpoints', () => { }); test('自分自身はフォローできない', async () => { - const res = await api('/following/create', { + const res = await api('following/create', { userId: alice.id, }, alice); @@ -325,13 +327,14 @@ describe('Endpoints', () => { }); test('空のパラメータで怒られる', async () => { - const res = await api('/following/create', {}, alice); + // @ts-expect-error params must not be empty + const res = await api('following/create', {}, alice); assert.strictEqual(res.status, 400); }); test('間違ったIDで怒られる', async () => { - const res = await api('/following/create', { + const res = await api('following/create', { userId: 'foo', }, alice); @@ -341,11 +344,11 @@ describe('Endpoints', () => { describe('following/delete', () => { test('フォロー解除できる', async () => { - await api('/following/create', { + await api('following/create', { userId: alice.id, }, bob); - const res = await api('/following/delete', { + const res = await api('following/delete', { userId: alice.id, }, bob); @@ -363,7 +366,7 @@ describe('Endpoints', () => { }); test('フォローしていない場合は怒る', async () => { - const res = await api('/following/delete', { + const res = await api('following/delete', { userId: alice.id, }, bob); @@ -371,7 +374,7 @@ describe('Endpoints', () => { }); test('存在しないユーザーはフォロー解除できない', async () => { - const res = await api('/following/delete', { + const res = await api('following/delete', { userId: '000000000000000000000000', }, alice); @@ -379,7 +382,7 @@ describe('Endpoints', () => { }); test('自分自身はフォロー解除できない', async () => { - const res = await api('/following/delete', { + const res = await api('following/delete', { userId: alice.id, }, alice); @@ -387,13 +390,14 @@ describe('Endpoints', () => { }); test('空のパラメータで怒られる', async () => { - const res = await api('/following/delete', {}, alice); + // @ts-expect-error params must not be empty + const res = await api('following/delete', {}, alice); assert.strictEqual(res.status, 400); }); test('間違ったIDで怒られる', async () => { - const res = await api('/following/delete', { + const res = await api('following/delete', { userId: 'kyoppie', }, alice); @@ -403,20 +407,20 @@ describe('Endpoints', () => { describe('channels/search', () => { test('空白検索で一覧を取得できる', async () => { - await api('/channels/create', { + await api('channels/create', { name: 'aaa', description: 'bbb', }, bob); - await api('/channels/create', { + await api('channels/create', { name: 'ccc1', description: 'ddd1', }, bob); - await api('/channels/create', { + await api('channels/create', { name: 'ccc2', description: 'ddd2', }, bob); - const res = await api('/channels/search', { + const res = await api('channels/search', { query: '', }, bob); @@ -425,7 +429,7 @@ describe('Endpoints', () => { assert.strictEqual(res.body.length, 3); }); test('名前のみの検索で名前を検索できる', async () => { - const res = await api('/channels/search', { + const res = await api('channels/search', { query: 'aaa', type: 'nameOnly', }, bob); @@ -436,7 +440,7 @@ describe('Endpoints', () => { assert.strictEqual(res.body[0].name, 'aaa'); }); test('名前のみの検索で名前を複数検索できる', async () => { - const res = await api('/channels/search', { + const res = await api('channels/search', { query: 'ccc', type: 'nameOnly', }, bob); @@ -446,7 +450,7 @@ describe('Endpoints', () => { assert.strictEqual(res.body.length, 2); }); test('名前のみの検索で説明は検索できない', async () => { - const res = await api('/channels/search', { + const res = await api('channels/search', { query: 'bbb', type: 'nameOnly', }, bob); @@ -456,7 +460,7 @@ describe('Endpoints', () => { assert.strictEqual(res.body.length, 0); }); test('名前と説明の検索で名前を検索できる', async () => { - const res = await api('/channels/search', { + const res = await api('channels/search', { query: 'ccc1', }, bob); @@ -466,7 +470,7 @@ describe('Endpoints', () => { assert.strictEqual(res.body[0].name, 'ccc1'); }); test('名前と説明での検索で説明を検索できる', async () => { - const res = await api('/channels/search', { + const res = await api('channels/search', { query: 'ddd1', }, bob); @@ -476,7 +480,7 @@ describe('Endpoints', () => { assert.strictEqual(res.body[0].name, 'ccc1'); }); test('名前と説明の検索で名前を複数検索できる', async () => { - const res = await api('/channels/search', { + const res = await api('channels/search', { query: 'ccc', }, bob); @@ -485,7 +489,7 @@ describe('Endpoints', () => { assert.strictEqual(res.body.length, 2); }); test('名前と説明での検索で説明を複数検索できる', async () => { - const res = await api('/channels/search', { + const res = await api('channels/search', { query: 'ddd', }, bob); @@ -506,7 +510,7 @@ describe('Endpoints', () => { await uploadFile(alice, { blob: new Blob([new Uint8Array(1024)]), }); - const res = await api('/drive', {}, alice); + const res = await api('drive', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); expect(res.body).toHaveProperty('usage', 1792); @@ -519,7 +523,7 @@ describe('Endpoints', () => { assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); - assert.strictEqual(res.body.name, 'Lenna.jpg'); + assert.strictEqual(res.body!.name, 'Lenna.jpg'); }); test('ファイルに名前を付けられる', async () => { @@ -527,7 +531,7 @@ describe('Endpoints', () => { assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); - assert.strictEqual(res.body.name, 'Belmond.jpg'); + assert.strictEqual(res.body!.name, 'Belmond.jpg'); }); test('ファイルに名前を付けられるが、拡張子は正しいものになる', async () => { @@ -535,11 +539,12 @@ describe('Endpoints', () => { assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); - assert.strictEqual(res.body.name, 'Belmond.png.jpg'); + assert.strictEqual(res.body!.name, 'Belmond.png.jpg'); }); test('ファイル無しで怒られる', async () => { - const res = await api('/drive/files/create', {}, alice); + // @ts-expect-error params must not be empty + const res = await api('drive/files/create', {}, alice); assert.strictEqual(res.status, 400); }); @@ -549,14 +554,14 @@ describe('Endpoints', () => { assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); - assert.strictEqual(res.body.name, 'image.svg'); - assert.strictEqual(res.body.type, 'image/svg+xml'); + assert.strictEqual(res.body!.name, 'image.svg'); + assert.strictEqual(res.body!.type, 'image/svg+xml'); }); for (const type of ['webp', 'avif']) { const mediaType = `image/${type}`; - const getWebpublicType = async (user: any, fileId: string): Promise => { + const getWebpublicType = async (user: misskey.entities.SignupResponse, fileId: string): Promise => { // drive/files/create does not expose webpublicType directly, so get it by posting it const res = await post(user, { text: mediaType, @@ -573,10 +578,10 @@ describe('Endpoints', () => { const res = await uploadFile(alice, { path }); assert.strictEqual(res.status, 200); - assert.strictEqual(res.body.name, path); - assert.strictEqual(res.body.type, mediaType); + assert.strictEqual(res.body!.name, path); + assert.strictEqual(res.body!.type, mediaType); - const webpublicType = await getWebpublicType(alice, res.body.id); + const webpublicType = await getWebpublicType(alice, res.body!.id); assert.strictEqual(webpublicType, 'image/webp'); }); @@ -584,10 +589,10 @@ describe('Endpoints', () => { const path = `without-alpha.${type}`; const res = await uploadFile(alice, { path }); assert.strictEqual(res.status, 200); - assert.strictEqual(res.body.name, path); - assert.strictEqual(res.body.type, mediaType); + assert.strictEqual(res.body!.name, path); + assert.strictEqual(res.body!.type, mediaType); - const webpublicType = await getWebpublicType(alice, res.body.id); + const webpublicType = await getWebpublicType(alice, res.body!.id); assert.strictEqual(webpublicType, 'image/webp'); }); } @@ -598,8 +603,8 @@ describe('Endpoints', () => { const file = (await uploadFile(alice)).body; const newName = 'いちごパスタ.png'; - const res = await api('/drive/files/update', { - fileId: file.id, + const res = await api('drive/files/update', { + fileId: file!.id, name: newName, }, alice); @@ -611,8 +616,8 @@ describe('Endpoints', () => { test('他人のファイルは更新できない', async () => { const file = (await uploadFile(alice)).body; - const res = await api('/drive/files/update', { - fileId: file.id, + const res = await api('drive/files/update', { + fileId: file!.id, name: 'いちごパスタ.png', }, bob); @@ -621,12 +626,12 @@ describe('Endpoints', () => { test('親フォルダを更新できる', async () => { const file = (await uploadFile(alice)).body; - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const res = await api('/drive/files/update', { - fileId: file.id, + const res = await api('drive/files/update', { + fileId: file!.id, folderId: folder.id, }, alice); @@ -638,17 +643,17 @@ describe('Endpoints', () => { test('親フォルダを無しにできる', async () => { const file = (await uploadFile(alice)).body; - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, alice)).body; - await api('/drive/files/update', { - fileId: file.id, + await api('drive/files/update', { + fileId: file!.id, folderId: folder.id, }, alice); - const res = await api('/drive/files/update', { - fileId: file.id, + const res = await api('drive/files/update', { + fileId: file!.id, folderId: null, }, alice); @@ -659,12 +664,12 @@ describe('Endpoints', () => { test('他人のフォルダには入れられない', async () => { const file = (await uploadFile(alice)).body; - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, bob)).body; - const res = await api('/drive/files/update', { - fileId: file.id, + const res = await api('drive/files/update', { + fileId: file!.id, folderId: folder.id, }, alice); @@ -674,8 +679,8 @@ describe('Endpoints', () => { test('存在しないフォルダで怒られる', async () => { const file = (await uploadFile(alice)).body; - const res = await api('/drive/files/update', { - fileId: file.id, + const res = await api('drive/files/update', { + fileId: file!.id, folderId: '000000000000000000000000', }, alice); @@ -685,8 +690,8 @@ describe('Endpoints', () => { test('不正なフォルダIDで怒られる', async () => { const file = (await uploadFile(alice)).body; - const res = await api('/drive/files/update', { - fileId: file.id, + const res = await api('drive/files/update', { + fileId: file!.id, folderId: 'foo', }, alice); @@ -694,7 +699,7 @@ describe('Endpoints', () => { }); test('ファイルが存在しなかったら怒る', async () => { - const res = await api('/drive/files/update', { + const res = await api('drive/files/update', { fileId: '000000000000000000000000', name: 'いちごパスタ.png', }, alice); @@ -706,8 +711,8 @@ describe('Endpoints', () => { const file = (await uploadFile(alice)).body; const newName = ''; - const res = await api('/drive/files/update', { - fileId: file.id, + const res = await api('drive/files/update', { + fileId: file!.id, name: newName, }, alice); @@ -715,7 +720,7 @@ describe('Endpoints', () => { }); test('間違ったIDで怒られる', async () => { - const res = await api('/drive/files/update', { + const res = await api('drive/files/update', { fileId: 'kyoppie', name: 'いちごパスタ.png', }, alice); @@ -726,7 +731,7 @@ describe('Endpoints', () => { describe('drive/folders/create', () => { test('フォルダを作成できる', async () => { - const res = await api('/drive/folders/create', { + const res = await api('drive/folders/create', { name: 'test', }, alice); @@ -738,11 +743,11 @@ describe('Endpoints', () => { describe('drive/folders/update', () => { test('名前を更新できる', async () => { - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: folder.id, name: 'new name', }, alice); @@ -753,11 +758,11 @@ describe('Endpoints', () => { }); test('他人のフォルダを更新できない', async () => { - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, bob)).body; - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: folder.id, name: 'new name', }, alice); @@ -766,14 +771,14 @@ describe('Endpoints', () => { }); test('親フォルダを更新できる', async () => { - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const parentFolder = (await api('/drive/folders/create', { + const parentFolder = (await api('drive/folders/create', { name: 'parent', }, alice)).body; - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: folder.id, parentId: parentFolder.id, }, alice); @@ -784,18 +789,18 @@ describe('Endpoints', () => { }); test('親フォルダを無しに更新できる', async () => { - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const parentFolder = (await api('/drive/folders/create', { + const parentFolder = (await api('drive/folders/create', { name: 'parent', }, alice)).body; - await api('/drive/folders/update', { + await api('drive/folders/update', { folderId: folder.id, parentId: parentFolder.id, }, alice); - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: folder.id, parentId: null, }, alice); @@ -806,14 +811,14 @@ describe('Endpoints', () => { }); test('他人のフォルダを親フォルダに設定できない', async () => { - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const parentFolder = (await api('/drive/folders/create', { + const parentFolder = (await api('drive/folders/create', { name: 'parent', }, bob)).body; - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: folder.id, parentId: parentFolder.id, }, alice); @@ -822,18 +827,18 @@ describe('Endpoints', () => { }); test('フォルダが循環するような構造にできない', async () => { - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const parentFolder = (await api('/drive/folders/create', { + const parentFolder = (await api('drive/folders/create', { name: 'parent', }, alice)).body; - await api('/drive/folders/update', { + await api('drive/folders/update', { folderId: parentFolder.id, parentId: folder.id, }, alice); - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: folder.id, parentId: parentFolder.id, }, alice); @@ -842,25 +847,25 @@ describe('Endpoints', () => { }); test('フォルダが循環するような構造にできない(再帰的)', async () => { - const folderA = (await api('/drive/folders/create', { + const folderA = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const folderB = (await api('/drive/folders/create', { + const folderB = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const folderC = (await api('/drive/folders/create', { + const folderC = (await api('drive/folders/create', { name: 'test', }, alice)).body; - await api('/drive/folders/update', { + await api('drive/folders/update', { folderId: folderB.id, parentId: folderA.id, }, alice); - await api('/drive/folders/update', { + await api('drive/folders/update', { folderId: folderC.id, parentId: folderB.id, }, alice); - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: folderA.id, parentId: folderC.id, }, alice); @@ -869,11 +874,11 @@ describe('Endpoints', () => { }); test('フォルダが循環するような構造にできない(自身)', async () => { - const folderA = (await api('/drive/folders/create', { + const folderA = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: folderA.id, parentId: folderA.id, }, alice); @@ -882,11 +887,11 @@ describe('Endpoints', () => { }); test('存在しない親フォルダを設定できない', async () => { - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: folder.id, parentId: '000000000000000000000000', }, alice); @@ -895,11 +900,11 @@ describe('Endpoints', () => { }); test('不正な親フォルダIDで怒られる', async () => { - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: folder.id, parentId: 'foo', }, alice); @@ -908,7 +913,7 @@ describe('Endpoints', () => { }); test('存在しないフォルダを更新できない', async () => { - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: '000000000000000000000000', }, alice); @@ -916,7 +921,7 @@ describe('Endpoints', () => { }); test('不正なフォルダIDで怒られる', async () => { - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: 'foo', }, alice); @@ -926,7 +931,7 @@ describe('Endpoints', () => { describe('messaging/messages/create', () => { test('メッセージを送信できる', async () => { - const res = await api('/messaging/messages/create', { + const res = await api('messaging/messages/create', { userId: bob.id, text: 'test', }, alice); @@ -937,7 +942,7 @@ describe('Endpoints', () => { }); test('自分自身にはメッセージを送信できない', async () => { - const res = await api('/messaging/messages/create', { + const res = await api('messaging/messages/create', { userId: alice.id, text: 'Yo', }, alice); @@ -946,7 +951,7 @@ describe('Endpoints', () => { }); test('存在しないユーザーにはメッセージを送信できない', async () => { - const res = await api('/messaging/messages/create', { + const res = await api('messaging/messages/create', { userId: '000000000000000000000000', text: 'test', }, alice); @@ -955,7 +960,7 @@ describe('Endpoints', () => { }); test('不正なユーザーIDで怒られる', async () => { - const res = await api('/messaging/messages/create', { + const res = await api('messaging/messages/create', { userId: 'foo', text: 'test', }, alice); @@ -964,7 +969,7 @@ describe('Endpoints', () => { }); test('テキストが無くて怒られる', async () => { - const res = await api('/messaging/messages/create', { + const res = await api('messaging/messages/create', { userId: bob.id, }, alice); @@ -972,7 +977,7 @@ describe('Endpoints', () => { }); test('文字数オーバーで怒られる', async () => { - const res = await api('/messaging/messages/create', { + const res = await api('messaging/messages/create', { userId: bob.id, text: '!'.repeat(3001), }, alice); @@ -994,7 +999,7 @@ describe('Endpoints', () => { visibleUserIds: [alice.id], }); - const res = await api('/notes/replies', { + const res = await api('notes/replies', { noteId: alicePost.id, }, carol); @@ -1006,7 +1011,7 @@ describe('Endpoints', () => { describe('notes/timeline', () => { test('フォロワー限定投稿が含まれる', async () => { - await api('/following/create', { + await api('following/create', { userId: carol.id, }, dave); @@ -1015,7 +1020,7 @@ describe('Endpoints', () => { visibility: 'followers', }); - const res = await api('/notes/timeline', {}, dave); + const res = await api('notes/timeline', {}, dave); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -1036,12 +1041,12 @@ describe('Endpoints', () => { test('他者に関するメモを更新できる', async () => { const memo = '10月まで低浮上とのこと。'; - const res1 = await api('/users/update-memo', { + const res1 = await api('users/update-memo', { memo, userId: bob.id, }, alice); - const res2 = await api('/users/show', { + const res2 = await api('users/show', { userId: bob.id, }, alice); assert.strictEqual(res1.status, 204); @@ -1051,12 +1056,12 @@ describe('Endpoints', () => { test('自分に関するメモを更新できる', async () => { const memo = 'チケットを月末までに買う。'; - const res1 = await api('/users/update-memo', { + const res1 = await api('users/update-memo', { memo, userId: alice.id, }, alice); - const res2 = await api('/users/show', { + const res2 = await api('users/show', { userId: alice.id, }, alice); assert.strictEqual(res1.status, 204); @@ -1066,17 +1071,17 @@ describe('Endpoints', () => { test('メモを削除できる', async () => { const memo = '10月まで低浮上とのこと。'; - await api('/users/update-memo', { + await api('users/update-memo', { memo, userId: bob.id, }, alice); - await api('/users/update-memo', { + await api('users/update-memo', { memo: '', userId: bob.id, }, alice); - const res = await api('/users/show', { + const res = await api('users/show', { userId: bob.id, }, alice); @@ -1089,21 +1094,21 @@ describe('Endpoints', () => { const memoCarolToBob = '例の件について今度問いただす。'; await Promise.all([ - api('/users/update-memo', { + api('users/update-memo', { memo: memoAliceToBob, userId: bob.id, }, alice), - api('/users/update-memo', { + api('users/update-memo', { memo: memoCarolToBob, userId: bob.id, }, carol), ]); const [resAlice, resCarol] = await Promise.all([ - api('/users/show', { + api('users/show', { userId: bob.id, }, alice), - api('/users/show', { + api('users/show', { userId: bob.id, }, carol), ]); diff --git a/packages/backend/test/e2e/exports.ts b/packages/backend/test/e2e/exports.ts index c0a08e2599..23ab875b29 100644 --- a/packages/backend/test/e2e/exports.ts +++ b/packages/backend/test/e2e/exports.ts @@ -18,7 +18,7 @@ describe('export-clips', () => { // XXX: Any better way to get the result? async function pollFirstDriveFile() { while (true) { - const files = (await api('/drive/files', {}, alice)).body; + const files = (await api('drive/files', {}, alice)).body; if (!files.length) { await new Promise(r => setTimeout(r, 100)); continue; @@ -26,7 +26,7 @@ describe('export-clips', () => { if (files.length > 1) { throw new Error('Too many files?'); } - const file = (await api('/drive/files/show', { fileId: files[0].id }, alice)).body; + const file = (await api('drive/files/show', { fileId: files[0].id }, alice)).body; const res = await fetch(new URL(new URL(file.url).pathname, `http://127.0.0.1:${port}`)); return await res.json(); } @@ -44,16 +44,16 @@ describe('export-clips', () => { beforeEach(async () => { // Clean all clips and files of alice - const clips = (await api('/clips/list', {}, alice)).body; + const clips = (await api('clips/list', {}, alice)).body; for (const clip of clips) { - const res = await api('/clips/delete', { clipId: clip.id }, alice); + const res = await api('clips/delete', { clipId: clip.id }, alice); if (res.status !== 204) { throw new Error('Failed to delete clip'); } } - const files = (await api('/drive/files', {}, alice)).body; + const files = (await api('drive/files', {}, alice)).body; for (const file of files) { - const res = await api('/drive/files/delete', { fileId: file.id }, alice); + const res = await api('drive/files/delete', { fileId: file.id }, alice); if (res.status !== 204) { throw new Error('Failed to delete file'); } @@ -61,13 +61,13 @@ describe('export-clips', () => { }); test('basic export', async () => { - let res = await api('/clips/create', { + let res = await api('clips/create', { name: 'foo', description: 'bar', }, alice); assert.strictEqual(res.status, 200); - res = await api('/i/export-clips', {}, alice); + res = await api('i/export-clips', {}, alice); assert.strictEqual(res.status, 204); const exported = await pollFirstDriveFile(); @@ -77,7 +77,7 @@ describe('export-clips', () => { }); test('export with notes', async () => { - let res = await api('/clips/create', { + let res = await api('clips/create', { name: 'foo', description: 'bar', }, alice); @@ -96,14 +96,14 @@ describe('export-clips', () => { }); for (const note of [note1, note2]) { - res = await api('/clips/add-note', { + res = await api('clips/add-note', { clipId: clip.id, noteId: note.id, }, alice); assert.strictEqual(res.status, 204); } - res = await api('/i/export-clips', {}, alice); + res = await api('i/export-clips', {}, alice); assert.strictEqual(res.status, 204); const exported = await pollFirstDriveFile(); @@ -116,14 +116,14 @@ describe('export-clips', () => { }); test('multiple clips', async () => { - let res = await api('/clips/create', { + let res = await api('clips/create', { name: 'kawaii', description: 'kawaii', }, alice); assert.strictEqual(res.status, 200); const clip1 = res.body; - res = await api('/clips/create', { + res = await api('clips/create', { name: 'yuri', description: 'yuri', }, alice); @@ -138,19 +138,19 @@ describe('export-clips', () => { text: 'baz2', }); - res = await api('/clips/add-note', { + res = await api('clips/add-note', { clipId: clip1.id, noteId: note1.id, }, alice); assert.strictEqual(res.status, 204); - res = await api('/clips/add-note', { + res = await api('clips/add-note', { clipId: clip2.id, noteId: note2.id, }, alice); assert.strictEqual(res.status, 204); - res = await api('/i/export-clips', {}, alice); + res = await api('i/export-clips', {}, alice); assert.strictEqual(res.status, 204); const exported = await pollFirstDriveFile(); @@ -163,7 +163,7 @@ describe('export-clips', () => { }); test('Clipping other user\'s note', async () => { - let res = await api('/clips/create', { + let res = await api('clips/create', { name: 'kawaii', description: 'kawaii', }, alice); @@ -175,13 +175,13 @@ describe('export-clips', () => { visibility: 'followers', }); - res = await api('/clips/add-note', { + res = await api('clips/add-note', { clipId: clip.id, noteId: note.id, }, alice); assert.strictEqual(res.status, 204); - res = await api('/i/export-clips', {}, alice); + res = await api('i/export-clips', {}, alice); assert.strictEqual(res.status, 204); const exported = await pollFirstDriveFile(); diff --git a/packages/backend/test/e2e/fetch-resource.ts b/packages/backend/test/e2e/fetch-resource.ts index ab380925e5..99c2567744 100644 --- a/packages/backend/test/e2e/fetch-resource.ts +++ b/packages/backend/test/e2e/fetch-resource.ts @@ -23,13 +23,13 @@ const JSON_UTF8 = 'application/json; charset=utf-8'; describe('Webリソース', () => { let alice: misskey.entities.SignupResponse; - let aliceUploadedFile: any; - let alicesPost: any; - let alicePage: any; - let alicePlay: any; - let aliceClip: any; - let aliceGalleryPost: any; - let aliceChannel: any; + let aliceUploadedFile: misskey.entities.DriveFile | null; + let alicesPost: misskey.entities.Note; + let alicePage: misskey.entities.Page; + let alicePlay: misskey.entities.Flash; + let aliceClip: misskey.entities.Clip; + let aliceGalleryPost: misskey.entities.GalleryPost; + let aliceChannel: misskey.entities.Channel; let bob: misskey.entities.SignupResponse; @@ -77,7 +77,7 @@ describe('Webリソース', () => { beforeAll(async () => { alice = await signup({ username: 'alice' }); - aliceUploadedFile = await uploadFile(alice); + aliceUploadedFile = (await uploadFile(alice)).body; alicesPost = await post(alice, { text: 'test', }); @@ -85,7 +85,7 @@ describe('Webリソース', () => { alicePlay = await play(alice, {}); aliceClip = await clip(alice, {}); aliceGalleryPost = await galleryPost(alice, { - fileIds: [aliceUploadedFile.body.id], + fileIds: [aliceUploadedFile!.id], }); aliceChannel = await channel(alice, {}); diff --git a/packages/backend/test/e2e/ff-visibility.ts b/packages/backend/test/e2e/ff-visibility.ts index 6dd1ea2427..96099d3df2 100644 --- a/packages/backend/test/e2e/ff-visibility.ts +++ b/packages/backend/test/e2e/ff-visibility.ts @@ -19,15 +19,15 @@ describe('FF visibility', () => { }, 1000 * 60 * 2); test('followingVisibility, followersVisibility がともに public なユーザーのフォロー/フォロワーを誰でも見れる', async () => { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', followersVisibility: 'public', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); @@ -39,36 +39,36 @@ describe('FF visibility', () => { test('followingVisibility が public であれば followersVisibility の設定に関わらずユーザーのフォローを誰でも見れる', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', followersVisibility: 'public', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 200); assert.strictEqual(Array.isArray(followingRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', followersVisibility: 'followers', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 200); assert.strictEqual(Array.isArray(followingRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', followersVisibility: 'private', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 200); @@ -78,36 +78,36 @@ describe('FF visibility', () => { test('followersVisibility が public であれば followingVisibility の設定に関わらずユーザーのフォロワーを誰でも見れる', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', followersVisibility: 'public', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 200); assert.strictEqual(Array.isArray(followersRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'public', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 200); assert.strictEqual(Array.isArray(followersRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'public', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 200); @@ -116,15 +116,15 @@ describe('FF visibility', () => { }); test('followingVisibility, followersVisibility がともに followers なユーザーのフォロー/フォロワーを自分で見れる', async () => { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'followers', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, alice); @@ -136,36 +136,36 @@ describe('FF visibility', () => { test('followingVisibility が followers なユーザーのフォローを followersVisibility の設定に関わらず自分で見れる', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'public', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, alice); assert.strictEqual(followingRes.status, 200); assert.strictEqual(Array.isArray(followingRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'followers', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, alice); assert.strictEqual(followingRes.status, 200); assert.strictEqual(Array.isArray(followingRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'private', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, alice); assert.strictEqual(followingRes.status, 200); @@ -175,36 +175,36 @@ describe('FF visibility', () => { test('followersVisibility が followers なユーザーのフォロワーを followingVisibility の設定に関わらず自分で見れる', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', followersVisibility: 'followers', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, alice); assert.strictEqual(followersRes.status, 200); assert.strictEqual(Array.isArray(followersRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'followers', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, alice); assert.strictEqual(followersRes.status, 200); assert.strictEqual(Array.isArray(followersRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'followers', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, alice); assert.strictEqual(followersRes.status, 200); @@ -213,15 +213,15 @@ describe('FF visibility', () => { }); test('followingVisibility, followersVisibility がともに followers なユーザーのフォロー/フォロワーを非フォロワーが見れない', async () => { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'followers', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); @@ -231,34 +231,34 @@ describe('FF visibility', () => { test('followingVisibility が followers なユーザーのフォローを followersVisibility の設定に関わらず非フォロワーが見れない', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'public', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 400); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'followers', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 400); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'private', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 400); @@ -267,34 +267,34 @@ describe('FF visibility', () => { test('followersVisibility が followers なユーザーのフォロワーを followingVisibility の設定に関わらず非フォロワーが見れない', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', followersVisibility: 'followers', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 400); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'followers', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 400); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'followers', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 400); @@ -302,19 +302,19 @@ describe('FF visibility', () => { }); test('followingVisibility, followersVisibility がともに followers なユーザーのフォロー/フォロワーをフォロワーが見れる', async () => { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'followers', }, alice); - await api('/following/create', { + await api('following/create', { userId: alice.id, }, bob); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); @@ -326,45 +326,45 @@ describe('FF visibility', () => { test('followingVisibility が followers なユーザーのフォローを followersVisibility の設定に関わらずフォロワーが見れる', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'public', }, alice); - await api('/following/create', { + await api('following/create', { userId: alice.id, }, bob); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 200); assert.strictEqual(Array.isArray(followingRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'followers', }, alice); - await api('/following/create', { + await api('following/create', { userId: alice.id, }, bob); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 200); assert.strictEqual(Array.isArray(followingRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'private', }, alice); - await api('/following/create', { + await api('following/create', { userId: alice.id, }, bob); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 200); @@ -374,45 +374,45 @@ describe('FF visibility', () => { test('followersVisibility が followers なユーザーのフォロワーを followingVisibility の設定に関わらずフォロワーが見れる', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', followersVisibility: 'followers', }, alice); - await api('/following/create', { + await api('following/create', { userId: alice.id, }, bob); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 200); assert.strictEqual(Array.isArray(followersRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'followers', }, alice); - await api('/following/create', { + await api('following/create', { userId: alice.id, }, bob); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 200); assert.strictEqual(Array.isArray(followersRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'followers', }, alice); - await api('/following/create', { + await api('following/create', { userId: alice.id, }, bob); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 200); @@ -421,15 +421,15 @@ describe('FF visibility', () => { }); test('followingVisibility, followersVisibility がともに private なユーザーのフォロー/フォロワーを自分で見れる', async () => { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'private', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, alice); @@ -441,36 +441,36 @@ describe('FF visibility', () => { test('followingVisibility が private なユーザーのフォローを followersVisibility の設定に関わらず自分で見れる', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'public', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, alice); assert.strictEqual(followingRes.status, 200); assert.strictEqual(Array.isArray(followingRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'followers', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, alice); assert.strictEqual(followingRes.status, 200); assert.strictEqual(Array.isArray(followingRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'private', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, alice); assert.strictEqual(followingRes.status, 200); @@ -480,36 +480,36 @@ describe('FF visibility', () => { test('followersVisibility が private なユーザーのフォロワーを followingVisibility の設定に関わらず自分で見れる', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', followersVisibility: 'private', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, alice); assert.strictEqual(followersRes.status, 200); assert.strictEqual(Array.isArray(followersRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'private', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, alice); assert.strictEqual(followersRes.status, 200); assert.strictEqual(Array.isArray(followersRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'private', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, alice); assert.strictEqual(followersRes.status, 200); @@ -518,15 +518,15 @@ describe('FF visibility', () => { }); test('followingVisibility, followersVisibility がともに private なユーザーのフォロー/フォロワーを他人が見れない', async () => { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'private', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); @@ -536,34 +536,34 @@ describe('FF visibility', () => { test('followingVisibility が private なユーザーのフォローを followersVisibility の設定に関わらず他人が見れない', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'public', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 400); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'followers', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 400); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'private', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 400); @@ -572,34 +572,34 @@ describe('FF visibility', () => { test('followersVisibility が private なユーザーのフォロワーを followingVisibility の設定に関わらず他人が見れない', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', followersVisibility: 'private', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 400); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'private', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 400); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'private', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 400); @@ -609,7 +609,7 @@ describe('FF visibility', () => { describe('AP', () => { test('followingVisibility が public 以外ならばAPからはフォローを取得できない', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', }, alice); @@ -617,7 +617,7 @@ describe('FF visibility', () => { assert.strictEqual(followingRes.status, 200); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', }, alice); @@ -625,7 +625,7 @@ describe('FF visibility', () => { assert.strictEqual(followingRes.status, 403); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', }, alice); @@ -636,7 +636,7 @@ describe('FF visibility', () => { test('followersVisibility が public 以外ならばAPからはフォロワーを取得できない', async () => { { - await api('/i/update', { + await api('i/update', { followersVisibility: 'public', }, alice); @@ -644,7 +644,7 @@ describe('FF visibility', () => { assert.strictEqual(followersRes.status, 200); } { - await api('/i/update', { + await api('i/update', { followersVisibility: 'followers', }, alice); @@ -652,7 +652,7 @@ describe('FF visibility', () => { assert.strictEqual(followersRes.status, 403); } { - await api('/i/update', { + await api('i/update', { followersVisibility: 'private', }, alice); diff --git a/packages/backend/test/e2e/move.ts b/packages/backend/test/e2e/move.ts index 1993ff5e12..cb08ba77d1 100644 --- a/packages/backend/test/e2e/move.ts +++ b/packages/backend/test/e2e/move.ts @@ -55,7 +55,7 @@ describe('Account Move', () => { }, 1000 * 10); test('Able to create an alias', async () => { - const res = await api('/i/update', { + const res = await api('i/update', { alsoKnownAs: [`@alice@${url.hostname}`], }, bob); @@ -67,7 +67,7 @@ describe('Account Move', () => { }); test('Able to create a local alias without hostname', async () => { - await api('/i/update', { + await api('i/update', { alsoKnownAs: ['@alice'], }, bob); @@ -77,7 +77,7 @@ describe('Account Move', () => { }); test('Able to create a local alias without @', async () => { - await api('/i/update', { + await api('i/update', { alsoKnownAs: ['alice'], }, bob); @@ -87,7 +87,7 @@ describe('Account Move', () => { }); test('Able to set remote user (but may fail)', async () => { - const res = await api('/i/update', { + const res = await api('i/update', { alsoKnownAs: ['@syuilo@example.com'], }, bob); @@ -97,7 +97,7 @@ describe('Account Move', () => { }); test('Unable to add duplicated aliases to alsoKnownAs', async () => { - const res = await api('/i/update', { + const res = await api('i/update', { alsoKnownAs: [`@alice@${url.hostname}`, `@alice@${url.hostname}`], }, bob); @@ -107,7 +107,7 @@ describe('Account Move', () => { }); test('Unable to add itself', async () => { - const res = await api('/i/update', { + const res = await api('i/update', { alsoKnownAs: [`@bob@${url.hostname}`], }, bob); @@ -117,7 +117,7 @@ describe('Account Move', () => { }); test('Unable to add a nonexisting local account to alsoKnownAs', async () => { - const res1 = await api('/i/update', { + const res1 = await api('i/update', { alsoKnownAs: [`@nonexist@${url.hostname}`], }, bob); @@ -125,7 +125,7 @@ describe('Account Move', () => { assert.strictEqual(res1.body.error.code, 'NO_SUCH_USER'); assert.strictEqual(res1.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5'); - const res2 = await api('/i/update', { + const res2 = await api('i/update', { alsoKnownAs: ['@alice', 'nonexist'], }, bob); @@ -135,7 +135,7 @@ describe('Account Move', () => { }); test('Able to add two existing local account to alsoKnownAs', async () => { - await api('/i/update', { + await api('i/update', { alsoKnownAs: [`@alice@${url.hostname}`, `@carol@${url.hostname}`], }, bob); @@ -146,10 +146,10 @@ describe('Account Move', () => { }); test('Able to properly overwrite alsoKnownAs', async () => { - await api('/i/update', { + await api('i/update', { alsoKnownAs: [`@alice@${url.hostname}`], }, bob); - await api('/i/update', { + await api('i/update', { alsoKnownAs: [`@carol@${url.hostname}`, `@dave@${url.hostname}`], }, bob); @@ -164,27 +164,27 @@ describe('Account Move', () => { let antennaId = ''; beforeAll(async () => { - await api('/i/update', { + await api('i/update', { alsoKnownAs: [`@alice@${url.hostname}`], }, root); - const listRoot = await api('/users/lists/create', { + const listRoot = await api('users/lists/create', { name: secureRndstr(8), }, root); - await api('/users/lists/push', { + await api('users/lists/push', { listId: listRoot.body.id, userId: alice.id, }, root); - await api('/following/create', { + await api('following/create', { userId: root.id, }, alice); - await api('/following/create', { + await api('following/create', { userId: eve.id, }, alice); - const antenna = await api('/antennas/create', { + const antenna = await api('antennas/create', { name: secureRndstr(8), src: 'home', - keywords: [secureRndstr(8)], + keywords: [[secureRndstr(8)]], excludeKeywords: [], users: [], caseSensitive: false, @@ -195,48 +195,48 @@ describe('Account Move', () => { }, alice); antennaId = antenna.body.id; - await api('/i/update', { + await api('i/update', { alsoKnownAs: [`@alice@${url.hostname}`], }, bob); - await api('/following/create', { + await api('following/create', { userId: alice.id, }, carol); - await api('/mute/create', { + await api('mute/create', { userId: alice.id, }, dave); - await api('/blocking/create', { + await api('blocking/create', { userId: alice.id, }, dave); - await api('/following/create', { + await api('following/create', { userId: eve.id, }, dave); - await api('/following/create', { + await api('following/create', { userId: dave.id, }, eve); - const listEve = await api('/users/lists/create', { + const listEve = await api('users/lists/create', { name: secureRndstr(8), }, eve); - await api('/users/lists/push', { + await api('users/lists/push', { listId: listEve.body.id, userId: bob.id, }, eve); - await api('/i/update', { + await api('i/update', { isLocked: true, }, frank); - await api('/following/create', { + await api('following/create', { userId: frank.id, }, alice); - await api('/following/requests/accept', { + await api('following/requests/accept', { userId: alice.id, }, frank); }, 1000 * 10); test('Prohibit the root account from moving', async () => { - const res = await api('/i/move', { + const res = await api('i/move', { moveToAccount: `@bob@${url.hostname}`, }, root); @@ -246,7 +246,7 @@ describe('Account Move', () => { }); test('Unable to move to a nonexisting local account', async () => { - const res = await api('/i/move', { + const res = await api('i/move', { moveToAccount: `@nonexist@${url.hostname}`, }, alice); @@ -256,7 +256,7 @@ describe('Account Move', () => { }); test('Unable to move if alsoKnownAs is invalid', async () => { - const res = await api('/i/move', { + const res = await api('i/move', { moveToAccount: `@carol@${url.hostname}`, }, alice); @@ -266,7 +266,7 @@ describe('Account Move', () => { }); test('Relationships have been properly migrated', async () => { - const move = await api('/i/move', { + const move = await api('i/move', { moveToAccount: `@bob@${url.hostname}`, }, alice); @@ -275,13 +275,13 @@ describe('Account Move', () => { await sleep(1000 * 3); // wait for jobs to finish // Unfollow delayed? - const aliceFollowings = await api('/users/following', { + const aliceFollowings = await api('users/following', { userId: alice.id, }, alice); assert.strictEqual(aliceFollowings.status, 200); assert.strictEqual(aliceFollowings.body.length, 3); - const carolFollowings = await api('/users/following', { + const carolFollowings = await api('users/following', { userId: carol.id, }, carol); assert.strictEqual(carolFollowings.status, 200); @@ -289,25 +289,25 @@ describe('Account Move', () => { assert.strictEqual(carolFollowings.body[0].followeeId, bob.id); assert.strictEqual(carolFollowings.body[1].followeeId, alice.id); - const blockings = await api('/blocking/list', {}, dave); + const blockings = await api('blocking/list', {}, dave); assert.strictEqual(blockings.status, 200); assert.strictEqual(blockings.body.length, 2); assert.strictEqual(blockings.body[0].blockeeId, bob.id); assert.strictEqual(blockings.body[1].blockeeId, alice.id); - const mutings = await api('/mute/list', {}, dave); + const mutings = await api('mute/list', {}, dave); assert.strictEqual(mutings.status, 200); assert.strictEqual(mutings.body.length, 2); assert.strictEqual(mutings.body[0].muteeId, bob.id); assert.strictEqual(mutings.body[1].muteeId, alice.id); - const rootLists = await api('/users/lists/list', {}, root); + const rootLists = await api('users/lists/list', {}, root); assert.strictEqual(rootLists.status, 200); assert.strictEqual(rootLists.body[0].userIds.length, 2); assert.ok(rootLists.body[0].userIds.find((id: string) => id === bob.id)); assert.ok(rootLists.body[0].userIds.find((id: string) => id === alice.id)); - const eveLists = await api('/users/lists/list', {}, eve); + const eveLists = await api('users/lists/list', {}, eve); assert.strictEqual(eveLists.status, 200); assert.strictEqual(eveLists.body[0].userIds.length, 1); assert.ok(eveLists.body[0].userIds.find((id: string) => id === bob.id)); @@ -315,13 +315,13 @@ describe('Account Move', () => { test('A locked account automatically accept the follow request if it had already accepted the old account.', async () => { await successfulApiCall({ - endpoint: '/following/create', + endpoint: 'following/create', parameters: { userId: frank.id, }, user: bob, }); - const followers = await api('/users/followers', { + const followers = await api('users/followers', { userId: frank.id, }, frank); @@ -333,7 +333,7 @@ describe('Account Move', () => { test('Unfollowed after 10 sec (24 hours in production).', async () => { await sleep(1000 * 8); - const following = await api('/users/following', { + const following = await api('users/following', { userId: alice.id, }, alice); @@ -342,7 +342,7 @@ describe('Account Move', () => { }); test('Unable to move if the destination account has already moved.', async () => { - const res = await api('/i/move', { + const res = await api('i/move', { moveToAccount: `@alice@${url.hostname}`, }, bob); @@ -352,7 +352,7 @@ describe('Account Move', () => { }); test('Follow and follower counts are properly adjusted', async () => { - await api('/following/create', { + await api('following/create', { userId: alice.id, }, eve); const newAlice = await Users.findOneByOrFail({ id: alice.id }); @@ -365,7 +365,7 @@ describe('Account Move', () => { assert.strictEqual(newEve.followingCount, 1); assert.strictEqual(newEve.followersCount, 1); - await api('/following/delete', { + await api('following/delete', { userId: alice.id, }, eve); newEve = await Users.findOneByOrFail({ id: eve.id }); @@ -374,49 +374,49 @@ describe('Account Move', () => { }); test.each([ - '/antennas/create', - '/channels/create', - '/channels/favorite', - '/channels/follow', - '/channels/unfavorite', - '/channels/unfollow', - '/clips/add-note', - '/clips/create', - '/clips/favorite', - '/clips/remove-note', - '/clips/unfavorite', - '/clips/update', - '/drive/files/upload-from-url', - '/flash/create', - '/flash/like', - '/flash/unlike', - '/flash/update', - '/following/create', - '/gallery/posts/create', - '/gallery/posts/like', - '/gallery/posts/unlike', - '/gallery/posts/update', - '/i/claim-achievement', - '/i/move', - '/i/import-blocking', - '/i/import-following', - '/i/import-muting', - '/i/import-user-lists', - '/i/pin', - '/mute/create', - '/notes/create', - '/notes/favorites/create', - '/notes/polls/vote', - '/notes/reactions/create', - '/pages/create', - '/pages/like', - '/pages/unlike', - '/pages/update', - '/renote-mute/create', - '/users/lists/create', - '/users/lists/pull', - '/users/lists/push', - ])('Prohibit access after moving: %s', async (endpoint) => { + 'antennas/create', + 'channels/create', + 'channels/favorite', + 'channels/follow', + 'channels/unfavorite', + 'channels/unfollow', + 'clips/add-note', + 'clips/create', + 'clips/favorite', + 'clips/remove-note', + 'clips/unfavorite', + 'clips/update', + 'drive/files/upload-from-url', + 'flash/create', + 'flash/like', + 'flash/unlike', + 'flash/update', + 'following/create', + 'gallery/posts/create', + 'gallery/posts/like', + 'gallery/posts/unlike', + 'gallery/posts/update', + 'i/claim-achievement', + 'i/move', + 'i/import-blocking', + 'i/import-following', + 'i/import-muting', + 'i/import-user-lists', + 'i/pin', + 'mute/create', + 'notes/create', + 'notes/favorites/create', + 'notes/polls/vote', + 'notes/reactions/create', + 'pages/create', + 'pages/like', + 'pages/unlike', + 'pages/update', + 'renote-mute/create', + 'users/lists/create', + 'users/lists/pull', + 'users/lists/push', + ] as const)('Prohibit access after moving: %s', async (endpoint) => { const res = await api(endpoint, {}, alice); assert.strictEqual(res.status, 403); assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED'); @@ -424,11 +424,11 @@ describe('Account Move', () => { }); test('Prohibit access after moving: /antennas/update', async () => { - const res = await api('/antennas/update', { + const res = await api('antennas/update', { antennaId, name: secureRndstr(8), src: 'users', - keywords: [secureRndstr(8)], + keywords: [[secureRndstr(8)]], excludeKeywords: [], users: [eve.id], caseSensitive: false, @@ -447,12 +447,12 @@ describe('Account Move', () => { const res = await uploadFile(alice); assert.strictEqual(res.status, 403); - assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED'); - assert.strictEqual(res.body.error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31'); + assert.strictEqual((res.body! as any as { error: misskey.api.APIError }).error.code, 'YOUR_ACCOUNT_MOVED'); + assert.strictEqual((res.body! as any as { error: misskey.api.APIError }).error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31'); }); test('Prohibit updating alsoKnownAs after moving', async () => { - const res = await api('/i/update', { + const res = await api('i/update', { alsoKnownAs: [`@eve@${url.hostname}`], }, alice); diff --git a/packages/backend/test/e2e/mute.ts b/packages/backend/test/e2e/mute.ts index abba3e1afd..5841f80166 100644 --- a/packages/backend/test/e2e/mute.ts +++ b/packages/backend/test/e2e/mute.ts @@ -19,21 +19,31 @@ describe('Mute', () => { alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); carol = await signup({ username: 'carol' }); + + // Mute: alice ==> carol + await api('mute/create', { + userId: carol.id, + }, alice); }, 1000 * 60 * 2); test('ミュート作成', async () => { - const res = await api('/mute/create', { - userId: carol.id, + const res = await api('mute/create', { + userId: bob.id, }, alice); assert.strictEqual(res.status, 204); + + // 単体でも走らせられるように副作用消す + await api('mute/delete', { + userId: bob.id, + }, alice); }); test('「自分宛ての投稿」にミュートしているユーザーの投稿が含まれない', async () => { const bobNote = await post(bob, { text: '@alice hi' }); const carolNote = await post(carol, { text: '@alice hi' }); - const res = await api('/notes/mentions', {}, alice); + const res = await api('notes/mentions', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -43,11 +53,11 @@ describe('Mute', () => { test('ミュートしているユーザーからメンションされても、hasUnreadMentions が true にならない', async () => { // 状態リセット - await api('/i/read-all-unread-notes', {}, alice); + await api('i/read-all-unread-notes', {}, alice); await post(carol, { text: '@alice hi' }); - const res = await api('/i', {}, alice); + const res = await api('i', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(res.body.hasUnreadMentions, false); @@ -55,7 +65,7 @@ describe('Mute', () => { test('ミュートしているユーザーからメンションされても、ストリームに unreadMention イベントが流れてこない', async () => { // 状態リセット - await api('/i/read-all-unread-notes', {}, alice); + await api('i/read-all-unread-notes', {}, alice); const fired = await waitFire(alice, 'main', () => post(carol, { text: '@alice hi' }), msg => msg.type === 'unreadMention'); @@ -64,8 +74,8 @@ describe('Mute', () => { test('ミュートしているユーザーからメンションされても、ストリームに unreadNotification イベントが流れてこない', async () => { // 状態リセット - await api('/i/read-all-unread-notes', {}, alice); - await api('/notifications/mark-all-as-read', {}, alice); + await api('i/read-all-unread-notes', {}, alice); + await api('notifications/mark-all-as-read', {}, alice); const fired = await waitFire(alice, 'main', () => post(carol, { text: '@alice hi' }), msg => msg.type === 'unreadNotification'); @@ -78,7 +88,7 @@ describe('Mute', () => { const bobNote = await post(bob, { text: 'hi' }); const carolNote = await post(carol, { text: 'hi' }); - const res = await api('/notes/local-timeline', {}, alice); + const res = await api('notes/local-timeline', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -94,7 +104,7 @@ describe('Mute', () => { renoteId: carolNote.id, }); - const res = await api('/notes/local-timeline', {}, alice); + const res = await api('notes/local-timeline', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -110,10 +120,199 @@ describe('Mute', () => { await react(bob, aliceNote, 'like'); await react(carol, aliceNote, 'like'); - const res = await api('/i/notifications', {}, alice); + const res = await api('i/notifications', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true); + assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false); + }); + + test('通知にミュートしているユーザーからのリプライが含まれない', async () => { + const aliceNote = await post(alice, { text: 'hi' }); + await post(bob, { text: '@alice hi', replyId: aliceNote.id }); + await post(carol, { text: '@alice hi', replyId: aliceNote.id }); + + const res = await api('i/notifications', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + + assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true); + assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false); + }); + + test('通知にミュートしているユーザーからのリプライが含まれない', async () => { + await post(alice, { text: 'hi' }); + await post(bob, { text: '@alice hi' }); + await post(carol, { text: '@alice hi' }); + + const res = await api('i/notifications', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + + assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true); + assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false); + }); + + test('通知にミュートしているユーザーからの引用リノートが含まれない', async () => { + const aliceNote = await post(alice, { text: 'hi' }); + await post(bob, { text: 'hi', renoteId: aliceNote.id }); + await post(carol, { text: 'hi', renoteId: aliceNote.id }); + + const res = await api('i/notifications', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); + + assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true); + assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false); + }); + + test('通知にミュートしているユーザーからのリノートが含まれない', async () => { + const aliceNote = await post(alice, { text: 'hi' }); + await post(bob, { renoteId: aliceNote.id }); + await post(carol, { renoteId: aliceNote.id }); + + const res = await api('i/notifications', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + + assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true); + assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false); + }); + + test('通知にミュートしているユーザーからのフォロー通知が含まれない', async () => { + await api('following/create', { userId: alice.id }, bob); + await api('following/create', { userId: alice.id }, carol); + + const res = await api('i/notifications', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + + assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true); + assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false); + + await api('following/delete', { userId: alice.id }, bob); + await api('following/delete', { userId: alice.id }, carol); + }); + + test('通知にミュートしているユーザーからのフォローリクエストが含まれない', async () => { + await api('i/update', { isLocked: true }, alice); + await api('following/create', { userId: alice.id }, bob); + await api('following/create', { userId: alice.id }, carol); + + const res = await api('i/notifications', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + + assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true); + assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false); + + await api('following/delete', { userId: alice.id }, bob); + await api('following/delete', { userId: alice.id }, carol); + }); + }); + + describe('Notification (Grouped)', () => { + test('通知にミュートしているユーザーの通知が含まれない(リアクション)', async () => { + const aliceNote = await post(alice, { text: 'hi' }); + await react(bob, aliceNote, 'like'); + await react(carol, aliceNote, 'like'); + + const res = await api('i/notifications-grouped', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true); + assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false); + }); + test('通知にミュートしているユーザーからのリプライが含まれない', async () => { + const aliceNote = await post(alice, { text: 'hi' }); + await post(bob, { text: '@alice hi', replyId: aliceNote.id }); + await post(carol, { text: '@alice hi', replyId: aliceNote.id }); + + const res = await api('i/notifications-grouped', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + + assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true); + assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false); + }); + + test('通知にミュートしているユーザーからのリプライが含まれない', async () => { + await post(alice, { text: 'hi' }); + await post(bob, { text: '@alice hi' }); + await post(carol, { text: '@alice hi' }); + + const res = await api('i/notifications-grouped', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + + assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true); + assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false); + }); + + test('通知にミュートしているユーザーからの引用リノートが含まれない', async () => { + const aliceNote = await post(alice, { text: 'hi' }); + await post(bob, { text: 'hi', renoteId: aliceNote.id }); + await post(carol, { text: 'hi', renoteId: aliceNote.id }); + + const res = await api('i/notifications-grouped', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + + assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true); + assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false); + }); + + test('通知にミュートしているユーザーからのリノートが含まれない', async () => { + const aliceNote = await post(alice, { text: 'hi' }); + await post(bob, { renoteId: aliceNote.id }); + await post(carol, { renoteId: aliceNote.id }); + + const res = await api('i/notifications-grouped', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + + assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true); + assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false); + }); + + test('通知にミュートしているユーザーからのフォロー通知が含まれない', async () => { + await api('following/create', { userId: alice.id }, bob); + await api('following/create', { userId: alice.id }, carol); + + const res = await api('i/notifications-grouped', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + + assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true); + assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false); + + await api('following/delete', { userId: alice.id }, bob); + await api('following/delete', { userId: alice.id }, carol); + }); + + test('通知にミュートしているユーザーからのフォローリクエストが含まれない', async () => { + await api('i/update', { isLocked: true }, alice); + await api('following/create', { userId: alice.id }, bob); + await api('following/create', { userId: alice.id }, carol); + + const res = await api('i/notifications-grouped', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true); assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false); }); diff --git a/packages/backend/test/e2e/note.ts b/packages/backend/test/e2e/note.ts index 9ab6bbd842..8cd4316574 100644 --- a/packages/backend/test/e2e/note.ts +++ b/packages/backend/test/e2e/note.ts @@ -8,12 +8,13 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import { MiNote } from '@/models/Note.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; -import { api, initTestDb, post, signup, uploadFile, uploadUrl } from '../utils.js'; +import { api, initTestDb, post, role, signup, uploadFile, uploadUrl } from '../utils.js'; import type * as misskey from 'cherrypick-js'; describe('Note', () => { let Notes: any; + let root: misskey.entities.SignupResponse; let alice: misskey.entities.SignupResponse; let bob: misskey.entities.SignupResponse; let tom: misskey.entities.SignupResponse; @@ -21,6 +22,7 @@ describe('Note', () => { beforeAll(async () => { const connection = await initTestDb(true); Notes = connection.getRepository(MiNote); + root = await signup({ username: 'root' }); alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); tom = await signup({ username: 'tom', host: 'example.com' }); @@ -31,7 +33,7 @@ describe('Note', () => { text: 'test', }; - const res = await api('/notes/create', post, alice); + const res = await api('notes/create', post, alice); assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); @@ -41,7 +43,7 @@ describe('Note', () => { test('ファイルを添付できる', async () => { const file = await uploadUrl(alice, 'https://raw.githubusercontent.com/kokonect-link/cherrypick/develop/packages/backend/test/resources/Lenna.jpg'); - const res = await api('/notes/create', { + const res = await api('notes/create', { fileIds: [file.id], }, alice); @@ -53,7 +55,7 @@ describe('Note', () => { test('他人のファイルで怒られる', async () => { const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/kokonect-link/cherrypick/develop/packages/backend/test/resources/Lenna.jpg'); - const res = await api('/notes/create', { + const res = await api('notes/create', { text: 'test', fileIds: [file.id], }, alice); @@ -64,7 +66,7 @@ describe('Note', () => { }, 1000 * 10); test('存在しないファイルで怒られる', async () => { - const res = await api('/notes/create', { + const res = await api('notes/create', { text: 'test', fileIds: ['000000000000000000000000'], }, alice); @@ -75,7 +77,7 @@ describe('Note', () => { }); test('不正なファイルIDで怒られる', async () => { - const res = await api('/notes/create', { + const res = await api('notes/create', { fileIds: ['kyoppie'], }, alice); assert.strictEqual(res.status, 400); @@ -93,7 +95,7 @@ describe('Note', () => { replyId: bobPost.id, }; - const res = await api('/notes/create', alicePost, alice); + const res = await api('notes/create', alicePost, alice); assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); @@ -111,7 +113,7 @@ describe('Note', () => { renoteId: bobPost.id, }; - const res = await api('/notes/create', alicePost, alice); + const res = await api('notes/create', alicePost, alice); assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); @@ -129,7 +131,7 @@ describe('Note', () => { renoteId: bobPost.id, }; - const res = await api('/notes/create', alicePost, alice); + const res = await api('notes/create', alicePost, alice); assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); @@ -142,7 +144,7 @@ describe('Note', () => { const bobPost = await post(bob, { text: 'test', }); - const res = await api('/notes/create', { + const res = await api('notes/create', { text: ' ', renoteId: bobPost.id, }, alice); @@ -152,7 +154,7 @@ describe('Note', () => { }); test('visibility: followersでrenoteできる', async () => { - const createRes = await api('/notes/create', { + const createRes = await api('notes/create', { text: 'test', visibility: 'followers', }, alice); @@ -160,7 +162,7 @@ describe('Note', () => { assert.strictEqual(createRes.status, 200); const renoteId = createRes.body.createdNote.id; - const renoteRes = await api('/notes/create', { + const renoteRes = await api('notes/create', { visibility: 'followers', renoteId, }, alice); @@ -169,18 +171,99 @@ describe('Note', () => { assert.strictEqual(renoteRes.body.createdNote.renoteId, renoteId); assert.strictEqual(renoteRes.body.createdNote.visibility, 'followers'); - const deleteRes = await api('/notes/delete', { + const deleteRes = await api('notes/delete', { noteId: renoteRes.body.createdNote.id, }, alice); assert.strictEqual(deleteRes.status, 204); }); + test('visibility: followersなノートに対してフォロワーはリプライできる', async () => { + await api('following/create', { + userId: alice.id, + }, bob); + + const aliceNote = await api('notes/create', { + text: 'direct note to bob', + visibility: 'followers', + }, alice); + + assert.strictEqual(aliceNote.status, 200); + + const replyId = aliceNote.body.createdNote.id; + const bobReply = await api('notes/create', { + text: 'reply to alice note', + replyId, + }, bob); + + assert.strictEqual(bobReply.status, 200); + assert.strictEqual(bobReply.body.createdNote.replyId, replyId); + + await api('following/delete', { + userId: alice.id, + }, bob); + }); + + test('visibility: followersなノートに対してフォロワーでないユーザーがリプライしようとすると怒られる', async () => { + const aliceNote = await api('notes/create', { + text: 'direct note to bob', + visibility: 'followers', + }, alice); + + assert.strictEqual(aliceNote.status, 200); + + const bobReply = await api('notes/create', { + text: 'reply to alice note', + replyId: aliceNote.body.createdNote.id, + }, bob); + + assert.strictEqual(bobReply.status, 400); + assert.strictEqual(bobReply.body.error.code, 'CANNOT_REPLY_TO_AN_INVISIBLE_NOTE'); + }); + + test('visibility: specifiedなノートに対してvisibility: specifiedで返信できる', async () => { + const aliceNote = await api('notes/create', { + text: 'direct note to bob', + visibility: 'specified', + visibleUserIds: [bob.id], + }, alice); + + assert.strictEqual(aliceNote.status, 200); + + const bobReply = await api('notes/create', { + text: 'reply to alice note', + replyId: aliceNote.body.createdNote.id, + visibility: 'specified', + visibleUserIds: [alice.id], + }, bob); + + assert.strictEqual(bobReply.status, 200); + }); + + test('visibility: specifiedなノートに対してvisibility: follwersで返信しようとすると怒られる', async () => { + const aliceNote = await api('notes/create', { + text: 'direct note to bob', + visibility: 'specified', + visibleUserIds: [bob.id], + }, alice); + + assert.strictEqual(aliceNote.status, 200); + + const bobReply = await api('notes/create', { + text: 'reply to alice note with visibility: followers', + replyId: aliceNote.body.createdNote.id, + visibility: 'followers', + }, bob); + + assert.strictEqual(bobReply.status, 400); + assert.strictEqual(bobReply.body.error.code, 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY'); + }); + test('文字数ぎりぎりで怒られない', async () => { const post = { text: '!'.repeat(MAX_NOTE_TEXT_LENGTH), // 3000文字 }; - const res = await api('/notes/create', post, alice); + const res = await api('notes/create', post, alice); assert.strictEqual(res.status, 200); }); @@ -188,7 +271,7 @@ describe('Note', () => { const post = { text: '!'.repeat(MAX_NOTE_TEXT_LENGTH + 1), // 3001文字 }; - const res = await api('/notes/create', post, alice); + const res = await api('notes/create', post, alice); assert.strictEqual(res.status, 400); }); @@ -197,7 +280,7 @@ describe('Note', () => { text: 'test', replyId: '000000000000000000000000', }; - const res = await api('/notes/create', post, alice); + const res = await api('notes/create', post, alice); assert.strictEqual(res.status, 400); }); @@ -205,7 +288,7 @@ describe('Note', () => { const post = { renoteId: '000000000000000000000000', }; - const res = await api('/notes/create', post, alice); + const res = await api('notes/create', post, alice); assert.strictEqual(res.status, 400); }); @@ -214,7 +297,7 @@ describe('Note', () => { text: 'test', replyId: 'foo', }; - const res = await api('/notes/create', post, alice); + const res = await api('notes/create', post, alice); assert.strictEqual(res.status, 400); }); @@ -222,7 +305,7 @@ describe('Note', () => { const post = { renoteId: 'foo', }; - const res = await api('/notes/create', post, alice); + const res = await api('notes/create', post, alice); assert.strictEqual(res.status, 400); }); @@ -231,7 +314,7 @@ describe('Note', () => { text: '@ghost yo', }; - const res = await api('/notes/create', post, alice); + const res = await api('notes/create', post, alice); assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); @@ -243,7 +326,7 @@ describe('Note', () => { text: '@bob @bob @bob yo', }; - const res = await api('/notes/create', post, alice); + const res = await api('notes/create', post, alice); assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); @@ -256,25 +339,25 @@ describe('Note', () => { describe('添付ファイル情報', () => { test('ファイルを添付した場合、投稿成功時にファイル情報入りのレスポンスが帰ってくる', async () => { const file = await uploadFile(alice); - const res = await api('/notes/create', { - fileIds: [file.body.id], + const res = await api('notes/create', { + fileIds: [file.body!.id], }, alice); assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); assert.strictEqual(res.body.createdNote.files.length, 1); - assert.strictEqual(res.body.createdNote.files[0].id, file.body.id); + assert.strictEqual(res.body.createdNote.files[0].id, file.body!.id); }); test('ファイルを添付した場合、タイムラインでファイル情報入りのレスポンスが帰ってくる', async () => { const file = await uploadFile(alice); - const createdNote = await api('/notes/create', { - fileIds: [file.body.id], + const createdNote = await api('notes/create', { + fileIds: [file.body!.id], }, alice); assert.strictEqual(createdNote.status, 200); - const res = await api('/notes', { + const res = await api('notes', { withFiles: true, }, alice); @@ -283,23 +366,23 @@ describe('Note', () => { const myNote = res.body.find((note: { id: string; files: { id: string }[] }) => note.id === createdNote.body.createdNote.id); assert.notEqual(myNote, null); assert.strictEqual(myNote.files.length, 1); - assert.strictEqual(myNote.files[0].id, file.body.id); + assert.strictEqual(myNote.files[0].id, file.body!.id); }); test('ファイルが添付されたノートをリノートした場合、タイムラインでファイル情報入りのレスポンスが帰ってくる', async () => { const file = await uploadFile(alice); - const createdNote = await api('/notes/create', { - fileIds: [file.body.id], + const createdNote = await api('notes/create', { + fileIds: [file.body!.id], }, alice); assert.strictEqual(createdNote.status, 200); - const renoted = await api('/notes/create', { + const renoted = await api('notes/create', { renoteId: createdNote.body.createdNote.id, }, alice); assert.strictEqual(renoted.status, 200); - const res = await api('/notes', { + const res = await api('notes', { renote: true, }, alice); @@ -308,24 +391,24 @@ describe('Note', () => { const myNote = res.body.find((note: { id: string }) => note.id === renoted.body.createdNote.id); assert.notEqual(myNote, null); assert.strictEqual(myNote.renote.files.length, 1); - assert.strictEqual(myNote.renote.files[0].id, file.body.id); + assert.strictEqual(myNote.renote.files[0].id, file.body!.id); }); test('ファイルが添付されたノートに返信した場合、タイムラインでファイル情報入りのレスポンスが帰ってくる', async () => { const file = await uploadFile(alice); - const createdNote = await api('/notes/create', { - fileIds: [file.body.id], + const createdNote = await api('notes/create', { + fileIds: [file.body!.id], }, alice); assert.strictEqual(createdNote.status, 200); - const reply = await api('/notes/create', { + const reply = await api('notes/create', { replyId: createdNote.body.createdNote.id, text: 'this is reply', }, alice); assert.strictEqual(reply.status, 200); - const res = await api('/notes', { + const res = await api('notes', { reply: true, }, alice); @@ -334,29 +417,29 @@ describe('Note', () => { const myNote = res.body.find((note: { id: string }) => note.id === reply.body.createdNote.id); assert.notEqual(myNote, null); assert.strictEqual(myNote.reply.files.length, 1); - assert.strictEqual(myNote.reply.files[0].id, file.body.id); + assert.strictEqual(myNote.reply.files[0].id, file.body!.id); }); test('ファイルが添付されたノートへの返信をリノートした場合、タイムラインでファイル情報入りのレスポンスが帰ってくる', async () => { const file = await uploadFile(alice); - const createdNote = await api('/notes/create', { - fileIds: [file.body.id], + const createdNote = await api('notes/create', { + fileIds: [file.body!.id], }, alice); assert.strictEqual(createdNote.status, 200); - const reply = await api('/notes/create', { + const reply = await api('notes/create', { replyId: createdNote.body.createdNote.id, text: 'this is reply', }, alice); assert.strictEqual(reply.status, 200); - const renoted = await api('/notes/create', { + const renoted = await api('notes/create', { renoteId: reply.body.createdNote.id, }, alice); assert.strictEqual(renoted.status, 200); - const res = await api('/notes', { + const res = await api('notes', { renote: true, }, alice); @@ -365,7 +448,7 @@ describe('Note', () => { const myNote = res.body.find((note: { id: string }) => note.id === renoted.body.createdNote.id); assert.notEqual(myNote, null); assert.strictEqual(myNote.renote.reply.files.length, 1); - assert.strictEqual(myNote.renote.reply.files[0].id, file.body.id); + assert.strictEqual(myNote.renote.reply.files[0].id, file.body!.id); }); test('NSFWが強制されている場合変更できない', async () => { @@ -391,26 +474,26 @@ describe('Note', () => { priority: 0, value: true, }, - }, - }, alice); + } as any, + }, root); assert.strictEqual(res.status, 200); const assign = await api('admin/roles/assign', { userId: alice.id, roleId: res.body.id, - }, alice); + }, root); assert.strictEqual(assign.status, 204); - assert.strictEqual(file.body.isSensitive, false); + assert.strictEqual(file.body!.isSensitive, false); const nsfwfile = await uploadFile(alice); assert.strictEqual(nsfwfile.status, 200); - assert.strictEqual(nsfwfile.body.isSensitive, true); + assert.strictEqual(nsfwfile.body!.isSensitive, true); const liftnsfw = await api('drive/files/update', { - fileId: nsfwfile.body.id, + fileId: nsfwfile.body!.id, isSensitive: false, }, alice); @@ -418,7 +501,7 @@ describe('Note', () => { assert.strictEqual(liftnsfw.body.error.code, 'RESTRICTED_BY_ROLE'); const oldaddnsfw = await api('drive/files/update', { - fileId: file.body.id, + fileId: file.body!.id, isSensitive: true, }, alice); @@ -427,17 +510,17 @@ describe('Note', () => { await api('admin/roles/unassign', { userId: alice.id, roleId: res.body.id, - }); + }, root); await api('admin/roles/delete', { roleId: res.body.id, - }, alice); + }, root); }); }); describe('notes/create', () => { test('投票を添付できる', async () => { - const res = await api('/notes/create', { + const res = await api('notes/create', { text: 'test', poll: { choices: ['foo', 'bar'], @@ -450,14 +533,15 @@ describe('Note', () => { }); test('投票の選択肢が無くて怒られる', async () => { - const res = await api('/notes/create', { + const res = await api('notes/create', { + // @ts-expect-error poll must not be empty poll: {}, }, alice); assert.strictEqual(res.status, 400); }); test('投票の選択肢が無くて怒られる (空の配列)', async () => { - const res = await api('/notes/create', { + const res = await api('notes/create', { poll: { choices: [], }, @@ -466,7 +550,7 @@ describe('Note', () => { }); test('投票の選択肢が1つで怒られる', async () => { - const res = await api('/notes/create', { + const res = await api('notes/create', { poll: { choices: ['Strawberry Pasta'], }, @@ -475,14 +559,14 @@ describe('Note', () => { }); test('投票できる', async () => { - const { body } = await api('/notes/create', { + const { body } = await api('notes/create', { text: 'test', poll: { choices: ['sakura', 'izumi', 'ako'], }, }, alice); - const res = await api('/notes/polls/vote', { + const res = await api('notes/polls/vote', { noteId: body.createdNote.id, choice: 1, }, alice); @@ -491,19 +575,19 @@ describe('Note', () => { }); test('複数投票できない', async () => { - const { body } = await api('/notes/create', { + const { body } = await api('notes/create', { text: 'test', poll: { choices: ['sakura', 'izumi', 'ako'], }, }, alice); - await api('/notes/polls/vote', { + await api('notes/polls/vote', { noteId: body.createdNote.id, choice: 0, }, alice); - const res = await api('/notes/polls/vote', { + const res = await api('notes/polls/vote', { noteId: body.createdNote.id, choice: 2, }, alice); @@ -512,7 +596,7 @@ describe('Note', () => { }); test('許可されている場合は複数投票できる', async () => { - const { body } = await api('/notes/create', { + const { body } = await api('notes/create', { text: 'test', poll: { choices: ['sakura', 'izumi', 'ako'], @@ -520,17 +604,17 @@ describe('Note', () => { }, }, alice); - await api('/notes/polls/vote', { + await api('notes/polls/vote', { noteId: body.createdNote.id, choice: 0, }, alice); - await api('/notes/polls/vote', { + await api('notes/polls/vote', { noteId: body.createdNote.id, choice: 1, }, alice); - const res = await api('/notes/polls/vote', { + const res = await api('notes/polls/vote', { noteId: body.createdNote.id, choice: 2, }, alice); @@ -539,7 +623,7 @@ describe('Note', () => { }); test('締め切られている場合は投票できない', async () => { - const { body } = await api('/notes/create', { + const { body } = await api('notes/create', { text: 'test', poll: { choices: ['sakura', 'izumi', 'ako'], @@ -549,7 +633,7 @@ describe('Note', () => { await new Promise(x => setTimeout(x, 2)); - const res = await api('/notes/polls/vote', { + const res = await api('notes/polls/vote', { noteId: body.createdNote.id, choice: 1, }, alice); @@ -562,13 +646,13 @@ describe('Note', () => { sensitiveWords: [ 'test', ], - }, alice); + }, root); assert.strictEqual(sensitive.status, 204); await new Promise(x => setTimeout(x, 2)); - const note1 = await api('/notes/create', { + const note1 = await api('notes/create', { text: 'hogetesthuge', }, alice); @@ -581,11 +665,11 @@ describe('Note', () => { sensitiveWords: [ '/Test/i', ], - }, alice); + }, root); assert.strictEqual(sensitive.status, 204); - const note2 = await api('/notes/create', { + const note2 = await api('notes/create', { text: 'hogetesthuge', }, alice); @@ -598,11 +682,11 @@ describe('Note', () => { sensitiveWords: [ 'Test hoge', ], - }, alice); + }, root); assert.strictEqual(sensitive.status, 204); - const note2 = await api('/notes/create', { + const note2 = await api('notes/create', { text: 'hogeTesthuge', }, alice); @@ -615,13 +699,13 @@ describe('Note', () => { prohibitedWords: [ 'test', ], - }, alice); + }, root); assert.strictEqual(prohibited.status, 204); await new Promise(x => setTimeout(x, 2)); - const note1 = await api('/notes/create', { + const note1 = await api('notes/create', { text: 'hogetesthuge', }, alice); @@ -634,11 +718,11 @@ describe('Note', () => { prohibitedWords: [ '/Test/i', ], - }, alice); + }, root); assert.strictEqual(prohibited.status, 204); - const note2 = await api('/notes/create', { + const note2 = await api('notes/create', { text: 'hogetesthuge', }, alice); @@ -651,11 +735,11 @@ describe('Note', () => { prohibitedWords: [ 'Test hoge', ], - }, alice); + }, root); assert.strictEqual(prohibited.status, 204); - const note2 = await api('/notes/create', { + const note2 = await api('notes/create', { text: 'hogeTesthuge', }, alice); @@ -668,18 +752,183 @@ describe('Note', () => { prohibitedWords: [ 'test', ], - }, alice); + }, root); assert.strictEqual(prohibited.status, 204); await new Promise(x => setTimeout(x, 2)); - const note1 = await api('/notes/create', { + const note1 = await api('notes/create', { text: 'hogetesthuge', }, tom); assert.strictEqual(note1.status, 400); }); + + test('メンションの数が上限を超えるとエラーになる', async () => { + const res = await api('admin/roles/create', { + name: 'test', + description: '', + color: null, + iconUrl: null, + displayOrder: 0, + target: 'manual', + condFormula: {}, + isAdministrator: false, + isModerator: false, + isPublic: false, + isExplorable: false, + asBadge: false, + canEditMembersByModerator: false, + policies: { + mentionLimit: { + useDefault: false, + priority: 1, + value: 0, + }, + } as any, + }, root); + + assert.strictEqual(res.status, 200); + + await new Promise(x => setTimeout(x, 2)); + + const assign = await api('admin/roles/assign', { + userId: alice.id, + roleId: res.body.id, + }, root); + + assert.strictEqual(assign.status, 204); + + await new Promise(x => setTimeout(x, 2)); + + const note = await api('notes/create', { + text: '@bob potentially annoying text', + }, alice); + + assert.strictEqual(note.status, 400); + assert.strictEqual(note.body.error.code, 'CONTAINS_TOO_MANY_MENTIONS'); + + await api('admin/roles/unassign', { + userId: alice.id, + roleId: res.body.id, + }, root); + + await api('admin/roles/delete', { + roleId: res.body.id, + }, root); + }); + + test('ダイレクト投稿もエラーになる', async () => { + const res = await api('admin/roles/create', { + name: 'test', + description: '', + color: null, + iconUrl: null, + displayOrder: 0, + target: 'manual', + condFormula: {}, + isAdministrator: false, + isModerator: false, + isPublic: false, + isExplorable: false, + asBadge: false, + canEditMembersByModerator: false, + policies: { + mentionLimit: { + useDefault: false, + priority: 1, + value: 0, + }, + } as any, + }, root); + + assert.strictEqual(res.status, 200); + + await new Promise(x => setTimeout(x, 2)); + + const assign = await api('admin/roles/assign', { + userId: alice.id, + roleId: res.body.id, + }, root); + + assert.strictEqual(assign.status, 204); + + await new Promise(x => setTimeout(x, 2)); + + const note = await api('notes/create', { + text: 'potentially annoying text', + visibility: 'specified', + visibleUserIds: [bob.id], + }, alice); + + assert.strictEqual(note.status, 400); + assert.strictEqual(note.body.error.code, 'CONTAINS_TOO_MANY_MENTIONS'); + + await api('admin/roles/unassign', { + userId: alice.id, + roleId: res.body.id, + }, root); + + await api('admin/roles/delete', { + roleId: res.body.id, + }, root); + }); + + test('ダイレクトの宛先とメンションが同じ場合は重複してカウントしない', async () => { + const res = await api('admin/roles/create', { + name: 'test', + description: '', + color: null, + iconUrl: null, + displayOrder: 0, + target: 'manual', + condFormula: {}, + isAdministrator: false, + isModerator: false, + isPublic: false, + isExplorable: false, + asBadge: false, + canEditMembersByModerator: false, + policies: { + mentionLimit: { + useDefault: false, + priority: 1, + value: 1, + }, + } as any, + }, root); + + assert.strictEqual(res.status, 200); + + await new Promise(x => setTimeout(x, 2)); + + const assign = await api('admin/roles/assign', { + userId: alice.id, + roleId: res.body.id, + }, root); + + assert.strictEqual(assign.status, 204); + + await new Promise(x => setTimeout(x, 2)); + + const note = await api('notes/create', { + text: '@bob potentially annoying text', + visibility: 'specified', + visibleUserIds: [bob.id], + }, alice); + + assert.strictEqual(note.status, 200); + + await api('admin/roles/unassign', { + userId: alice.id, + roleId: res.body.id, + }, root); + + await api('admin/roles/delete', { + roleId: res.body.id, + }, root); + }); }); describe('notes/delete', () => { @@ -713,4 +962,61 @@ describe('Note', () => { assert.strictEqual(mainNote.repliesCount, 0); }); }); + + describe('notes/translate', () => { + describe('翻訳機能の利用が許可されていない場合', () => { + let cannotTranslateRole: misskey.entities.Role; + + beforeAll(async () => { + cannotTranslateRole = await role(root, {}, { canUseTranslator: false }); + await api('admin/roles/assign', { roleId: cannotTranslateRole.id, userId: alice.id }, root); + }); + + test('翻訳機能の利用が許可されていない場合翻訳できない', async () => { + const aliceNote = await post(alice, { text: 'Hello' }); + const res = await api('notes/translate', { + noteId: aliceNote.id, + targetLang: 'ja', + }, alice); + + assert.strictEqual(res.status, 400); + assert.strictEqual(res.body.error.code, 'UNAVAILABLE'); + }); + + afterAll(async () => { + await api('admin/roles/unassign', { roleId: cannotTranslateRole.id, userId: alice.id }, root); + }); + }); + + test('存在しないノートは翻訳できない', async () => { + const res = await api('notes/translate', { noteId: 'foo', targetLang: 'ja' }, alice); + + assert.strictEqual(res.status, 400); + assert.strictEqual(res.body.error.code, 'NO_SUCH_NOTE'); + }); + + test('不可視なノートは翻訳できない', async () => { + const aliceNote = await post(alice, { visibility: 'followers', text: 'Hello' }); + const bobTranslateAttempt = await api('notes/translate', { noteId: aliceNote.id, targetLang: 'ja' }, bob); + + assert.strictEqual(bobTranslateAttempt.status, 400); + assert.strictEqual(bobTranslateAttempt.body.error.code, 'CANNOT_TRANSLATE_INVISIBLE_NOTE'); + }); + + test('text: null なノートを翻訳すると空のレスポンスが返ってくる', async () => { + const aliceNote = await post(alice, { text: null, poll: { choices: ['kinoko', 'takenoko'] } }); + const res = await api('notes/translate', { noteId: aliceNote.id, targetLang: 'ja' }, alice); + + assert.strictEqual(res.status, 204); + }); + + test('サーバーに DeepL 認証キーが登録されていない場合翻訳できない', async () => { + const aliceNote = await post(alice, { text: 'Hello' }); + const res = await api('notes/translate', { noteId: aliceNote.id, targetLang: 'ja' }, alice); + + // NOTE: デフォルトでは登録されていないので落ちる + assert.strictEqual(res.status, 400); + assert.strictEqual(res.body.error.code, 'UNAVAILABLE'); + }); + }); }); diff --git a/packages/backend/test/e2e/renote-mute.ts b/packages/backend/test/e2e/renote-mute.ts index 7775bcdb84..99016f1e30 100644 --- a/packages/backend/test/e2e/renote-mute.ts +++ b/packages/backend/test/e2e/renote-mute.ts @@ -22,7 +22,7 @@ describe('Renote Mute', () => { }, 1000 * 60 * 2); test('ミュート作成', async () => { - const res = await api('/renote-mute/create', { + const res = await api('renote-mute/create', { userId: carol.id, }, alice); @@ -37,7 +37,7 @@ describe('Renote Mute', () => { // redisに追加されるのを待つ await sleep(100); - const res = await api('/notes/local-timeline', {}, alice); + const res = await api('notes/local-timeline', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -54,7 +54,7 @@ describe('Renote Mute', () => { // redisに追加されるのを待つ await sleep(100); - const res = await api('/notes/local-timeline', {}, alice); + const res = await api('notes/local-timeline', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); diff --git a/packages/backend/test/e2e/streaming.ts b/packages/backend/test/e2e/streaming.ts index aee1ce21df..8c89377d69 100644 --- a/packages/backend/test/e2e/streaming.ts +++ b/packages/backend/test/e2e/streaming.ts @@ -40,9 +40,9 @@ describe('Streaming', () => { let chinatsu: misskey.entities.SignupResponse; let takumi: misskey.entities.SignupResponse; - let kyokoNote: any; - let kanakoNote: any; - let takumiNote: any; + let kyokoNote: misskey.entities.Note; + let kanakoNote: misskey.entities.Note; + let takumiNote: misskey.entities.Note; let list: any; beforeAll(async () => { @@ -68,6 +68,9 @@ describe('Streaming', () => { // Follow: ayano => akari await follow(ayano, akari); + // Follow: kyoko => chitose + await api('following/create', { userId: chitose.id }, kyoko); + // Mute: chitose => kanako await api('mute/create', { userId: kanako.id }, chitose); @@ -155,22 +158,41 @@ describe('Streaming', () => { assert.strictEqual(fired, true); }); - /* なんか失敗する test('フォローしているユーザーの visibility: followers な投稿への返信が流れる', async () => { - const note = await api('notes/create', { text: 'foo', visibility: 'followers' }, kyoko); + const note = await post(kyoko, { text: 'foo', visibility: 'followers' }); const fired = await waitFire( ayano, 'homeTimeline', // ayano:home - () => api('notes/create', { text: 'bar', visibility: 'followers', replyId: note.body.id }, kyoko), // kyoko posts + () => api('notes/create', { text: 'bar', visibility: 'followers', replyId: note.id }, kyoko), // kyoko posts msg => msg.type === 'note' && msg.body.userId === kyoko.id && msg.body.reply.text === 'foo', ); assert.strictEqual(fired, true); }); - */ test('フォローしているユーザーのフォローしていないユーザーの visibility: followers な投稿への返信が流れない', async () => { - // TODO + const chitoseNote = await post(chitose, { text: 'followers-only post', visibility: 'followers' }); + + const fired = await waitFire( + ayano, 'homeTimeline', // ayano:home + () => api('notes/create', { text: 'reply to chitose\'s followers-only post', replyId: chitoseNote.id }, kyoko), // kyoko's reply to chitose's followers-only post + msg => msg.type === 'note' && msg.body.userId === kyoko.id, // wait kyoko + ); + + assert.strictEqual(fired, false); + }); + + test('フォローしているユーザーのフォローしていないユーザーの visibility: followers な投稿への返信のリノートが流れない', async () => { + const chitoseNote = await post(chitose, { text: 'followers-only post', visibility: 'followers' }); + const kyokoReply = await post(kyoko, { text: 'reply to followers-only post', replyId: chitoseNote.id }); + + const fired = await waitFire( + ayano, 'homeTimeline', // ayano:home + () => api('notes/create', { renoteId: kyokoReply.id }, kyoko), // kyoko's renote of kyoko's reply to chitose's followers-only post + msg => msg.type === 'note' && msg.body.userId === kyoko.id, // wait kyoko + ); + + assert.strictEqual(fired, false); }); test('フォローしていないユーザーの投稿は流れない', async () => { @@ -202,6 +224,79 @@ describe('Streaming', () => { assert.strictEqual(fired, false); }); + + /** + * TODO: 落ちる + * @see https://github.com/misskey-dev/misskey/issues/13474 + test('visibility: specified なノートで visibleUserIds に自分が含まれているときそのノートへのリプライが流れてくる', async () => { + const chitoseToKyokoAndAyano = await post(chitose, { text: 'direct note from chitose to kyoko and ayano', visibility: 'specified', visibleUserIds: [kyoko.id, ayano.id] }); + + const fired = await waitFire( + ayano, 'homeTimeline', // ayano:home + () => api('notes/create', { text: 'direct reply from kyoko to chitose and ayano', replyId: chitoseToKyokoAndAyano.id, visibility: 'specified', visibleUserIds: [chitose.id, ayano.id] }, kyoko), + msg => msg.type === 'note' && msg.body.userId === kyoko.id, + ); + + assert.strictEqual(fired, true); + }); + */ + + test('visibility: specified な投稿に対するリプライで visibleUserIds が拡張されたとき、その拡張されたユーザーの HTL にはそのリプライが流れない', async () => { + const chitoseToKyoko = await post(chitose, { text: 'direct note from chitose to kyoko', visibility: 'specified', visibleUserIds: [kyoko.id] }); + + const fired = await waitFire( + ayano, 'homeTimeline', // ayano:home + () => api('notes/create', { text: 'direct reply from kyoko to chitose and ayano', replyId: chitoseToKyoko.id, visibility: 'specified', visibleUserIds: [chitose.id, ayano.id] }, kyoko), + msg => msg.type === 'note' && msg.body.userId === kyoko.id, + ); + + assert.strictEqual(fired, false); + }); + + test('visibility: specified な投稿に対するリプライで visibleUserIds が収縮されたとき、その収縮されたユーザーの HTL にはそのリプライが流れない', async () => { + const chitoseToKyokoAndAyano = await post(chitose, { text: 'direct note from chitose to kyoko and ayano', visibility: 'specified', visibleUserIds: [kyoko.id, ayano.id] }); + + const fired = await waitFire( + ayano, 'homeTimeline', // ayano:home + () => api('notes/create', { text: 'direct reply from kyoko to chitose', replyId: chitoseToKyokoAndAyano.id, visibility: 'specified', visibleUserIds: [chitose.id] }, kyoko), + msg => msg.type === 'note' && msg.body.userId === kyoko.id, + ); + + assert.strictEqual(fired, false); + }); + + test('withRenotes: false のときリノートが流れない', async () => { + const fired = await waitFire( + ayano, 'homeTimeline', // ayano:home + () => api('notes/create', { renoteId: kyokoNote.id }, kyoko), // kyoko renote + msg => msg.type === 'note' && msg.body.userId === kyoko.id, // wait kyoko + { withRenotes: false }, + ); + + assert.strictEqual(fired, false); + }); + + test('withRenotes: false のとき引用リノートが流れる', async () => { + const fired = await waitFire( + ayano, 'homeTimeline', // ayano:home + () => api('notes/create', { text: 'quote', renoteId: kyokoNote.id }, kyoko), // kyoko quote + msg => msg.type === 'note' && msg.body.userId === kyoko.id, // wait kyoko + { withRenotes: false }, + ); + + assert.strictEqual(fired, true); + }); + + test('withRenotes: false のとき投票のみのリノートが流れる', async () => { + const fired = await waitFire( + ayano, 'homeTimeline', // ayano:home + () => api('notes/create', { poll: { choices: ['kinoko', 'takenoko'] }, renoteId: kyokoNote.id }, kyoko), // kyoko renote with poll + msg => msg.type === 'note' && msg.body.userId === kyoko.id, // wait kyoko + { withRenotes: false }, + ); + + assert.strictEqual(fired, true); + }); }); // Home describe('Local Timeline', () => { @@ -504,7 +599,7 @@ describe('Streaming', () => { // #10443 test('ミュートしているサーバのノートがリストTLに流れない', async () => { - await api('/i/update', { + await api('i/update', { mutedInstances: ['example.com'], }, chitose); @@ -521,7 +616,7 @@ describe('Streaming', () => { // #10443 test('ミュートしているサーバのノートに対するリプライがリストTLに流れない', async () => { - await api('/i/update', { + await api('i/update', { mutedInstances: ['example.com'], }, chitose); @@ -538,7 +633,7 @@ describe('Streaming', () => { // #10443 test('ミュートしているサーバのノートに対するリノートがリストTLに流れない', async () => { - await api('/i/update', { + await api('i/update', { mutedInstances: ['example.com'], }, chitose); diff --git a/packages/backend/test/e2e/thread-mute.ts b/packages/backend/test/e2e/thread-mute.ts index 98a1908984..0a57139719 100644 --- a/packages/backend/test/e2e/thread-mute.ts +++ b/packages/backend/test/e2e/thread-mute.ts @@ -24,12 +24,12 @@ describe('Note thread mute', () => { const bobNote = await post(bob, { text: '@alice @carol root note' }); const aliceReply = await post(alice, { replyId: bobNote.id, text: '@bob @carol child note' }); - await api('/notes/thread-muting/create', { noteId: bobNote.id }, alice); + await api('notes/thread-muting/create', { noteId: bobNote.id }, alice); const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' }); const carolReplyWithoutMention = await post(carol, { replyId: aliceReply.id, text: 'child note' }); - const res = await api('/notes/mentions', {}, alice); + const res = await api('notes/mentions', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -40,15 +40,15 @@ describe('Note thread mute', () => { test('ミュートしているスレッドからメンションされても、hasUnreadMentions が true にならない', async () => { // 状態リセット - await api('/i/read-all-unread-notes', {}, alice); + await api('i/read-all-unread-notes', {}, alice); const bobNote = await post(bob, { text: '@alice @carol root note' }); - await api('/notes/thread-muting/create', { noteId: bobNote.id }, alice); + await api('notes/thread-muting/create', { noteId: bobNote.id }, alice); const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' }); - const res = await api('/i', {}, alice); + const res = await api('i', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(res.body.hasUnreadMentions, false); @@ -56,11 +56,11 @@ describe('Note thread mute', () => { test('ミュートしているスレッドからメンションされても、ストリームに unreadMention イベントが流れてこない', () => new Promise(async done => { // 状態リセット - await api('/i/read-all-unread-notes', {}, alice); + await api('i/read-all-unread-notes', {}, alice); const bobNote = await post(bob, { text: '@alice @carol root note' }); - await api('/notes/thread-muting/create', { noteId: bobNote.id }, alice); + await api('notes/thread-muting/create', { noteId: bobNote.id }, alice); let fired = false; @@ -84,12 +84,12 @@ describe('Note thread mute', () => { const bobNote = await post(bob, { text: '@alice @carol root note' }); const aliceReply = await post(alice, { replyId: bobNote.id, text: '@bob @carol child note' }); - await api('/notes/thread-muting/create', { noteId: bobNote.id }, alice); + await api('notes/thread-muting/create', { noteId: bobNote.id }, alice); const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' }); const carolReplyWithoutMention = await post(carol, { replyId: aliceReply.id, text: 'child note' }); - const res = await api('/i/notifications', {}, alice); + const res = await api('i/notifications', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts index 0e71d707dd..5487292afc 100644 --- a/packages/backend/test/e2e/timelines.ts +++ b/packages/backend/test/e2e/timelines.ts @@ -26,7 +26,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi'); @@ -35,14 +35,14 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーのノートが含まれる', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi' }); const carolNote = await post(carol, { text: 'hi' }); await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -51,14 +51,14 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーの visibility: followers なノートが含まれる', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); const carolNote = await post(carol, { text: 'hi' }); await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi'); @@ -68,14 +68,14 @@ describe('Timelines', () => { test.concurrent('withReplies: false でフォローしているユーザーの他人への返信が含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -84,15 +84,15 @@ describe('Timelines', () => { test.concurrent('withReplies: true でフォローしているユーザーの他人への返信が含まれる', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); - await api('/following/update', { userId: bob.id, withReplies: true }, alice); + await api('following/create', { userId: bob.id }, alice); + await api('following/update', { userId: bob.id, withReplies: true }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -101,15 +101,15 @@ describe('Timelines', () => { test.concurrent('withReplies: true でフォローしているユーザーの他人へのDM返信が含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); - await api('/following/update', { userId: bob.id, withReplies: true }, alice); + await api('following/create', { userId: bob.id }, alice); + await api('following/update', { userId: bob.id, withReplies: true }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] }); await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -118,15 +118,15 @@ describe('Timelines', () => { test.concurrent('withReplies: true でフォローしているユーザーの他人の visibility: followers な投稿への返信が含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); - await api('/following/update', { userId: bob.id, withReplies: true }, alice); + await api('following/create', { userId: bob.id }, alice); + await api('following/update', { userId: bob.id, withReplies: true }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi', visibility: 'followers' }); const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -135,17 +135,17 @@ describe('Timelines', () => { test.concurrent('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの visibility: followers な投稿への返信が含まれる', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); - await api('/following/create', { userId: carol.id }, alice); - await api('/following/create', { userId: carol.id }, bob); - await api('/following/update', { userId: bob.id, withReplies: true }, alice); + await api('following/create', { userId: bob.id }, alice); + await api('following/create', { userId: carol.id }, alice); + await api('following/create', { userId: carol.id }, bob); + await api('following/update', { userId: bob.id, withReplies: true }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi', visibility: 'followers' }); const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true); @@ -155,16 +155,16 @@ describe('Timelines', () => { test.concurrent('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの投稿への visibility: specified な返信が含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); - await api('/following/create', { userId: carol.id }, alice); - await api('/following/update', { userId: bob.id, withReplies: true }, alice); + await api('following/create', { userId: bob.id }, alice); + await api('following/create', { userId: carol.id }, alice); + await api('following/update', { userId: bob.id, withReplies: true }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] }); await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true); @@ -173,14 +173,14 @@ describe('Timelines', () => { test.concurrent('withReplies: false でフォローしているユーザーのそのユーザー自身への返信が含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const bobNote1 = await post(bob, { text: 'hi' }); const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id }); await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); @@ -189,14 +189,14 @@ describe('Timelines', () => { test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const aliceNote = await post(alice, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id }); await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); @@ -210,7 +210,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); @@ -219,14 +219,14 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーの他人の投稿のリノートが含まれる', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { renoteId: carolNote.id }); await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -235,14 +235,14 @@ describe('Timelines', () => { test.concurrent('[withRenotes: false] フォローしているユーザーの他人の投稿のリノートが含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { renoteId: carolNote.id }); await waitForPushToTl(); - const res = await api('/notes/timeline', { + const res = await api('notes/timeline', { withRenotes: false, }, alice); @@ -253,14 +253,14 @@ describe('Timelines', () => { test.concurrent('[withRenotes: false] フォローしているユーザーの他人の投稿の引用が含まれる', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id }); await waitForPushToTl(); - const res = await api('/notes/timeline', { + const res = await api('notes/timeline', { withRenotes: false, }, alice); @@ -271,13 +271,13 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーの他人への visibility: specified なノートが含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] }); await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -285,15 +285,15 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーが行ったミュートしているユーザーのリノートが含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); - await api('/mute/create', { userId: carol.id }, alice); + await api('following/create', { userId: bob.id }, alice); + await api('mute/create', { userId: carol.id }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id }); await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -302,16 +302,16 @@ describe('Timelines', () => { test.concurrent('withReplies: true でフォローしているユーザーが行ったミュートしているユーザーの投稿への返信が含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); - await api('/following/update', { userId: bob.id, withReplies: true }, alice); - await api('/mute/create', { userId: carol.id }, alice); + await api('following/create', { userId: bob.id }, alice); + await api('following/update', { userId: bob.id, withReplies: true }, alice); + await api('mute/create', { userId: carol.id }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -321,13 +321,13 @@ describe('Timelines', () => { const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); const bobNote = await post(bob, { text: 'hi' }); await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -336,13 +336,13 @@ describe('Timelines', () => { const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -350,7 +350,7 @@ describe('Timelines', () => { test.concurrent('[withFiles: true] フォローしているユーザーのファイル付きノートのみ含まれる', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const [bobFile, carolFile] = await Promise.all([ uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'), @@ -363,7 +363,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100, withFiles: true }, alice); + const res = await api('notes/timeline', { limit: 100, withFiles: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); @@ -374,14 +374,14 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーのチャンネル投稿が含まれない', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - const channel = await api('/channels/create', { name: 'channel' }, bob).then(x => x.body); - await api('/following/create', { userId: bob.id }, alice); + const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -393,7 +393,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi'); @@ -402,13 +402,13 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーの自身を visibleUserIds に指定した visibility: specified なノートが含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] }); await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi'); @@ -421,7 +421,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -429,13 +429,13 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーの自身を visibleUserIds に指定していない visibility: specified なノートが含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] }); await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -448,7 +448,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'ok'); @@ -463,7 +463,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'ok'); @@ -479,7 +479,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -494,7 +494,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/local-timeline', { limit: 100 }, alice); + const res = await api('notes/local-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -508,7 +508,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/local-timeline', { limit: 100 }, alice); + const res = await api('notes/local-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true); @@ -522,7 +522,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/local-timeline', { limit: 100 }, alice); + const res = await api('notes/local-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); @@ -531,12 +531,12 @@ describe('Timelines', () => { test.concurrent('チャンネル投稿が含まれない', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - const channel = await api('/channels/create', { name: 'channel' }, bob).then(x => x.body); + const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body); const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); await waitForPushToTl(); - const res = await api('/notes/local-timeline', { limit: 100 }, alice); + const res = await api('notes/local-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -548,7 +548,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/local-timeline', { limit: 100 }, alice); + const res = await api('notes/local-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -557,14 +557,14 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーの visibility: home なノートが含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: carol.id }, alice); + await api('following/create', { userId: carol.id }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi', visibility: 'home' }); const bobNote = await post(bob, { text: 'hi' }); await waitForPushToTl(); - const res = await api('/notes/local-timeline', { limit: 100 }, alice); + const res = await api('notes/local-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -573,14 +573,14 @@ describe('Timelines', () => { test.concurrent('ミュートしているユーザーのノートが含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/mute/create', { userId: carol.id }, alice); + await api('mute/create', { userId: carol.id }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi' }); await waitForPushToTl(); - const res = await api('/notes/local-timeline', { limit: 100 }, alice); + const res = await api('notes/local-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -589,15 +589,15 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーが行ったミュートしているユーザーのリノートが含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); - await api('/mute/create', { userId: carol.id }, alice); + await api('following/create', { userId: bob.id }, alice); + await api('mute/create', { userId: carol.id }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id }); await waitForPushToTl(); - const res = await api('/notes/local-timeline', { limit: 100 }, alice); + const res = await api('notes/local-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -606,16 +606,16 @@ describe('Timelines', () => { test.concurrent('withReplies: true でフォローしているユーザーが行ったミュートしているユーザーの投稿への返信が含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); - await api('/following/update', { userId: bob.id, withReplies: true }, alice); - await api('/mute/create', { userId: carol.id }, alice); + await api('following/create', { userId: bob.id }, alice); + await api('following/update', { userId: bob.id, withReplies: true }, alice); + await api('mute/create', { userId: carol.id }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); await waitForPushToTl(); - const res = await api('/notes/local-timeline', { limit: 100 }, alice); + const res = await api('notes/local-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -624,14 +624,14 @@ describe('Timelines', () => { test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const aliceNote = await post(alice, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id }); await waitForPushToTl(); - const res = await api('/notes/local-timeline', { limit: 100 }, alice); + const res = await api('notes/local-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); @@ -645,7 +645,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/local-timeline', { limit: 100, withReplies: true }, alice); + const res = await api('notes/local-timeline', { limit: 100, withReplies: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -659,7 +659,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/local-timeline', { limit: 100, withFiles: true }, alice); + const res = await api('notes/local-timeline', { limit: 100, withFiles: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); @@ -674,7 +674,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/hybrid-timeline', { limit: 100 }, alice); + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -686,7 +686,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/hybrid-timeline', { limit: 100 }, alice); + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -694,13 +694,13 @@ describe('Timelines', () => { test.concurrent('フォローしているローカルユーザーの visibility: home なノートが含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); await waitForPushToTl(); - const res = await api('/notes/hybrid-timeline', { limit: 100 }, alice); + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -708,14 +708,14 @@ describe('Timelines', () => { test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const aliceNote = await post(alice, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id }); await waitForPushToTl(); - const res = await api('/notes/hybrid-timeline', { limit: 100 }, alice); + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); @@ -729,7 +729,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/hybrid-timeline', { limit: 100 }, alice); + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true); @@ -742,7 +742,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/local-timeline', { limit: 100 }, alice); + const res = await api('notes/local-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -751,13 +751,13 @@ describe('Timelines', () => { const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); const bobNote = await post(bob, { text: 'hi' }); await waitForPushToTl(); - const res = await api('/notes/hybrid-timeline', { limit: 100 }, alice); + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -766,13 +766,13 @@ describe('Timelines', () => { const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); await waitForPushToTl(); - const res = await api('/notes/hybrid-timeline', { limit: 100 }, alice); + const res = await api('notes/hybrid-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -785,7 +785,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/hybrid-timeline', { limit: 100, withReplies: true }, alice); + const res = await api('notes/hybrid-timeline', { limit: 100, withReplies: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -799,7 +799,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/hybrid-timeline', { limit: 100, withFiles: true }, alice); + const res = await api('notes/hybrid-timeline', { limit: 100, withFiles: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); @@ -810,14 +810,14 @@ describe('Timelines', () => { test.concurrent('リスインしているフォローしていないユーザーのノートが含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice); + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi' }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -825,14 +825,14 @@ describe('Timelines', () => { test.concurrent('リスインしているフォローしていないユーザーの visibility: home なノートが含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice); + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -840,14 +840,14 @@ describe('Timelines', () => { test.concurrent('リスインしているフォローしていないユーザーの visibility: followers なノートが含まれない', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice); + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -855,15 +855,15 @@ describe('Timelines', () => { test.concurrent('リスインしているフォローしていないユーザーの他人への返信が含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice); + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -871,15 +871,15 @@ describe('Timelines', () => { test.concurrent('リスインしているフォローしていないユーザーのユーザー自身への返信が含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice); + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); await sleep(1000); const bobNote1 = await post(bob, { text: 'hi' }); const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); @@ -888,32 +888,50 @@ describe('Timelines', () => { test.concurrent('withReplies: false でリスインしているフォローしていないユーザーからの自分への返信が含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice); + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); + await api('users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: false }, alice); await sleep(1000); const aliceNote = await post(alice, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id, withReplies: false }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); + test.concurrent('withReplies: false でリスインしているフォローしていないユーザーの他人への返信が含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); + await api('users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: false }, alice); + await sleep(1000); + const carolNote = await post(carol, { text: 'hi' }); + const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); + + await waitForPushToTl(); + + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + test.concurrent('withReplies: true でリスインしているフォローしていないユーザーの他人への返信が含まれる', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice); - await api('/users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: true }, alice); + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); + await api('users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: true }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -921,15 +939,15 @@ describe('Timelines', () => { test.concurrent('リスインしているフォローしているユーザーの visibility: home なノートが含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); - const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -937,15 +955,15 @@ describe('Timelines', () => { test.concurrent('リスインしているフォローしているユーザーの visibility: followers なノートが含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); - const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi'); @@ -954,14 +972,14 @@ describe('Timelines', () => { test.concurrent('リスインしている自分の visibility: followers なノートが含まれる', async () => { const [alice] = await Promise.all([signup(), signup()]); - const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('/users/lists/push', { listId: list.id, userId: alice.id }, alice); + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: alice.id }, alice); await sleep(1000); const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi'); @@ -970,15 +988,15 @@ describe('Timelines', () => { test.concurrent('リスインしているユーザーのチャンネルノートが含まれない', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - const channel = await api('/channels/create', { name: 'channel' }, bob).then(x => x.body); - const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice); + const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body); + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -986,15 +1004,15 @@ describe('Timelines', () => { test.concurrent('[withFiles: true] リスインしているユーザーのファイル付きノートのみ含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice); + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'); const bobNote1 = await post(bob, { text: 'hi' }); const bobNote2 = await post(bob, { fileIds: [file.id] }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id, withFiles: true }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id, withFiles: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); @@ -1003,14 +1021,14 @@ describe('Timelines', () => { test.concurrent('リスインしているユーザーの自身宛ての visibility: specified なノートが含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice); + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi'); @@ -1019,15 +1037,15 @@ describe('Timelines', () => { test.concurrent('リスインしているユーザーの自身宛てではない visibility: specified なノートが含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body); - await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice); - await api('/users/lists/push', { listId: list.id, userId: carol.id }, alice); + const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body); + await api('users/lists/push', { listId: list.id, userId: bob.id }, alice); + await api('users/lists/push', { listId: list.id, userId: carol.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -1041,7 +1059,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id }, alice); + const res = await api('users/notes', { userId: bob.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -1053,7 +1071,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id }, alice); + const res = await api('users/notes', { userId: bob.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -1061,13 +1079,13 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーの visibility: followers なノートが含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id }, alice); + const res = await api('users/notes', { userId: bob.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi'); @@ -1080,7 +1098,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/users/notes', { userId: alice.id }, alice); + const res = await api('users/notes', { userId: alice.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi'); @@ -1089,12 +1107,12 @@ describe('Timelines', () => { test.concurrent('チャンネル投稿が含まれない', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - const channel = await api('/channels/create', { name: 'channel' }, bob).then(x => x.body); + const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body); const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id }, alice); + const res = await api('users/notes', { userId: bob.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -1108,7 +1126,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id }, alice); + const res = await api('users/notes', { userId: bob.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), false); @@ -1123,7 +1141,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id, withReplies: true }, alice); + const res = await api('users/notes', { userId: bob.id, withReplies: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); @@ -1138,7 +1156,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id, withReplies: true }, alice); + const res = await api('users/notes', { userId: bob.id, withReplies: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), false); @@ -1153,7 +1171,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id, withFiles: true }, alice); + const res = await api('users/notes', { userId: bob.id, withFiles: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); @@ -1162,12 +1180,12 @@ describe('Timelines', () => { test.concurrent('[withChannelNotes: true] チャンネル投稿が含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - const channel = await api('/channels/create', { name: 'channel' }, bob).then(x => x.body); + const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body); const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id, withChannelNotes: true }, alice); + const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -1175,12 +1193,12 @@ describe('Timelines', () => { test.concurrent('[withChannelNotes: true] 他人が取得した場合センシティブチャンネル投稿が含まれない', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - const channel = await api('/channels/create', { name: 'channel', isSensitive: true }, bob).then(x => x.body); + const channel = await api('channels/create', { name: 'channel', isSensitive: true }, bob).then(x => x.body); const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id, withChannelNotes: true }, alice); + const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -1188,12 +1206,12 @@ describe('Timelines', () => { test.concurrent('[withChannelNotes: true] 自分が取得した場合センシティブチャンネル投稿が含まれる', async () => { const [bob] = await Promise.all([signup()]); - const channel = await api('/channels/create', { name: 'channel', isSensitive: true }, bob).then(x => x.body); + const channel = await api('channels/create', { name: 'channel', isSensitive: true }, bob).then(x => x.body); const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id, withChannelNotes: true }, bob); + const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, bob); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -1201,14 +1219,14 @@ describe('Timelines', () => { test.concurrent('ミュートしているユーザーに関連する投稿が含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/mute/create', { userId: carol.id }, alice); + await api('mute/create', { userId: carol.id }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id }); await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id }, alice); + const res = await api('users/notes', { userId: bob.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -1216,7 +1234,7 @@ describe('Timelines', () => { test.concurrent('ミュートしていても userId に指定したユーザーの投稿が含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - await api('/mute/create', { userId: bob.id }, alice); + await api('mute/create', { userId: bob.id }, alice); await sleep(1000); const bobNote1 = await post(bob, { text: 'hi' }); const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id }); @@ -1224,7 +1242,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id }, alice); + const res = await api('users/notes', { userId: bob.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); @@ -1238,7 +1256,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/users/notes', { userId: alice.id, withReplies: true }, alice); + const res = await api('users/notes', { userId: alice.id, withReplies: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); }); @@ -1250,7 +1268,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id, withReplies: true }, alice); + const res = await api('users/notes', { userId: bob.id, withReplies: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); diff --git a/packages/backend/test/e2e/user-notes.ts b/packages/backend/test/e2e/user-notes.ts index 59b65e60f0..a927928f06 100644 --- a/packages/backend/test/e2e/user-notes.ts +++ b/packages/backend/test/e2e/user-notes.ts @@ -11,9 +11,9 @@ import type * as misskey from 'cherrypick-js'; describe('users/notes', () => { let alice: misskey.entities.SignupResponse; - let jpgNote: any; - let pngNote: any; - let jpgPngNote: any; + let jpgNote: misskey.entities.Note; + let pngNote: misskey.entities.Note; + let jpgPngNote: misskey.entities.Note; beforeAll(async () => { alice = await signup({ username: 'alice' }); @@ -31,7 +31,7 @@ describe('users/notes', () => { }, 1000 * 60 * 2); test('withFiles', async () => { - const res = await api('/users/notes', { + const res = await api('users/notes', { userId: alice.id, withFiles: true, }, alice); diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts index bca01a5856..5424cb822d 100644 --- a/packages/backend/test/e2e/users.ts +++ b/packages/backend/test/e2e/users.ts @@ -8,7 +8,7 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import { inspect } from 'node:util'; import { DEFAULT_POLICIES } from '@/core/RoleService.js'; -import { api, page, post, role, signup, successfulApiCall, uploadFile } from '../utils.js'; +import { api, post, role, signup, successfulApiCall, uploadFile } from '../utils.js'; import type * as misskey from 'cherrypick-js'; describe('ユーザー', () => { @@ -24,31 +24,12 @@ describe('ユーザー', () => { }, {}); }; - // BUG cherrypick-jsとjson-schemaと実際に返ってくるデータが全部違う - type UserLite = misskey.entities.UserLite & { - badgeRoles: any[], - }; - - type UserDetailedNotMe = UserLite & - misskey.entities.UserDetailed & { - roles: any[], - }; - - type MeDetailed = UserDetailedNotMe & - misskey.entities.MeDetailed & { - achievements: object[], - loggedInDays: number, - policies: object, - }; - - type User = MeDetailed & { token: string }; - - const show = async (id: string, me = root): Promise => { - return successfulApiCall({ endpoint: 'users/show', parameters: { userId: id }, user: me }) as any; + const show = async (id: string, me = root): Promise => { + return successfulApiCall({ endpoint: 'users/show', parameters: { userId: id }, user: me }); }; // UserLiteのキーが過不足なく入っている? - const userLite = (user: User): Partial => { + const userLite = (user: misskey.entities.UserLite): Partial => { return stripUndefined({ id: user.id, name: user.name, @@ -71,7 +52,7 @@ describe('ユーザー', () => { }; // UserDetailedNotMeのキーが過不足なく入っている? - const userDetailedNotMe = (user: User): Partial => { + const userDetailedNotMe = (user: misskey.entities.SignupResponse): Partial => { return stripUndefined({ ...userLite(user), url: user.url, @@ -111,7 +92,7 @@ describe('ユーザー', () => { }; // Relations関連のキーが過不足なく入っている? - const userDetailedNotMeWithRelations = (user: User): Partial => { + const userDetailedNotMeWithRelations = (user: misskey.entities.SignupResponse): Partial => { return stripUndefined({ ...userDetailedNotMe(user), isFollowing: user.isFollowing ?? false, @@ -128,7 +109,7 @@ describe('ユーザー', () => { }; // MeDetailedのキーが過不足なく入っている? - const meDetailed = (user: User, security = false): Partial => { + const meDetailed = (user: misskey.entities.SignupResponse, security = false): Partial => { return stripUndefined({ ...userDetailedNotMe(user), avatarId: user.avatarId, @@ -160,6 +141,7 @@ describe('ユーザー', () => { mutedWords: user.mutedWords, hardMutedWords: user.hardMutedWords, mutedInstances: user.mutedInstances, + // @ts-expect-error 後方互換性 mutingNotificationTypes: user.mutingNotificationTypes, notificationRecieveConfig: user.notificationRecieveConfig, emailNotificationTypes: user.emailNotificationTypes, @@ -174,61 +156,53 @@ describe('ユーザー', () => { }); }; - let root: User; - let alice: User; + let root: misskey.entities.SignupResponse; + let alice: misskey.entities.SignupResponse; let aliceNote: misskey.entities.Note; - let alicePage: misskey.entities.Page; - let aliceList: misskey.entities.UserList; - - let bob: User; - let bobNote: misskey.entities.Note; - - let carol: User; - let dave: User; - let ellen: User; - let frank: User; - - let usersReplying: User[]; - - let userNoNote: User; - let userNotExplorable: User; - let userLocking: User; - let userAdmin: User; - let roleAdmin: any; - let userModerator: User; - let roleModerator: any; - let userRolePublic: User; - let rolePublic: any; - let userRoleBadge: User; - let roleBadge: any; - let userSilenced: User; - let roleSilenced: any; - let userSuspended: User; - let userDeletedBySelf: User; - let userDeletedByAdmin: User; - let userFollowingAlice: User; - let userFollowedByAlice: User; - let userBlockingAlice: User; - let userBlockedByAlice: User; - let userMutingAlice: User; - let userMutedByAlice: User; - let userRnMutingAlice: User; - let userRnMutedByAlice: User; - let userFollowRequesting: User; - let userFollowRequested: User; + + let bob: misskey.entities.SignupResponse; + + // NOTE: これがないと落ちる(bob の updatedAt が null になってしまうため?) + let bobNote: misskey.entities.Note; // eslint-disable-line @typescript-eslint/no-unused-vars + + let carol: misskey.entities.SignupResponse; + + let usersReplying: misskey.entities.SignupResponse[]; + + let userNoNote: misskey.entities.SignupResponse; + let userNotExplorable: misskey.entities.SignupResponse; + let userLocking: misskey.entities.SignupResponse; + let userAdmin: misskey.entities.SignupResponse; + let roleAdmin: misskey.entities.Role; + let userModerator: misskey.entities.SignupResponse; + let roleModerator: misskey.entities.Role; + let userRolePublic: misskey.entities.SignupResponse; + let rolePublic: misskey.entities.Role; + let userRoleBadge: misskey.entities.SignupResponse; + let roleBadge: misskey.entities.Role; + let userSilenced: misskey.entities.SignupResponse; + let roleSilenced: misskey.entities.Role; + let userSuspended: misskey.entities.SignupResponse; + let userDeletedBySelf: misskey.entities.SignupResponse; + let userDeletedByAdmin: misskey.entities.SignupResponse; + let userFollowingAlice: misskey.entities.SignupResponse; + let userFollowedByAlice: misskey.entities.SignupResponse; + let userBlockingAlice: misskey.entities.SignupResponse; + let userBlockedByAlice: misskey.entities.SignupResponse; + let userMutingAlice: misskey.entities.SignupResponse; + let userMutedByAlice: misskey.entities.SignupResponse; + let userRnMutingAlice: misskey.entities.SignupResponse; + let userRnMutedByAlice: misskey.entities.SignupResponse; + let userFollowRequesting: misskey.entities.SignupResponse; + let userFollowRequested: misskey.entities.SignupResponse; beforeAll(async () => { root = await signup({ username: 'root' }); alice = await signup({ username: 'alice' }); - aliceNote = await post(alice, { text: 'test' }) as any; - alicePage = await page(alice); - aliceList = (await api('users/list/create', { name: 'aliceList' }, alice)).body; + aliceNote = await post(alice, { text: 'test' }); bob = await signup({ username: 'bob' }); - bobNote = await post(bob, { text: 'test' }) as any; + bobNote = await post(bob, { text: 'test' }); carol = await signup({ username: 'carol' }); - dave = await signup({ username: 'dave' }); - ellen = await signup({ username: 'ellen' }); - frank = await signup({ username: 'frank' }); // @alice -> @replyingへのリプライ。Promise.allで一気に作るとtimeoutしてしまうのでreduceで一つ一つawaitする usersReplying = await [...Array(10)].map((_, i) => i).reduce(async (acc, i) => { @@ -239,7 +213,7 @@ describe('ユーザー', () => { } return (await acc).concat(u); - }, Promise.resolve([] as User[])); + }, Promise.resolve([] as misskey.entities.SignupResponse[])); userNoNote = await signup({ username: 'userNoNote' }); userNotExplorable = await signup({ username: 'userNotExplorable' }); @@ -307,7 +281,7 @@ describe('ユーザー', () => { beforeEach(async () => { alice = { ...alice, - ...await successfulApiCall({ endpoint: 'i', parameters: {}, user: alice }) as any, + ...await successfulApiCall({ endpoint: 'i', parameters: {}, user: alice }), }; aliceNote = await successfulApiCall({ endpoint: 'notes/show', parameters: { noteId: aliceNote.id }, user: alice }); }); @@ -320,7 +294,7 @@ describe('ユーザー', () => { endpoint: 'signup', parameters: { username: 'zoe', password: 'password' }, user: undefined, - }) as unknown as User; // BUG MeDetailedに足りないキーがある + }) as unknown as misskey.entities.SignupResponse; // BUG MeDetailedに足りないキーがある // signupの時はtokenが含まれる特別なMeDetailedが返ってくる assert.match(response.token, /[a-zA-Z0-9]{16}/); @@ -330,7 +304,7 @@ describe('ユーザー', () => { assert.strictEqual(response.name, null); assert.strictEqual(response.username, 'zoe'); assert.strictEqual(response.host, null); - assert.match(response.avatarUrl, /^[-a-zA-Z0-9@:%._\+~#&?=\/]+$/); + response.avatarUrl && assert.match(response.avatarUrl, /^[-a-zA-Z0-9@:%._\+~#&?=\/]+$/); assert.strictEqual(response.avatarBlurhash, null); assert.deepStrictEqual(response.avatarDecorations, []); assert.strictEqual(response.isBot, false); @@ -403,6 +377,7 @@ describe('ユーザー', () => { assert.deepStrictEqual(response.unreadAnnouncements, []); assert.deepStrictEqual(response.mutedWords, []); assert.deepStrictEqual(response.mutedInstances, []); + // @ts-expect-error 後方互換のため assert.deepStrictEqual(response.mutingNotificationTypes, []); assert.deepStrictEqual(response.notificationRecieveConfig, {}); assert.deepStrictEqual(response.emailNotificationTypes, ['follow', 'receiveFollowRequest', 'groupInvited']); @@ -432,66 +407,66 @@ describe('ユーザー', () => { //#region 自分の情報の更新(i/update) test.each([ - { parameters: (): object => ({ name: null }) }, - { parameters: (): object => ({ name: 'x'.repeat(50) }) }, - { parameters: (): object => ({ name: 'x' }) }, - { parameters: (): object => ({ name: 'My name' }) }, - { parameters: (): object => ({ description: null }) }, - { parameters: (): object => ({ description: 'x'.repeat(1500) }) }, - { parameters: (): object => ({ description: 'x' }) }, - { parameters: (): object => ({ description: 'My description' }) }, - { parameters: (): object => ({ location: null }) }, - { parameters: (): object => ({ location: 'x'.repeat(50) }) }, - { parameters: (): object => ({ location: 'x' }) }, - { parameters: (): object => ({ location: 'My location' }) }, - { parameters: (): object => ({ birthday: '0000-00-00' }) }, - { parameters: (): object => ({ birthday: '9999-99-99' }) }, - { parameters: (): object => ({ lang: 'en-US' }) }, - { parameters: (): object => ({ fields: [] }) }, - { parameters: (): object => ({ fields: [{ name: 'x', value: 'x' }] }) }, - { parameters: (): object => ({ fields: [{ name: 'x'.repeat(3000), value: 'x'.repeat(3000) }] }) }, // BUG? fieldには制限がない - { parameters: (): object => ({ fields: Array(16).fill({ name: 'x', value: 'y' }) }) }, - { parameters: (): object => ({ isLocked: true }) }, - { parameters: (): object => ({ isLocked: false }) }, - { parameters: (): object => ({ isExplorable: false }) }, - { parameters: (): object => ({ isExplorable: true }) }, - { parameters: (): object => ({ hideOnlineStatus: true }) }, - { parameters: (): object => ({ hideOnlineStatus: false }) }, - { parameters: (): object => ({ publicReactions: false }) }, - { parameters: (): object => ({ publicReactions: true }) }, - { parameters: (): object => ({ autoAcceptFollowed: true }) }, - { parameters: (): object => ({ autoAcceptFollowed: false }) }, - { parameters: (): object => ({ noCrawle: true }) }, - { parameters: (): object => ({ noCrawle: false }) }, - { parameters: (): object => ({ preventAiLearning: false }) }, - { parameters: (): object => ({ preventAiLearning: true }) }, - { parameters: (): object => ({ isBot: true }) }, - { parameters: (): object => ({ isBot: false }) }, - { parameters: (): object => ({ isCat: true }) }, - { parameters: (): object => ({ isCat: false }) }, - { parameters: (): object => ({ injectFeaturedNote: true }) }, - { parameters: (): object => ({ injectFeaturedNote: false }) }, - { parameters: (): object => ({ receiveAnnouncementEmail: true }) }, - { parameters: (): object => ({ receiveAnnouncementEmail: false }) }, - { parameters: (): object => ({ alwaysMarkNsfw: true }) }, - { parameters: (): object => ({ alwaysMarkNsfw: false }) }, - { parameters: (): object => ({ autoSensitive: true }) }, - { parameters: (): object => ({ autoSensitive: false }) }, - { parameters: (): object => ({ followingVisibility: 'private' }) }, - { parameters: (): object => ({ followingVisibility: 'followers' }) }, - { parameters: (): object => ({ followingVisibility: 'public' }) }, - { parameters: (): object => ({ followersVisibility: 'private' }) }, - { parameters: (): object => ({ followersVisibility: 'followers' }) }, - { parameters: (): object => ({ followersVisibility: 'public' }) }, - { parameters: (): object => ({ mutedWords: Array(19).fill(['xxxxx']) }) }, - { parameters: (): object => ({ mutedWords: [['x'.repeat(194)]] }) }, - { parameters: (): object => ({ mutedWords: [] }) }, - { parameters: (): object => ({ mutedInstances: ['xxxx.xxxxx'] }) }, - { parameters: (): object => ({ mutedInstances: [] }) }, - { parameters: (): object => ({ notificationRecieveConfig: { mention: { type: 'following' } } }) }, - { parameters: (): object => ({ notificationRecieveConfig: {} }) }, - { parameters: (): object => ({ emailNotificationTypes: ['mention', 'reply', 'quote', 'follow', 'receiveFollowRequest', 'groupInvited'] }) }, - { parameters: (): object => ({ emailNotificationTypes: [] }) }, + { parameters: () => ({ name: null }) }, + { parameters: () => ({ name: 'x'.repeat(50) }) }, + { parameters: () => ({ name: 'x' }) }, + { parameters: () => ({ name: 'My name' }) }, + { parameters: () => ({ description: null }) }, + { parameters: () => ({ description: 'x'.repeat(1500) }) }, + { parameters: () => ({ description: 'x' }) }, + { parameters: () => ({ description: 'My description' }) }, + { parameters: () => ({ location: null }) }, + { parameters: () => ({ location: 'x'.repeat(50) }) }, + { parameters: () => ({ location: 'x' }) }, + { parameters: () => ({ location: 'My location' }) }, + { parameters: () => ({ birthday: '0000-00-00' }) }, + { parameters: () => ({ birthday: '9999-99-99' }) }, + { parameters: () => ({ lang: 'en-US' as const }) }, + { parameters: () => ({ fields: [] }) }, + { parameters: () => ({ fields: [{ name: 'x', value: 'x' }] }) }, + { parameters: () => ({ fields: [{ name: 'x'.repeat(3000), value: 'x'.repeat(3000) }] }) }, // BUG? fieldには制限がない + { parameters: () => ({ fields: Array(16).fill({ name: 'x', value: 'y' }) }) }, + { parameters: () => ({ isLocked: true }) }, + { parameters: () => ({ isLocked: false }) }, + { parameters: () => ({ isExplorable: false }) }, + { parameters: () => ({ isExplorable: true }) }, + { parameters: () => ({ hideOnlineStatus: true }) }, + { parameters: () => ({ hideOnlineStatus: false }) }, + { parameters: () => ({ publicReactions: false }) }, + { parameters: () => ({ publicReactions: true }) }, + { parameters: () => ({ autoAcceptFollowed: true }) }, + { parameters: () => ({ autoAcceptFollowed: false }) }, + { parameters: () => ({ noCrawle: true }) }, + { parameters: () => ({ noCrawle: false }) }, + { parameters: () => ({ preventAiLearning: false }) }, + { parameters: () => ({ preventAiLearning: true }) }, + { parameters: () => ({ isBot: true }) }, + { parameters: () => ({ isBot: false }) }, + { parameters: () => ({ isCat: true }) }, + { parameters: () => ({ isCat: false }) }, + { parameters: () => ({ injectFeaturedNote: true }) }, + { parameters: () => ({ injectFeaturedNote: false }) }, + { parameters: () => ({ receiveAnnouncementEmail: true }) }, + { parameters: () => ({ receiveAnnouncementEmail: false }) }, + { parameters: () => ({ alwaysMarkNsfw: true }) }, + { parameters: () => ({ alwaysMarkNsfw: false }) }, + { parameters: () => ({ autoSensitive: true }) }, + { parameters: () => ({ autoSensitive: false }) }, + { parameters: () => ({ followingVisibility: 'private' as const }) }, + { parameters: () => ({ followingVisibility: 'followers' as const }) }, + { parameters: () => ({ followingVisibility: 'public' as const }) }, + { parameters: () => ({ followersVisibility: 'private' as const }) }, + { parameters: () => ({ followersVisibility: 'followers' as const }) }, + { parameters: () => ({ followersVisibility: 'public' as const }) }, + { parameters: () => ({ mutedWords: Array(19).fill(['xxxxx']) }) }, + { parameters: () => ({ mutedWords: [['x'.repeat(194)]] }) }, + { parameters: () => ({ mutedWords: [] }) }, + { parameters: () => ({ mutedInstances: ['xxxx.xxxxx'] }) }, + { parameters: () => ({ mutedInstances: [] }) }, + { parameters: () => ({ notificationRecieveConfig: { mention: { type: 'following' } } }) }, + { parameters: () => ({ notificationRecieveConfig: {} }) }, + { parameters: () => ({ emailNotificationTypes: ['mention', 'reply', 'quote', 'follow', 'receiveFollowRequest'] }) }, + { parameters: () => ({ emailNotificationTypes: [] }) }, ] as const)('を書き換えることができる($#)', async ({ parameters }) => { const response = await successfulApiCall({ endpoint: 'i/update', parameters: parameters(), user: alice }); const expected = { ...meDetailed(alice, true), ...parameters() }; @@ -500,13 +475,13 @@ describe('ユーザー', () => { test('を書き換えることができる(Avatar)', async () => { const aliceFile = (await uploadFile(alice)).body; - const parameters = { avatarId: aliceFile.id }; + const parameters = { avatarId: aliceFile!.id }; const response = await successfulApiCall({ endpoint: 'i/update', parameters: parameters, user: alice }); assert.match(response.avatarUrl ?? '.', /^[-a-zA-Z0-9@:%._\+~#&?=\/]+$/); assert.match(response.avatarBlurhash ?? '.', /[ -~]{54}/); const expected = { ...meDetailed(alice, true), - avatarId: aliceFile.id, + avatarId: aliceFile!.id, avatarBlurhash: response.avatarBlurhash, avatarUrl: response.avatarUrl, }; @@ -525,13 +500,13 @@ describe('ユーザー', () => { test('を書き換えることができる(Banner)', async () => { const aliceFile = (await uploadFile(alice)).body; - const parameters = { bannerId: aliceFile.id }; + const parameters = { bannerId: aliceFile!.id }; const response = await successfulApiCall({ endpoint: 'i/update', parameters: parameters, user: alice }); assert.match(response.bannerUrl ?? '.', /^[-a-zA-Z0-9@:%._\+~#&?=\/]+$/); assert.match(response.bannerBlurhash ?? '.', /[ -~]{54}/); const expected = { ...meDetailed(alice, true), - bannerId: aliceFile.id, + bannerId: aliceFile!.id, bannerBlurhash: response.bannerBlurhash, bannerUrl: response.bannerUrl, }; @@ -581,13 +556,13 @@ describe('ユーザー', () => { //#region ユーザー(users) test.each([ - { label: 'ID昇順', parameters: { limit: 5 }, selector: (u: UserLite): string => u.id }, - { label: 'フォロワー昇順', parameters: { sort: '+follower' }, selector: (u: UserDetailedNotMe): string => String(u.followersCount) }, - { label: 'フォロワー降順', parameters: { sort: '-follower' }, selector: (u: UserDetailedNotMe): string => String(u.followersCount) }, - { label: '登録日時昇順', parameters: { sort: '+createdAt' }, selector: (u: UserDetailedNotMe): string => u.createdAt }, - { label: '登録日時降順', parameters: { sort: '-createdAt' }, selector: (u: UserDetailedNotMe): string => u.createdAt }, - { label: '投稿日時昇順', parameters: { sort: '+updatedAt' }, selector: (u: UserDetailedNotMe): string => String(u.updatedAt) }, - { label: '投稿日時降順', parameters: { sort: '-updatedAt' }, selector: (u: UserDetailedNotMe): string => String(u.updatedAt) }, + { label: 'ID昇順', parameters: { limit: 5 }, selector: (u: misskey.entities.UserLite): string => u.id }, + { label: 'フォロワー昇順', parameters: { sort: '+follower' }, selector: (u: misskey.entities.UserDetailedNotMe): string => String(u.followersCount) }, + { label: 'フォロワー降順', parameters: { sort: '-follower' }, selector: (u: misskey.entities.UserDetailedNotMe): string => String(u.followersCount) }, + { label: '登録日時昇順', parameters: { sort: '+createdAt' }, selector: (u: misskey.entities.UserDetailedNotMe): string => u.createdAt }, + { label: '登録日時降順', parameters: { sort: '-createdAt' }, selector: (u: misskey.entities.UserDetailedNotMe): string => u.createdAt }, + { label: '投稿日時昇順', parameters: { sort: '+updatedAt' }, selector: (u: misskey.entities.UserDetailedNotMe): string => String(u.updatedAt) }, + { label: '投稿日時降順', parameters: { sort: '-updatedAt' }, selector: (u: misskey.entities.UserDetailedNotMe): string => String(u.updatedAt) }, ] as const)('をリスト形式で取得することができる($label)', async ({ parameters, selector }) => { const response = await successfulApiCall({ endpoint: 'users', parameters, user: alice }); @@ -600,15 +575,15 @@ describe('ユーザー', () => { assert.deepStrictEqual(response, expected); }); test.each([ - { label: '「見つけやすくする」がOFFのユーザーが含まれない', user: (): User => userNotExplorable, excluded: true }, - { label: 'ミュートユーザーが含まれない', user: (): User => userMutedByAlice, excluded: true }, - { label: 'ブロックされているユーザーが含まれない', user: (): User => userBlockedByAlice, excluded: true }, - { label: 'ブロックしてきているユーザーが含まれる', user: (): User => userBlockingAlice, excluded: true }, - { label: '承認制ユーザーが含まれる', user: (): User => userLocking }, - { label: 'サイレンスユーザーが含まれる', user: (): User => userSilenced }, - { label: 'サスペンドユーザーが含まれない', user: (): User => userSuspended, excluded: true }, - { label: '削除済ユーザーが含まれる', user: (): User => userDeletedBySelf }, - { label: '削除済(byAdmin)ユーザーが含まれる', user: (): User => userDeletedByAdmin }, + { label: '「見つけやすくする」がOFFのユーザーが含まれない', user: () => userNotExplorable, excluded: true }, + { label: 'ミュートユーザーが含まれない', user: () => userMutedByAlice, excluded: true }, + { label: 'ブロックされているユーザーが含まれない', user: () => userBlockedByAlice, excluded: true }, + { label: 'ブロックしてきているユーザーが含まれる', user: () => userBlockingAlice, excluded: true }, + { label: '承認制ユーザーが含まれる', user: () => userLocking }, + { label: 'サイレンスユーザーが含まれる', user: () => userSilenced }, + { label: 'サスペンドユーザーが含まれない', user: () => userSuspended, excluded: true }, + { label: '削除済ユーザーが含まれる', user: () => userDeletedBySelf }, + { label: '削除済(byAdmin)ユーザーが含まれる', user: () => userDeletedByAdmin }, ] as const)('をリスト形式で取得することができ、結果に$label', async ({ user, excluded }) => { const parameters = { limit: 100 }; const response = await successfulApiCall({ endpoint: 'users', parameters, user: alice }); @@ -622,39 +597,44 @@ describe('ユーザー', () => { //#region ユーザー情報(users/show) test.each([ - { label: 'ID指定で自分自身を', parameters: (): object => ({ userId: alice.id }), user: (): User => alice, type: meDetailed }, - { label: 'ID指定で他人を', parameters: (): object => ({ userId: alice.id }), user: (): User => bob, type: userDetailedNotMeWithRelations }, - { label: 'ID指定かつ未認証', parameters: (): object => ({ userId: alice.id }), user: undefined, type: userDetailedNotMe }, - { label: '@指定で自分自身を', parameters: (): object => ({ username: alice.username }), user: (): User => alice, type: meDetailed }, - { label: '@指定で他人を', parameters: (): object => ({ username: alice.username }), user: (): User => bob, type: userDetailedNotMeWithRelations }, - { label: '@指定かつ未認証', parameters: (): object => ({ username: alice.username }), user: undefined, type: userDetailedNotMe }, + { label: 'ID指定で自分自身を', parameters: () => ({ userId: alice.id }), user: () => alice, type: meDetailed }, + { label: 'ID指定で他人を', parameters: () => ({ userId: alice.id }), user: () => bob, type: userDetailedNotMeWithRelations }, + { label: 'ID指定かつ未認証', parameters: () => ({ userId: alice.id }), user: undefined, type: userDetailedNotMe }, + { label: '@指定で自分自身を', parameters: () => ({ username: alice.username }), user: () => alice, type: meDetailed }, + { label: '@指定で他人を', parameters: () => ({ username: alice.username }), user: () => bob, type: userDetailedNotMeWithRelations }, + { label: '@指定かつ未認証', parameters: () => ({ username: alice.username }), user: undefined, type: userDetailedNotMe }, ] as const)('を取得することができる($label)', async ({ parameters, user, type }) => { const response = await successfulApiCall({ endpoint: 'users/show', parameters: parameters(), user: user?.() }); const expected = type(alice); assert.deepStrictEqual(response, expected); }); test.each([ - { label: 'Administratorになっている', user: (): User => userAdmin, me: (): User => userAdmin, selector: (user: User): unknown => user.isAdmin }, - { label: '自分以外から見たときはAdministratorか判定できない', user: (): User => userAdmin, selector: (user: User): unknown => user.isAdmin, expected: (): undefined => undefined }, - { label: 'Moderatorになっている', user: (): User => userModerator, me: (): User => userModerator, selector: (user: User): unknown => user.isModerator }, - { label: '自分以外から見たときはModeratorか判定できない', user: (): User => userModerator, selector: (user: User): unknown => user.isModerator, expected: (): undefined => undefined }, - { label: 'サイレンスになっている', user: (): User => userSilenced, selector: (user: User): unknown => user.isSilenced }, - //{ label: 'サスペンドになっている', user: (): User => userSuspended, selector: (user: User): unknown => user.isSuspended }, - { label: '削除済みになっている', user: (): User => userDeletedBySelf, me: (): User => userDeletedBySelf, selector: (user: User): unknown => user.isDeleted }, - { label: '自分以外から見たときは削除済みか判定できない', user: (): User => userDeletedBySelf, selector: (user: User): unknown => user.isDeleted, expected: (): undefined => undefined }, - { label: '削除済み(byAdmin)になっている', user: (): User => userDeletedByAdmin, me: (): User => userDeletedByAdmin, selector: (user: User): unknown => user.isDeleted }, - { label: '自分以外から見たときは削除済み(byAdmin)か判定できない', user: (): User => userDeletedByAdmin, selector: (user: User): unknown => user.isDeleted, expected: (): undefined => undefined }, - { label: 'フォロー中になっている', user: (): User => userFollowedByAlice, selector: (user: User): unknown => user.isFollowing }, - { label: 'フォローされている', user: (): User => userFollowingAlice, selector: (user: User): unknown => user.isFollowed }, - { label: 'ブロック中になっている', user: (): User => userBlockedByAlice, selector: (user: User): unknown => user.isBlocking }, - { label: 'ブロックされている', user: (): User => userBlockingAlice, selector: (user: User): unknown => user.isBlocked }, - { label: 'ミュート中になっている', user: (): User => userMutedByAlice, selector: (user: User): unknown => user.isMuted }, - { label: 'リノートミュート中になっている', user: (): User => userRnMutedByAlice, selector: (user: User): unknown => user.isRenoteMuted }, - { label: 'フォローリクエスト中になっている', user: (): User => userFollowRequested, me: (): User => userFollowRequesting, selector: (user: User): unknown => user.hasPendingFollowRequestFromYou }, - { label: 'フォローリクエストされている', user: (): User => userFollowRequesting, me: (): User => userFollowRequested, selector: (user: User): unknown => user.hasPendingFollowRequestToYou }, + { label: 'Administratorになっている', user: () => userAdmin, me: () => userAdmin, selector: (user: misskey.entities.MeDetailed) => user.isAdmin }, + // @ts-expect-error UserDetailedNotMe doesn't include isAdmin + { label: '自分以外から見たときはAdministratorか判定できない', user: () => userAdmin, selector: (user: misskey.entities.UserDetailedNotMe) => user.isAdmin, expected: () => undefined }, + { label: 'Moderatorになっている', user: () => userModerator, me: () => userModerator, selector: (user: misskey.entities.MeDetailed) => user.isModerator }, + // @ts-expect-error UserDetailedNotMe doesn't include isModerator + { label: '自分以外から見たときはModeratorか判定できない', user: () => userModerator, selector: (user: misskey.entities.UserDetailedNotMe) => user.isModerator, expected: () => undefined }, + { label: 'サイレンスになっている', user: () => userSilenced, selector: (user: misskey.entities.UserDetailed) => user.isSilenced }, + // FIXME: 落ちる + //{ label: 'サスペンドになっている', user: () => userSuspended, selector: (user: misskey.entities.UserDetailed) => user.isSuspended }, + { label: '削除済みになっている', user: () => userDeletedBySelf, me: () => userDeletedBySelf, selector: (user: misskey.entities.MeDetailed) => user.isDeleted }, + // @ts-expect-error UserDetailedNotMe doesn't include isDeleted + { label: '自分以外から見たときは削除済みか判定できない', user: () => userDeletedBySelf, selector: (user: misskey.entities.UserDetailedNotMe) => user.isDeleted, expected: () => undefined }, + { label: '削除済み(byAdmin)になっている', user: () => userDeletedByAdmin, me: () => userDeletedByAdmin, selector: (user: misskey.entities.MeDetailed) => user.isDeleted }, + // @ts-expect-error UserDetailedNotMe doesn't include isDeleted + { label: '自分以外から見たときは削除済み(byAdmin)か判定できない', user: () => userDeletedByAdmin, selector: (user: misskey.entities.UserDetailedNotMe) => user.isDeleted, expected: () => undefined }, + { label: 'フォロー中になっている', user: () => userFollowedByAlice, selector: (user: misskey.entities.UserDetailed) => user.isFollowing }, + { label: 'フォローされている', user: () => userFollowingAlice, selector: (user: misskey.entities.UserDetailed) => user.isFollowed }, + { label: 'ブロック中になっている', user: () => userBlockedByAlice, selector: (user: misskey.entities.UserDetailed) => user.isBlocking }, + { label: 'ブロックされている', user: () => userBlockingAlice, selector: (user: misskey.entities.UserDetailed) => user.isBlocked }, + { label: 'ミュート中になっている', user: () => userMutedByAlice, selector: (user: misskey.entities.UserDetailed) => user.isMuted }, + { label: 'リノートミュート中になっている', user: () => userRnMutedByAlice, selector: (user: misskey.entities.UserDetailed) => user.isRenoteMuted }, + { label: 'フォローリクエスト中になっている', user: () => userFollowRequested, me: () => userFollowRequesting, selector: (user: misskey.entities.UserDetailed) => user.hasPendingFollowRequestFromYou }, + { label: 'フォローリクエストされている', user: () => userFollowRequesting, me: () => userFollowRequested, selector: (user: misskey.entities.UserDetailed) => user.hasPendingFollowRequestToYou }, ] as const)('を取得することができ、$labelこと', async ({ user, me, selector, expected }) => { const response = await successfulApiCall({ endpoint: 'users/show', parameters: { userId: user().id }, user: me?.() ?? alice }); - assert.strictEqual(selector(response), (expected ?? ((): true => true))()); + assert.strictEqual(selector(response as any), (expected ?? ((): true => true))()); }); test('を取得することができ、Publicなロールがセットされていること', async () => { const response = await successfulApiCall({ endpoint: 'users/show', parameters: { userId: userRolePublic.id }, user: alice }); @@ -696,17 +676,18 @@ describe('ユーザー', () => { assert.deepStrictEqual(response, expected); }); test.each([ - { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: (): User => userNotExplorable }, - { label: 'ミュートユーザーが含まれる', user: (): User => userMutedByAlice }, - { label: 'ブロックされているユーザーが含まれる', user: (): User => userBlockedByAlice }, - { label: 'ブロックしてきているユーザーが含まれる', user: (): User => userBlockingAlice }, - { label: '承認制ユーザーが含まれる', user: (): User => userLocking }, - { label: 'サイレンスユーザーが含まれる', user: (): User => userSilenced }, - { label: 'サスペンドユーザーが(モデレーターが見るときは)含まれる', user: (): User => userSuspended, me: (): User => root }, + { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: () => userNotExplorable }, + { label: 'ミュートユーザーが含まれる', user: () => userMutedByAlice }, + { label: 'ブロックされているユーザーが含まれる', user: () => userBlockedByAlice }, + { label: 'ブロックしてきているユーザーが含まれる', user: () => userBlockingAlice }, + { label: '承認制ユーザーが含まれる', user: () => userLocking }, + { label: 'サイレンスユーザーが含まれる', user: () => userSilenced }, + { label: 'サスペンドユーザーが(モデレーターが見るときは)含まれる', user: () => userSuspended, me: () => root }, // BUG サスペンドユーザーを一般ユーザーから見るとrootユーザーが返ってくる - //{ label: 'サスペンドユーザーが(一般ユーザーが見るときは)含まれない', user: (): User => userSuspended, me: (): User => bob, excluded: true }, - { label: '削除済ユーザーが含まれる', user: (): User => userDeletedBySelf }, - { label: '削除済(byAdmin)ユーザーが含まれる', user: (): User => userDeletedByAdmin }, + //{ label: 'サスペンドユーザーが(一般ユーザーが見るときは)含まれない', user: () => userSuspended, me: () => bob, excluded: true }, + { label: '削除済ユーザーが含まれる', user: () => userDeletedBySelf }, + { label: '削除済(byAdmin)ユーザーが含まれる', user: () => userDeletedByAdmin }, + // @ts-expect-error excluded は上でコメントアウトされているので ] as const)('をID指定のリスト形式で取得することができ、結果に$label', async ({ user, me, excluded }) => { const parameters = { userIds: [user().id] }; const response = await successfulApiCall({ endpoint: 'users/show', parameters, user: me?.() ?? alice }); @@ -731,15 +712,15 @@ describe('ユーザー', () => { assert.deepStrictEqual(response, expected); }); test.each([ - { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: (): User => userNotExplorable }, - { label: 'ミュートユーザーが含まれる', user: (): User => userMutedByAlice }, - { label: 'ブロックされているユーザーが含まれる', user: (): User => userBlockedByAlice }, - { label: 'ブロックしてきているユーザーが含まれる', user: (): User => userBlockingAlice }, - { label: '承認制ユーザーが含まれる', user: (): User => userLocking }, - { label: 'サイレンスユーザーが含まれる', user: (): User => userSilenced }, - { label: 'サスペンドユーザーが含まれない', user: (): User => userSuspended, excluded: true }, - { label: '削除済ユーザーが含まれる', user: (): User => userDeletedBySelf }, - { label: '削除済(byAdmin)ユーザーが含まれる', user: (): User => userDeletedByAdmin }, + { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: () => userNotExplorable }, + { label: 'ミュートユーザーが含まれる', user: () => userMutedByAlice }, + { label: 'ブロックされているユーザーが含まれる', user: () => userBlockedByAlice }, + { label: 'ブロックしてきているユーザーが含まれる', user: () => userBlockingAlice }, + { label: '承認制ユーザーが含まれる', user: () => userLocking }, + { label: 'サイレンスユーザーが含まれる', user: () => userSilenced }, + { label: 'サスペンドユーザーが含まれない', user: () => userSuspended, excluded: true }, + { label: '削除済ユーザーが含まれる', user: () => userDeletedBySelf }, + { label: '削除済(byAdmin)ユーザーが含まれる', user: () => userDeletedByAdmin }, ] as const)('を検索することができ、結果に$labelが含まれる', async ({ user, excluded }) => { const parameters = { query: user().username, limit: 1 }; const response = await successfulApiCall({ endpoint: 'users/search', parameters, user: alice }); @@ -753,30 +734,30 @@ describe('ユーザー', () => { //#region ID指定検索(users/search-by-username-and-host) test.each([ - { label: '自分', parameters: { username: 'alice' }, user: (): User[] => [alice] }, - { label: '自分かつusernameが大文字', parameters: { username: 'ALICE' }, user: (): User[] => [alice] }, - { label: 'ローカルのフォロイーでノートなし', parameters: { username: 'userFollowedByAlice' }, user: (): User[] => [userFollowedByAlice] }, - { label: 'ローカルでノートなしは検索に載らない', parameters: { username: 'userNoNote' }, user: (): User[] => [] }, - { label: 'ローカルの他人1', parameters: { username: 'bob' }, user: (): User[] => [bob] }, - { label: 'ローカルの他人2', parameters: { username: 'bob', host: null }, user: (): User[] => [bob] }, - { label: 'ローカルの他人3', parameters: { username: 'bob', host: '.' }, user: (): User[] => [bob] }, - { label: 'ローカル', parameters: { host: null, limit: 1 }, user: (): User[] => [userFollowedByAlice] }, - { label: 'ローカル', parameters: { host: '.', limit: 1 }, user: (): User[] => [userFollowedByAlice] }, + { label: '自分', parameters: { username: 'alice' }, user: () => [alice] }, + { label: '自分かつusernameが大文字', parameters: { username: 'ALICE' }, user: () => [alice] }, + { label: 'ローカルのフォロイーでノートなし', parameters: { username: 'userFollowedByAlice' }, user: () => [userFollowedByAlice] }, + { label: 'ローカルでノートなしは検索に載らない', parameters: { username: 'userNoNote' }, user: () => [] }, + { label: 'ローカルの他人1', parameters: { username: 'bob' }, user: () => [bob] }, + { label: 'ローカルの他人2', parameters: { username: 'bob', host: null }, user: () => [bob] }, + { label: 'ローカルの他人3', parameters: { username: 'bob', host: '.' }, user: () => [bob] }, + { label: 'ローカル', parameters: { host: null, limit: 1 }, user: () => [userFollowedByAlice] }, + { label: 'ローカル', parameters: { host: '.', limit: 1 }, user: () => [userFollowedByAlice] }, ])('をID&ホスト指定で検索できる($label)', async ({ parameters, user }) => { const response = await successfulApiCall({ endpoint: 'users/search-by-username-and-host', parameters, user: alice }); const expected = await Promise.all(user().map(u => show(u.id, alice))); assert.deepStrictEqual(response, expected); }); test.each([ - { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: (): User => userNotExplorable }, - { label: 'ミュートユーザーが含まれる', user: (): User => userMutedByAlice }, - { label: 'ブロックされているユーザーが含まれる', user: (): User => userBlockedByAlice }, - { label: 'ブロックしてきているユーザーが含まれる', user: (): User => userBlockingAlice }, - { label: '承認制ユーザーが含まれる', user: (): User => userLocking }, - { label: 'サイレンスユーザーが含まれる', user: (): User => userSilenced }, - { label: 'サスペンドユーザーが含まれない', user: (): User => userSuspended, excluded: true }, - { label: '削除済ユーザーが含まれる', user: (): User => userDeletedBySelf }, - { label: '削除済(byAdmin)ユーザーが含まれる', user: (): User => userDeletedByAdmin }, + { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: () => userNotExplorable }, + { label: 'ミュートユーザーが含まれる', user: () => userMutedByAlice }, + { label: 'ブロックされているユーザーが含まれる', user: () => userBlockedByAlice }, + { label: 'ブロックしてきているユーザーが含まれる', user: () => userBlockingAlice }, + { label: '承認制ユーザーが含まれる', user: () => userLocking }, + { label: 'サイレンスユーザーが含まれる', user: () => userSilenced }, + { label: 'サスペンドユーザーが含まれない', user: () => userSuspended, excluded: true }, + { label: '削除済ユーザーが含まれる', user: () => userDeletedBySelf }, + { label: '削除済(byAdmin)ユーザーが含まれる', user: () => userDeletedByAdmin }, ] as const)('をID&ホスト指定で検索でき、結果に$label', async ({ user, excluded }) => { const parameters = { username: user().username }; const response = await successfulApiCall({ endpoint: 'users/search-by-username-and-host', parameters, user: alice }); @@ -798,15 +779,15 @@ describe('ユーザー', () => { assert.deepStrictEqual(response, expected); }); test.each([ - { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: (): User => userNotExplorable }, - { label: 'ミュートユーザーが含まれる', user: (): User => userMutedByAlice }, - { label: 'ブロックされているユーザーが含まれる', user: (): User => userBlockedByAlice }, - { label: 'ブロックしてきているユーザーが含まれない', user: (): User => userBlockingAlice, excluded: true }, - { label: '承認制ユーザーが含まれる', user: (): User => userLocking }, - { label: 'サイレンスユーザーが含まれる', user: (): User => userSilenced }, - //{ label: 'サスペンドユーザーが含まれない', user: (): User => userSuspended, excluded: true }, - { label: '削除済ユーザーが含まれる', user: (): User => userDeletedBySelf }, - { label: '削除済(byAdmin)ユーザーが含まれる', user: (): User => userDeletedByAdmin }, + { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: () => userNotExplorable }, + { label: 'ミュートユーザーが含まれる', user: () => userMutedByAlice }, + { label: 'ブロックされているユーザーが含まれる', user: () => userBlockedByAlice }, + { label: 'ブロックしてきているユーザーが含まれない', user: () => userBlockingAlice, excluded: true }, + { label: '承認制ユーザーが含まれる', user: () => userLocking }, + { label: 'サイレンスユーザーが含まれる', user: () => userSilenced }, + //{ label: 'サスペンドユーザーが含まれない', user: () => userSuspended, excluded: true }, + { label: '削除済ユーザーが含まれる', user: () => userDeletedBySelf }, + { label: '削除済(byAdmin)ユーザーが含まれる', user: () => userDeletedByAdmin }, ] as const)('がよくリプライをするユーザーのリストを取得でき、結果に$label', async ({ user, excluded }) => { const replyTo = (await successfulApiCall({ endpoint: 'users/notes', parameters: { userId: user().id }, user: undefined }))[0]; await post(alice, { text: `@${user().username} test`, replyId: replyTo.id }); @@ -820,12 +801,12 @@ describe('ユーザー', () => { //#region ハッシュタグ(hashtags/users) test.each([ - { label: 'フォロワー昇順', sort: { sort: '+follower' }, selector: (u: UserDetailedNotMe): string => String(u.followersCount) }, - { label: 'フォロワー降順', sort: { sort: '-follower' }, selector: (u: UserDetailedNotMe): string => String(u.followersCount) }, - { label: '登録日時昇順', sort: { sort: '+createdAt' }, selector: (u: UserDetailedNotMe): string => u.createdAt }, - { label: '登録日時降順', sort: { sort: '-createdAt' }, selector: (u: UserDetailedNotMe): string => u.createdAt }, - { label: '投稿日時昇順', sort: { sort: '+updatedAt' }, selector: (u: UserDetailedNotMe): string => String(u.updatedAt) }, - { label: '投稿日時降順', sort: { sort: '-updatedAt' }, selector: (u: UserDetailedNotMe): string => String(u.updatedAt) }, + { label: 'フォロワー昇順', sort: { sort: '+follower' }, selector: (u: misskey.entities.UserDetailedNotMe): string => String(u.followersCount) }, + { label: 'フォロワー降順', sort: { sort: '-follower' }, selector: (u: misskey.entities.UserDetailedNotMe): string => String(u.followersCount) }, + { label: '登録日時昇順', sort: { sort: '+createdAt' }, selector: (u: misskey.entities.UserDetailedNotMe): string => u.createdAt }, + { label: '登録日時降順', sort: { sort: '-createdAt' }, selector: (u: misskey.entities.UserDetailedNotMe): string => u.createdAt }, + { label: '投稿日時昇順', sort: { sort: '+updatedAt' }, selector: (u: misskey.entities.UserDetailedNotMe): string => String(u.updatedAt) }, + { label: '投稿日時降順', sort: { sort: '-updatedAt' }, selector: (u: misskey.entities.UserDetailedNotMe): string => String(u.updatedAt) }, ] as const)('をハッシュタグ指定で取得することができる($label)', async ({ sort, selector }) => { const hashtag = 'test_hashtag'; await successfulApiCall({ endpoint: 'i/update', parameters: { description: `#${hashtag}` }, user: alice }); @@ -839,15 +820,15 @@ describe('ユーザー', () => { assert.deepStrictEqual(response, expected); }); test.each([ - { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: (): User => userNotExplorable }, - { label: 'ミュートユーザーが含まれる', user: (): User => userMutedByAlice }, - { label: 'ブロックされているユーザーが含まれる', user: (): User => userBlockedByAlice }, - { label: 'ブロックしてきているユーザーが含まれる', user: (): User => userBlockingAlice }, - { label: '承認制ユーザーが含まれる', user: (): User => userLocking }, - { label: 'サイレンスユーザーが含まれる', user: (): User => userSilenced }, - { label: 'サスペンドユーザーが含まれない', user: (): User => userSuspended, excluded: true }, - { label: '削除済ユーザーが含まれる', user: (): User => userDeletedBySelf }, - { label: '削除済(byAdmin)ユーザーが含まれる', user: (): User => userDeletedByAdmin }, + { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: () => userNotExplorable }, + { label: 'ミュートユーザーが含まれる', user: () => userMutedByAlice }, + { label: 'ブロックされているユーザーが含まれる', user: () => userBlockedByAlice }, + { label: 'ブロックしてきているユーザーが含まれる', user: () => userBlockingAlice }, + { label: '承認制ユーザーが含まれる', user: () => userLocking }, + { label: 'サイレンスユーザーが含まれる', user: () => userSilenced }, + { label: 'サスペンドユーザーが含まれない', user: () => userSuspended, excluded: true }, + { label: '削除済ユーザーが含まれる', user: () => userDeletedBySelf }, + { label: '削除済(byAdmin)ユーザーが含まれる', user: () => userDeletedByAdmin }, ] as const)('をハッシュタグ指定で取得することができ、結果に$label', async ({ user, excluded }) => { const hashtag = `user_test${user().username}`; if (user() !== userSuspended) { diff --git a/packages/backend/test/global.d.ts b/packages/backend/test/global.d.ts new file mode 100644 index 0000000000..0363073356 --- /dev/null +++ b/packages/backend/test/global.d.ts @@ -0,0 +1,7 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type FIXME = any; diff --git a/packages/backend/test/jest.setup.ts b/packages/backend/test/jest.setup.ts index cf5b9bf24d..861bc6db66 100644 --- a/packages/backend/test/jest.setup.ts +++ b/packages/backend/test/jest.setup.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + import { initTestDb, sendEnvResetRequest } from './utils.js'; beforeAll(async () => { diff --git a/packages/backend/test/prelude/get-api-validator.ts b/packages/backend/test/prelude/get-api-validator.ts index b86a7a978d..7aa7a92702 100644 --- a/packages/backend/test/prelude/get-api-validator.ts +++ b/packages/backend/test/prelude/get-api-validator.ts @@ -4,10 +4,10 @@ */ import Ajv from 'ajv'; -import { Schema } from '@/misc/schema'; +import { Schema } from '@/misc/json-schema.js'; export const getValidator = (paramDef: Schema) => { - const ajv = new Ajv({ + const ajv = new Ajv.default({ useDefaults: true, }); ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/); diff --git a/packages/backend/test/resources/kick_gaba7.m4a b/packages/backend/test/resources/kick_gaba7.m4a new file mode 100644 index 0000000000..321df6349f Binary files /dev/null and b/packages/backend/test/resources/kick_gaba7.m4a differ diff --git a/packages/backend/test/tsconfig.json b/packages/backend/test/tsconfig.json index 4597ff8780..2b562acda8 100644 --- a/packages/backend/test/tsconfig.json +++ b/packages/backend/test/tsconfig.json @@ -5,7 +5,7 @@ "noImplicitAny": true, "noImplicitReturns": true, "noUnusedParameters": false, - "noUnusedLocals": true, + "noUnusedLocals": false, "noFallthroughCasesInSwitch": true, "declaration": false, "sourceMap": true, @@ -18,6 +18,7 @@ "strict": true, "strictNullChecks": true, "strictPropertyInitialization": false, + "skipLibCheck": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "resolveJsonModule": true, diff --git a/packages/backend/test/unit/AnnouncementService.ts b/packages/backend/test/unit/AnnouncementService.ts index fc35837420..aa082ff2f2 100644 --- a/packages/backend/test/unit/AnnouncementService.ts +++ b/packages/backend/test/unit/AnnouncementService.ts @@ -51,7 +51,7 @@ describe('AnnouncementService', () => { function createAnnouncement(data: Partial = {}) { return announcementsRepository.insert({ - id: genAidx(data.createdAt ?? new Date()), + id: genAidx(data.createdAt?.getTime() ?? Date.now()), updatedAt: null, title: 'Title', text: 'Text', diff --git a/packages/backend/test/unit/ApMfmService.ts b/packages/backend/test/unit/ApMfmService.ts index 3fa55194e1..033b0e0bcf 100644 --- a/packages/backend/test/unit/ApMfmService.ts +++ b/packages/backend/test/unit/ApMfmService.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + import * as assert from 'assert'; import { Test } from '@nestjs/testing'; diff --git a/packages/backend/test/unit/FetchInstanceMetadataService.ts b/packages/backend/test/unit/FetchInstanceMetadataService.ts index e6e68ccd6d..bf8f3ab0e3 100644 --- a/packages/backend/test/unit/FetchInstanceMetadataService.ts +++ b/packages/backend/test/unit/FetchInstanceMetadataService.ts @@ -19,8 +19,8 @@ import { DI } from '@/di-symbols.js'; import type { TestingModule } from '@nestjs/testing'; function mockRedis() { - const hash = {}; - const set = jest.fn((key, value) => { + const hash = {} as any; + const set = jest.fn((key: string, value) => { const ret = hash[key]; hash[key] = value; return ret; @@ -56,12 +56,13 @@ describe('FetchInstanceMetadataService', () => { } else if (token === DI.redis) { return mockRedis; } + return null; }) .compile(); app.enableShutdownHooks(); - fetchInstanceMetadataService = app.get(FetchInstanceMetadataService); + fetchInstanceMetadataService = app.get(FetchInstanceMetadataService) as jest.Mocked; federatedInstanceService = app.get(FederatedInstanceService) as jest.Mocked; redisClient = app.get(DI.redis) as jest.Mocked; httpRequestService = app.get(HttpRequestService) as jest.Mocked; @@ -74,11 +75,12 @@ describe('FetchInstanceMetadataService', () => { test('Lock and update', async () => { redisClient.set = mockRedis(); const now = Date.now(); - federatedInstanceService.fetch.mockReturnValue({ infoUpdatedAt: { getTime: () => { return now - 10 * 1000 * 60 * 60 * 24; } } }); + federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => { return now - 10 * 1000 * 60 * 60 * 24; } } } as any); httpRequestService.getJson.mockImplementation(() => { throw Error(); }); const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock'); const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock'); - await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' }); + + await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any); expect(tryLockSpy).toHaveBeenCalledTimes(1); expect(unlockSpy).toHaveBeenCalledTimes(1); expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(1); @@ -88,11 +90,12 @@ describe('FetchInstanceMetadataService', () => { test('Lock and don\'t update', async () => { redisClient.set = mockRedis(); const now = Date.now(); - federatedInstanceService.fetch.mockReturnValue({ infoUpdatedAt: { getTime: () => now } }); + federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => now } } as any); httpRequestService.getJson.mockImplementation(() => { throw Error(); }); const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock'); const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock'); - await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' }); + + await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any); expect(tryLockSpy).toHaveBeenCalledTimes(1); expect(unlockSpy).toHaveBeenCalledTimes(1); expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(1); @@ -101,15 +104,33 @@ describe('FetchInstanceMetadataService', () => { test('Do nothing when lock not acquired', async () => { redisClient.set = mockRedis(); - federatedInstanceService.fetch.mockReturnValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } }); + const now = Date.now(); + federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } } as any); httpRequestService.getJson.mockImplementation(() => { throw Error(); }); + await fetchInstanceMetadataService.tryLock('example.com'); const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock'); const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock'); - await fetchInstanceMetadataService.tryLock('example.com'); - await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' }); - expect(tryLockSpy).toHaveBeenCalledTimes(2); + + await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any); + expect(tryLockSpy).toHaveBeenCalledTimes(1); expect(unlockSpy).toHaveBeenCalledTimes(0); expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(0); expect(httpRequestService.getJson).toHaveBeenCalledTimes(0); }); + + test('Do when lock not acquired but forced', async () => { + redisClient.set = mockRedis(); + const now = Date.now(); + federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } } as any); + httpRequestService.getJson.mockImplementation(() => { throw Error(); }); + await fetchInstanceMetadataService.tryLock('example.com'); + const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock'); + const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock'); + + await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any, true); + expect(tryLockSpy).toHaveBeenCalledTimes(0); + expect(unlockSpy).toHaveBeenCalledTimes(1); + expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(0); + expect(httpRequestService.getJson).toHaveBeenCalled(); + }); }); diff --git a/packages/backend/test/unit/FileInfoService.ts b/packages/backend/test/unit/FileInfoService.ts index 2eec80d763..40d187f5a8 100644 --- a/packages/backend/test/unit/FileInfoService.ts +++ b/packages/backend/test/unit/FileInfoService.ts @@ -15,6 +15,7 @@ import { GlobalModule } from '@/GlobalModule.js'; import { FileInfoService } from '@/core/FileInfoService.js'; //import { DI } from '@/di-symbols.js'; import { AiService } from '@/core/AiService.js'; +import { LoggerService } from '@/core/LoggerService.js'; import type { TestingModule } from '@nestjs/testing'; import type { MockFunctionMetadata } from 'jest-mock'; @@ -35,6 +36,7 @@ describe('FileInfoService', () => { ], providers: [ AiService, + LoggerService, FileInfoService, ], }) @@ -323,8 +325,26 @@ describe('FileInfoService', () => { }); }); - /* - * video/webmとして検出されてしまう + test('MPEG-4 AUDIO (M4A)', async () => { + const path = `${resources}/kick_gaba7.m4a`; + const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; + delete info.warnings; + delete info.blurhash; + delete info.sensitive; + delete info.porn; + delete info.width; + delete info.height; + delete info.orientation; + assert.deepStrictEqual(info, { + size: 9817, + md5: '74c9279a4abe98789565f1dc1a541a42', + type: { + mime: 'audio/mp4', + ext: 'm4a', + }, + }); + }); + test('WEBM AUDIO', async () => { const path = `${resources}/kick_gaba7.webm`; const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; @@ -337,13 +357,12 @@ describe('FileInfoService', () => { delete info.orientation; assert.deepStrictEqual(info, { size: 8879, - md5: '3350083dec312419cfdc06c16413aca7', + md5: '53bc1adcb6acbbda67ff9bd484896438', type: { mime: 'audio/webm', ext: 'webm', }, }); }); - */ }); }); diff --git a/packages/backend/test/unit/NoteCreateService.ts b/packages/backend/test/unit/NoteCreateService.ts new file mode 100644 index 0000000000..f2d4c8ffbb --- /dev/null +++ b/packages/backend/test/unit/NoteCreateService.ts @@ -0,0 +1,144 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Test } from '@nestjs/testing'; + +import { CoreModule } from '@/core/CoreModule.js'; +import { NoteCreateService } from '@/core/NoteCreateService.js'; +import { GlobalModule } from '@/GlobalModule.js'; +import { MiNote } from '@/models/Note.js'; +import { IPoll } from '@/models/Poll.js'; +import { MiDriveFile } from '@/models/DriveFile.js'; + +describe('NoteCreateService', () => { + let noteCreateService: NoteCreateService; + + beforeAll(async () => { + const app = await Test.createTestingModule({ + imports: [GlobalModule, CoreModule], + }).compile(); + noteCreateService = app.get(NoteCreateService); + }); + + describe('is-renote', () => { + const base: MiNote = { + id: 'some-note-id', + replyId: null, + reply: null, + renoteId: null, + renote: null, + threadId: null, + text: null, + name: null, + cw: null, + userId: 'some-user-id', + user: null, + localOnly: false, + reactionAcceptance: null, + renoteCount: 0, + repliesCount: 0, + clippedCount: 0, + reactions: {}, + visibility: 'public', + uri: null, + url: null, + fileIds: [], + attachedFileTypes: [], + visibleUserIds: [], + mentions: [], + mentionedRemoteUsers: '', + reactionAndUserPairCache: [], + emojis: [], + tags: [], + hasPoll: false, + channelId: null, + channel: null, + userHost: null, + replyUserId: null, + replyUserHost: null, + renoteUserId: null, + renoteUserHost: null, + }; + + const poll: IPoll = { + choices: ['kinoko', 'takenoko'], + multiple: false, + expiresAt: null, + }; + + const file: MiDriveFile = { + id: 'some-file-id', + userId: null, + user: null, + userHost: null, + md5: '', + name: '', + type: '', + size: 0, + comment: null, + blurhash: null, + properties: {}, + storedInternal: false, + url: '', + thumbnailUrl: null, + webpublicUrl: null, + webpublicType: null, + accessKey: null, + thumbnailAccessKey: null, + webpublicAccessKey: null, + uri: null, + src: null, + folderId: null, + folder: null, + isSensitive: false, + maybeSensitive: false, + maybePorn: false, + isLink: false, + requestHeaders: null, + requestIp: null, + }; + + test('note without renote should not be Renote', () => { + const note = { renote: null }; + expect(noteCreateService['isRenote'](note)).toBe(false); + }); + + test('note with renote should be Renote and not be Quote', () => { + const note = { renote: base }; + expect(noteCreateService['isRenote'](note)).toBe(true); + expect(noteCreateService['isQuote'](note)).toBe(false); + }); + + test('note with renote and text should be Quote', () => { + const note = { renote: base, text: 'some-text' }; + expect(noteCreateService['isRenote'](note)).toBe(true); + expect(noteCreateService['isQuote'](note)).toBe(true); + }); + + test('note with renote and cw should be Quote', () => { + const note = { renote: base, cw: 'some-cw' }; + expect(noteCreateService['isRenote'](note)).toBe(true); + expect(noteCreateService['isQuote'](note)).toBe(true); + }); + + test('note with renote and reply should be Quote', () => { + const note = { renote: base, reply: { ...base, id: 'another-note-id' } }; + expect(noteCreateService['isRenote'](note)).toBe(true); + expect(noteCreateService['isQuote'](note)).toBe(true); + }); + + test('note with renote and poll should be Quote', () => { + const note = { renote: base, poll }; + expect(noteCreateService['isRenote'](note)).toBe(true); + expect(noteCreateService['isQuote'](note)).toBe(true); + }); + + test('note with renote and non-empty files should be Quote', () => { + const note = { renote: base, files: [file] }; + expect(noteCreateService['isRenote'](note)).toBe(true); + expect(noteCreateService['isQuote'](note)).toBe(true); + }); + }); +}); diff --git a/packages/backend/test/unit/ReactionService.ts b/packages/backend/test/unit/ReactionService.ts index d1c31cac3a..1957f4544c 100644 --- a/packages/backend/test/unit/ReactionService.ts +++ b/packages/backend/test/unit/ReactionService.ts @@ -90,4 +90,45 @@ describe('ReactionService', () => { assert.strictEqual(await reactionService.normalize('unknown'), '❤'); }); }); + + describe('convertLegacyReactions', () => { + test('空の入力に対しては何もしない', () => { + const input = {}; + assert.deepStrictEqual(reactionService.convertLegacyReactions(input), input); + }); + + test('Unicode絵文字リアクションを変換してしまわない', () => { + const input = { '👍': 1, '🍮': 2 }; + assert.deepStrictEqual(reactionService.convertLegacyReactions(input), input); + }); + + test('カスタム絵文字リアクションを変換してしまわない', () => { + const input = { ':like@.:': 1, ':pudding@example.tld:': 2 }; + assert.deepStrictEqual(reactionService.convertLegacyReactions(input), input); + }); + + test('文字列によるレガシーなリアクションを変換する', () => { + const input = { 'like': 1, 'pudding': 2 }; + const output = { '👍': 1, '🍮': 2 }; + assert.deepStrictEqual(reactionService.convertLegacyReactions(input), output); + }); + + test('host部分が省略されたレガシーなカスタム絵文字リアクションを変換する', () => { + const input = { ':custom_emoji:': 1 }; + const output = { ':custom_emoji@.:': 1 }; + assert.deepStrictEqual(reactionService.convertLegacyReactions(input), output); + }); + + test('「0個のリアクション」情報を削除する', () => { + const input = { 'angry': 0 }; + const output = {}; + assert.deepStrictEqual(reactionService.convertLegacyReactions(input), output); + }); + + test('host部分の有無によりデコードすると同じ表記になるカスタム絵文字リアクションの個数情報を正しく足し合わせる', () => { + const input = { ':custom_emoji:': 1, ':custom_emoji@.:': 2 }; + const output = { ':custom_emoji@.:': 3 }; + assert.deepStrictEqual(reactionService.convertLegacyReactions(input), output); + }); + }); }); diff --git a/packages/backend/test/unit/RelayService.ts b/packages/backend/test/unit/RelayService.ts index f2a67dba46..9676abf07b 100644 --- a/packages/backend/test/unit/RelayService.ts +++ b/packages/backend/test/unit/RelayService.ts @@ -90,7 +90,8 @@ describe('RelayService', () => { expect(queueService.deliver).toHaveBeenCalled(); expect(queueService.deliver.mock.lastCall![1]?.type).toBe('Undo'); - expect(queueService.deliver.mock.lastCall![1]?.object.type).toBe('Follow'); + expect(typeof queueService.deliver.mock.lastCall![1]?.object).toBe('object'); + expect((queueService.deliver.mock.lastCall![1]?.object as any).type).toBe('Follow'); expect(queueService.deliver.mock.lastCall![2]).toBe('https://example.com'); //expect(queueService.deliver.mock.lastCall![0].username).toBe('relay.actor'); diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts index 5222745b7f..19d03570e0 100644 --- a/packages/backend/test/unit/RoleService.ts +++ b/packages/backend/test/unit/RoleService.ts @@ -228,11 +228,14 @@ describe('RoleService', () => { }, target: 'conditional', condFormula: { + id: '232a4221-9816-49a6-a967-ae0fac52ec5e', type: 'and', values: [{ + id: '2a37ef43-2d93-4c4d-87f6-f2fdb7d9b530', type: 'followersMoreThanOrEq', value: 10, }, { + id: '1bd67839-b126-4f92-bad0-4e285dab453b', type: 'createdMoreThan', sec: 60 * 60 * 24 * 7, }], @@ -251,6 +254,34 @@ describe('RoleService', () => { expect(user2Policies.canManageCustomEmojis).toBe(true); }); + test('コンディショナルロール: マニュアルロールにアサイン済み', async () => { + const [user1, user2, role1] = await Promise.all([ + createUser(), + createUser(), + createRole({ + name: 'manual role', + }), + ]); + const role2 = await createRole({ + name: 'conditional role', + target: 'conditional', + condFormula: { + // idはバックエンドのロジックに必要ない? + id: 'bdc612bd-9d54-4675-ae83-0499c82ea670', + type: 'roleAssignedTo', + roleId: role1.id, + }, + }); + await roleService.assign(user2.id, role1.id); + + const [u1role, u2role] = await Promise.all([ + roleService.getUserRoles(user1.id), + roleService.getUserRoles(user2.id), + ]); + expect(u1role.some(r => r.id === role2.id)).toBe(false); + expect(u2role.some(r => r.id === role2.id)).toBe(true); + }); + test('expired role', async () => { const user = await createUser(); const role = await createRole({ diff --git a/packages/backend/test/unit/entities/UserEntityService.ts b/packages/backend/test/unit/entities/UserEntityService.ts new file mode 100644 index 0000000000..ee16d421c4 --- /dev/null +++ b/packages/backend/test/unit/entities/UserEntityService.ts @@ -0,0 +1,528 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Test, TestingModule } from '@nestjs/testing'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { GlobalModule } from '@/GlobalModule.js'; +import { CoreModule } from '@/core/CoreModule.js'; +import type { MiUser } from '@/models/User.js'; +import { secureRndstr } from '@/misc/secure-rndstr.js'; +import { genAidx } from '@/misc/id/aidx.js'; +import { + BlockingsRepository, + FollowingsRepository, FollowRequestsRepository, + MiUserProfile, MutingsRepository, RenoteMutingsRepository, + UserMemoRepository, + UserProfilesRepository, + UsersRepository, +} from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; +import { AvatarDecorationService } from '@/core/AvatarDecorationService.js'; +import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { PageEntityService } from '@/core/entities/PageEntityService.js'; +import { CustomEmojiService } from '@/core/CustomEmojiService.js'; +import { AnnouncementService } from '@/core/AnnouncementService.js'; +import { RoleService } from '@/core/RoleService.js'; +import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; +import { IdService } from '@/core/IdService.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; +import { CacheService } from '@/core/CacheService.js'; +import { ApResolverService } from '@/core/activitypub/ApResolverService.js'; +import { ApNoteService } from '@/core/activitypub/models/ApNoteService.js'; +import { ApImageService } from '@/core/activitypub/models/ApImageService.js'; +import { ApMfmService } from '@/core/activitypub/ApMfmService.js'; +import { MfmService } from '@/core/MfmService.js'; +import { HashtagService } from '@/core/HashtagService.js'; +import UsersChart from '@/core/chart/charts/users.js'; +import { ChartLoggerService } from '@/core/chart/ChartLoggerService.js'; +import InstanceChart from '@/core/chart/charts/instance.js'; +import { ApLoggerService } from '@/core/activitypub/ApLoggerService.js'; +import { AccountMoveService } from '@/core/AccountMoveService.js'; +import { ReactionService } from '@/core/ReactionService.js'; +import { NotificationService } from '@/core/NotificationService.js'; + +process.env.NODE_ENV = 'test'; + +describe('UserEntityService', () => { + describe('pack/packMany', () => { + let app: TestingModule; + let service: UserEntityService; + let usersRepository: UsersRepository; + let userProfileRepository: UserProfilesRepository; + let userMemosRepository: UserMemoRepository; + let followingRepository: FollowingsRepository; + let followingRequestRepository: FollowRequestsRepository; + let blockingRepository: BlockingsRepository; + let mutingRepository: MutingsRepository; + let renoteMutingsRepository: RenoteMutingsRepository; + + async function createUser(userData: Partial = {}, profileData: Partial = {}) { + const un = secureRndstr(16); + const user = await usersRepository + .insert({ + ...userData, + id: genAidx(Date.now()), + username: un, + usernameLower: un, + }) + .then(x => usersRepository.findOneByOrFail(x.identifiers[0])); + + await userProfileRepository.insert({ + ...profileData, + userId: user.id, + }); + + return user; + } + + async function memo(writer: MiUser, target: MiUser, memo: string) { + await userMemosRepository.insert({ + id: genAidx(Date.now()), + userId: writer.id, + targetUserId: target.id, + memo, + }); + } + + async function follow(follower: MiUser, followee: MiUser) { + await followingRepository.insert({ + id: genAidx(Date.now()), + followerId: follower.id, + followeeId: followee.id, + }); + } + + async function requestFollow(requester: MiUser, requestee: MiUser) { + await followingRequestRepository.insert({ + id: genAidx(Date.now()), + followerId: requester.id, + followeeId: requestee.id, + }); + } + + async function block(blocker: MiUser, blockee: MiUser) { + await blockingRepository.insert({ + id: genAidx(Date.now()), + blockerId: blocker.id, + blockeeId: blockee.id, + }); + } + + async function mute(mutant: MiUser, mutee: MiUser) { + await mutingRepository.insert({ + id: genAidx(Date.now()), + muterId: mutant.id, + muteeId: mutee.id, + }); + } + + async function muteRenote(mutant: MiUser, mutee: MiUser) { + await renoteMutingsRepository.insert({ + id: genAidx(Date.now()), + muterId: mutant.id, + muteeId: mutee.id, + }); + } + + function randomIntRange(weight = 10) { + return [...Array(Math.floor(Math.random() * weight))].map((it, idx) => idx); + } + + beforeAll(async () => { + const services = [ + UserEntityService, + ApPersonService, + NoteEntityService, + PageEntityService, + CustomEmojiService, + AnnouncementService, + RoleService, + FederatedInstanceService, + IdService, + AvatarDecorationService, + UtilityService, + EmojiEntityService, + ModerationLogService, + GlobalEventService, + DriveFileEntityService, + MetaService, + FetchInstanceMetadataService, + CacheService, + ApResolverService, + ApNoteService, + ApImageService, + ApMfmService, + MfmService, + HashtagService, + UsersChart, + ChartLoggerService, + InstanceChart, + ApLoggerService, + AccountMoveService, + ReactionService, + NotificationService, + ]; + + app = await Test.createTestingModule({ + imports: [GlobalModule, CoreModule], + providers: [ + ...services, + ...services.map(x => ({ provide: x.name, useExisting: x })), + ], + }).compile(); + await app.init(); + app.enableShutdownHooks(); + + service = app.get(UserEntityService); + usersRepository = app.get(DI.usersRepository); + userProfileRepository = app.get(DI.userProfilesRepository); + userMemosRepository = app.get(DI.userMemosRepository); + followingRepository = app.get(DI.followingsRepository); + followingRequestRepository = app.get(DI.followRequestsRepository); + blockingRepository = app.get(DI.blockingsRepository); + mutingRepository = app.get(DI.mutingsRepository); + renoteMutingsRepository = app.get(DI.renoteMutingsRepository); + }); + + afterAll(async () => { + await app.close(); + }); + + test('UserLite', async() => { + const me = await createUser(); + const who = await createUser(); + + await memo(me, who, 'memo'); + + const actual = await service.pack(who, me, { schema: 'UserLite' }) as any; + // no detail + expect(actual.memo).toBeUndefined(); + // no detail and me + expect(actual.birthday).toBeUndefined(); + // no detail and me + expect(actual.achievements).toBeUndefined(); + }); + + test('UserDetailedNotMe', async() => { + const me = await createUser(); + const who = await createUser({}, { birthday: '2000-01-01' }); + + await memo(me, who, 'memo'); + + const actual = await service.pack(who, me, { schema: 'UserDetailedNotMe' }) as any; + // is detail + expect(actual.memo).toBe('memo'); + // is detail + expect(actual.birthday).toBe('2000-01-01'); + // no detail and me + expect(actual.achievements).toBeUndefined(); + }); + + test('MeDetailed', async() => { + const achievements = [{ name: 'achievement', unlockedAt: new Date().getTime() }]; + const me = await createUser({}, { + birthday: '2000-01-01', + achievements: achievements, + }); + await memo(me, me, 'memo'); + + const actual = await service.pack(me, me, { schema: 'MeDetailed' }) as any; + // is detail + expect(actual.memo).toBe('memo'); + // is detail + expect(actual.birthday).toBe('2000-01-01'); + // is detail and me + expect(actual.achievements).toEqual(achievements); + }); + + describe('packManyによるpreloadがある時、preloadが無い時とpackの結果が同じになるか見たい', () => { + test('no-preload', async() => { + const me = await createUser(); + // meがフォローしてる人たち + const followeeMe = await Promise.all(randomIntRange().map(() => createUser())); + for (const who of followeeMe) { + await follow(me, who); + const actual = await service.pack(who, me, { schema: 'UserDetailed' }) as any; + expect(actual.isFollowing).toBe(true); + expect(actual.isFollowed).toBe(false); + expect(actual.hasPendingFollowRequestFromYou).toBe(false); + expect(actual.hasPendingFollowRequestToYou).toBe(false); + expect(actual.isBlocking).toBe(false); + expect(actual.isBlocked).toBe(false); + expect(actual.isMuted).toBe(false); + expect(actual.isRenoteMuted).toBe(false); + } + + // meをフォローしてる人たち + const followerMe = await Promise.all(randomIntRange().map(() => createUser())); + for (const who of followerMe) { + await follow(who, me); + const actual = await service.pack(who, me, { schema: 'UserDetailed' }) as any; + expect(actual.isFollowing).toBe(false); + expect(actual.isFollowed).toBe(true); + expect(actual.hasPendingFollowRequestFromYou).toBe(false); + expect(actual.hasPendingFollowRequestToYou).toBe(false); + expect(actual.isBlocking).toBe(false); + expect(actual.isBlocked).toBe(false); + expect(actual.isMuted).toBe(false); + expect(actual.isRenoteMuted).toBe(false); + } + + // meがフォローリクエストを送った人たち + const requestsFromYou = await Promise.all(randomIntRange().map(() => createUser())); + for (const who of requestsFromYou) { + await requestFollow(me, who); + const actual = await service.pack(who, me, { schema: 'UserDetailed' }) as any; + expect(actual.isFollowing).toBe(false); + expect(actual.isFollowed).toBe(false); + expect(actual.hasPendingFollowRequestFromYou).toBe(true); + expect(actual.hasPendingFollowRequestToYou).toBe(false); + expect(actual.isBlocking).toBe(false); + expect(actual.isBlocked).toBe(false); + expect(actual.isMuted).toBe(false); + expect(actual.isRenoteMuted).toBe(false); + } + + // meにフォローリクエストを送った人たち + const requestsToYou = await Promise.all(randomIntRange().map(() => createUser())); + for (const who of requestsToYou) { + await requestFollow(who, me); + const actual = await service.pack(who, me, { schema: 'UserDetailed' }) as any; + expect(actual.isFollowing).toBe(false); + expect(actual.isFollowed).toBe(false); + expect(actual.hasPendingFollowRequestFromYou).toBe(false); + expect(actual.hasPendingFollowRequestToYou).toBe(true); + expect(actual.isBlocking).toBe(false); + expect(actual.isBlocked).toBe(false); + expect(actual.isMuted).toBe(false); + expect(actual.isRenoteMuted).toBe(false); + } + + // meがブロックしてる人たち + const blockingYou = await Promise.all(randomIntRange().map(() => createUser())); + for (const who of blockingYou) { + await block(me, who); + const actual = await service.pack(who, me, { schema: 'UserDetailed' }) as any; + expect(actual.isFollowing).toBe(false); + expect(actual.isFollowed).toBe(false); + expect(actual.hasPendingFollowRequestFromYou).toBe(false); + expect(actual.hasPendingFollowRequestToYou).toBe(false); + expect(actual.isBlocking).toBe(true); + expect(actual.isBlocked).toBe(false); + expect(actual.isMuted).toBe(false); + expect(actual.isRenoteMuted).toBe(false); + } + + // meをブロックしてる人たち + const blockingMe = await Promise.all(randomIntRange().map(() => createUser())); + for (const who of blockingMe) { + await block(who, me); + const actual = await service.pack(who, me, { schema: 'UserDetailed' }) as any; + expect(actual.isFollowing).toBe(false); + expect(actual.isFollowed).toBe(false); + expect(actual.hasPendingFollowRequestFromYou).toBe(false); + expect(actual.hasPendingFollowRequestToYou).toBe(false); + expect(actual.isBlocking).toBe(false); + expect(actual.isBlocked).toBe(true); + expect(actual.isMuted).toBe(false); + expect(actual.isRenoteMuted).toBe(false); + } + + // meがミュートしてる人たち + const muters = await Promise.all(randomIntRange().map(() => createUser())); + for (const who of muters) { + await mute(me, who); + const actual = await service.pack(who, me, { schema: 'UserDetailed' }) as any; + expect(actual.isFollowing).toBe(false); + expect(actual.isFollowed).toBe(false); + expect(actual.hasPendingFollowRequestFromYou).toBe(false); + expect(actual.hasPendingFollowRequestToYou).toBe(false); + expect(actual.isBlocking).toBe(false); + expect(actual.isBlocked).toBe(false); + expect(actual.isMuted).toBe(true); + expect(actual.isRenoteMuted).toBe(false); + } + + // meがリノートミュートしてる人たち + const renoteMuters = await Promise.all(randomIntRange().map(() => createUser())); + for (const who of renoteMuters) { + await muteRenote(me, who); + const actual = await service.pack(who, me, { schema: 'UserDetailed' }) as any; + expect(actual.isFollowing).toBe(false); + expect(actual.isFollowed).toBe(false); + expect(actual.hasPendingFollowRequestFromYou).toBe(false); + expect(actual.hasPendingFollowRequestToYou).toBe(false); + expect(actual.isBlocking).toBe(false); + expect(actual.isBlocked).toBe(false); + expect(actual.isMuted).toBe(false); + expect(actual.isRenoteMuted).toBe(true); + } + }); + + test('preload', async() => { + const me = await createUser(); + + { + // meがフォローしてる人たち + const followeeMe = await Promise.all(randomIntRange().map(() => createUser())); + for (const who of followeeMe) { + await follow(me, who); + } + const actualList = await service.packMany(followeeMe, me, { schema: 'UserDetailed' }) as any; + for (const actual of actualList) { + expect(actual.isFollowing).toBe(true); + expect(actual.isFollowed).toBe(false); + expect(actual.hasPendingFollowRequestFromYou).toBe(false); + expect(actual.hasPendingFollowRequestToYou).toBe(false); + expect(actual.isBlocking).toBe(false); + expect(actual.isBlocked).toBe(false); + expect(actual.isMuted).toBe(false); + expect(actual.isRenoteMuted).toBe(false); + } + } + + { + // meをフォローしてる人たち + const followerMe = await Promise.all(randomIntRange().map(() => createUser())); + for (const who of followerMe) { + await follow(who, me); + } + const actualList = await service.packMany(followerMe, me, { schema: 'UserDetailed' }) as any; + for (const actual of actualList) { + expect(actual.isFollowing).toBe(false); + expect(actual.isFollowed).toBe(true); + expect(actual.hasPendingFollowRequestFromYou).toBe(false); + expect(actual.hasPendingFollowRequestToYou).toBe(false); + expect(actual.isBlocking).toBe(false); + expect(actual.isBlocked).toBe(false); + expect(actual.isMuted).toBe(false); + expect(actual.isRenoteMuted).toBe(false); + } + } + + { + // meがフォローリクエストを送った人たち + const requestsFromYou = await Promise.all(randomIntRange().map(() => createUser())); + for (const who of requestsFromYou) { + await requestFollow(me, who); + } + const actualList = await service.packMany(requestsFromYou, me, { schema: 'UserDetailed' }) as any; + for (const actual of actualList) { + expect(actual.isFollowing).toBe(false); + expect(actual.isFollowed).toBe(false); + expect(actual.hasPendingFollowRequestFromYou).toBe(true); + expect(actual.hasPendingFollowRequestToYou).toBe(false); + expect(actual.isBlocking).toBe(false); + expect(actual.isBlocked).toBe(false); + expect(actual.isMuted).toBe(false); + expect(actual.isRenoteMuted).toBe(false); + } + } + + { + // meにフォローリクエストを送った人たち + const requestsToYou = await Promise.all(randomIntRange().map(() => createUser())); + for (const who of requestsToYou) { + await requestFollow(who, me); + } + const actualList = await service.packMany(requestsToYou, me, { schema: 'UserDetailed' }) as any; + for (const actual of actualList) { + expect(actual.isFollowing).toBe(false); + expect(actual.isFollowed).toBe(false); + expect(actual.hasPendingFollowRequestFromYou).toBe(false); + expect(actual.hasPendingFollowRequestToYou).toBe(true); + expect(actual.isBlocking).toBe(false); + expect(actual.isBlocked).toBe(false); + expect(actual.isMuted).toBe(false); + expect(actual.isRenoteMuted).toBe(false); + } + } + + { + // meがブロックしてる人たち + const blockingYou = await Promise.all(randomIntRange().map(() => createUser())); + for (const who of blockingYou) { + await block(me, who); + } + const actualList = await service.packMany(blockingYou, me, { schema: 'UserDetailed' }) as any; + for (const actual of actualList) { + expect(actual.isFollowing).toBe(false); + expect(actual.isFollowed).toBe(false); + expect(actual.hasPendingFollowRequestFromYou).toBe(false); + expect(actual.hasPendingFollowRequestToYou).toBe(false); + expect(actual.isBlocking).toBe(true); + expect(actual.isBlocked).toBe(false); + expect(actual.isMuted).toBe(false); + expect(actual.isRenoteMuted).toBe(false); + } + } + + { + // meをブロックしてる人たち + const blockingMe = await Promise.all(randomIntRange().map(() => createUser())); + for (const who of blockingMe) { + await block(who, me); + } + const actualList = await service.packMany(blockingMe, me, { schema: 'UserDetailed' }) as any; + for (const actual of actualList) { + expect(actual.isFollowing).toBe(false); + expect(actual.isFollowed).toBe(false); + expect(actual.hasPendingFollowRequestFromYou).toBe(false); + expect(actual.hasPendingFollowRequestToYou).toBe(false); + expect(actual.isBlocking).toBe(false); + expect(actual.isBlocked).toBe(true); + expect(actual.isMuted).toBe(false); + expect(actual.isRenoteMuted).toBe(false); + } + } + + { + // meがミュートしてる人たち + const muters = await Promise.all(randomIntRange().map(() => createUser())); + for (const who of muters) { + await mute(me, who); + } + const actualList = await service.packMany(muters, me, { schema: 'UserDetailed' }) as any; + for (const actual of actualList) { + expect(actual.isFollowing).toBe(false); + expect(actual.isFollowed).toBe(false); + expect(actual.hasPendingFollowRequestFromYou).toBe(false); + expect(actual.hasPendingFollowRequestToYou).toBe(false); + expect(actual.isBlocking).toBe(false); + expect(actual.isBlocked).toBe(false); + expect(actual.isMuted).toBe(true); + expect(actual.isRenoteMuted).toBe(false); + } + } + + { + // meがリノートミュートしてる人たち + const renoteMuters = await Promise.all(randomIntRange().map(() => createUser())); + for (const who of renoteMuters) { + await muteRenote(me, who); + } + const actualList = await service.packMany(renoteMuters, me, { schema: 'UserDetailed' }) as any; + for (const actual of actualList) { + expect(actual.isFollowing).toBe(false); + expect(actual.isFollowed).toBe(false); + expect(actual.hasPendingFollowRequestFromYou).toBe(false); + expect(actual.hasPendingFollowRequestToYou).toBe(false); + expect(actual.isBlocking).toBe(false); + expect(actual.isBlocked).toBe(false); + expect(actual.isMuted).toBe(false); + expect(actual.isRenoteMuted).toBe(true); + } + } + }); + }); + }); +}); diff --git a/packages/backend/test/unit/misc/is-renote.ts b/packages/backend/test/unit/misc/is-renote.ts new file mode 100644 index 0000000000..0b713e8bf6 --- /dev/null +++ b/packages/backend/test/unit/misc/is-renote.ts @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { isQuote, isRenote } from '@/misc/is-renote.js'; +import { MiNote } from '@/models/Note.js'; + +const base: MiNote = { + id: 'some-note-id', + replyId: null, + reply: null, + renoteId: null, + renote: null, + threadId: null, + text: null, + name: null, + cw: null, + userId: 'some-user-id', + user: null, + localOnly: false, + reactionAcceptance: null, + renoteCount: 0, + repliesCount: 0, + clippedCount: 0, + reactions: {}, + visibility: 'public', + uri: null, + url: null, + fileIds: [], + attachedFileTypes: [], + visibleUserIds: [], + mentions: [], + mentionedRemoteUsers: '', + reactionAndUserPairCache: [], + emojis: [], + tags: [], + hasPoll: false, + channelId: null, + channel: null, + userHost: null, + replyUserId: null, + replyUserHost: null, + renoteUserId: null, + renoteUserHost: null, +}; + +describe('misc:is-renote', () => { + test('note without renoteId should not be Renote', () => { + expect(isRenote(base)).toBe(false); + }); + + test('note with renoteId should be Renote and not be Quote', () => { + const note: MiNote = { ...base, renoteId: 'some-renote-id' }; + expect(isRenote(note)).toBe(true); + expect(isQuote(note as any)).toBe(false); + }); + + test('note with renoteId and text should be Quote', () => { + const note: MiNote = { ...base, renoteId: 'some-renote-id', text: 'some-text' }; + expect(isRenote(note)).toBe(true); + expect(isQuote(note as any)).toBe(true); + }); + + test('note with renoteId and cw should be Quote', () => { + const note: MiNote = { ...base, renoteId: 'some-renote-id', cw: 'some-cw' }; + expect(isRenote(note)).toBe(true); + expect(isQuote(note as any)).toBe(true); + }); + + test('note with renoteId and replyId should be Quote', () => { + const note: MiNote = { ...base, renoteId: 'some-renote-id', replyId: 'some-reply-id' }; + expect(isRenote(note)).toBe(true); + expect(isQuote(note as any)).toBe(true); + }); + + test('note with renoteId and poll should be Quote', () => { + const note: MiNote = { ...base, renoteId: 'some-renote-id', hasPoll: true }; + expect(isRenote(note)).toBe(true); + expect(isQuote(note as any)).toBe(true); + }); + + test('note with renoteId and non-empty fileIds should be Quote', () => { + const note: MiNote = { ...base, renoteId: 'some-renote-id', fileIds: ['some-file-id'] }; + expect(isRenote(note)).toBe(true); + expect(isQuote(note as any)).toBe(true); + }); +}); diff --git a/packages/backend/test/unit/misc/loader.ts b/packages/backend/test/unit/misc/loader.ts index fa37950951..2cf54e1555 100644 --- a/packages/backend/test/unit/misc/loader.ts +++ b/packages/backend/test/unit/misc/loader.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + import { DebounceLoader } from '@/misc/loader.js'; class Mock { diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index 004f1477c6..2c92f2c74b 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -9,11 +9,10 @@ import { basename, isAbsolute } from 'node:path'; import { randomUUID } from 'node:crypto'; import { inspect } from 'node:util'; import WebSocket, { ClientOptions } from 'ws'; -import fetch, { File, RequestInit } from 'node-fetch'; +import fetch, { File, RequestInit, type Headers } 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 '@/postgres.js'; import { loadConfig } from '@/config.js'; @@ -21,7 +20,7 @@ import type * as misskey from 'cherrypick-js'; export { server as startServer, jobQueue as startJobQueue } from '@/boot/common.js'; -interface UserToken { +export interface UserToken { token: string; bearer?: boolean; } @@ -35,20 +34,15 @@ export const cookie = (me: UserToken): string => { return `token=${me.token};`; }; -export const api = async (endpoint: string, params: any, me?: UserToken) => { - const normalized = endpoint.replace(/^\//, ''); - return await request(`api/${normalized}`, params, me); -}; - -export type ApiRequest = { - endpoint: string, - parameters: object, +export type ApiRequest = { + endpoint: E, + parameters: P, user: UserToken | undefined, }; -export const successfulApiCall = async (request: ApiRequest, assertion: { +export const successfulApiCall = async (request: ApiRequest, assertion: { status?: number, -} = {}): Promise => { +} = {}): Promise> => { const { endpoint, parameters, user } = request; const res = await api(endpoint, parameters, user); const status = assertion.status ?? (res.body == null ? 204 : 200); @@ -56,7 +50,7 @@ export const successfulApiCall = async (request: ApiRequest, assertion: { return res.body; }; -export const failedApiCall = async (request: ApiRequest, assertion: { +export const failedApiCall = async (request: ApiRequest, assertion: { status: number, code: string, id: string @@ -70,7 +64,7 @@ export const failedApiCall = async (request: ApiRequest, assertion: { return res.body; }; -const request = async (path: string, params: any, me?: UserToken): Promise<{ +export const api = async (path: E, params: misskey.Endpoints[E]['req'], me?: UserToken): Promise<{ status: number, headers: Headers, body: any @@ -86,7 +80,7 @@ const request = async (path: string, params: any, me?: UserToken): Promise<{ bodyAuth.i = me.token; } - const res = await relativeFetch(path, { + const res = await relativeFetch(`api/${path}`, { method: 'POST', headers, body: JSON.stringify(Object.assign(bodyAuth, params)), @@ -141,7 +135,7 @@ export const signup = async (params?: Partial => { +export const post = async (user: UserToken, params: misskey.Endpoints['notes/create']['req']): Promise => { const q = params; const res = await api('notes/create', q, user); @@ -159,8 +153,8 @@ export const createAppToken = async (user: UserToken, permissions: (typeof missk }; // 非公開ノートをAPI越しに見たときのノート NoteEntityService.ts -export const hiddenNote = (note: any): any => { - const temp = { +export const hiddenNote = (note: misskey.entities.Note): misskey.entities.Note => { + const temp: misskey.entities.Note = { ...note, fileIds: [], files: [], @@ -173,21 +167,22 @@ export const hiddenNote = (note: any): any => { return temp; }; -export const react = async (user: UserToken, note: any, reaction: string): Promise => { +export const react = async (user: UserToken, note: misskey.entities.Note, reaction: string): Promise => { await api('notes/reactions/create', { noteId: note.id, reaction: reaction, }, user); }; -export const userList = async (user: UserToken, userList: any = {}): Promise => { +export const userList = async (user: UserToken, userList: Partial = {}): Promise => { const res = await api('users/lists/create', { name: 'test', + ...userList, }, user); return res.body; }; -export const page = async (user: UserToken, page: any = {}): Promise => { +export const page = async (user: UserToken, page: Partial = {}): Promise => { const res = await api('pages/create', { alignCenter: false, content: [ @@ -198,7 +193,7 @@ export const page = async (user: UserToken, page: any = {}): Promise => { }, ], eyeCatchingImageId: null, - font: 'sans-serif', + font: 'sans-serif' as any, hideTitleWhenPinned: false, name: '1678594845072', script: '', @@ -210,7 +205,7 @@ export const page = async (user: UserToken, page: any = {}): Promise => { return res.body; }; -export const play = async (user: UserToken, play: any = {}): Promise => { +export const play = async (user: UserToken, play: Partial = {}): Promise => { const res = await api('flash/create', { permissions: [], script: 'test', @@ -221,7 +216,7 @@ export const play = async (user: UserToken, play: any = {}): Promise => { return res.body; }; -export const clip = async (user: UserToken, clip: any = {}): Promise => { +export const clip = async (user: UserToken, clip: Partial = {}): Promise => { const res = await api('clips/create', { description: null, isPublic: true, @@ -231,18 +226,18 @@ export const clip = async (user: UserToken, clip: any = {}): Promise => { return res.body; }; -export const galleryPost = async (user: UserToken, channel: any = {}): Promise => { +export const galleryPost = async (user: UserToken, galleryPost: Partial = {}): Promise => { const res = await api('gallery/posts/create', { description: null, fileIds: [], isSensitive: false, title: 'test', - ...channel, + ...galleryPost, }, user); return res.body; }; -export const channel = async (user: UserToken, channel: any = {}): Promise => { +export const channel = async (user: UserToken, channel: Partial = {}): Promise => { const res = await api('channels/create', { bannerId: null, description: null, @@ -252,7 +247,7 @@ export const channel = async (user: UserToken, channel: any = {}): Promise return res.body; }; -export const role = async (user: UserToken, role: any = {}, policies: any = {}): Promise => { +export const role = async (user: UserToken, role: Partial = {}, policies: any = {}): Promise => { const res = await api('admin/roles/create', { asBadge: false, canEditMembersByModerator: false, @@ -260,7 +255,7 @@ export const role = async (user: UserToken, role: any = {}, policies: any = {}): condFormula: { id: 'ebef1684-672d-49b6-ad82-1b3ec3784f85', type: 'isRemote', - }, + } as any, description: '', displayOrder: 0, iconUrl: null, @@ -298,7 +293,7 @@ interface UploadOptions { export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadOptions = {}): Promise<{ status: number, headers: Headers, - body: misskey.Endpoints['drive/files/create']['res'] | null + body: misskey.entities.DriveFile | null }> => { const absPath = path == null ? new URL('resources/Lenna.jpg', import.meta.url) @@ -335,14 +330,14 @@ export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadO }; }; -export const uploadUrl = async (user: UserToken, url: string): Promise> => { +export const uploadUrl = async (user: UserToken, url: string): Promise => { const marker = Math.random().toString(); const catcher = makeStreamCatcher( user, 'main', (msg) => msg.type === 'urlUploadFinished' && msg.body.marker === marker, - (msg) => msg.body.file as Packed<'DriveFile'>, + (msg) => msg.body.file, 60 * 1000, ); @@ -355,7 +350,7 @@ export const uploadUrl = async (user: UserToken, url: string): Promise) => any, params?: any): Promise { +export function connectStream(user: UserToken, channel: C, listener: (message: Record) => any, params?: misskey.Channels[C]['params']): Promise { return new Promise((res, rej) => { const url = new URL(`ws://127.0.0.1:${port}/streaming`); const options: ClientOptions = {}; @@ -390,7 +385,7 @@ export function connectStream(user: UserToken, channel: string, listener: (messa }); } -export const waitFire = async (user: UserToken, channel: string, trgr: () => any, cond: (msg: Record) => boolean, params?: any) => { +export const waitFire = async (user: UserToken, channel: C, trgr: () => any, cond: (msg: Record) => boolean, params?: misskey.Channels[C]['params']) => { return new Promise(async (res, rej) => { let timer: NodeJS.Timeout | null = null; @@ -435,7 +430,7 @@ export const waitFire = async (user: UserToken, channel: string, trgr: () => any */ export function makeStreamCatcher( user: UserToken, - channel: string, + channel: keyof misskey.Channels, cond: (message: Record) => boolean, extractor: (message: Record) => T, timeout = 60 * 1000): Promise { diff --git a/packages/cherrypick-js/.eslintignore b/packages/cherrypick-js/.eslintignore index f22128f047..52ea8b3362 100644 --- a/packages/cherrypick-js/.eslintignore +++ b/packages/cherrypick-js/.eslintignore @@ -5,3 +5,4 @@ node_modules /jest.config.ts /test /test-d +build.js diff --git a/packages/cherrypick-js/api-extractor.json b/packages/cherrypick-js/api-extractor.json index f80d0f20a8..a95281a6d5 100644 --- a/packages/cherrypick-js/api-extractor.json +++ b/packages/cherrypick-js/api-extractor.json @@ -45,7 +45,7 @@ * * SUPPORTED TOKENS: , , */ - "mainEntryPointFilePath": "/built/dts/index.d.ts", + "mainEntryPointFilePath": "/built/index.d.ts", /** * A list of NPM package names whose exports should be treated as part of this package. diff --git a/packages/cherrypick-js/build.js b/packages/cherrypick-js/build.js new file mode 100644 index 0000000000..0b79f4b915 --- /dev/null +++ b/packages/cherrypick-js/build.js @@ -0,0 +1,105 @@ +import * as esbuild from "esbuild"; +import { build } from "esbuild"; +import { globSync } from "glob"; +import { execa } from "execa"; +import fs from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname } from "node:path"; + +const _filename = fileURLToPath(import.meta.url); +const _dirname = dirname(_filename); +const _package = JSON.parse(fs.readFileSync(_dirname + '/package.json', 'utf-8')); + +const entryPoints = globSync("./src/**/**.{ts,tsx}"); + +/** @type {import('esbuild').BuildOptions} */ +const options = { + entryPoints, + minify: process.env.NODE_ENV === 'production', + outdir: "./built", + target: "es2022", + platform: "browser", + format: "esm", + sourcemap: 'linked', +}; + +// built配下をすべて削除する +fs.rmSync('./built', { recursive: true, force: true }); + +if (process.argv.map(arg => arg.toLowerCase()).includes("--watch")) { + await watchSrc(); +} else { + await buildSrc(); +} + +async function buildSrc() { + console.log(`[${_package.name}] start building...`); + + await build(options) + .then(it => { + console.log(`[${_package.name}] build succeeded.`); + }) + .catch((err) => { + process.stderr.write(err.stderr); + process.exit(1); + }); + + if (process.env.NODE_ENV === 'production') { + console.log(`[${_package.name}] skip building d.ts because NODE_ENV is production.`); + } else { + await buildDts(); + } + + console.log(`[${_package.name}] finish building.`); +} + +function buildDts() { + return execa( + 'tsc', + [ + '--project', 'tsconfig.json', + '--outDir', 'built', + '--declaration', 'true', + '--emitDeclarationOnly', 'true', + ], + { + stdout: process.stdout, + stderr: process.stderr, + } + ); +} + +async function watchSrc() { + const plugins = [{ + name: 'gen-dts', + setup(build) { + build.onStart(() => { + console.log(`[${_package.name}] detect changed...`); + }); + build.onEnd(async result => { + if (result.errors.length > 0) { + console.error(`[${_package.name}] watch build failed:`, result); + return; + } + await buildDts(); + }); + }, + }]; + + console.log(`[${_package.name}] start watching...`) + + const context = await esbuild.context({ ...options, plugins }); + await context.watch(); + + await new Promise((resolve, reject) => { + process.on('SIGHUP', resolve); + process.on('SIGINT', resolve); + process.on('SIGTERM', resolve); + process.on('SIGKILL', resolve); + process.on('uncaughtException', reject); + process.on('exit', resolve); + }).finally(async () => { + await context.dispose(); + console.log(`[${_package.name}] finish watching.`); + }); +} diff --git a/packages/cherrypick-js/etc/cherrypick-js.api.md b/packages/cherrypick-js/etc/cherrypick-js.api.md index a5b0803c4f..ce0a6b31e0 100644 --- a/packages/cherrypick-js/etc/cherrypick-js.api.md +++ b/packages/cherrypick-js/etc/cherrypick-js.api.md @@ -29,322 +29,328 @@ type Ad = components['schemas']['Ad']; // Warning: (ae-forgotten-export) The symbol "operations" needs to be exported by the entry point index.d.ts // // @public (undocumented) -type AdminAbuseReportResolverCreateRequest = operations['admin/abuse-report-resolver/create']['requestBody']['content']['application/json']; +type AdminAbuseReportResolverCreateRequest = operations['admin___abuse-report-resolver___create']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminAbuseReportResolverCreateResponse = operations['admin/abuse-report-resolver/create']['responses']['200']['content']['application/json']; +type AdminAbuseReportResolverCreateResponse = operations['admin___abuse-report-resolver___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminAbuseReportResolverDeleteRequest = operations['admin/abuse-report-resolver/delete']['requestBody']['content']['application/json']; +type AdminAbuseReportResolverDeleteRequest = operations['admin___abuse-report-resolver___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminAbuseReportResolverListRequest = operations['admin/abuse-report-resolver/list']['requestBody']['content']['application/json']; +type AdminAbuseReportResolverListRequest = operations['admin___abuse-report-resolver___list']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminAbuseReportResolverListResponse = operations['admin/abuse-report-resolver/list']['responses']['200']['content']['application/json']; +type AdminAbuseReportResolverListResponse = operations['admin___abuse-report-resolver___list']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminAbuseReportResolverUpdateRequest = operations['admin/abuse-report-resolver/update']['requestBody']['content']['application/json']; +type AdminAbuseReportResolverUpdateRequest = operations['admin___abuse-report-resolver___update']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminAbuseUserReportsRequest = operations['admin/abuse-user-reports']['requestBody']['content']['application/json']; +type AdminAbuseUserReportsRequest = operations['admin___abuse-user-reports']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminAbuseUserReportsResponse = operations['admin/abuse-user-reports']['responses']['200']['content']['application/json']; +type AdminAbuseUserReportsResponse = operations['admin___abuse-user-reports']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminAccountsCreateRequest = operations['admin/accounts/create']['requestBody']['content']['application/json']; +type AdminAccountsCreateRequest = operations['admin___accounts___create']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminAccountsCreateResponse = operations['admin/accounts/create']['responses']['200']['content']['application/json']; +type AdminAccountsCreateResponse = operations['admin___accounts___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminAccountsDeleteRequest = operations['admin/accounts/delete']['requestBody']['content']['application/json']; +type AdminAccountsDeleteRequest = operations['admin___accounts___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminAccountsFindByEmailRequest = operations['admin/accounts/find-by-email']['requestBody']['content']['application/json']; +type AdminAccountsFindByEmailRequest = operations['admin___accounts___find-by-email']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminAccountsFindByEmailResponse = operations['admin/accounts/find-by-email']['responses']['200']['content']['application/json']; +type AdminAccountsFindByEmailResponse = operations['admin___accounts___find-by-email']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminAdCreateRequest = operations['admin/ad/create']['requestBody']['content']['application/json']; +type AdminAdCreateRequest = operations['admin___ad___create']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminAdCreateResponse = operations['admin/ad/create']['responses']['200']['content']['application/json']; +type AdminAdCreateResponse = operations['admin___ad___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminAdDeleteRequest = operations['admin/ad/delete']['requestBody']['content']['application/json']; +type AdminAdDeleteRequest = operations['admin___ad___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminAdListRequest = operations['admin/ad/list']['requestBody']['content']['application/json']; +type AdminAdListRequest = operations['admin___ad___list']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminAdListResponse = operations['admin/ad/list']['responses']['200']['content']['application/json']; +type AdminAdListResponse = operations['admin___ad___list']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminAdUpdateRequest = operations['admin/ad/update']['requestBody']['content']['application/json']; +type AdminAdUpdateRequest = operations['admin___ad___update']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminAnnouncementsCreateRequest = operations['admin/announcements/create']['requestBody']['content']['application/json']; +type AdminAnnouncementsCreateRequest = operations['admin___announcements___create']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminAnnouncementsCreateResponse = operations['admin/announcements/create']['responses']['200']['content']['application/json']; +type AdminAnnouncementsCreateResponse = operations['admin___announcements___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminAnnouncementsDeleteRequest = operations['admin/announcements/delete']['requestBody']['content']['application/json']; +type AdminAnnouncementsDeleteRequest = operations['admin___announcements___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminAnnouncementsListRequest = operations['admin/announcements/list']['requestBody']['content']['application/json']; +type AdminAnnouncementsListRequest = operations['admin___announcements___list']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminAnnouncementsListResponse = operations['admin/announcements/list']['responses']['200']['content']['application/json']; +type AdminAnnouncementsListResponse = operations['admin___announcements___list']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminAnnouncementsUpdateRequest = operations['admin/announcements/update']['requestBody']['content']['application/json']; +type AdminAnnouncementsUpdateRequest = operations['admin___announcements___update']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminAvatarDecorationsCreateRequest = operations['admin/avatar-decorations/create']['requestBody']['content']['application/json']; +type AdminAvatarDecorationsCreateRequest = operations['admin___avatar-decorations___create']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminAvatarDecorationsDeleteRequest = operations['admin/avatar-decorations/delete']['requestBody']['content']['application/json']; +type AdminAvatarDecorationsDeleteRequest = operations['admin___avatar-decorations___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminAvatarDecorationsListRequest = operations['admin/avatar-decorations/list']['requestBody']['content']['application/json']; +type AdminAvatarDecorationsListRequest = operations['admin___avatar-decorations___list']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminAvatarDecorationsListResponse = operations['admin/avatar-decorations/list']['responses']['200']['content']['application/json']; +type AdminAvatarDecorationsListResponse = operations['admin___avatar-decorations___list']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminAvatarDecorationsUpdateRequest = operations['admin/avatar-decorations/update']['requestBody']['content']['application/json']; +type AdminAvatarDecorationsUpdateRequest = operations['admin___avatar-decorations___update']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminDeleteAccountRequest = operations['admin/delete-account']['requestBody']['content']['application/json']; +type AdminDeleteAccountRequest = operations['admin___delete-account']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminDeleteAllFilesOfAUserRequest = operations['admin/delete-all-files-of-a-user']['requestBody']['content']['application/json']; +type AdminDeleteAllFilesOfAUserRequest = operations['admin___delete-all-files-of-a-user']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminDriveFilesRequest = operations['admin/drive/files']['requestBody']['content']['application/json']; +type AdminDriveFilesRequest = operations['admin___drive___files']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminDriveFilesResponse = operations['admin/drive/files']['responses']['200']['content']['application/json']; +type AdminDriveFilesResponse = operations['admin___drive___files']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminDriveShowFileRequest = operations['admin/drive/show-file']['requestBody']['content']['application/json']; +type AdminDriveShowFileRequest = operations['admin___drive___show-file']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminDriveShowFileResponse = operations['admin/drive/show-file']['responses']['200']['content']['application/json']; +type AdminDriveShowFileResponse = operations['admin___drive___show-file']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminEmojiAddAliasesBulkRequest = operations['admin/emoji/add-aliases-bulk']['requestBody']['content']['application/json']; +type AdminEmojiAddAliasesBulkRequest = operations['admin___emoji___add-aliases-bulk']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminEmojiAddRequest = operations['admin/emoji/add']['requestBody']['content']['application/json']; +type AdminEmojiAddRequest = operations['admin___emoji___add']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminEmojiAddsRequest = operations['admin/emoji/adds']['requestBody']['content']['application/json']; +type AdminEmojiAddResponse = operations['admin___emoji___add']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminEmojiCopyRequest = operations['admin/emoji/copy']['requestBody']['content']['application/json']; +type AdminEmojiAddsRequest = operations['admin___emoji___adds']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminEmojiCopyResponse = operations['admin/emoji/copy']['responses']['200']['content']['application/json']; +type AdminEmojiAddsResponse = operations['admin___emoji___adds']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminEmojiDeleteBulkRequest = operations['admin/emoji/delete-bulk']['requestBody']['content']['application/json']; +type AdminEmojiCopyRequest = operations['admin___emoji___copy']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminEmojiDeleteRequest = operations['admin/emoji/delete']['requestBody']['content']['application/json']; +type AdminEmojiCopyResponse = operations['admin___emoji___copy']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminEmojiImportZipRequest = operations['admin/emoji/import-zip']['requestBody']['content']['application/json']; +type AdminEmojiDeleteBulkRequest = operations['admin___emoji___delete-bulk']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminEmojiListRemoteRequest = operations['admin/emoji/list-remote']['requestBody']['content']['application/json']; +type AdminEmojiDeleteRequest = operations['admin___emoji___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminEmojiListRemoteResponse = operations['admin/emoji/list-remote']['responses']['200']['content']['application/json']; +type AdminEmojiImportZipRequest = operations['admin___emoji___import-zip']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminEmojiListRequest = operations['admin/emoji/list']['requestBody']['content']['application/json']; +type AdminEmojiListRemoteRequest = operations['admin___emoji___list-remote']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminEmojiListResponse = operations['admin/emoji/list']['responses']['200']['content']['application/json']; +type AdminEmojiListRemoteResponse = operations['admin___emoji___list-remote']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminEmojiRemoveAliasesBulkRequest = operations['admin/emoji/remove-aliases-bulk']['requestBody']['content']['application/json']; +type AdminEmojiListRequest = operations['admin___emoji___list']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminEmojiSetAliasesBulkRequest = operations['admin/emoji/set-aliases-bulk']['requestBody']['content']['application/json']; +type AdminEmojiListResponse = operations['admin___emoji___list']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminEmojiSetCategoryBulkRequest = operations['admin/emoji/set-category-bulk']['requestBody']['content']['application/json']; +type AdminEmojiRemoveAliasesBulkRequest = operations['admin___emoji___remove-aliases-bulk']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminEmojiSetLicenseBulkRequest = operations['admin/emoji/set-license-bulk']['requestBody']['content']['application/json']; +type AdminEmojiSetAliasesBulkRequest = operations['admin___emoji___set-aliases-bulk']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminEmojiStealRequest = operations['admin/emoji/steal']['requestBody']['content']['application/json']; +type AdminEmojiSetCategoryBulkRequest = operations['admin___emoji___set-category-bulk']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminEmojiStealResponse = operations['admin/emoji/steal']['responses']['200']['content']['application/json']; +type AdminEmojiSetLicenseBulkRequest = operations['admin___emoji___set-license-bulk']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminEmojiUpdateRequest = operations['admin/emoji/update']['requestBody']['content']['application/json']; +type AdminEmojiStealRequest = operations['admin___emoji___steal']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminFederationDeleteAllFilesRequest = operations['admin/federation/delete-all-files']['requestBody']['content']['application/json']; +type AdminEmojiStealResponse = operations['admin___emoji___steal']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminFederationRefreshRemoteInstanceMetadataRequest = operations['admin/federation/refresh-remote-instance-metadata']['requestBody']['content']['application/json']; +type AdminEmojiUpdateRequest = operations['admin___emoji___update']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminFederationRemoveAllFollowingRequest = operations['admin/federation/remove-all-following']['requestBody']['content']['application/json']; +type AdminFederationDeleteAllFilesRequest = operations['admin___federation___delete-all-files']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminFederationUpdateInstanceRequest = operations['admin/federation/update-instance']['requestBody']['content']['application/json']; +type AdminFederationRefreshRemoteInstanceMetadataRequest = operations['admin___federation___refresh-remote-instance-metadata']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminGetIndexStatsResponse = operations['admin/get-index-stats']['responses']['200']['content']['application/json']; +type AdminFederationRemoveAllFollowingRequest = operations['admin___federation___remove-all-following']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminGetTableStatsResponse = operations['admin/get-table-stats']['responses']['200']['content']['application/json']; +type AdminFederationUpdateInstanceRequest = operations['admin___federation___update-instance']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminGetUserIpsRequest = operations['admin/get-user-ips']['requestBody']['content']['application/json']; +type AdminGetIndexStatsResponse = operations['admin___get-index-stats']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminGetUserIpsResponse = operations['admin/get-user-ips']['responses']['200']['content']['application/json']; +type AdminGetTableStatsResponse = operations['admin___get-table-stats']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminInviteCreateRequest = operations['admin/invite/create']['requestBody']['content']['application/json']; +type AdminGetUserIpsRequest = operations['admin___get-user-ips']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminInviteCreateResponse = operations['admin/invite/create']['responses']['200']['content']['application/json']; +type AdminGetUserIpsResponse = operations['admin___get-user-ips']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminInviteListRequest = operations['admin/invite/list']['requestBody']['content']['application/json']; +type AdminInviteCreateRequest = operations['admin___invite___create']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminInviteListResponse = operations['admin/invite/list']['responses']['200']['content']['application/json']; +type AdminInviteCreateResponse = operations['admin___invite___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminMetaResponse = operations['admin/meta']['responses']['200']['content']['application/json']; +type AdminInviteListRequest = operations['admin___invite___list']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminPromoCreateRequest = operations['admin/promo/create']['requestBody']['content']['application/json']; +type AdminInviteListResponse = operations['admin___invite___list']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminQueueDeliverDelayedResponse = operations['admin/queue/deliver-delayed']['responses']['200']['content']['application/json']; +type AdminMetaResponse = operations['admin___meta']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminQueueInboxDelayedResponse = operations['admin/queue/inbox-delayed']['responses']['200']['content']['application/json']; +type AdminPromoCreateRequest = operations['admin___promo___create']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminQueuePromoteRequest = operations['admin/queue/promote']['requestBody']['content']['application/json']; +type AdminQueueDeliverDelayedResponse = operations['admin___queue___deliver-delayed']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminQueueStatsResponse = operations['admin/queue/stats']['responses']['200']['content']['application/json']; +type AdminQueueInboxDelayedResponse = operations['admin___queue___inbox-delayed']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminRelaysAddRequest = operations['admin/relays/add']['requestBody']['content']['application/json']; +type AdminQueuePromoteRequest = operations['admin___queue___promote']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminRelaysAddResponse = operations['admin/relays/add']['responses']['200']['content']['application/json']; +type AdminQueueStatsResponse = operations['admin___queue___stats']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminRelaysListResponse = operations['admin/relays/list']['responses']['200']['content']['application/json']; +type AdminRelaysAddRequest = operations['admin___relays___add']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminRelaysRemoveRequest = operations['admin/relays/remove']['requestBody']['content']['application/json']; +type AdminRelaysAddResponse = operations['admin___relays___add']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminResetPasswordRequest = operations['admin/reset-password']['requestBody']['content']['application/json']; +type AdminRelaysListResponse = operations['admin___relays___list']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminResetPasswordResponse = operations['admin/reset-password']['responses']['200']['content']['application/json']; +type AdminRelaysRemoveRequest = operations['admin___relays___remove']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminResolveAbuseUserReportRequest = operations['admin/resolve-abuse-user-report']['requestBody']['content']['application/json']; +type AdminResetPasswordRequest = operations['admin___reset-password']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminRolesAssignRequest = operations['admin/roles/assign']['requestBody']['content']['application/json']; +type AdminResetPasswordResponse = operations['admin___reset-password']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminRolesCreateRequest = operations['admin/roles/create']['requestBody']['content']['application/json']; +type AdminResolveAbuseUserReportRequest = operations['admin___resolve-abuse-user-report']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminRolesCreateResponse = operations['admin/roles/create']['responses']['200']['content']['application/json']; +type AdminRolesAssignRequest = operations['admin___roles___assign']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminRolesDeleteRequest = operations['admin/roles/delete']['requestBody']['content']['application/json']; +type AdminRolesCreateRequest = operations['admin___roles___create']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminRolesListResponse = operations['admin/roles/list']['responses']['200']['content']['application/json']; +type AdminRolesCreateResponse = operations['admin___roles___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminRolesShowRequest = operations['admin/roles/show']['requestBody']['content']['application/json']; +type AdminRolesDeleteRequest = operations['admin___roles___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminRolesShowResponse = operations['admin/roles/show']['responses']['200']['content']['application/json']; +type AdminRolesListResponse = operations['admin___roles___list']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminRolesUnassignRequest = operations['admin/roles/unassign']['requestBody']['content']['application/json']; +type AdminRolesShowRequest = operations['admin___roles___show']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminRolesUpdateDefaultPoliciesRequest = operations['admin/roles/update-default-policies']['requestBody']['content']['application/json']; +type AdminRolesShowResponse = operations['admin___roles___show']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminRolesUpdateRequest = operations['admin/roles/update']['requestBody']['content']['application/json']; +type AdminRolesUnassignRequest = operations['admin___roles___unassign']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminRolesUsersRequest = operations['admin/roles/users']['requestBody']['content']['application/json']; +type AdminRolesUpdateDefaultPoliciesRequest = operations['admin___roles___update-default-policies']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminRolesUsersResponse = operations['admin/roles/users']['responses']['200']['content']['application/json']; +type AdminRolesUpdateRequest = operations['admin___roles___update']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminSendEmailRequest = operations['admin/send-email']['requestBody']['content']['application/json']; +type AdminRolesUsersRequest = operations['admin___roles___users']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminServerInfoResponse = operations['admin/server-info']['responses']['200']['content']['application/json']; +type AdminRolesUsersResponse = operations['admin___roles___users']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminShowModerationLogsRequest = operations['admin/show-moderation-logs']['requestBody']['content']['application/json']; +type AdminSendEmailRequest = operations['admin___send-email']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminShowModerationLogsResponse = operations['admin/show-moderation-logs']['responses']['200']['content']['application/json']; +type AdminServerInfoResponse = operations['admin___server-info']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminShowUserRequest = operations['admin/show-user']['requestBody']['content']['application/json']; +type AdminShowModerationLogsRequest = operations['admin___show-moderation-logs']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminShowUserResponse = operations['admin/show-user']['responses']['200']['content']['application/json']; +type AdminShowModerationLogsResponse = operations['admin___show-moderation-logs']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminShowUsersRequest = operations['admin/show-users']['requestBody']['content']['application/json']; +type AdminShowUserRequest = operations['admin___show-user']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminShowUsersResponse = operations['admin/show-users']['responses']['200']['content']['application/json']; +type AdminShowUserResponse = operations['admin___show-user']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminSuspendUserRequest = operations['admin/suspend-user']['requestBody']['content']['application/json']; +type AdminShowUsersRequest = operations['admin___show-users']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminUnsetUserAvatarRequest = operations['admin/unset-user-avatar']['requestBody']['content']['application/json']; +type AdminShowUsersResponse = operations['admin___show-users']['responses']['200']['content']['application/json']; // @public (undocumented) -type AdminUnsetUserBannerRequest = operations['admin/unset-user-banner']['requestBody']['content']['application/json']; +type AdminSuspendUserRequest = operations['admin___suspend-user']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminUnsuspendUserRequest = operations['admin/unsuspend-user']['requestBody']['content']['application/json']; +type AdminUnsetUserAvatarRequest = operations['admin___unset-user-avatar']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminUpdateMetaRequest = operations['admin/update-meta']['requestBody']['content']['application/json']; +type AdminUnsetUserBannerRequest = operations['admin___unset-user-banner']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminUpdateUserNoteRequest = operations['admin/update-user-note']['requestBody']['content']['application/json']; +type AdminUnsuspendUserRequest = operations['admin___unsuspend-user']['requestBody']['content']['application/json']; + +// @public (undocumented) +type AdminUpdateMetaRequest = operations['admin___update-meta']['requestBody']['content']['application/json']; + +// @public (undocumented) +type AdminUpdateUserNoteRequest = operations['admin___update-user-note']['requestBody']['content']['application/json']; // @public (undocumented) type Announcement = components['schemas']['Announcement']; @@ -364,40 +370,40 @@ type AnnouncementsResponse = operations['announcements']['responses']['200']['co type Antenna = components['schemas']['Antenna']; // @public (undocumented) -type AntennasCreateRequest = operations['antennas/create']['requestBody']['content']['application/json']; +type AntennasCreateRequest = operations['antennas___create']['requestBody']['content']['application/json']; // @public (undocumented) -type AntennasCreateResponse = operations['antennas/create']['responses']['200']['content']['application/json']; +type AntennasCreateResponse = operations['antennas___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type AntennasDeleteRequest = operations['antennas/delete']['requestBody']['content']['application/json']; +type AntennasDeleteRequest = operations['antennas___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type AntennasListResponse = operations['antennas/list']['responses']['200']['content']['application/json']; +type AntennasListResponse = operations['antennas___list']['responses']['200']['content']['application/json']; // @public (undocumented) -type AntennasNotesRequest = operations['antennas/notes']['requestBody']['content']['application/json']; +type AntennasNotesRequest = operations['antennas___notes']['requestBody']['content']['application/json']; // @public (undocumented) -type AntennasNotesResponse = operations['antennas/notes']['responses']['200']['content']['application/json']; +type AntennasNotesResponse = operations['antennas___notes']['responses']['200']['content']['application/json']; // @public (undocumented) -type AntennasShowRequest = operations['antennas/show']['requestBody']['content']['application/json']; +type AntennasShowRequest = operations['antennas___show']['requestBody']['content']['application/json']; // @public (undocumented) -type AntennasShowResponse = operations['antennas/show']['responses']['200']['content']['application/json']; +type AntennasShowResponse = operations['antennas___show']['responses']['200']['content']['application/json']; // @public (undocumented) -type AntennasUpdateRequest = operations['antennas/update']['requestBody']['content']['application/json']; +type AntennasUpdateRequest = operations['antennas___update']['requestBody']['content']['application/json']; // @public (undocumented) -type AntennasUpdateResponse = operations['antennas/update']['responses']['200']['content']['application/json']; +type AntennasUpdateResponse = operations['antennas___update']['responses']['200']['content']['application/json']; // @public (undocumented) -type ApGetRequest = operations['ap/get']['requestBody']['content']['application/json']; +type ApGetRequest = operations['ap___get']['requestBody']['content']['application/json']; // @public (undocumented) -type ApGetResponse = operations['ap/get']['responses']['200']['content']['application/json']; +type ApGetResponse = operations['ap___get']['responses']['200']['content']['application/json']; declare namespace api { export { @@ -438,73 +444,73 @@ type APIError = { type App = components['schemas']['App']; // @public (undocumented) -type AppCreateRequest = operations['app/create']['requestBody']['content']['application/json']; +type AppCreateRequest = operations['app___create']['requestBody']['content']['application/json']; // @public (undocumented) -type AppCreateResponse = operations['app/create']['responses']['200']['content']['application/json']; +type AppCreateResponse = operations['app___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type AppShowRequest = operations['app/show']['requestBody']['content']['application/json']; +type AppShowRequest = operations['app___show']['requestBody']['content']['application/json']; // @public (undocumented) -type AppShowResponse = operations['app/show']['responses']['200']['content']['application/json']; +type AppShowResponse = operations['app___show']['responses']['200']['content']['application/json']; // @public (undocumented) -type ApShowRequest = operations['ap/show']['requestBody']['content']['application/json']; +type ApShowRequest = operations['ap___show']['requestBody']['content']['application/json']; // @public (undocumented) -type ApShowResponse = operations['ap/show']['responses']['200']['content']['application/json']; +type ApShowResponse = operations['ap___show']['responses']['200']['content']['application/json']; // @public (undocumented) -type AuthAcceptRequest = operations['auth/accept']['requestBody']['content']['application/json']; +type AuthAcceptRequest = operations['auth___accept']['requestBody']['content']['application/json']; // @public (undocumented) -type AuthSessionGenerateRequest = operations['auth/session/generate']['requestBody']['content']['application/json']; +type AuthSessionGenerateRequest = operations['auth___session___generate']['requestBody']['content']['application/json']; // @public (undocumented) -type AuthSessionGenerateResponse = operations['auth/session/generate']['responses']['200']['content']['application/json']; +type AuthSessionGenerateResponse = operations['auth___session___generate']['responses']['200']['content']['application/json']; // @public (undocumented) -type AuthSessionShowRequest = operations['auth/session/show']['requestBody']['content']['application/json']; +type AuthSessionShowRequest = operations['auth___session___show']['requestBody']['content']['application/json']; // @public (undocumented) -type AuthSessionShowResponse = operations['auth/session/show']['responses']['200']['content']['application/json']; +type AuthSessionShowResponse = operations['auth___session___show']['responses']['200']['content']['application/json']; // @public (undocumented) -type AuthSessionUserkeyRequest = operations['auth/session/userkey']['requestBody']['content']['application/json']; +type AuthSessionUserkeyRequest = operations['auth___session___userkey']['requestBody']['content']['application/json']; // @public (undocumented) -type AuthSessionUserkeyResponse = operations['auth/session/userkey']['responses']['200']['content']['application/json']; +type AuthSessionUserkeyResponse = operations['auth___session___userkey']['responses']['200']['content']['application/json']; // @public (undocumented) type Blocking = components['schemas']['Blocking']; // @public (undocumented) -type BlockingCreateRequest = operations['blocking/create']['requestBody']['content']['application/json']; +type BlockingCreateRequest = operations['blocking___create']['requestBody']['content']['application/json']; // @public (undocumented) -type BlockingCreateResponse = operations['blocking/create']['responses']['200']['content']['application/json']; +type BlockingCreateResponse = operations['blocking___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type BlockingDeleteRequest = operations['blocking/delete']['requestBody']['content']['application/json']; +type BlockingDeleteRequest = operations['blocking___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type BlockingDeleteResponse = operations['blocking/delete']['responses']['200']['content']['application/json']; +type BlockingDeleteResponse = operations['blocking___delete']['responses']['200']['content']['application/json']; // @public (undocumented) -type BlockingListRequest = operations['blocking/list']['requestBody']['content']['application/json']; +type BlockingListRequest = operations['blocking___list']['requestBody']['content']['application/json']; // @public (undocumented) -type BlockingListResponse = operations['blocking/list']['responses']['200']['content']['application/json']; +type BlockingListResponse = operations['blocking___list']['responses']['200']['content']['application/json']; // @public (undocumented) -type BubbleGameRankingRequest = operations['bubble-game/ranking']['requestBody']['content']['application/json']; +type BubbleGameRankingRequest = operations['bubble-game___ranking']['requestBody']['content']['application/json']; // @public (undocumented) -type BubbleGameRankingResponse = operations['bubble-game/ranking']['responses']['200']['content']['application/json']; +type BubbleGameRankingResponse = operations['bubble-game___ranking']['responses']['200']['content']['application/json']; // @public (undocumented) -type BubbleGameRegisterRequest = operations['bubble-game/register']['requestBody']['content']['application/json']; +type BubbleGameRegisterRequest = operations['bubble-game___register']['requestBody']['content']['application/json']; // @public (undocumented) type Channel = components['schemas']['Channel']; @@ -554,6 +560,7 @@ export type Channels = { unreadNotification: (payload: Notification_2) => void; unreadMention: (payload: Note['id']) => void; readAllUnreadMentions: () => void; + notificationFlushed: () => void; unreadSpecifiedNote: (payload: Note['id']) => void; readAllUnreadSpecifiedNotes: () => void; readAllMessagingMessages: () => void; @@ -780,184 +787,184 @@ export type Channels = { }; // @public (undocumented) -type ChannelsCreateRequest = operations['channels/create']['requestBody']['content']['application/json']; +type ChannelsCreateRequest = operations['channels___create']['requestBody']['content']['application/json']; // @public (undocumented) -type ChannelsCreateResponse = operations['channels/create']['responses']['200']['content']['application/json']; +type ChannelsCreateResponse = operations['channels___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type ChannelsFavoriteRequest = operations['channels/favorite']['requestBody']['content']['application/json']; +type ChannelsFavoriteRequest = operations['channels___favorite']['requestBody']['content']['application/json']; // @public (undocumented) -type ChannelsFeaturedResponse = operations['channels/featured']['responses']['200']['content']['application/json']; +type ChannelsFeaturedResponse = operations['channels___featured']['responses']['200']['content']['application/json']; // @public (undocumented) -type ChannelsFollowedRequest = operations['channels/followed']['requestBody']['content']['application/json']; +type ChannelsFollowedRequest = operations['channels___followed']['requestBody']['content']['application/json']; // @public (undocumented) -type ChannelsFollowedResponse = operations['channels/followed']['responses']['200']['content']['application/json']; +type ChannelsFollowedResponse = operations['channels___followed']['responses']['200']['content']['application/json']; // @public (undocumented) -type ChannelsFollowRequest = operations['channels/follow']['requestBody']['content']['application/json']; +type ChannelsFollowRequest = operations['channels___follow']['requestBody']['content']['application/json']; // @public (undocumented) -type ChannelsMyFavoritesResponse = operations['channels/my-favorites']['responses']['200']['content']['application/json']; +type ChannelsMyFavoritesResponse = operations['channels___my-favorites']['responses']['200']['content']['application/json']; // @public (undocumented) -type ChannelsOwnedRequest = operations['channels/owned']['requestBody']['content']['application/json']; +type ChannelsOwnedRequest = operations['channels___owned']['requestBody']['content']['application/json']; // @public (undocumented) -type ChannelsOwnedResponse = operations['channels/owned']['responses']['200']['content']['application/json']; +type ChannelsOwnedResponse = operations['channels___owned']['responses']['200']['content']['application/json']; // @public (undocumented) -type ChannelsSearchRequest = operations['channels/search']['requestBody']['content']['application/json']; +type ChannelsSearchRequest = operations['channels___search']['requestBody']['content']['application/json']; // @public (undocumented) -type ChannelsSearchResponse = operations['channels/search']['responses']['200']['content']['application/json']; +type ChannelsSearchResponse = operations['channels___search']['responses']['200']['content']['application/json']; // @public (undocumented) -type ChannelsShowRequest = operations['channels/show']['requestBody']['content']['application/json']; +type ChannelsShowRequest = operations['channels___show']['requestBody']['content']['application/json']; // @public (undocumented) -type ChannelsShowResponse = operations['channels/show']['responses']['200']['content']['application/json']; +type ChannelsShowResponse = operations['channels___show']['responses']['200']['content']['application/json']; // @public (undocumented) -type ChannelsTimelineRequest = operations['channels/timeline']['requestBody']['content']['application/json']; +type ChannelsTimelineRequest = operations['channels___timeline']['requestBody']['content']['application/json']; // @public (undocumented) -type ChannelsTimelineResponse = operations['channels/timeline']['responses']['200']['content']['application/json']; +type ChannelsTimelineResponse = operations['channels___timeline']['responses']['200']['content']['application/json']; // @public (undocumented) -type ChannelsUnfavoriteRequest = operations['channels/unfavorite']['requestBody']['content']['application/json']; +type ChannelsUnfavoriteRequest = operations['channels___unfavorite']['requestBody']['content']['application/json']; // @public (undocumented) -type ChannelsUnfollowRequest = operations['channels/unfollow']['requestBody']['content']['application/json']; +type ChannelsUnfollowRequest = operations['channels___unfollow']['requestBody']['content']['application/json']; // @public (undocumented) -type ChannelsUpdateRequest = operations['channels/update']['requestBody']['content']['application/json']; +type ChannelsUpdateRequest = operations['channels___update']['requestBody']['content']['application/json']; // @public (undocumented) -type ChannelsUpdateResponse = operations['channels/update']['responses']['200']['content']['application/json']; +type ChannelsUpdateResponse = operations['channels___update']['responses']['200']['content']['application/json']; // @public (undocumented) -type ChartsActiveUsersRequest = operations['charts/active-users']['requestBody']['content']['application/json']; +type ChartsActiveUsersRequest = operations['charts___active-users']['requestBody']['content']['application/json']; // @public (undocumented) -type ChartsActiveUsersResponse = operations['charts/active-users']['responses']['200']['content']['application/json']; +type ChartsActiveUsersResponse = operations['charts___active-users']['responses']['200']['content']['application/json']; // @public (undocumented) -type ChartsApRequestRequest = operations['charts/ap-request']['requestBody']['content']['application/json']; +type ChartsApRequestRequest = operations['charts___ap-request']['requestBody']['content']['application/json']; // @public (undocumented) -type ChartsApRequestResponse = operations['charts/ap-request']['responses']['200']['content']['application/json']; +type ChartsApRequestResponse = operations['charts___ap-request']['responses']['200']['content']['application/json']; // @public (undocumented) -type ChartsDriveRequest = operations['charts/drive']['requestBody']['content']['application/json']; +type ChartsDriveRequest = operations['charts___drive']['requestBody']['content']['application/json']; // @public (undocumented) -type ChartsDriveResponse = operations['charts/drive']['responses']['200']['content']['application/json']; +type ChartsDriveResponse = operations['charts___drive']['responses']['200']['content']['application/json']; // @public (undocumented) -type ChartsFederationRequest = operations['charts/federation']['requestBody']['content']['application/json']; +type ChartsFederationRequest = operations['charts___federation']['requestBody']['content']['application/json']; // @public (undocumented) -type ChartsFederationResponse = operations['charts/federation']['responses']['200']['content']['application/json']; +type ChartsFederationResponse = operations['charts___federation']['responses']['200']['content']['application/json']; // @public (undocumented) -type ChartsInstanceRequest = operations['charts/instance']['requestBody']['content']['application/json']; +type ChartsInstanceRequest = operations['charts___instance']['requestBody']['content']['application/json']; // @public (undocumented) -type ChartsInstanceResponse = operations['charts/instance']['responses']['200']['content']['application/json']; +type ChartsInstanceResponse = operations['charts___instance']['responses']['200']['content']['application/json']; // @public (undocumented) -type ChartsNotesRequest = operations['charts/notes']['requestBody']['content']['application/json']; +type ChartsNotesRequest = operations['charts___notes']['requestBody']['content']['application/json']; // @public (undocumented) -type ChartsNotesResponse = operations['charts/notes']['responses']['200']['content']['application/json']; +type ChartsNotesResponse = operations['charts___notes']['responses']['200']['content']['application/json']; // @public (undocumented) -type ChartsUserDriveRequest = operations['charts/user/drive']['requestBody']['content']['application/json']; +type ChartsUserDriveRequest = operations['charts___user___drive']['requestBody']['content']['application/json']; // @public (undocumented) -type ChartsUserDriveResponse = operations['charts/user/drive']['responses']['200']['content']['application/json']; +type ChartsUserDriveResponse = operations['charts___user___drive']['responses']['200']['content']['application/json']; // @public (undocumented) -type ChartsUserFollowingRequest = operations['charts/user/following']['requestBody']['content']['application/json']; +type ChartsUserFollowingRequest = operations['charts___user___following']['requestBody']['content']['application/json']; // @public (undocumented) -type ChartsUserFollowingResponse = operations['charts/user/following']['responses']['200']['content']['application/json']; +type ChartsUserFollowingResponse = operations['charts___user___following']['responses']['200']['content']['application/json']; // @public (undocumented) -type ChartsUserNotesRequest = operations['charts/user/notes']['requestBody']['content']['application/json']; +type ChartsUserNotesRequest = operations['charts___user___notes']['requestBody']['content']['application/json']; // @public (undocumented) -type ChartsUserNotesResponse = operations['charts/user/notes']['responses']['200']['content']['application/json']; +type ChartsUserNotesResponse = operations['charts___user___notes']['responses']['200']['content']['application/json']; // @public (undocumented) -type ChartsUserPvRequest = operations['charts/user/pv']['requestBody']['content']['application/json']; +type ChartsUserPvRequest = operations['charts___user___pv']['requestBody']['content']['application/json']; // @public (undocumented) -type ChartsUserPvResponse = operations['charts/user/pv']['responses']['200']['content']['application/json']; +type ChartsUserPvResponse = operations['charts___user___pv']['responses']['200']['content']['application/json']; // @public (undocumented) -type ChartsUserReactionsRequest = operations['charts/user/reactions']['requestBody']['content']['application/json']; +type ChartsUserReactionsRequest = operations['charts___user___reactions']['requestBody']['content']['application/json']; // @public (undocumented) -type ChartsUserReactionsResponse = operations['charts/user/reactions']['responses']['200']['content']['application/json']; +type ChartsUserReactionsResponse = operations['charts___user___reactions']['responses']['200']['content']['application/json']; // @public (undocumented) -type ChartsUsersRequest = operations['charts/users']['requestBody']['content']['application/json']; +type ChartsUsersRequest = operations['charts___users']['requestBody']['content']['application/json']; // @public (undocumented) -type ChartsUsersResponse = operations['charts/users']['responses']['200']['content']['application/json']; +type ChartsUsersResponse = operations['charts___users']['responses']['200']['content']['application/json']; // @public (undocumented) type Clip = components['schemas']['Clip']; // @public (undocumented) -type ClipsAddNoteRequest = operations['clips/add-note']['requestBody']['content']['application/json']; +type ClipsAddNoteRequest = operations['clips___add-note']['requestBody']['content']['application/json']; // @public (undocumented) -type ClipsCreateRequest = operations['clips/create']['requestBody']['content']['application/json']; +type ClipsCreateRequest = operations['clips___create']['requestBody']['content']['application/json']; // @public (undocumented) -type ClipsCreateResponse = operations['clips/create']['responses']['200']['content']['application/json']; +type ClipsCreateResponse = operations['clips___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type ClipsDeleteRequest = operations['clips/delete']['requestBody']['content']['application/json']; +type ClipsDeleteRequest = operations['clips___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type ClipsFavoriteRequest = operations['clips/favorite']['requestBody']['content']['application/json']; +type ClipsFavoriteRequest = operations['clips___favorite']['requestBody']['content']['application/json']; // @public (undocumented) -type ClipsListResponse = operations['clips/list']['responses']['200']['content']['application/json']; +type ClipsListResponse = operations['clips___list']['responses']['200']['content']['application/json']; // @public (undocumented) -type ClipsMyFavoritesResponse = operations['clips/my-favorites']['responses']['200']['content']['application/json']; +type ClipsMyFavoritesResponse = operations['clips___my-favorites']['responses']['200']['content']['application/json']; // @public (undocumented) -type ClipsNotesRequest = operations['clips/notes']['requestBody']['content']['application/json']; +type ClipsNotesRequest = operations['clips___notes']['requestBody']['content']['application/json']; // @public (undocumented) -type ClipsNotesResponse = operations['clips/notes']['responses']['200']['content']['application/json']; +type ClipsNotesResponse = operations['clips___notes']['responses']['200']['content']['application/json']; // @public (undocumented) -type ClipsRemoveNoteRequest = operations['clips/remove-note']['requestBody']['content']['application/json']; +type ClipsRemoveNoteRequest = operations['clips___remove-note']['requestBody']['content']['application/json']; // @public (undocumented) -type ClipsShowRequest = operations['clips/show']['requestBody']['content']['application/json']; +type ClipsShowRequest = operations['clips___show']['requestBody']['content']['application/json']; // @public (undocumented) -type ClipsShowResponse = operations['clips/show']['responses']['200']['content']['application/json']; +type ClipsShowResponse = operations['clips___show']['responses']['200']['content']['application/json']; // @public (undocumented) -type ClipsUnfavoriteRequest = operations['clips/unfavorite']['requestBody']['content']['application/json']; +type ClipsUnfavoriteRequest = operations['clips___unfavorite']['requestBody']['content']['application/json']; // @public (undocumented) -type ClipsUpdateRequest = operations['clips/update']['requestBody']['content']['application/json']; +type ClipsUpdateRequest = operations['clips___update']['requestBody']['content']['application/json']; // @public (undocumented) -type ClipsUpdateResponse = operations['clips/update']['responses']['200']['content']['application/json']; +type ClipsUpdateResponse = operations['clips___update']['responses']['200']['content']['application/json']; // @public (undocumented) type DateString = string; @@ -966,109 +973,109 @@ type DateString = string; type DriveFile = components['schemas']['DriveFile']; // @public (undocumented) -type DriveFilesAttachedNotesRequest = operations['drive/files/attached-notes']['requestBody']['content']['application/json']; +type DriveFilesAttachedNotesRequest = operations['drive___files___attached-notes']['requestBody']['content']['application/json']; // @public (undocumented) -type DriveFilesAttachedNotesResponse = operations['drive/files/attached-notes']['responses']['200']['content']['application/json']; +type DriveFilesAttachedNotesResponse = operations['drive___files___attached-notes']['responses']['200']['content']['application/json']; // @public (undocumented) -type DriveFilesCheckExistenceRequest = operations['drive/files/check-existence']['requestBody']['content']['application/json']; +type DriveFilesCheckExistenceRequest = operations['drive___files___check-existence']['requestBody']['content']['application/json']; // @public (undocumented) -type DriveFilesCheckExistenceResponse = operations['drive/files/check-existence']['responses']['200']['content']['application/json']; +type DriveFilesCheckExistenceResponse = operations['drive___files___check-existence']['responses']['200']['content']['application/json']; // @public (undocumented) -type DriveFilesCreateRequest = operations['drive/files/create']['requestBody']['content']['multipart/form-data']; +type DriveFilesCreateRequest = operations['drive___files___create']['requestBody']['content']['multipart/form-data']; // @public (undocumented) -type DriveFilesCreateResponse = operations['drive/files/create']['responses']['200']['content']['application/json']; +type DriveFilesCreateResponse = operations['drive___files___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type DriveFilesDeleteRequest = operations['drive/files/delete']['requestBody']['content']['application/json']; +type DriveFilesDeleteRequest = operations['drive___files___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type DriveFilesFindByHashRequest = operations['drive/files/find-by-hash']['requestBody']['content']['application/json']; +type DriveFilesFindByHashRequest = operations['drive___files___find-by-hash']['requestBody']['content']['application/json']; // @public (undocumented) -type DriveFilesFindByHashResponse = operations['drive/files/find-by-hash']['responses']['200']['content']['application/json']; +type DriveFilesFindByHashResponse = operations['drive___files___find-by-hash']['responses']['200']['content']['application/json']; // @public (undocumented) -type DriveFilesFindRequest = operations['drive/files/find']['requestBody']['content']['application/json']; +type DriveFilesFindRequest = operations['drive___files___find']['requestBody']['content']['application/json']; // @public (undocumented) -type DriveFilesFindResponse = operations['drive/files/find']['responses']['200']['content']['application/json']; +type DriveFilesFindResponse = operations['drive___files___find']['responses']['200']['content']['application/json']; // @public (undocumented) -type DriveFilesRequest = operations['drive/files']['requestBody']['content']['application/json']; +type DriveFilesRequest = operations['drive___files']['requestBody']['content']['application/json']; // @public (undocumented) -type DriveFilesResponse = operations['drive/files']['responses']['200']['content']['application/json']; +type DriveFilesResponse = operations['drive___files']['responses']['200']['content']['application/json']; // @public (undocumented) -type DriveFilesShowRequest = operations['drive/files/show']['requestBody']['content']['application/json']; +type DriveFilesShowRequest = operations['drive___files___show']['requestBody']['content']['application/json']; // @public (undocumented) -type DriveFilesShowResponse = operations['drive/files/show']['responses']['200']['content']['application/json']; +type DriveFilesShowResponse = operations['drive___files___show']['responses']['200']['content']['application/json']; // @public (undocumented) -type DriveFilesUpdateRequest = operations['drive/files/update']['requestBody']['content']['application/json']; +type DriveFilesUpdateRequest = operations['drive___files___update']['requestBody']['content']['application/json']; // @public (undocumented) -type DriveFilesUpdateResponse = operations['drive/files/update']['responses']['200']['content']['application/json']; +type DriveFilesUpdateResponse = operations['drive___files___update']['responses']['200']['content']['application/json']; // @public (undocumented) -type DriveFilesUploadFromUrlRequest = operations['drive/files/upload-from-url']['requestBody']['content']['application/json']; +type DriveFilesUploadFromUrlRequest = operations['drive___files___upload-from-url']['requestBody']['content']['application/json']; // @public (undocumented) type DriveFolder = components['schemas']['DriveFolder']; // @public (undocumented) -type DriveFoldersCreateRequest = operations['drive/folders/create']['requestBody']['content']['application/json']; +type DriveFoldersCreateRequest = operations['drive___folders___create']['requestBody']['content']['application/json']; // @public (undocumented) -type DriveFoldersCreateResponse = operations['drive/folders/create']['responses']['200']['content']['application/json']; +type DriveFoldersCreateResponse = operations['drive___folders___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type DriveFoldersDeleteRequest = operations['drive/folders/delete']['requestBody']['content']['application/json']; +type DriveFoldersDeleteRequest = operations['drive___folders___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type DriveFoldersFindRequest = operations['drive/folders/find']['requestBody']['content']['application/json']; +type DriveFoldersFindRequest = operations['drive___folders___find']['requestBody']['content']['application/json']; // @public (undocumented) -type DriveFoldersFindResponse = operations['drive/folders/find']['responses']['200']['content']['application/json']; +type DriveFoldersFindResponse = operations['drive___folders___find']['responses']['200']['content']['application/json']; // @public (undocumented) -type DriveFoldersRequest = operations['drive/folders']['requestBody']['content']['application/json']; +type DriveFoldersRequest = operations['drive___folders']['requestBody']['content']['application/json']; // @public (undocumented) -type DriveFoldersResponse = operations['drive/folders']['responses']['200']['content']['application/json']; +type DriveFoldersResponse = operations['drive___folders']['responses']['200']['content']['application/json']; // @public (undocumented) -type DriveFoldersShowRequest = operations['drive/folders/show']['requestBody']['content']['application/json']; +type DriveFoldersShowRequest = operations['drive___folders___show']['requestBody']['content']['application/json']; // @public (undocumented) -type DriveFoldersShowResponse = operations['drive/folders/show']['responses']['200']['content']['application/json']; +type DriveFoldersShowResponse = operations['drive___folders___show']['responses']['200']['content']['application/json']; // @public (undocumented) -type DriveFoldersUpdateRequest = operations['drive/folders/update']['requestBody']['content']['application/json']; +type DriveFoldersUpdateRequest = operations['drive___folders___update']['requestBody']['content']['application/json']; // @public (undocumented) -type DriveFoldersUpdateResponse = operations['drive/folders/update']['responses']['200']['content']['application/json']; +type DriveFoldersUpdateResponse = operations['drive___folders___update']['responses']['200']['content']['application/json']; // @public (undocumented) type DriveResponse = operations['drive']['responses']['200']['content']['application/json']; // @public (undocumented) -type DriveStreamRequest = operations['drive/stream']['requestBody']['content']['application/json']; +type DriveStreamRequest = operations['drive___stream']['requestBody']['content']['application/json']; // @public (undocumented) -type DriveStreamResponse = operations['drive/stream']['responses']['200']['content']['application/json']; +type DriveStreamResponse = operations['drive___stream']['responses']['200']['content']['application/json']; // @public (undocumented) -type EmailAddressAvailableRequest = operations['email-address/available']['requestBody']['content']['application/json']; +type EmailAddressAvailableRequest = operations['email-address___available']['requestBody']['content']['application/json']; // @public (undocumented) -type EmailAddressAvailableResponse = operations['email-address/available']['responses']['200']['content']['application/json']; +type EmailAddressAvailableResponse = operations['email-address___available']['responses']['200']['content']['application/json']; // @public (undocumented) type EmojiAdded = { @@ -1212,7 +1219,9 @@ declare namespace entities { AdminDriveShowFileResponse, AdminEmojiAddAliasesBulkRequest, AdminEmojiAddRequest, + AdminEmojiAddResponse, AdminEmojiAddsRequest, + AdminEmojiAddsResponse, AdminEmojiCopyRequest, AdminEmojiCopyResponse, AdminEmojiDeleteBulkRequest, @@ -1806,6 +1815,7 @@ declare namespace entities { RoleCondFormulaLogics, RoleCondFormulaValueNot, RoleCondFormulaValueIsLocalOrRemote, + RoleCondFormulaValueAssignedRole, RoleCondFormulaValueCreated, RoleCondFormulaFollowersOrFollowingOrNotes, RoleCondFormulaValue, @@ -1813,7 +1823,10 @@ declare namespace entities { Role, RolePolicies, ReversiGameLite, - ReversiGameDetailed + ReversiGameDetailed, + MetaLite, + MetaDetailedOnly, + MetaDetailed } } export { entities } @@ -1822,46 +1835,46 @@ export { entities } type Error_2 = components['schemas']['Error']; // @public (undocumented) -type FederationFollowersRequest = operations['federation/followers']['requestBody']['content']['application/json']; +type FederationFollowersRequest = operations['federation___followers']['requestBody']['content']['application/json']; // @public (undocumented) -type FederationFollowersResponse = operations['federation/followers']['responses']['200']['content']['application/json']; +type FederationFollowersResponse = operations['federation___followers']['responses']['200']['content']['application/json']; // @public (undocumented) -type FederationFollowingRequest = operations['federation/following']['requestBody']['content']['application/json']; +type FederationFollowingRequest = operations['federation___following']['requestBody']['content']['application/json']; // @public (undocumented) -type FederationFollowingResponse = operations['federation/following']['responses']['200']['content']['application/json']; +type FederationFollowingResponse = operations['federation___following']['responses']['200']['content']['application/json']; // @public (undocumented) type FederationInstance = components['schemas']['FederationInstance']; // @public (undocumented) -type FederationInstancesRequest = operations['federation/instances']['requestBody']['content']['application/json']; +type FederationInstancesRequest = operations['federation___instances']['requestBody']['content']['application/json']; // @public (undocumented) -type FederationInstancesResponse = operations['federation/instances']['responses']['200']['content']['application/json']; +type FederationInstancesResponse = operations['federation___instances']['responses']['200']['content']['application/json']; // @public (undocumented) -type FederationShowInstanceRequest = operations['federation/show-instance']['requestBody']['content']['application/json']; +type FederationShowInstanceRequest = operations['federation___show-instance']['requestBody']['content']['application/json']; // @public (undocumented) -type FederationShowInstanceResponse = operations['federation/show-instance']['responses']['200']['content']['application/json']; +type FederationShowInstanceResponse = operations['federation___show-instance']['responses']['200']['content']['application/json']; // @public (undocumented) -type FederationStatsRequest = operations['federation/stats']['requestBody']['content']['application/json']; +type FederationStatsRequest = operations['federation___stats']['requestBody']['content']['application/json']; // @public (undocumented) -type FederationStatsResponse = operations['federation/stats']['responses']['200']['content']['application/json']; +type FederationStatsResponse = operations['federation___stats']['responses']['200']['content']['application/json']; // @public (undocumented) -type FederationUpdateRemoteUserRequest = operations['federation/update-remote-user']['requestBody']['content']['application/json']; +type FederationUpdateRemoteUserRequest = operations['federation___update-remote-user']['requestBody']['content']['application/json']; // @public (undocumented) -type FederationUsersRequest = operations['federation/users']['requestBody']['content']['application/json']; +type FederationUsersRequest = operations['federation___users']['requestBody']['content']['application/json']; // @public (undocumented) -type FederationUsersResponse = operations['federation/users']['responses']['200']['content']['application/json']; +type FederationUsersResponse = operations['federation___users']['responses']['200']['content']['application/json']; // @public (undocumented) type FetchExternalResourcesRequest = operations['fetch-external-resources']['requestBody']['content']['application/json']; @@ -1893,49 +1906,49 @@ type FetchRssResponse = operations['fetch-rss']['responses']['200']['content'][' type Flash = components['schemas']['Flash']; // @public (undocumented) -type FlashCreateRequest = operations['flash/create']['requestBody']['content']['application/json']; +type FlashCreateRequest = operations['flash___create']['requestBody']['content']['application/json']; // @public (undocumented) -type FlashCreateResponse = operations['flash/create']['responses']['200']['content']['application/json']; +type FlashCreateResponse = operations['flash___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type FlashDeleteRequest = operations['flash/delete']['requestBody']['content']['application/json']; +type FlashDeleteRequest = operations['flash___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type FlashFeaturedResponse = operations['flash/featured']['responses']['200']['content']['application/json']; +type FlashFeaturedResponse = operations['flash___featured']['responses']['200']['content']['application/json']; // @public (undocumented) -type FlashGenTokenRequest = operations['flash/gen-token']['requestBody']['content']['application/json']; +type FlashGenTokenRequest = operations['flash___gen-token']['requestBody']['content']['application/json']; // @public (undocumented) -type FlashGenTokenResponse = operations['flash/gen-token']['responses']['200']['content']['application/json']; +type FlashGenTokenResponse = operations['flash___gen-token']['responses']['200']['content']['application/json']; // @public (undocumented) -type FlashLikeRequest = operations['flash/like']['requestBody']['content']['application/json']; +type FlashLikeRequest = operations['flash___like']['requestBody']['content']['application/json']; // @public (undocumented) -type FlashMyLikesRequest = operations['flash/my-likes']['requestBody']['content']['application/json']; +type FlashMyLikesRequest = operations['flash___my-likes']['requestBody']['content']['application/json']; // @public (undocumented) -type FlashMyLikesResponse = operations['flash/my-likes']['responses']['200']['content']['application/json']; +type FlashMyLikesResponse = operations['flash___my-likes']['responses']['200']['content']['application/json']; // @public (undocumented) -type FlashMyRequest = operations['flash/my']['requestBody']['content']['application/json']; +type FlashMyRequest = operations['flash___my']['requestBody']['content']['application/json']; // @public (undocumented) -type FlashMyResponse = operations['flash/my']['responses']['200']['content']['application/json']; +type FlashMyResponse = operations['flash___my']['responses']['200']['content']['application/json']; // @public (undocumented) -type FlashShowRequest = operations['flash/show']['requestBody']['content']['application/json']; +type FlashShowRequest = operations['flash___show']['requestBody']['content']['application/json']; // @public (undocumented) -type FlashShowResponse = operations['flash/show']['responses']['200']['content']['application/json']; +type FlashShowResponse = operations['flash___show']['responses']['200']['content']['application/json']; // @public (undocumented) -type FlashUnlikeRequest = operations['flash/unlike']['requestBody']['content']['application/json']; +type FlashUnlikeRequest = operations['flash___unlike']['requestBody']['content']['application/json']; // @public (undocumented) -type FlashUpdateRequest = operations['flash/update']['requestBody']['content']['application/json']; +type FlashUpdateRequest = operations['flash___update']['requestBody']['content']['application/json']; // @public (undocumented) export const followersVisibilities: readonly ["public", "followers", "private"]; @@ -1944,97 +1957,97 @@ export const followersVisibilities: readonly ["public", "followers", "private"]; type Following = components['schemas']['Following']; // @public (undocumented) -type FollowingCreateRequest = operations['following/create']['requestBody']['content']['application/json']; +type FollowingCreateRequest = operations['following___create']['requestBody']['content']['application/json']; // @public (undocumented) -type FollowingCreateResponse = operations['following/create']['responses']['200']['content']['application/json']; +type FollowingCreateResponse = operations['following___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type FollowingDeleteRequest = operations['following/delete']['requestBody']['content']['application/json']; +type FollowingDeleteRequest = operations['following___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type FollowingDeleteResponse = operations['following/delete']['responses']['200']['content']['application/json']; +type FollowingDeleteResponse = operations['following___delete']['responses']['200']['content']['application/json']; // @public (undocumented) -type FollowingInvalidateRequest = operations['following/invalidate']['requestBody']['content']['application/json']; +type FollowingInvalidateRequest = operations['following___invalidate']['requestBody']['content']['application/json']; // @public (undocumented) -type FollowingInvalidateResponse = operations['following/invalidate']['responses']['200']['content']['application/json']; +type FollowingInvalidateResponse = operations['following___invalidate']['responses']['200']['content']['application/json']; // @public (undocumented) -type FollowingRequestsAcceptRequest = operations['following/requests/accept']['requestBody']['content']['application/json']; +type FollowingRequestsAcceptRequest = operations['following___requests___accept']['requestBody']['content']['application/json']; // @public (undocumented) -type FollowingRequestsCancelRequest = operations['following/requests/cancel']['requestBody']['content']['application/json']; +type FollowingRequestsCancelRequest = operations['following___requests___cancel']['requestBody']['content']['application/json']; // @public (undocumented) -type FollowingRequestsCancelResponse = operations['following/requests/cancel']['responses']['200']['content']['application/json']; +type FollowingRequestsCancelResponse = operations['following___requests___cancel']['responses']['200']['content']['application/json']; // @public (undocumented) -type FollowingRequestsListRequest = operations['following/requests/list']['requestBody']['content']['application/json']; +type FollowingRequestsListRequest = operations['following___requests___list']['requestBody']['content']['application/json']; // @public (undocumented) -type FollowingRequestsListResponse = operations['following/requests/list']['responses']['200']['content']['application/json']; +type FollowingRequestsListResponse = operations['following___requests___list']['responses']['200']['content']['application/json']; // @public (undocumented) -type FollowingRequestsRejectRequest = operations['following/requests/reject']['requestBody']['content']['application/json']; +type FollowingRequestsRejectRequest = operations['following___requests___reject']['requestBody']['content']['application/json']; // @public (undocumented) -type FollowingUpdateAllRequest = operations['following/update-all']['requestBody']['content']['application/json']; +type FollowingUpdateAllRequest = operations['following___update-all']['requestBody']['content']['application/json']; // @public (undocumented) -type FollowingUpdateRequest = operations['following/update']['requestBody']['content']['application/json']; +type FollowingUpdateRequest = operations['following___update']['requestBody']['content']['application/json']; // @public (undocumented) -type FollowingUpdateResponse = operations['following/update']['responses']['200']['content']['application/json']; +type FollowingUpdateResponse = operations['following___update']['responses']['200']['content']['application/json']; // @public (undocumented) export const followingVisibilities: readonly ["public", "followers", "private"]; // @public (undocumented) -type GalleryFeaturedRequest = operations['gallery/featured']['requestBody']['content']['application/json']; +type GalleryFeaturedRequest = operations['gallery___featured']['requestBody']['content']['application/json']; // @public (undocumented) -type GalleryFeaturedResponse = operations['gallery/featured']['responses']['200']['content']['application/json']; +type GalleryFeaturedResponse = operations['gallery___featured']['responses']['200']['content']['application/json']; // @public (undocumented) -type GalleryPopularResponse = operations['gallery/popular']['responses']['200']['content']['application/json']; +type GalleryPopularResponse = operations['gallery___popular']['responses']['200']['content']['application/json']; // @public (undocumented) type GalleryPost = components['schemas']['GalleryPost']; // @public (undocumented) -type GalleryPostsCreateRequest = operations['gallery/posts/create']['requestBody']['content']['application/json']; +type GalleryPostsCreateRequest = operations['gallery___posts___create']['requestBody']['content']['application/json']; // @public (undocumented) -type GalleryPostsCreateResponse = operations['gallery/posts/create']['responses']['200']['content']['application/json']; +type GalleryPostsCreateResponse = operations['gallery___posts___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type GalleryPostsDeleteRequest = operations['gallery/posts/delete']['requestBody']['content']['application/json']; +type GalleryPostsDeleteRequest = operations['gallery___posts___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type GalleryPostsLikeRequest = operations['gallery/posts/like']['requestBody']['content']['application/json']; +type GalleryPostsLikeRequest = operations['gallery___posts___like']['requestBody']['content']['application/json']; // @public (undocumented) -type GalleryPostsRequest = operations['gallery/posts']['requestBody']['content']['application/json']; +type GalleryPostsRequest = operations['gallery___posts']['requestBody']['content']['application/json']; // @public (undocumented) -type GalleryPostsResponse = operations['gallery/posts']['responses']['200']['content']['application/json']; +type GalleryPostsResponse = operations['gallery___posts']['responses']['200']['content']['application/json']; // @public (undocumented) -type GalleryPostsShowRequest = operations['gallery/posts/show']['requestBody']['content']['application/json']; +type GalleryPostsShowRequest = operations['gallery___posts___show']['requestBody']['content']['application/json']; // @public (undocumented) -type GalleryPostsShowResponse = operations['gallery/posts/show']['responses']['200']['content']['application/json']; +type GalleryPostsShowResponse = operations['gallery___posts___show']['responses']['200']['content']['application/json']; // @public (undocumented) -type GalleryPostsUnlikeRequest = operations['gallery/posts/unlike']['requestBody']['content']['application/json']; +type GalleryPostsUnlikeRequest = operations['gallery___posts___unlike']['requestBody']['content']['application/json']; // @public (undocumented) -type GalleryPostsUpdateRequest = operations['gallery/posts/update']['requestBody']['content']['application/json']; +type GalleryPostsUpdateRequest = operations['gallery___posts___update']['requestBody']['content']['application/json']; // @public (undocumented) -type GalleryPostsUpdateResponse = operations['gallery/posts/update']['responses']['200']['content']['application/json']; +type GalleryPostsUpdateResponse = operations['gallery___posts___update']['responses']['200']['content']['application/json']; // @public (undocumented) type GetAvatarDecorationsResponse = operations['get-avatar-decorations']['responses']['200']['content']['application/json']; @@ -2046,286 +2059,286 @@ type GetOnlineUsersCountResponse = operations['get-online-users-count']['respons type Hashtag = components['schemas']['Hashtag']; // @public (undocumented) -type HashtagsListRequest = operations['hashtags/list']['requestBody']['content']['application/json']; +type HashtagsListRequest = operations['hashtags___list']['requestBody']['content']['application/json']; // @public (undocumented) -type HashtagsListResponse = operations['hashtags/list']['responses']['200']['content']['application/json']; +type HashtagsListResponse = operations['hashtags___list']['responses']['200']['content']['application/json']; // @public (undocumented) -type HashtagsSearchRequest = operations['hashtags/search']['requestBody']['content']['application/json']; +type HashtagsSearchRequest = operations['hashtags___search']['requestBody']['content']['application/json']; // @public (undocumented) -type HashtagsSearchResponse = operations['hashtags/search']['responses']['200']['content']['application/json']; +type HashtagsSearchResponse = operations['hashtags___search']['responses']['200']['content']['application/json']; // @public (undocumented) -type HashtagsShowRequest = operations['hashtags/show']['requestBody']['content']['application/json']; +type HashtagsShowRequest = operations['hashtags___show']['requestBody']['content']['application/json']; // @public (undocumented) -type HashtagsShowResponse = operations['hashtags/show']['responses']['200']['content']['application/json']; +type HashtagsShowResponse = operations['hashtags___show']['responses']['200']['content']['application/json']; // @public (undocumented) -type HashtagsTrendResponse = operations['hashtags/trend']['responses']['200']['content']['application/json']; +type HashtagsTrendResponse = operations['hashtags___trend']['responses']['200']['content']['application/json']; // @public (undocumented) -type HashtagsUsersRequest = operations['hashtags/users']['requestBody']['content']['application/json']; +type HashtagsUsersRequest = operations['hashtags___users']['requestBody']['content']['application/json']; // @public (undocumented) -type HashtagsUsersResponse = operations['hashtags/users']['responses']['200']['content']['application/json']; +type HashtagsUsersResponse = operations['hashtags___users']['responses']['200']['content']['application/json']; // @public (undocumented) -type I2faDoneRequest = operations['i/2fa/done']['requestBody']['content']['application/json']; +type I2faDoneRequest = operations['i___2fa___done']['requestBody']['content']['application/json']; // @public (undocumented) -type I2faDoneResponse = operations['i/2fa/done']['responses']['200']['content']['application/json']; +type I2faDoneResponse = operations['i___2fa___done']['responses']['200']['content']['application/json']; // @public (undocumented) -type I2faKeyDoneRequest = operations['i/2fa/key-done']['requestBody']['content']['application/json']; +type I2faKeyDoneRequest = operations['i___2fa___key-done']['requestBody']['content']['application/json']; // @public (undocumented) -type I2faKeyDoneResponse = operations['i/2fa/key-done']['responses']['200']['content']['application/json']; +type I2faKeyDoneResponse = operations['i___2fa___key-done']['responses']['200']['content']['application/json']; // @public (undocumented) -type I2faPasswordLessRequest = operations['i/2fa/password-less']['requestBody']['content']['application/json']; +type I2faPasswordLessRequest = operations['i___2fa___password-less']['requestBody']['content']['application/json']; // @public (undocumented) -type I2faRegisterKeyRequest = operations['i/2fa/register-key']['requestBody']['content']['application/json']; +type I2faRegisterKeyRequest = operations['i___2fa___register-key']['requestBody']['content']['application/json']; // @public (undocumented) -type I2faRegisterKeyResponse = operations['i/2fa/register-key']['responses']['200']['content']['application/json']; +type I2faRegisterKeyResponse = operations['i___2fa___register-key']['responses']['200']['content']['application/json']; // @public (undocumented) -type I2faRegisterRequest = operations['i/2fa/register']['requestBody']['content']['application/json']; +type I2faRegisterRequest = operations['i___2fa___register']['requestBody']['content']['application/json']; // @public (undocumented) -type I2faRegisterResponse = operations['i/2fa/register']['responses']['200']['content']['application/json']; +type I2faRegisterResponse = operations['i___2fa___register']['responses']['200']['content']['application/json']; // @public (undocumented) -type I2faRemoveKeyRequest = operations['i/2fa/remove-key']['requestBody']['content']['application/json']; +type I2faRemoveKeyRequest = operations['i___2fa___remove-key']['requestBody']['content']['application/json']; // @public (undocumented) -type I2faUnregisterRequest = operations['i/2fa/unregister']['requestBody']['content']['application/json']; +type I2faUnregisterRequest = operations['i___2fa___unregister']['requestBody']['content']['application/json']; // @public (undocumented) -type I2faUpdateKeyRequest = operations['i/2fa/update-key']['requestBody']['content']['application/json']; +type I2faUpdateKeyRequest = operations['i___2fa___update-key']['requestBody']['content']['application/json']; // @public (undocumented) -type IAppsRequest = operations['i/apps']['requestBody']['content']['application/json']; +type IAppsRequest = operations['i___apps']['requestBody']['content']['application/json']; // @public (undocumented) -type IAppsResponse = operations['i/apps']['responses']['200']['content']['application/json']; +type IAppsResponse = operations['i___apps']['responses']['200']['content']['application/json']; // @public (undocumented) -type IAuthorizedAppsRequest = operations['i/authorized-apps']['requestBody']['content']['application/json']; +type IAuthorizedAppsRequest = operations['i___authorized-apps']['requestBody']['content']['application/json']; // @public (undocumented) -type IAuthorizedAppsResponse = operations['i/authorized-apps']['responses']['200']['content']['application/json']; +type IAuthorizedAppsResponse = operations['i___authorized-apps']['responses']['200']['content']['application/json']; // @public (undocumented) -type IChangePasswordRequest = operations['i/change-password']['requestBody']['content']['application/json']; +type IChangePasswordRequest = operations['i___change-password']['requestBody']['content']['application/json']; // @public (undocumented) -type IClaimAchievementRequest = operations['i/claim-achievement']['requestBody']['content']['application/json']; +type IClaimAchievementRequest = operations['i___claim-achievement']['requestBody']['content']['application/json']; // @public (undocumented) type ID = string; // @public (undocumented) -type IDeleteAccountRequest = operations['i/delete-account']['requestBody']['content']['application/json']; +type IDeleteAccountRequest = operations['i___delete-account']['requestBody']['content']['application/json']; // @public (undocumented) -type IExportFollowingRequest = operations['i/export-following']['requestBody']['content']['application/json']; +type IExportFollowingRequest = operations['i___export-following']['requestBody']['content']['application/json']; // @public (undocumented) -type IFavoritesRequest = operations['i/favorites']['requestBody']['content']['application/json']; +type IFavoritesRequest = operations['i___favorites']['requestBody']['content']['application/json']; // @public (undocumented) -type IFavoritesResponse = operations['i/favorites']['responses']['200']['content']['application/json']; +type IFavoritesResponse = operations['i___favorites']['responses']['200']['content']['application/json']; // @public (undocumented) -type IGalleryLikesRequest = operations['i/gallery/likes']['requestBody']['content']['application/json']; +type IGalleryLikesRequest = operations['i___gallery___likes']['requestBody']['content']['application/json']; // @public (undocumented) -type IGalleryLikesResponse = operations['i/gallery/likes']['responses']['200']['content']['application/json']; +type IGalleryLikesResponse = operations['i___gallery___likes']['responses']['200']['content']['application/json']; // @public (undocumented) -type IGalleryPostsRequest = operations['i/gallery/posts']['requestBody']['content']['application/json']; +type IGalleryPostsRequest = operations['i___gallery___posts']['requestBody']['content']['application/json']; // @public (undocumented) -type IGalleryPostsResponse = operations['i/gallery/posts']['responses']['200']['content']['application/json']; +type IGalleryPostsResponse = operations['i___gallery___posts']['responses']['200']['content']['application/json']; // @public (undocumented) -type IImportAntennasRequest = operations['i/import-antennas']['requestBody']['content']['application/json']; +type IImportAntennasRequest = operations['i___import-antennas']['requestBody']['content']['application/json']; // @public (undocumented) -type IImportBlockingRequest = operations['i/import-blocking']['requestBody']['content']['application/json']; +type IImportBlockingRequest = operations['i___import-blocking']['requestBody']['content']['application/json']; // @public (undocumented) -type IImportFollowingRequest = operations['i/import-following']['requestBody']['content']['application/json']; +type IImportFollowingRequest = operations['i___import-following']['requestBody']['content']['application/json']; // @public (undocumented) -type IImportMutingRequest = operations['i/import-muting']['requestBody']['content']['application/json']; +type IImportMutingRequest = operations['i___import-muting']['requestBody']['content']['application/json']; // @public (undocumented) -type IImportUserListsRequest = operations['i/import-user-lists']['requestBody']['content']['application/json']; +type IImportUserListsRequest = operations['i___import-user-lists']['requestBody']['content']['application/json']; // @public (undocumented) -type IMoveRequest = operations['i/move']['requestBody']['content']['application/json']; +type IMoveRequest = operations['i___move']['requestBody']['content']['application/json']; // @public (undocumented) -type IMoveResponse = operations['i/move']['responses']['200']['content']['application/json']; +type IMoveResponse = operations['i___move']['responses']['200']['content']['application/json']; // @public (undocumented) -type INotificationsGroupedRequest = operations['i/notifications-grouped']['requestBody']['content']['application/json']; +type INotificationsGroupedRequest = operations['i___notifications-grouped']['requestBody']['content']['application/json']; // @public (undocumented) -type INotificationsGroupedResponse = operations['i/notifications-grouped']['responses']['200']['content']['application/json']; +type INotificationsGroupedResponse = operations['i___notifications-grouped']['responses']['200']['content']['application/json']; // @public (undocumented) -type INotificationsRequest = operations['i/notifications']['requestBody']['content']['application/json']; +type INotificationsRequest = operations['i___notifications']['requestBody']['content']['application/json']; // @public (undocumented) -type INotificationsResponse = operations['i/notifications']['responses']['200']['content']['application/json']; +type INotificationsResponse = operations['i___notifications']['responses']['200']['content']['application/json']; // @public (undocumented) type InviteCode = components['schemas']['InviteCode']; // @public (undocumented) -type InviteCreateResponse = operations['invite/create']['responses']['200']['content']['application/json']; +type InviteCreateResponse = operations['invite___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type InviteDeleteRequest = operations['invite/delete']['requestBody']['content']['application/json']; +type InviteDeleteRequest = operations['invite___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type InviteLimitResponse = operations['invite/limit']['responses']['200']['content']['application/json']; +type InviteLimitResponse = operations['invite___limit']['responses']['200']['content']['application/json']; // @public (undocumented) -type InviteListRequest = operations['invite/list']['requestBody']['content']['application/json']; +type InviteListRequest = operations['invite___list']['requestBody']['content']['application/json']; // @public (undocumented) -type InviteListResponse = operations['invite/list']['responses']['200']['content']['application/json']; +type InviteListResponse = operations['invite___list']['responses']['200']['content']['application/json']; // @public (undocumented) -type IPageLikesRequest = operations['i/page-likes']['requestBody']['content']['application/json']; +type IPageLikesRequest = operations['i___page-likes']['requestBody']['content']['application/json']; // @public (undocumented) -type IPageLikesResponse = operations['i/page-likes']['responses']['200']['content']['application/json']; +type IPageLikesResponse = operations['i___page-likes']['responses']['200']['content']['application/json']; // @public (undocumented) -type IPagesRequest = operations['i/pages']['requestBody']['content']['application/json']; +type IPagesRequest = operations['i___pages']['requestBody']['content']['application/json']; // @public (undocumented) -type IPagesResponse = operations['i/pages']['responses']['200']['content']['application/json']; +type IPagesResponse = operations['i___pages']['responses']['200']['content']['application/json']; // @public (undocumented) -type IPinRequest = operations['i/pin']['requestBody']['content']['application/json']; +type IPinRequest = operations['i___pin']['requestBody']['content']['application/json']; // @public (undocumented) -type IPinResponse = operations['i/pin']['responses']['200']['content']['application/json']; +type IPinResponse = operations['i___pin']['responses']['200']['content']['application/json']; // @public (undocumented) -type IReadAnnouncementRequest = operations['i/read-announcement']['requestBody']['content']['application/json']; +type IReadAnnouncementRequest = operations['i___read-announcement']['requestBody']['content']['application/json']; // @public (undocumented) -type IRegenerateTokenRequest = operations['i/regenerate-token']['requestBody']['content']['application/json']; +type IRegenerateTokenRequest = operations['i___regenerate-token']['requestBody']['content']['application/json']; // @public (undocumented) -type IRegistryGetAllRequest = operations['i/registry/get-all']['requestBody']['content']['application/json']; +type IRegistryGetAllRequest = operations['i___registry___get-all']['requestBody']['content']['application/json']; // @public (undocumented) -type IRegistryGetAllResponse = operations['i/registry/get-all']['responses']['200']['content']['application/json']; +type IRegistryGetAllResponse = operations['i___registry___get-all']['responses']['200']['content']['application/json']; // @public (undocumented) -type IRegistryGetDetailRequest = operations['i/registry/get-detail']['requestBody']['content']['application/json']; +type IRegistryGetDetailRequest = operations['i___registry___get-detail']['requestBody']['content']['application/json']; // @public (undocumented) -type IRegistryGetDetailResponse = operations['i/registry/get-detail']['responses']['200']['content']['application/json']; +type IRegistryGetDetailResponse = operations['i___registry___get-detail']['responses']['200']['content']['application/json']; // @public (undocumented) -type IRegistryGetRequest = operations['i/registry/get']['requestBody']['content']['application/json']; +type IRegistryGetRequest = operations['i___registry___get']['requestBody']['content']['application/json']; // @public (undocumented) -type IRegistryGetResponse = operations['i/registry/get']['responses']['200']['content']['application/json']; +type IRegistryGetResponse = operations['i___registry___get']['responses']['200']['content']['application/json']; // @public (undocumented) -type IRegistryKeysRequest = operations['i/registry/keys']['requestBody']['content']['application/json']; +type IRegistryKeysRequest = operations['i___registry___keys']['requestBody']['content']['application/json']; // @public (undocumented) -type IRegistryKeysResponse = operations['i/registry/keys']['responses']['200']['content']['application/json']; +type IRegistryKeysResponse = operations['i___registry___keys']['responses']['200']['content']['application/json']; // @public (undocumented) -type IRegistryKeysWithTypeRequest = operations['i/registry/keys-with-type']['requestBody']['content']['application/json']; +type IRegistryKeysWithTypeRequest = operations['i___registry___keys-with-type']['requestBody']['content']['application/json']; // @public (undocumented) -type IRegistryKeysWithTypeResponse = operations['i/registry/keys-with-type']['responses']['200']['content']['application/json']; +type IRegistryKeysWithTypeResponse = operations['i___registry___keys-with-type']['responses']['200']['content']['application/json']; // @public (undocumented) -type IRegistryRemoveRequest = operations['i/registry/remove']['requestBody']['content']['application/json']; +type IRegistryRemoveRequest = operations['i___registry___remove']['requestBody']['content']['application/json']; // @public (undocumented) -type IRegistryScopesWithDomainResponse = operations['i/registry/scopes-with-domain']['responses']['200']['content']['application/json']; +type IRegistryScopesWithDomainResponse = operations['i___registry___scopes-with-domain']['responses']['200']['content']['application/json']; // @public (undocumented) -type IRegistrySetRequest = operations['i/registry/set']['requestBody']['content']['application/json']; +type IRegistrySetRequest = operations['i___registry___set']['requestBody']['content']['application/json']; // @public (undocumented) type IResponse = operations['i']['responses']['200']['content']['application/json']; // @public (undocumented) -type IRevokeTokenRequest = operations['i/revoke-token']['requestBody']['content']['application/json']; +type IRevokeTokenRequest = operations['i___revoke-token']['requestBody']['content']['application/json']; // @public (undocumented) function isAPIError(reason: Record): reason is APIError; // @public (undocumented) -type ISigninHistoryRequest = operations['i/signin-history']['requestBody']['content']['application/json']; +type ISigninHistoryRequest = operations['i___signin-history']['requestBody']['content']['application/json']; // @public (undocumented) -type ISigninHistoryResponse = operations['i/signin-history']['responses']['200']['content']['application/json']; +type ISigninHistoryResponse = operations['i___signin-history']['responses']['200']['content']['application/json']; // @public (undocumented) -type IUnpinRequest = operations['i/unpin']['requestBody']['content']['application/json']; +type IUnpinRequest = operations['i___unpin']['requestBody']['content']['application/json']; // @public (undocumented) -type IUnpinResponse = operations['i/unpin']['responses']['200']['content']['application/json']; +type IUnpinResponse = operations['i___unpin']['responses']['200']['content']['application/json']; // @public (undocumented) -type IUpdateEmailRequest = operations['i/update-email']['requestBody']['content']['application/json']; +type IUpdateEmailRequest = operations['i___update-email']['requestBody']['content']['application/json']; // @public (undocumented) -type IUpdateEmailResponse = operations['i/update-email']['responses']['200']['content']['application/json']; +type IUpdateEmailResponse = operations['i___update-email']['responses']['200']['content']['application/json']; // @public (undocumented) -type IUpdateRequest = operations['i/update']['requestBody']['content']['application/json']; +type IUpdateRequest = operations['i___update']['requestBody']['content']['application/json']; // @public (undocumented) -type IUpdateResponse = operations['i/update']['responses']['200']['content']['application/json']; +type IUpdateResponse = operations['i___update']['responses']['200']['content']['application/json']; // @public (undocumented) -type IUserGroupInvitesRequest = operations['i/user-group-invites']['requestBody']['content']['application/json']; +type IUserGroupInvitesRequest = operations['i___user-group-invites']['requestBody']['content']['application/json']; // @public (undocumented) -type IUserGroupInvitesResponse = operations['i/user-group-invites']['responses']['200']['content']['application/json']; +type IUserGroupInvitesResponse = operations['i___user-group-invites']['responses']['200']['content']['application/json']; // @public (undocumented) -type IWebhooksCreateRequest = operations['i/webhooks/create']['requestBody']['content']['application/json']; +type IWebhooksCreateRequest = operations['i___webhooks___create']['requestBody']['content']['application/json']; // @public (undocumented) -type IWebhooksCreateResponse = operations['i/webhooks/create']['responses']['200']['content']['application/json']; +type IWebhooksCreateResponse = operations['i___webhooks___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type IWebhooksDeleteRequest = operations['i/webhooks/delete']['requestBody']['content']['application/json']; +type IWebhooksDeleteRequest = operations['i___webhooks___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type IWebhooksListResponse = operations['i/webhooks/list']['responses']['200']['content']['application/json']; +type IWebhooksListResponse = operations['i___webhooks___list']['responses']['200']['content']['application/json']; // @public (undocumented) -type IWebhooksShowRequest = operations['i/webhooks/show']['requestBody']['content']['application/json']; +type IWebhooksShowRequest = operations['i___webhooks___show']['requestBody']['content']['application/json']; // @public (undocumented) -type IWebhooksShowResponse = operations['i/webhooks/show']['responses']['200']['content']['application/json']; +type IWebhooksShowResponse = operations['i___webhooks___show']['responses']['200']['content']['application/json']; // @public (undocumented) -type IWebhooksUpdateRequest = operations['i/webhooks/update']['requestBody']['content']['application/json']; +type IWebhooksUpdateRequest = operations['i___webhooks___update']['requestBody']['content']['application/json']; // @public (undocumented) type MeDetailed = components['schemas']['MeDetailed']; @@ -2334,31 +2347,40 @@ type MeDetailed = components['schemas']['MeDetailed']; type MeDetailedOnly = components['schemas']['MeDetailedOnly']; // @public (undocumented) -type MessagingHistoryRequest = operations['messaging/history']['requestBody']['content']['application/json']; +type MessagingHistoryRequest = operations['messaging___history']['requestBody']['content']['application/json']; // @public (undocumented) -type MessagingHistoryResponse = operations['messaging/history']['responses']['200']['content']['application/json']; +type MessagingHistoryResponse = operations['messaging___history']['responses']['200']['content']['application/json']; // @public (undocumented) type MessagingMessage = components['schemas']['MessagingMessage']; // @public (undocumented) -type MessagingMessagesCreateRequest = operations['messaging/messages/create']['requestBody']['content']['application/json']; +type MessagingMessagesCreateRequest = operations['messaging___messages___create']['requestBody']['content']['application/json']; // @public (undocumented) -type MessagingMessagesCreateResponse = operations['messaging/messages/create']['responses']['200']['content']['application/json']; +type MessagingMessagesCreateResponse = operations['messaging___messages___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type MessagingMessagesDeleteRequest = operations['messaging/messages/delete']['requestBody']['content']['application/json']; +type MessagingMessagesDeleteRequest = operations['messaging___messages___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type MessagingMessagesReadRequest = operations['messaging/messages/read']['requestBody']['content']['application/json']; +type MessagingMessagesReadRequest = operations['messaging___messages___read']['requestBody']['content']['application/json']; // @public (undocumented) -type MessagingMessagesRequest = operations['messaging/messages']['requestBody']['content']['application/json']; +type MessagingMessagesRequest = operations['messaging___messages']['requestBody']['content']['application/json']; // @public (undocumented) -type MessagingMessagesResponse = operations['messaging/messages']['responses']['200']['content']['application/json']; +type MessagingMessagesResponse = operations['messaging___messages']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type MetaDetailed = components['schemas']['MetaDetailed']; + +// @public (undocumented) +type MetaDetailedOnly = components['schemas']['MetaDetailedOnly']; + +// @public (undocumented) +type MetaLite = components['schemas']['MetaLite']; // @public (undocumented) type MetaRequest = operations['meta']['requestBody']['content']['application/json']; @@ -2367,10 +2389,10 @@ type MetaRequest = operations['meta']['requestBody']['content']['application/jso type MetaResponse = operations['meta']['responses']['200']['content']['application/json']; // @public (undocumented) -type MiauthGenTokenRequest = operations['miauth/gen-token']['requestBody']['content']['application/json']; +type MiauthGenTokenRequest = operations['miauth___gen-token']['requestBody']['content']['application/json']; // @public (undocumented) -type MiauthGenTokenResponse = operations['miauth/gen-token']['responses']['200']['content']['application/json']; +type MiauthGenTokenResponse = operations['miauth___gen-token']['responses']['200']['content']['application/json']; // @public (undocumented) type ModerationLog = { @@ -2453,6 +2475,9 @@ type ModerationLog = { } | { type: 'unsuspendRemoteInstance'; info: ModerationLogPayloads['unsuspendRemoteInstance']; +} | { + type: 'updateRemoteInstanceNote'; + info: ModerationLogPayloads['updateRemoteInstanceNote']; } | { type: 'markSensitiveDriveFile'; info: ModerationLogPayloads['markSensitiveDriveFile']; @@ -2492,31 +2517,31 @@ type ModerationLog = { }); // @public (undocumented) -export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner"]; +export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "updateRemoteInstanceNote", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner"]; // @public (undocumented) -type MuteCreateRequest = operations['mute/create']['requestBody']['content']['application/json']; +type MuteCreateRequest = operations['mute___create']['requestBody']['content']['application/json']; // @public (undocumented) -type MuteDeleteRequest = operations['mute/delete']['requestBody']['content']['application/json']; +type MuteDeleteRequest = operations['mute___delete']['requestBody']['content']['application/json']; // @public (undocumented) export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"]; // @public (undocumented) -type MuteListRequest = operations['mute/list']['requestBody']['content']['application/json']; +type MuteListRequest = operations['mute___list']['requestBody']['content']['application/json']; // @public (undocumented) -type MuteListResponse = operations['mute/list']['responses']['200']['content']['application/json']; +type MuteListResponse = operations['mute___list']['responses']['200']['content']['application/json']; // @public (undocumented) type Muting = components['schemas']['Muting']; // @public (undocumented) -type MyAppsRequest = operations['my/apps']['requestBody']['content']['application/json']; +type MyAppsRequest = operations['my___apps']['requestBody']['content']['application/json']; // @public (undocumented) -type MyAppsResponse = operations['my/apps']['responses']['200']['content']['application/json']; +type MyAppsResponse = operations['my___apps']['responses']['200']['content']['application/json']; // @public (undocumented) type Note = components['schemas']['Note']; @@ -2528,106 +2553,106 @@ type NoteFavorite = components['schemas']['NoteFavorite']; type NoteReaction = components['schemas']['NoteReaction']; // @public (undocumented) -type NotesChildrenRequest = operations['notes/children']['requestBody']['content']['application/json']; +type NotesChildrenRequest = operations['notes___children']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesChildrenResponse = operations['notes/children']['responses']['200']['content']['application/json']; +type NotesChildrenResponse = operations['notes___children']['responses']['200']['content']['application/json']; // @public (undocumented) -type NotesClipsRequest = operations['notes/clips']['requestBody']['content']['application/json']; +type NotesClipsRequest = operations['notes___clips']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesClipsResponse = operations['notes/clips']['responses']['200']['content']['application/json']; +type NotesClipsResponse = operations['notes___clips']['responses']['200']['content']['application/json']; // @public (undocumented) -type NotesConversationRequest = operations['notes/conversation']['requestBody']['content']['application/json']; +type NotesConversationRequest = operations['notes___conversation']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesConversationResponse = operations['notes/conversation']['responses']['200']['content']['application/json']; +type NotesConversationResponse = operations['notes___conversation']['responses']['200']['content']['application/json']; // @public (undocumented) -type NotesCreateRequest = operations['notes/create']['requestBody']['content']['application/json']; +type NotesCreateRequest = operations['notes___create']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesCreateResponse = operations['notes/create']['responses']['200']['content']['application/json']; +type NotesCreateResponse = operations['notes___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type NotesDeleteRequest = operations['notes/delete']['requestBody']['content']['application/json']; +type NotesDeleteRequest = operations['notes___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesEventsSearchRequest = operations['notes/events/search']['requestBody']['content']['application/json']; +type NotesEventsSearchRequest = operations['notes___events___search']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesEventsSearchResponse = operations['notes/events/search']['responses']['200']['content']['application/json']; +type NotesEventsSearchResponse = operations['notes___events___search']['responses']['200']['content']['application/json']; // @public (undocumented) -type NotesFavoritesCreateRequest = operations['notes/favorites/create']['requestBody']['content']['application/json']; +type NotesFavoritesCreateRequest = operations['notes___favorites___create']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesFavoritesDeleteRequest = operations['notes/favorites/delete']['requestBody']['content']['application/json']; +type NotesFavoritesDeleteRequest = operations['notes___favorites___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesFeaturedRequest = operations['notes/featured']['requestBody']['content']['application/json']; +type NotesFeaturedRequest = operations['notes___featured']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesFeaturedResponse = operations['notes/featured']['responses']['200']['content']['application/json']; +type NotesFeaturedResponse = operations['notes___featured']['responses']['200']['content']['application/json']; // @public (undocumented) -type NotesGlobalTimelineRequest = operations['notes/global-timeline']['requestBody']['content']['application/json']; +type NotesGlobalTimelineRequest = operations['notes___global-timeline']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesGlobalTimelineResponse = operations['notes/global-timeline']['responses']['200']['content']['application/json']; +type NotesGlobalTimelineResponse = operations['notes___global-timeline']['responses']['200']['content']['application/json']; // @public (undocumented) -type NotesHybridTimelineRequest = operations['notes/hybrid-timeline']['requestBody']['content']['application/json']; +type NotesHybridTimelineRequest = operations['notes___hybrid-timeline']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesHybridTimelineResponse = operations['notes/hybrid-timeline']['responses']['200']['content']['application/json']; +type NotesHybridTimelineResponse = operations['notes___hybrid-timeline']['responses']['200']['content']['application/json']; // @public (undocumented) -type NotesLocalTimelineRequest = operations['notes/local-timeline']['requestBody']['content']['application/json']; +type NotesLocalTimelineRequest = operations['notes___local-timeline']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesLocalTimelineResponse = operations['notes/local-timeline']['responses']['200']['content']['application/json']; +type NotesLocalTimelineResponse = operations['notes___local-timeline']['responses']['200']['content']['application/json']; // @public (undocumented) -type NotesMentionsRequest = operations['notes/mentions']['requestBody']['content']['application/json']; +type NotesMentionsRequest = operations['notes___mentions']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesMentionsResponse = operations['notes/mentions']['responses']['200']['content']['application/json']; +type NotesMentionsResponse = operations['notes___mentions']['responses']['200']['content']['application/json']; // @public (undocumented) -type NotesPollsRecommendationRequest = operations['notes/polls/recommendation']['requestBody']['content']['application/json']; +type NotesPollsRecommendationRequest = operations['notes___polls___recommendation']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesPollsRecommendationResponse = operations['notes/polls/recommendation']['responses']['200']['content']['application/json']; +type NotesPollsRecommendationResponse = operations['notes___polls___recommendation']['responses']['200']['content']['application/json']; // @public (undocumented) -type NotesPollsVoteRequest = operations['notes/polls/vote']['requestBody']['content']['application/json']; +type NotesPollsVoteRequest = operations['notes___polls___vote']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesReactionsCreateRequest = operations['notes/reactions/create']['requestBody']['content']['application/json']; +type NotesReactionsCreateRequest = operations['notes___reactions___create']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesReactionsDeleteRequest = operations['notes/reactions/delete']['requestBody']['content']['application/json']; +type NotesReactionsDeleteRequest = operations['notes___reactions___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesReactionsRequest = operations['notes/reactions']['requestBody']['content']['application/json']; +type NotesReactionsRequest = operations['notes___reactions']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesReactionsResponse = operations['notes/reactions']['responses']['200']['content']['application/json']; +type NotesReactionsResponse = operations['notes___reactions']['responses']['200']['content']['application/json']; // @public (undocumented) -type NotesRenotesRequest = operations['notes/renotes']['requestBody']['content']['application/json']; +type NotesRenotesRequest = operations['notes___renotes']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesRenotesResponse = operations['notes/renotes']['responses']['200']['content']['application/json']; +type NotesRenotesResponse = operations['notes___renotes']['responses']['200']['content']['application/json']; // @public (undocumented) -type NotesRepliesRequest = operations['notes/replies']['requestBody']['content']['application/json']; +type NotesRepliesRequest = operations['notes___replies']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesRepliesResponse = operations['notes/replies']['responses']['200']['content']['application/json']; +type NotesRepliesResponse = operations['notes___replies']['responses']['200']['content']['application/json']; // @public (undocumented) type NotesRequest = operations['notes']['requestBody']['content']['application/json']; @@ -2636,58 +2661,58 @@ type NotesRequest = operations['notes']['requestBody']['content']['application/j type NotesResponse = operations['notes']['responses']['200']['content']['application/json']; // @public (undocumented) -type NotesSearchByTagRequest = operations['notes/search-by-tag']['requestBody']['content']['application/json']; +type NotesSearchByTagRequest = operations['notes___search-by-tag']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesSearchByTagResponse = operations['notes/search-by-tag']['responses']['200']['content']['application/json']; +type NotesSearchByTagResponse = operations['notes___search-by-tag']['responses']['200']['content']['application/json']; // @public (undocumented) -type NotesSearchRequest = operations['notes/search']['requestBody']['content']['application/json']; +type NotesSearchRequest = operations['notes___search']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesSearchResponse = operations['notes/search']['responses']['200']['content']['application/json']; +type NotesSearchResponse = operations['notes___search']['responses']['200']['content']['application/json']; // @public (undocumented) -type NotesShowRequest = operations['notes/show']['requestBody']['content']['application/json']; +type NotesShowRequest = operations['notes___show']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesShowResponse = operations['notes/show']['responses']['200']['content']['application/json']; +type NotesShowResponse = operations['notes___show']['responses']['200']['content']['application/json']; // @public (undocumented) -type NotesStateRequest = operations['notes/state']['requestBody']['content']['application/json']; +type NotesStateRequest = operations['notes___state']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesStateResponse = operations['notes/state']['responses']['200']['content']['application/json']; +type NotesStateResponse = operations['notes___state']['responses']['200']['content']['application/json']; // @public (undocumented) -type NotesThreadMutingCreateRequest = operations['notes/thread-muting/create']['requestBody']['content']['application/json']; +type NotesThreadMutingCreateRequest = operations['notes___thread-muting___create']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesThreadMutingDeleteRequest = operations['notes/thread-muting/delete']['requestBody']['content']['application/json']; +type NotesThreadMutingDeleteRequest = operations['notes___thread-muting___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesTimelineRequest = operations['notes/timeline']['requestBody']['content']['application/json']; +type NotesTimelineRequest = operations['notes___timeline']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesTimelineResponse = operations['notes/timeline']['responses']['200']['content']['application/json']; +type NotesTimelineResponse = operations['notes___timeline']['responses']['200']['content']['application/json']; // @public (undocumented) -type NotesTranslateRequest = operations['notes/translate']['requestBody']['content']['application/json']; +type NotesTranslateRequest = operations['notes___translate']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesTranslateResponse = operations['notes/translate']['responses']['200']['content']['application/json']; +type NotesTranslateResponse = operations['notes___translate']['responses']['200']['content']['application/json']; // @public (undocumented) -type NotesUnrenoteRequest = operations['notes/unrenote']['requestBody']['content']['application/json']; +type NotesUnrenoteRequest = operations['notes___unrenote']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesUpdateRequest = operations['notes/update']['requestBody']['content']['application/json']; +type NotesUpdateRequest = operations['notes___update']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesUserListTimelineRequest = operations['notes/user-list-timeline']['requestBody']['content']['application/json']; +type NotesUserListTimelineRequest = operations['notes___user-list-timeline']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesUserListTimelineResponse = operations['notes/user-list-timeline']['responses']['200']['content']['application/json']; +type NotesUserListTimelineResponse = operations['notes___user-list-timeline']['responses']['200']['content']['application/json']; // @public (undocumented) export const noteVisibilities: readonly ["public", "home", "followers", "specified"]; @@ -2696,7 +2721,7 @@ export const noteVisibilities: readonly ["public", "home", "followers", "specifi type Notification_2 = components['schemas']['Notification']; // @public (undocumented) -type NotificationsCreateRequest = operations['notifications/create']['requestBody']['content']['application/json']; +type NotificationsCreateRequest = operations['notifications___create']['requestBody']['content']['application/json']; // @public (undocumented) export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "achievementEarned"]; @@ -2720,31 +2745,31 @@ type PageEvent = { type PagePushRequest = operations['page-push']['requestBody']['content']['application/json']; // @public (undocumented) -type PagesCreateRequest = operations['pages/create']['requestBody']['content']['application/json']; +type PagesCreateRequest = operations['pages___create']['requestBody']['content']['application/json']; // @public (undocumented) -type PagesCreateResponse = operations['pages/create']['responses']['200']['content']['application/json']; +type PagesCreateResponse = operations['pages___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type PagesDeleteRequest = operations['pages/delete']['requestBody']['content']['application/json']; +type PagesDeleteRequest = operations['pages___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type PagesFeaturedResponse = operations['pages/featured']['responses']['200']['content']['application/json']; +type PagesFeaturedResponse = operations['pages___featured']['responses']['200']['content']['application/json']; // @public (undocumented) -type PagesLikeRequest = operations['pages/like']['requestBody']['content']['application/json']; +type PagesLikeRequest = operations['pages___like']['requestBody']['content']['application/json']; // @public (undocumented) -type PagesShowRequest = operations['pages/show']['requestBody']['content']['application/json']; +type PagesShowRequest = operations['pages___show']['requestBody']['content']['application/json']; // @public (undocumented) -type PagesShowResponse = operations['pages/show']['responses']['200']['content']['application/json']; +type PagesShowResponse = operations['pages___show']['responses']['200']['content']['application/json']; // @public (undocumented) -type PagesUnlikeRequest = operations['pages/unlike']['requestBody']['content']['application/json']; +type PagesUnlikeRequest = operations['pages___unlike']['requestBody']['content']['application/json']; // @public (undocumented) -type PagesUpdateRequest = operations['pages/update']['requestBody']['content']['application/json']; +type PagesUpdateRequest = operations['pages___update']['requestBody']['content']['application/json']; // @public (undocumented) function parse(acct: string): Acct; @@ -2759,7 +2784,7 @@ type PingResponse = operations['ping']['responses']['200']['content']['applicati type PinnedUsersResponse = operations['pinned-users']['responses']['200']['content']['application/json']; // @public (undocumented) -type PromoReadRequest = operations['promo/read']['requestBody']['content']['application/json']; +type PromoReadRequest = operations['promo___read']['requestBody']['content']['application/json']; // @public (undocumented) type QueueCount = components['schemas']['QueueCount']; @@ -2784,16 +2809,16 @@ type QueueStats = { type QueueStatsLog = QueueStats[]; // @public (undocumented) -type RenoteMuteCreateRequest = operations['renote-mute/create']['requestBody']['content']['application/json']; +type RenoteMuteCreateRequest = operations['renote-mute___create']['requestBody']['content']['application/json']; // @public (undocumented) -type RenoteMuteDeleteRequest = operations['renote-mute/delete']['requestBody']['content']['application/json']; +type RenoteMuteDeleteRequest = operations['renote-mute___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type RenoteMuteListRequest = operations['renote-mute/list']['requestBody']['content']['application/json']; +type RenoteMuteListRequest = operations['renote-mute___list']['requestBody']['content']['application/json']; // @public (undocumented) -type RenoteMuteListResponse = operations['renote-mute/list']['responses']['200']['content']['application/json']; +type RenoteMuteListResponse = operations['renote-mute___list']['responses']['200']['content']['application/json']; // @public (undocumented) type RenoteMuting = components['schemas']['RenoteMuting']; @@ -2808,7 +2833,7 @@ type ResetPasswordRequest = operations['reset-password']['requestBody']['content type RetentionResponse = operations['retention']['responses']['200']['content']['application/json']; // @public (undocumented) -type ReversiCancelMatchRequest = operations['reversi/cancel-match']['requestBody']['content']['application/json']; +type ReversiCancelMatchRequest = operations['reversi___cancel-match']['requestBody']['content']['application/json']; // @public (undocumented) type ReversiGameDetailed = components['schemas']['ReversiGameDetailed']; @@ -2817,34 +2842,34 @@ type ReversiGameDetailed = components['schemas']['ReversiGameDetailed']; type ReversiGameLite = components['schemas']['ReversiGameLite']; // @public (undocumented) -type ReversiGamesRequest = operations['reversi/games']['requestBody']['content']['application/json']; +type ReversiGamesRequest = operations['reversi___games']['requestBody']['content']['application/json']; // @public (undocumented) -type ReversiGamesResponse = operations['reversi/games']['responses']['200']['content']['application/json']; +type ReversiGamesResponse = operations['reversi___games']['responses']['200']['content']['application/json']; // @public (undocumented) -type ReversiInvitationsResponse = operations['reversi/invitations']['responses']['200']['content']['application/json']; +type ReversiInvitationsResponse = operations['reversi___invitations']['responses']['200']['content']['application/json']; // @public (undocumented) -type ReversiMatchRequest = operations['reversi/match']['requestBody']['content']['application/json']; +type ReversiMatchRequest = operations['reversi___match']['requestBody']['content']['application/json']; // @public (undocumented) -type ReversiMatchResponse = operations['reversi/match']['responses']['200']['content']['application/json']; +type ReversiMatchResponse = operations['reversi___match']['responses']['200']['content']['application/json']; // @public (undocumented) -type ReversiShowGameRequest = operations['reversi/show-game']['requestBody']['content']['application/json']; +type ReversiShowGameRequest = operations['reversi___show-game']['requestBody']['content']['application/json']; // @public (undocumented) -type ReversiShowGameResponse = operations['reversi/show-game']['responses']['200']['content']['application/json']; +type ReversiShowGameResponse = operations['reversi___show-game']['responses']['200']['content']['application/json']; // @public (undocumented) -type ReversiSurrenderRequest = operations['reversi/surrender']['requestBody']['content']['application/json']; +type ReversiSurrenderRequest = operations['reversi___surrender']['requestBody']['content']['application/json']; // @public (undocumented) -type ReversiVerifyRequest = operations['reversi/verify']['requestBody']['content']['application/json']; +type ReversiVerifyRequest = operations['reversi___verify']['requestBody']['content']['application/json']; // @public (undocumented) -type ReversiVerifyResponse = operations['reversi/verify']['responses']['200']['content']['application/json']; +type ReversiVerifyResponse = operations['reversi___verify']['responses']['200']['content']['application/json']; // @public (undocumented) type Role = components['schemas']['Role']; @@ -2858,6 +2883,9 @@ type RoleCondFormulaLogics = components['schemas']['RoleCondFormulaLogics']; // @public (undocumented) type RoleCondFormulaValue = components['schemas']['RoleCondFormulaValue']; +// @public (undocumented) +type RoleCondFormulaValueAssignedRole = components['schemas']['RoleCondFormulaValueAssignedRole']; + // @public (undocumented) type RoleCondFormulaValueCreated = components['schemas']['RoleCondFormulaValueCreated']; @@ -2874,25 +2902,25 @@ type RoleLite = components['schemas']['RoleLite']; type RolePolicies = components['schemas']['RolePolicies']; // @public (undocumented) -type RolesListResponse = operations['roles/list']['responses']['200']['content']['application/json']; +type RolesListResponse = operations['roles___list']['responses']['200']['content']['application/json']; // @public (undocumented) -type RolesNotesRequest = operations['roles/notes']['requestBody']['content']['application/json']; +type RolesNotesRequest = operations['roles___notes']['requestBody']['content']['application/json']; // @public (undocumented) -type RolesNotesResponse = operations['roles/notes']['responses']['200']['content']['application/json']; +type RolesNotesResponse = operations['roles___notes']['responses']['200']['content']['application/json']; // @public (undocumented) -type RolesShowRequest = operations['roles/show']['requestBody']['content']['application/json']; +type RolesShowRequest = operations['roles___show']['requestBody']['content']['application/json']; // @public (undocumented) -type RolesShowResponse = operations['roles/show']['responses']['200']['content']['application/json']; +type RolesShowResponse = operations['roles___show']['responses']['200']['content']['application/json']; // @public (undocumented) -type RolesUsersRequest = operations['roles/users']['requestBody']['content']['application/json']; +type RolesUsersRequest = operations['roles___users']['requestBody']['content']['application/json']; // @public (undocumented) -type RolesUsersResponse = operations['roles/users']['responses']['200']['content']['application/json']; +type RolesUsersResponse = operations['roles___users']['responses']['200']['content']['application/json']; // @public (undocumented) type ServerInfoResponse = operations['server-info']['responses']['200']['content']['application/json']; @@ -3011,25 +3039,25 @@ export class Stream extends EventEmitter { type SwitchCaseResponseType = Endpoints[E]['res'] extends SwitchCase ? IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : Endpoints[E]['res']['$switch']['$default'] : Endpoints[E]['res']; // @public (undocumented) -type SwRegisterRequest = operations['sw/register']['requestBody']['content']['application/json']; +type SwRegisterRequest = operations['sw___register']['requestBody']['content']['application/json']; // @public (undocumented) -type SwRegisterResponse = operations['sw/register']['responses']['200']['content']['application/json']; +type SwRegisterResponse = operations['sw___register']['responses']['200']['content']['application/json']; // @public (undocumented) -type SwShowRegistrationRequest = operations['sw/show-registration']['requestBody']['content']['application/json']; +type SwShowRegistrationRequest = operations['sw___show-registration']['requestBody']['content']['application/json']; // @public (undocumented) -type SwShowRegistrationResponse = operations['sw/show-registration']['responses']['200']['content']['application/json']; +type SwShowRegistrationResponse = operations['sw___show-registration']['responses']['200']['content']['application/json']; // @public (undocumented) -type SwUnregisterRequest = operations['sw/unregister']['requestBody']['content']['application/json']; +type SwUnregisterRequest = operations['sw___unregister']['requestBody']['content']['application/json']; // @public (undocumented) -type SwUpdateRegistrationRequest = operations['sw/update-registration']['requestBody']['content']['application/json']; +type SwUpdateRegistrationRequest = operations['sw___update-registration']['requestBody']['content']['application/json']; // @public (undocumented) -type SwUpdateRegistrationResponse = operations['sw/update-registration']['responses']['200']['content']['application/json']; +type SwUpdateRegistrationResponse = operations['sw___update-registration']['responses']['200']['content']['application/json']; // @public (undocumented) type TestRequest = operations['test']['requestBody']['content']['application/json']; @@ -3062,193 +3090,193 @@ type UserList = components['schemas']['UserList']; type UserLite = components['schemas']['UserLite']; // @public (undocumented) -type UsernameAvailableRequest = operations['username/available']['requestBody']['content']['application/json']; +type UsernameAvailableRequest = operations['username___available']['requestBody']['content']['application/json']; // @public (undocumented) -type UsernameAvailableResponse = operations['username/available']['responses']['200']['content']['application/json']; +type UsernameAvailableResponse = operations['username___available']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersAchievementsRequest = operations['users/achievements']['requestBody']['content']['application/json']; +type UsersAchievementsRequest = operations['users___achievements']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersAchievementsResponse = operations['users/achievements']['responses']['200']['content']['application/json']; +type UsersAchievementsResponse = operations['users___achievements']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersClipsRequest = operations['users/clips']['requestBody']['content']['application/json']; +type UsersClipsRequest = operations['users___clips']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersClipsResponse = operations['users/clips']['responses']['200']['content']['application/json']; +type UsersClipsResponse = operations['users___clips']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersFeaturedNotesRequest = operations['users/featured-notes']['requestBody']['content']['application/json']; +type UsersFeaturedNotesRequest = operations['users___featured-notes']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersFeaturedNotesResponse = operations['users/featured-notes']['responses']['200']['content']['application/json']; +type UsersFeaturedNotesResponse = operations['users___featured-notes']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersFlashsRequest = operations['users/flashs']['requestBody']['content']['application/json']; +type UsersFlashsRequest = operations['users___flashs']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersFlashsResponse = operations['users/flashs']['responses']['200']['content']['application/json']; +type UsersFlashsResponse = operations['users___flashs']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersFollowersRequest = operations['users/followers']['requestBody']['content']['application/json']; +type UsersFollowersRequest = operations['users___followers']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersFollowersResponse = operations['users/followers']['responses']['200']['content']['application/json']; +type UsersFollowersResponse = operations['users___followers']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersFollowingRequest = operations['users/following']['requestBody']['content']['application/json']; +type UsersFollowingRequest = operations['users___following']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersFollowingResponse = operations['users/following']['responses']['200']['content']['application/json']; +type UsersFollowingResponse = operations['users___following']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersGalleryPostsRequest = operations['users/gallery/posts']['requestBody']['content']['application/json']; +type UsersGalleryPostsRequest = operations['users___gallery___posts']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersGalleryPostsResponse = operations['users/gallery/posts']['responses']['200']['content']['application/json']; +type UsersGalleryPostsResponse = operations['users___gallery___posts']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersGetFrequentlyRepliedUsersRequest = operations['users/get-frequently-replied-users']['requestBody']['content']['application/json']; +type UsersGetFrequentlyRepliedUsersRequest = operations['users___get-frequently-replied-users']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersGetFrequentlyRepliedUsersResponse = operations['users/get-frequently-replied-users']['responses']['200']['content']['application/json']; +type UsersGetFrequentlyRepliedUsersResponse = operations['users___get-frequently-replied-users']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersGroupsCreateRequest = operations['users/groups/create']['requestBody']['content']['application/json']; +type UsersGroupsCreateRequest = operations['users___groups___create']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersGroupsCreateResponse = operations['users/groups/create']['responses']['200']['content']['application/json']; +type UsersGroupsCreateResponse = operations['users___groups___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersGroupsDeleteRequest = operations['users/groups/delete']['requestBody']['content']['application/json']; +type UsersGroupsDeleteRequest = operations['users___groups___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersGroupsInvitationsAcceptRequest = operations['users/groups/invitations/accept']['requestBody']['content']['application/json']; +type UsersGroupsInvitationsAcceptRequest = operations['users___groups___invitations___accept']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersGroupsInvitationsRejectRequest = operations['users/groups/invitations/reject']['requestBody']['content']['application/json']; +type UsersGroupsInvitationsRejectRequest = operations['users___groups___invitations___reject']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersGroupsInviteRequest = operations['users/groups/invite']['requestBody']['content']['application/json']; +type UsersGroupsInviteRequest = operations['users___groups___invite']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersGroupsJoinedResponse = operations['users/groups/joined']['responses']['200']['content']['application/json']; +type UsersGroupsJoinedResponse = operations['users___groups___joined']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersGroupsLeaveRequest = operations['users/groups/leave']['requestBody']['content']['application/json']; +type UsersGroupsLeaveRequest = operations['users___groups___leave']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersGroupsOwnedResponse = operations['users/groups/owned']['responses']['200']['content']['application/json']; +type UsersGroupsOwnedResponse = operations['users___groups___owned']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersGroupsPullRequest = operations['users/groups/pull']['requestBody']['content']['application/json']; +type UsersGroupsPullRequest = operations['users___groups___pull']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersGroupsShowRequest = operations['users/groups/show']['requestBody']['content']['application/json']; +type UsersGroupsShowRequest = operations['users___groups___show']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersGroupsShowResponse = operations['users/groups/show']['responses']['200']['content']['application/json']; +type UsersGroupsShowResponse = operations['users___groups___show']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersGroupsTransferRequest = operations['users/groups/transfer']['requestBody']['content']['application/json']; +type UsersGroupsTransferRequest = operations['users___groups___transfer']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersGroupsTransferResponse = operations['users/groups/transfer']['responses']['200']['content']['application/json']; +type UsersGroupsTransferResponse = operations['users___groups___transfer']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersGroupsUpdateRequest = operations['users/groups/update']['requestBody']['content']['application/json']; +type UsersGroupsUpdateRequest = operations['users___groups___update']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersGroupsUpdateResponse = operations['users/groups/update']['responses']['200']['content']['application/json']; +type UsersGroupsUpdateResponse = operations['users___groups___update']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersListsCreateFromPublicRequest = operations['users/lists/create-from-public']['requestBody']['content']['application/json']; +type UsersListsCreateFromPublicRequest = operations['users___lists___create-from-public']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersListsCreateFromPublicResponse = operations['users/lists/create-from-public']['responses']['200']['content']['application/json']; +type UsersListsCreateFromPublicResponse = operations['users___lists___create-from-public']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersListsCreateRequest = operations['users/lists/create']['requestBody']['content']['application/json']; +type UsersListsCreateRequest = operations['users___lists___create']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersListsCreateResponse = operations['users/lists/create']['responses']['200']['content']['application/json']; +type UsersListsCreateResponse = operations['users___lists___create']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersListsDeleteRequest = operations['users/lists/delete']['requestBody']['content']['application/json']; +type UsersListsDeleteRequest = operations['users___lists___delete']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersListsFavoriteRequest = operations['users/lists/favorite']['requestBody']['content']['application/json']; +type UsersListsFavoriteRequest = operations['users___lists___favorite']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersListsGetMembershipsRequest = operations['users/lists/get-memberships']['requestBody']['content']['application/json']; +type UsersListsGetMembershipsRequest = operations['users___lists___get-memberships']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersListsGetMembershipsResponse = operations['users/lists/get-memberships']['responses']['200']['content']['application/json']; +type UsersListsGetMembershipsResponse = operations['users___lists___get-memberships']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersListsListRequest = operations['users/lists/list']['requestBody']['content']['application/json']; +type UsersListsListRequest = operations['users___lists___list']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersListsListResponse = operations['users/lists/list']['responses']['200']['content']['application/json']; +type UsersListsListResponse = operations['users___lists___list']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersListsPullRequest = operations['users/lists/pull']['requestBody']['content']['application/json']; +type UsersListsPullRequest = operations['users___lists___pull']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersListsPushRequest = operations['users/lists/push']['requestBody']['content']['application/json']; +type UsersListsPushRequest = operations['users___lists___push']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersListsShowRequest = operations['users/lists/show']['requestBody']['content']['application/json']; +type UsersListsShowRequest = operations['users___lists___show']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersListsShowResponse = operations['users/lists/show']['responses']['200']['content']['application/json']; +type UsersListsShowResponse = operations['users___lists___show']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersListsUnfavoriteRequest = operations['users/lists/unfavorite']['requestBody']['content']['application/json']; +type UsersListsUnfavoriteRequest = operations['users___lists___unfavorite']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersListsUpdateMembershipRequest = operations['users/lists/update-membership']['requestBody']['content']['application/json']; +type UsersListsUpdateMembershipRequest = operations['users___lists___update-membership']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersListsUpdateRequest = operations['users/lists/update']['requestBody']['content']['application/json']; +type UsersListsUpdateRequest = operations['users___lists___update']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersListsUpdateResponse = operations['users/lists/update']['responses']['200']['content']['application/json']; +type UsersListsUpdateResponse = operations['users___lists___update']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersNotesRequest = operations['users/notes']['requestBody']['content']['application/json']; +type UsersNotesRequest = operations['users___notes']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersNotesResponse = operations['users/notes']['responses']['200']['content']['application/json']; +type UsersNotesResponse = operations['users___notes']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersPagesRequest = operations['users/pages']['requestBody']['content']['application/json']; +type UsersPagesRequest = operations['users___pages']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersPagesResponse = operations['users/pages']['responses']['200']['content']['application/json']; +type UsersPagesResponse = operations['users___pages']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersReactionsRequest = operations['users/reactions']['requestBody']['content']['application/json']; +type UsersReactionsRequest = operations['users___reactions']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersReactionsResponse = operations['users/reactions']['responses']['200']['content']['application/json']; +type UsersReactionsResponse = operations['users___reactions']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersRecommendationRequest = operations['users/recommendation']['requestBody']['content']['application/json']; +type UsersRecommendationRequest = operations['users___recommendation']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersRecommendationResponse = operations['users/recommendation']['responses']['200']['content']['application/json']; +type UsersRecommendationResponse = operations['users___recommendation']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersRelationRequest = operations['users/relation']['requestBody']['content']['application/json']; +type UsersRelationRequest = operations['users___relation']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersRelationResponse = operations['users/relation']['responses']['200']['content']['application/json']; +type UsersRelationResponse = operations['users___relation']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersReportAbuseRequest = operations['users/report-abuse']['requestBody']['content']['application/json']; +type UsersReportAbuseRequest = operations['users___report-abuse']['requestBody']['content']['application/json']; // @public (undocumented) type UsersRequest = operations['users']['requestBody']['content']['application/json']; @@ -3257,37 +3285,37 @@ type UsersRequest = operations['users']['requestBody']['content']['application/j type UsersResponse = operations['users']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersSearchByUsernameAndHostRequest = operations['users/search-by-username-and-host']['requestBody']['content']['application/json']; +type UsersSearchByUsernameAndHostRequest = operations['users___search-by-username-and-host']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersSearchByUsernameAndHostResponse = operations['users/search-by-username-and-host']['responses']['200']['content']['application/json']; +type UsersSearchByUsernameAndHostResponse = operations['users___search-by-username-and-host']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersSearchRequest = operations['users/search']['requestBody']['content']['application/json']; +type UsersSearchRequest = operations['users___search']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersSearchResponse = operations['users/search']['responses']['200']['content']['application/json']; +type UsersSearchResponse = operations['users___search']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersShowRequest = operations['users/show']['requestBody']['content']['application/json']; +type UsersShowRequest = operations['users___show']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersShowResponse = operations['users/show']['responses']['200']['content']['application/json']; +type UsersShowResponse = operations['users___show']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersStatsRequest = operations['users/stats']['requestBody']['content']['application/json']; +type UsersStatsRequest = operations['users___stats']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersStatsResponse = operations['users/stats']['responses']['200']['content']['application/json']; +type UsersStatsResponse = operations['users___stats']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersTranslateRequest = operations['users/translate']['requestBody']['content']['application/json']; +type UsersTranslateRequest = operations['users___translate']['requestBody']['content']['application/json']; // @public (undocumented) -type UsersTranslateResponse = operations['users/translate']['responses']['200']['content']['application/json']; +type UsersTranslateResponse = operations['users___translate']['responses']['200']['content']['application/json']; // @public (undocumented) -type UsersUpdateMemoRequest = operations['users/update-memo']['requestBody']['content']['application/json']; +type UsersUpdateMemoRequest = operations['users___update-memo']['requestBody']['content']['application/json']; // Warnings were encountered during analysis: // diff --git a/packages/cherrypick-js/generator/src/generator.ts b/packages/cherrypick-js/generator/src/generator.ts index 872e40e43c..5c4aaf840d 100644 --- a/packages/cherrypick-js/generator/src/generator.ts +++ b/packages/cherrypick-js/generator/src/generator.ts @@ -60,13 +60,17 @@ async function generateEndpoints( // cherrypick-jsはPOST固定で送っているので、こちらも決め打ちする。別メソッドに対応することがあればこちらも直す必要あり const paths = openApiDocs.paths ?? {}; const postPathItems = Object.keys(paths) - .map(it => paths[it]?.post) + .map(it => ({ + _path_: it.replace(/^\//, ''), + ...paths[it]?.post, + })) .filter(filterUndefined); for (const operation of postPathItems) { + const path = operation._path_; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const operationId = operation.operationId!; - const endpoint = new Endpoint(operationId); + const endpoint = new Endpoint(path); endpoints.push(endpoint); if (isRequestBodyObject(operation.requestBody)) { @@ -76,19 +80,21 @@ async function generateEndpoints( // いまのところ複数のメディアタイプをとるエンドポイントは無いので決め打ちする endpoint.request = new OperationTypeAlias( operationId, + path, supportMediaTypes[0], OperationsAliasType.REQUEST, ); } } - if (isResponseObject(operation.responses['200']) && operation.responses['200'].content) { + if (operation.responses && isResponseObject(operation.responses['200']) && operation.responses['200'].content) { const resContent = operation.responses['200'].content; const supportMediaTypes = Object.keys(resContent); if (supportMediaTypes.length > 0) { // いまのところ複数のメディアタイプを返すエンドポイントは無いので決め打ちする endpoint.response = new OperationTypeAlias( operationId, + path, supportMediaTypes[0], OperationsAliasType.RESPONSE, ); @@ -98,6 +104,8 @@ async function generateEndpoints( const entitiesOutputLine: string[] = []; + entitiesOutputLine.push('/* eslint @typescript-eslint/naming-convention: 0 */'); + entitiesOutputLine.push(`import { operations } from '${toImportPath(typeFileName)}';`); entitiesOutputLine.push(''); @@ -138,12 +146,19 @@ async function generateApiClientJSDoc( endpointsFileName: string, warningsOutputPath: string, ) { - const endpoints: { operationId: string; description: string; }[] = []; + const endpoints: { + operationId: string; + path: string; + description: string; + }[] = []; // cherrypick-jsはPOST固定で送っているので、こちらも決め打ちする。別メソッドに対応することがあればこちらも直す必要あり const paths = openApiDocs.paths ?? {}; const postPathItems = Object.keys(paths) - .map(it => paths[it]?.post) + .map(it => ({ + _path_: it.replace(/^\//, ''), + ...paths[it]?.post, + })) .filter(filterUndefined); for (const operation of postPathItems) { @@ -153,6 +168,7 @@ async function generateApiClientJSDoc( if (operation.description) { endpoints.push({ operationId: operationId, + path: operation._path_, description: operation.description, }); } @@ -173,7 +189,7 @@ async function generateApiClientJSDoc( ' /**', ` * ${endpoint.description.split('\n').join('\n * ')}`, ' */', - ` request(`, + ` request(`, ' endpoint: E,', ' params: P,', ' credential?: string | null,', @@ -232,21 +248,24 @@ interface IOperationTypeAlias { class OperationTypeAlias implements IOperationTypeAlias { public readonly operationId: string; + public readonly path: string; public readonly mediaType: string; public readonly type: OperationsAliasType; constructor( operationId: string, + path: string, mediaType: string, type: OperationsAliasType, ) { this.operationId = operationId; + this.path = path; this.mediaType = mediaType; this.type = type; } generateName(): string { - const nameBase = this.operationId.replace(/\//g, '-'); + const nameBase = this.path.replace(/\//g, '-'); return toPascal(nameBase + this.type); } @@ -279,19 +298,19 @@ const emptyRequest = new EmptyTypeAlias(OperationsAliasType.REQUEST); const emptyResponse = new EmptyTypeAlias(OperationsAliasType.RESPONSE); class Endpoint { - public readonly operationId: string; + public readonly path: string; public request?: IOperationTypeAlias; public response?: IOperationTypeAlias; - constructor(operationId: string) { - this.operationId = operationId; + constructor(path: string) { + this.path = path; } toLine(): string { const reqName = this.request?.generateName() ?? emptyRequest.generateName(); const resName = this.response?.generateName() ?? emptyResponse.generateName(); - return `'${this.operationId}': { req: ${reqName}; res: ${resName} };`; + return `'${this.path}': { req: ${reqName}; res: ${resName} };`; } } diff --git a/packages/cherrypick-js/package.json b/packages/cherrypick-js/package.json index 3b88c71a35..5693cdb379 100644 --- a/packages/cherrypick-js/package.json +++ b/packages/cherrypick-js/package.json @@ -1,26 +1,24 @@ { "type": "module", "name": "cherrypick-js", - "version": "4.7.0", - "basedMisskeyVersion": "2024.2.0", + "version": "4.8.0", + "basedMisskeyVersion": "2024.3.1", "description": "CherryPick SDK for JavaScript", - "types": "./built/dts/index.d.ts", + "main": "./built/index.js", + "types": "./built/index.d.ts", "exports": { ".": { - "import": "./built/esm/index.js", - "types": "./built/dts/index.d.ts" + "import": "./built/index.js", + "types": "./built/index.d.ts" }, "./*": { - "import": "./built/esm/*", - "types": "./built/dts/*" + "import": "./built/*", + "types": "./built/*" } }, "scripts": { - "build": "npm run ts", - "ts": "npm run ts-esm && npm run ts-dts", - "ts-esm": "tsc --outDir built/esm", - "ts-dts": "tsc --outDir built/dts --declaration true --emitDeclarationOnly true --declarationMap true", - "watch": "nodemon -w src -e ts,js,cjs,mjs,json --exec \"pnpm run ts\"", + "build": "node ./build.js", + "watch": "nodemon -w package.json -e json --exec \"node ./build.js --watch\"", "tsd": "tsd", "api": "pnpm api-extractor run --local --verbose", "api-prod": "pnpm api-extractor run --verbose", @@ -39,28 +37,27 @@ "@microsoft/api-extractor": "7.39.1", "@misskey-dev/eslint-plugin": "1.0.0", "@swc/jest": "0.2.31", - "@types/jest": "29.5.11", - "@types/node": "20.11.17", - "@typescript-eslint/eslint-plugin": "6.18.1", - "@typescript-eslint/parser": "6.18.1", - "eslint": "8.56.0", + "@types/jest": "29.5.12", + "@types/node": "20.11.22", + "@typescript-eslint/eslint-plugin": "7.1.0", + "@typescript-eslint/parser": "7.1.0", + "eslint": "8.57.0", "jest": "29.7.0", "jest-fetch-mock": "3.0.3", "jest-websocket-mock": "2.5.0", "mock-socket": "9.3.1", "ncp": "2.0.0", - "nodemon": "3.0.3", - "tsd": "0.30.4", - "typescript": "5.3.3" + "nodemon": "3.1.0", + "execa": "8.0.1", + "tsd": "0.30.7", + "typescript": "5.3.3", + "esbuild": "0.19.11", + "glob": "10.3.10" }, "files": [ - "built", - "built/esm", - "built/dts" + "built" ], "dependencies": { - "@swc/cli": "0.1.63", - "@swc/core": "1.3.105", "eventemitter3": "5.0.1", "reconnecting-websocket": "4.4.0" } diff --git a/packages/cherrypick-js/src/api.ts b/packages/cherrypick-js/src/api.ts index 134ead0d79..959a634a74 100644 --- a/packages/cherrypick-js/src/api.ts +++ b/packages/cherrypick-js/src/api.ts @@ -3,7 +3,7 @@ import './autogen/apiClientJSDoc.js'; import { SwitchCaseResponseType } from './api.types.js'; import type { Endpoints } from './api.types.js'; -export { +export type { SwitchCaseResponseType, } from './api.types.js'; diff --git a/packages/cherrypick-js/src/autogen/apiClientJSDoc.ts b/packages/cherrypick-js/src/autogen/apiClientJSDoc.ts index 2feb126e5a..30c3f965a2 100644 --- a/packages/cherrypick-js/src/autogen/apiClientJSDoc.ts +++ b/packages/cherrypick-js/src/autogen/apiClientJSDoc.ts @@ -17,7 +17,8 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* + * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *arr-create* */ request( endpoint: E, @@ -28,7 +29,8 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* + * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *arr-list* */ request( endpoint: E, @@ -39,7 +41,8 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *No* + * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. + * **Credential required**: *No* / **Permission**: *arr-delete* */ request( endpoint: E, @@ -50,7 +53,8 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* + * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *arr-update* */ request( endpoint: E, @@ -3371,6 +3375,17 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:notifications* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + /** * No description provided. * diff --git a/packages/cherrypick-js/src/autogen/endpoint.ts b/packages/cherrypick-js/src/autogen/endpoint.ts index d3e58c10e2..5ab518a98d 100644 --- a/packages/cherrypick-js/src/autogen/endpoint.ts +++ b/packages/cherrypick-js/src/autogen/endpoint.ts @@ -41,7 +41,9 @@ import type { AdminDriveShowFileResponse, AdminEmojiAddAliasesBulkRequest, AdminEmojiAddRequest, + AdminEmojiAddResponse, AdminEmojiAddsRequest, + AdminEmojiAddsResponse, AdminEmojiCopyRequest, AdminEmojiCopyResponse, AdminEmojiDeleteBulkRequest, @@ -626,8 +628,8 @@ export type Endpoints = { 'admin/drive/files': { req: AdminDriveFilesRequest; res: AdminDriveFilesResponse }; 'admin/drive/show-file': { req: AdminDriveShowFileRequest; res: AdminDriveShowFileResponse }; 'admin/emoji/add-aliases-bulk': { req: AdminEmojiAddAliasesBulkRequest; res: EmptyResponse }; - 'admin/emoji/add': { req: AdminEmojiAddRequest; res: EmptyResponse }; - 'admin/emoji/adds': { req: AdminEmojiAddsRequest; res: EmptyResponse }; + 'admin/emoji/add': { req: AdminEmojiAddRequest; res: AdminEmojiAddResponse }; + 'admin/emoji/adds': { req: AdminEmojiAddsRequest; res: AdminEmojiAddsResponse }; 'admin/emoji/copy': { req: AdminEmojiCopyRequest; res: AdminEmojiCopyResponse }; 'admin/emoji/delete-bulk': { req: AdminEmojiDeleteBulkRequest; res: EmptyResponse }; 'admin/emoji/delete': { req: AdminEmojiDeleteRequest; res: EmptyResponse }; @@ -900,6 +902,7 @@ export type Endpoints = { 'notes/unrenote': { req: NotesUnrenoteRequest; res: EmptyResponse }; 'notes/user-list-timeline': { req: NotesUserListTimelineRequest; res: NotesUserListTimelineResponse }; 'notifications/create': { req: NotificationsCreateRequest; res: EmptyResponse }; + 'notifications/flush': { req: EmptyRequest; res: EmptyResponse }; 'notifications/mark-all-as-read': { req: EmptyRequest; res: EmptyResponse }; 'notifications/test-notification': { req: EmptyRequest; res: EmptyResponse }; 'page-push': { req: PagePushRequest; res: EmptyResponse }; diff --git a/packages/cherrypick-js/src/autogen/entities.ts b/packages/cherrypick-js/src/autogen/entities.ts index fe18f807e9..ed0bf4aa1c 100644 --- a/packages/cherrypick-js/src/autogen/entities.ts +++ b/packages/cherrypick-js/src/autogen/entities.ts @@ -1,598 +1,601 @@ +/* eslint @typescript-eslint/naming-convention: 0 */ import { operations } from './types.js'; export type EmptyRequest = Record | undefined; export type EmptyResponse = Record | undefined; -export type AdminMetaResponse = operations['admin/meta']['responses']['200']['content']['application/json']; -export type AdminAbuseReportResolverCreateRequest = operations['admin/abuse-report-resolver/create']['requestBody']['content']['application/json']; -export type AdminAbuseReportResolverCreateResponse = operations['admin/abuse-report-resolver/create']['responses']['200']['content']['application/json']; -export type AdminAbuseReportResolverListRequest = operations['admin/abuse-report-resolver/list']['requestBody']['content']['application/json']; -export type AdminAbuseReportResolverListResponse = operations['admin/abuse-report-resolver/list']['responses']['200']['content']['application/json']; -export type AdminAbuseReportResolverDeleteRequest = operations['admin/abuse-report-resolver/delete']['requestBody']['content']['application/json']; -export type AdminAbuseReportResolverUpdateRequest = operations['admin/abuse-report-resolver/update']['requestBody']['content']['application/json']; -export type AdminAbuseUserReportsRequest = operations['admin/abuse-user-reports']['requestBody']['content']['application/json']; -export type AdminAbuseUserReportsResponse = operations['admin/abuse-user-reports']['responses']['200']['content']['application/json']; -export type AdminAccountsCreateRequest = operations['admin/accounts/create']['requestBody']['content']['application/json']; -export type AdminAccountsCreateResponse = operations['admin/accounts/create']['responses']['200']['content']['application/json']; -export type AdminAccountsDeleteRequest = operations['admin/accounts/delete']['requestBody']['content']['application/json']; -export type AdminAccountsFindByEmailRequest = operations['admin/accounts/find-by-email']['requestBody']['content']['application/json']; -export type AdminAccountsFindByEmailResponse = operations['admin/accounts/find-by-email']['responses']['200']['content']['application/json']; -export type AdminAdCreateRequest = operations['admin/ad/create']['requestBody']['content']['application/json']; -export type AdminAdCreateResponse = operations['admin/ad/create']['responses']['200']['content']['application/json']; -export type AdminAdDeleteRequest = operations['admin/ad/delete']['requestBody']['content']['application/json']; -export type AdminAdListRequest = operations['admin/ad/list']['requestBody']['content']['application/json']; -export type AdminAdListResponse = operations['admin/ad/list']['responses']['200']['content']['application/json']; -export type AdminAdUpdateRequest = operations['admin/ad/update']['requestBody']['content']['application/json']; -export type AdminAnnouncementsCreateRequest = operations['admin/announcements/create']['requestBody']['content']['application/json']; -export type AdminAnnouncementsCreateResponse = operations['admin/announcements/create']['responses']['200']['content']['application/json']; -export type AdminAnnouncementsDeleteRequest = operations['admin/announcements/delete']['requestBody']['content']['application/json']; -export type AdminAnnouncementsListRequest = operations['admin/announcements/list']['requestBody']['content']['application/json']; -export type AdminAnnouncementsListResponse = operations['admin/announcements/list']['responses']['200']['content']['application/json']; -export type AdminAnnouncementsUpdateRequest = operations['admin/announcements/update']['requestBody']['content']['application/json']; -export type AdminAvatarDecorationsCreateRequest = operations['admin/avatar-decorations/create']['requestBody']['content']['application/json']; -export type AdminAvatarDecorationsDeleteRequest = operations['admin/avatar-decorations/delete']['requestBody']['content']['application/json']; -export type AdminAvatarDecorationsListRequest = operations['admin/avatar-decorations/list']['requestBody']['content']['application/json']; -export type AdminAvatarDecorationsListResponse = operations['admin/avatar-decorations/list']['responses']['200']['content']['application/json']; -export type AdminAvatarDecorationsUpdateRequest = operations['admin/avatar-decorations/update']['requestBody']['content']['application/json']; -export type AdminDeleteAllFilesOfAUserRequest = operations['admin/delete-all-files-of-a-user']['requestBody']['content']['application/json']; -export type AdminUnsetUserAvatarRequest = operations['admin/unset-user-avatar']['requestBody']['content']['application/json']; -export type AdminUnsetUserBannerRequest = operations['admin/unset-user-banner']['requestBody']['content']['application/json']; -export type AdminDriveFilesRequest = operations['admin/drive/files']['requestBody']['content']['application/json']; -export type AdminDriveFilesResponse = operations['admin/drive/files']['responses']['200']['content']['application/json']; -export type AdminDriveShowFileRequest = operations['admin/drive/show-file']['requestBody']['content']['application/json']; -export type AdminDriveShowFileResponse = operations['admin/drive/show-file']['responses']['200']['content']['application/json']; -export type AdminEmojiAddAliasesBulkRequest = operations['admin/emoji/add-aliases-bulk']['requestBody']['content']['application/json']; -export type AdminEmojiAddRequest = operations['admin/emoji/add']['requestBody']['content']['application/json']; -export type AdminEmojiAddsRequest = operations['admin/emoji/adds']['requestBody']['content']['application/json']; -export type AdminEmojiCopyRequest = operations['admin/emoji/copy']['requestBody']['content']['application/json']; -export type AdminEmojiCopyResponse = operations['admin/emoji/copy']['responses']['200']['content']['application/json']; -export type AdminEmojiDeleteBulkRequest = operations['admin/emoji/delete-bulk']['requestBody']['content']['application/json']; -export type AdminEmojiDeleteRequest = operations['admin/emoji/delete']['requestBody']['content']['application/json']; -export type AdminEmojiImportZipRequest = operations['admin/emoji/import-zip']['requestBody']['content']['application/json']; -export type AdminEmojiListRemoteRequest = operations['admin/emoji/list-remote']['requestBody']['content']['application/json']; -export type AdminEmojiListRemoteResponse = operations['admin/emoji/list-remote']['responses']['200']['content']['application/json']; -export type AdminEmojiListRequest = operations['admin/emoji/list']['requestBody']['content']['application/json']; -export type AdminEmojiListResponse = operations['admin/emoji/list']['responses']['200']['content']['application/json']; -export type AdminEmojiRemoveAliasesBulkRequest = operations['admin/emoji/remove-aliases-bulk']['requestBody']['content']['application/json']; -export type AdminEmojiSetAliasesBulkRequest = operations['admin/emoji/set-aliases-bulk']['requestBody']['content']['application/json']; -export type AdminEmojiSetCategoryBulkRequest = operations['admin/emoji/set-category-bulk']['requestBody']['content']['application/json']; -export type AdminEmojiSetLicenseBulkRequest = operations['admin/emoji/set-license-bulk']['requestBody']['content']['application/json']; -export type AdminEmojiStealRequest = operations['admin/emoji/steal']['requestBody']['content']['application/json']; -export type AdminEmojiStealResponse = operations['admin/emoji/steal']['responses']['200']['content']['application/json']; -export type AdminEmojiUpdateRequest = operations['admin/emoji/update']['requestBody']['content']['application/json']; -export type AdminFederationDeleteAllFilesRequest = operations['admin/federation/delete-all-files']['requestBody']['content']['application/json']; -export type AdminFederationRefreshRemoteInstanceMetadataRequest = operations['admin/federation/refresh-remote-instance-metadata']['requestBody']['content']['application/json']; -export type AdminFederationRemoveAllFollowingRequest = operations['admin/federation/remove-all-following']['requestBody']['content']['application/json']; -export type AdminFederationUpdateInstanceRequest = operations['admin/federation/update-instance']['requestBody']['content']['application/json']; -export type AdminGetIndexStatsResponse = operations['admin/get-index-stats']['responses']['200']['content']['application/json']; -export type AdminGetTableStatsResponse = operations['admin/get-table-stats']['responses']['200']['content']['application/json']; -export type AdminGetUserIpsRequest = operations['admin/get-user-ips']['requestBody']['content']['application/json']; -export type AdminGetUserIpsResponse = operations['admin/get-user-ips']['responses']['200']['content']['application/json']; -export type AdminInviteCreateRequest = operations['admin/invite/create']['requestBody']['content']['application/json']; -export type AdminInviteCreateResponse = operations['admin/invite/create']['responses']['200']['content']['application/json']; -export type AdminInviteListRequest = operations['admin/invite/list']['requestBody']['content']['application/json']; -export type AdminInviteListResponse = operations['admin/invite/list']['responses']['200']['content']['application/json']; -export type AdminPromoCreateRequest = operations['admin/promo/create']['requestBody']['content']['application/json']; -export type AdminQueueDeliverDelayedResponse = operations['admin/queue/deliver-delayed']['responses']['200']['content']['application/json']; -export type AdminQueueInboxDelayedResponse = operations['admin/queue/inbox-delayed']['responses']['200']['content']['application/json']; -export type AdminQueuePromoteRequest = operations['admin/queue/promote']['requestBody']['content']['application/json']; -export type AdminQueueStatsResponse = operations['admin/queue/stats']['responses']['200']['content']['application/json']; -export type AdminRelaysAddRequest = operations['admin/relays/add']['requestBody']['content']['application/json']; -export type AdminRelaysAddResponse = operations['admin/relays/add']['responses']['200']['content']['application/json']; -export type AdminRelaysListResponse = operations['admin/relays/list']['responses']['200']['content']['application/json']; -export type AdminRelaysRemoveRequest = operations['admin/relays/remove']['requestBody']['content']['application/json']; -export type AdminResetPasswordRequest = operations['admin/reset-password']['requestBody']['content']['application/json']; -export type AdminResetPasswordResponse = operations['admin/reset-password']['responses']['200']['content']['application/json']; -export type AdminResolveAbuseUserReportRequest = operations['admin/resolve-abuse-user-report']['requestBody']['content']['application/json']; -export type AdminSendEmailRequest = operations['admin/send-email']['requestBody']['content']['application/json']; -export type AdminServerInfoResponse = operations['admin/server-info']['responses']['200']['content']['application/json']; -export type AdminShowModerationLogsRequest = operations['admin/show-moderation-logs']['requestBody']['content']['application/json']; -export type AdminShowModerationLogsResponse = operations['admin/show-moderation-logs']['responses']['200']['content']['application/json']; -export type AdminShowUserRequest = operations['admin/show-user']['requestBody']['content']['application/json']; -export type AdminShowUserResponse = operations['admin/show-user']['responses']['200']['content']['application/json']; -export type AdminShowUsersRequest = operations['admin/show-users']['requestBody']['content']['application/json']; -export type AdminShowUsersResponse = operations['admin/show-users']['responses']['200']['content']['application/json']; -export type AdminSuspendUserRequest = operations['admin/suspend-user']['requestBody']['content']['application/json']; -export type AdminUnsuspendUserRequest = operations['admin/unsuspend-user']['requestBody']['content']['application/json']; -export type AdminUpdateMetaRequest = operations['admin/update-meta']['requestBody']['content']['application/json']; -export type AdminDeleteAccountRequest = operations['admin/delete-account']['requestBody']['content']['application/json']; -export type AdminUpdateUserNoteRequest = operations['admin/update-user-note']['requestBody']['content']['application/json']; -export type AdminRolesCreateRequest = operations['admin/roles/create']['requestBody']['content']['application/json']; -export type AdminRolesCreateResponse = operations['admin/roles/create']['responses']['200']['content']['application/json']; -export type AdminRolesDeleteRequest = operations['admin/roles/delete']['requestBody']['content']['application/json']; -export type AdminRolesListResponse = operations['admin/roles/list']['responses']['200']['content']['application/json']; -export type AdminRolesShowRequest = operations['admin/roles/show']['requestBody']['content']['application/json']; -export type AdminRolesShowResponse = operations['admin/roles/show']['responses']['200']['content']['application/json']; -export type AdminRolesUpdateRequest = operations['admin/roles/update']['requestBody']['content']['application/json']; -export type AdminRolesAssignRequest = operations['admin/roles/assign']['requestBody']['content']['application/json']; -export type AdminRolesUnassignRequest = operations['admin/roles/unassign']['requestBody']['content']['application/json']; -export type AdminRolesUpdateDefaultPoliciesRequest = operations['admin/roles/update-default-policies']['requestBody']['content']['application/json']; -export type AdminRolesUsersRequest = operations['admin/roles/users']['requestBody']['content']['application/json']; -export type AdminRolesUsersResponse = operations['admin/roles/users']['responses']['200']['content']['application/json']; +export type AdminMetaResponse = operations['admin___meta']['responses']['200']['content']['application/json']; +export type AdminAbuseReportResolverCreateRequest = operations['admin___abuse-report-resolver___create']['requestBody']['content']['application/json']; +export type AdminAbuseReportResolverCreateResponse = operations['admin___abuse-report-resolver___create']['responses']['200']['content']['application/json']; +export type AdminAbuseReportResolverListRequest = operations['admin___abuse-report-resolver___list']['requestBody']['content']['application/json']; +export type AdminAbuseReportResolverListResponse = operations['admin___abuse-report-resolver___list']['responses']['200']['content']['application/json']; +export type AdminAbuseReportResolverDeleteRequest = operations['admin___abuse-report-resolver___delete']['requestBody']['content']['application/json']; +export type AdminAbuseReportResolverUpdateRequest = operations['admin___abuse-report-resolver___update']['requestBody']['content']['application/json']; +export type AdminAbuseUserReportsRequest = operations['admin___abuse-user-reports']['requestBody']['content']['application/json']; +export type AdminAbuseUserReportsResponse = operations['admin___abuse-user-reports']['responses']['200']['content']['application/json']; +export type AdminAccountsCreateRequest = operations['admin___accounts___create']['requestBody']['content']['application/json']; +export type AdminAccountsCreateResponse = operations['admin___accounts___create']['responses']['200']['content']['application/json']; +export type AdminAccountsDeleteRequest = operations['admin___accounts___delete']['requestBody']['content']['application/json']; +export type AdminAccountsFindByEmailRequest = operations['admin___accounts___find-by-email']['requestBody']['content']['application/json']; +export type AdminAccountsFindByEmailResponse = operations['admin___accounts___find-by-email']['responses']['200']['content']['application/json']; +export type AdminAdCreateRequest = operations['admin___ad___create']['requestBody']['content']['application/json']; +export type AdminAdCreateResponse = operations['admin___ad___create']['responses']['200']['content']['application/json']; +export type AdminAdDeleteRequest = operations['admin___ad___delete']['requestBody']['content']['application/json']; +export type AdminAdListRequest = operations['admin___ad___list']['requestBody']['content']['application/json']; +export type AdminAdListResponse = operations['admin___ad___list']['responses']['200']['content']['application/json']; +export type AdminAdUpdateRequest = operations['admin___ad___update']['requestBody']['content']['application/json']; +export type AdminAnnouncementsCreateRequest = operations['admin___announcements___create']['requestBody']['content']['application/json']; +export type AdminAnnouncementsCreateResponse = operations['admin___announcements___create']['responses']['200']['content']['application/json']; +export type AdminAnnouncementsDeleteRequest = operations['admin___announcements___delete']['requestBody']['content']['application/json']; +export type AdminAnnouncementsListRequest = operations['admin___announcements___list']['requestBody']['content']['application/json']; +export type AdminAnnouncementsListResponse = operations['admin___announcements___list']['responses']['200']['content']['application/json']; +export type AdminAnnouncementsUpdateRequest = operations['admin___announcements___update']['requestBody']['content']['application/json']; +export type AdminAvatarDecorationsCreateRequest = operations['admin___avatar-decorations___create']['requestBody']['content']['application/json']; +export type AdminAvatarDecorationsDeleteRequest = operations['admin___avatar-decorations___delete']['requestBody']['content']['application/json']; +export type AdminAvatarDecorationsListRequest = operations['admin___avatar-decorations___list']['requestBody']['content']['application/json']; +export type AdminAvatarDecorationsListResponse = operations['admin___avatar-decorations___list']['responses']['200']['content']['application/json']; +export type AdminAvatarDecorationsUpdateRequest = operations['admin___avatar-decorations___update']['requestBody']['content']['application/json']; +export type AdminDeleteAllFilesOfAUserRequest = operations['admin___delete-all-files-of-a-user']['requestBody']['content']['application/json']; +export type AdminUnsetUserAvatarRequest = operations['admin___unset-user-avatar']['requestBody']['content']['application/json']; +export type AdminUnsetUserBannerRequest = operations['admin___unset-user-banner']['requestBody']['content']['application/json']; +export type AdminDriveFilesRequest = operations['admin___drive___files']['requestBody']['content']['application/json']; +export type AdminDriveFilesResponse = operations['admin___drive___files']['responses']['200']['content']['application/json']; +export type AdminDriveShowFileRequest = operations['admin___drive___show-file']['requestBody']['content']['application/json']; +export type AdminDriveShowFileResponse = operations['admin___drive___show-file']['responses']['200']['content']['application/json']; +export type AdminEmojiAddAliasesBulkRequest = operations['admin___emoji___add-aliases-bulk']['requestBody']['content']['application/json']; +export type AdminEmojiAddRequest = operations['admin___emoji___add']['requestBody']['content']['application/json']; +export type AdminEmojiAddResponse = operations['admin___emoji___add']['responses']['200']['content']['application/json']; +export type AdminEmojiAddsRequest = operations['admin___emoji___adds']['requestBody']['content']['application/json']; +export type AdminEmojiAddsResponse = operations['admin___emoji___adds']['responses']['200']['content']['application/json']; +export type AdminEmojiCopyRequest = operations['admin___emoji___copy']['requestBody']['content']['application/json']; +export type AdminEmojiCopyResponse = operations['admin___emoji___copy']['responses']['200']['content']['application/json']; +export type AdminEmojiDeleteBulkRequest = operations['admin___emoji___delete-bulk']['requestBody']['content']['application/json']; +export type AdminEmojiDeleteRequest = operations['admin___emoji___delete']['requestBody']['content']['application/json']; +export type AdminEmojiImportZipRequest = operations['admin___emoji___import-zip']['requestBody']['content']['application/json']; +export type AdminEmojiListRemoteRequest = operations['admin___emoji___list-remote']['requestBody']['content']['application/json']; +export type AdminEmojiListRemoteResponse = operations['admin___emoji___list-remote']['responses']['200']['content']['application/json']; +export type AdminEmojiListRequest = operations['admin___emoji___list']['requestBody']['content']['application/json']; +export type AdminEmojiListResponse = operations['admin___emoji___list']['responses']['200']['content']['application/json']; +export type AdminEmojiRemoveAliasesBulkRequest = operations['admin___emoji___remove-aliases-bulk']['requestBody']['content']['application/json']; +export type AdminEmojiSetAliasesBulkRequest = operations['admin___emoji___set-aliases-bulk']['requestBody']['content']['application/json']; +export type AdminEmojiSetCategoryBulkRequest = operations['admin___emoji___set-category-bulk']['requestBody']['content']['application/json']; +export type AdminEmojiSetLicenseBulkRequest = operations['admin___emoji___set-license-bulk']['requestBody']['content']['application/json']; +export type AdminEmojiStealRequest = operations['admin___emoji___steal']['requestBody']['content']['application/json']; +export type AdminEmojiStealResponse = operations['admin___emoji___steal']['responses']['200']['content']['application/json']; +export type AdminEmojiUpdateRequest = operations['admin___emoji___update']['requestBody']['content']['application/json']; +export type AdminFederationDeleteAllFilesRequest = operations['admin___federation___delete-all-files']['requestBody']['content']['application/json']; +export type AdminFederationRefreshRemoteInstanceMetadataRequest = operations['admin___federation___refresh-remote-instance-metadata']['requestBody']['content']['application/json']; +export type AdminFederationRemoveAllFollowingRequest = operations['admin___federation___remove-all-following']['requestBody']['content']['application/json']; +export type AdminFederationUpdateInstanceRequest = operations['admin___federation___update-instance']['requestBody']['content']['application/json']; +export type AdminGetIndexStatsResponse = operations['admin___get-index-stats']['responses']['200']['content']['application/json']; +export type AdminGetTableStatsResponse = operations['admin___get-table-stats']['responses']['200']['content']['application/json']; +export type AdminGetUserIpsRequest = operations['admin___get-user-ips']['requestBody']['content']['application/json']; +export type AdminGetUserIpsResponse = operations['admin___get-user-ips']['responses']['200']['content']['application/json']; +export type AdminInviteCreateRequest = operations['admin___invite___create']['requestBody']['content']['application/json']; +export type AdminInviteCreateResponse = operations['admin___invite___create']['responses']['200']['content']['application/json']; +export type AdminInviteListRequest = operations['admin___invite___list']['requestBody']['content']['application/json']; +export type AdminInviteListResponse = operations['admin___invite___list']['responses']['200']['content']['application/json']; +export type AdminPromoCreateRequest = operations['admin___promo___create']['requestBody']['content']['application/json']; +export type AdminQueueDeliverDelayedResponse = operations['admin___queue___deliver-delayed']['responses']['200']['content']['application/json']; +export type AdminQueueInboxDelayedResponse = operations['admin___queue___inbox-delayed']['responses']['200']['content']['application/json']; +export type AdminQueuePromoteRequest = operations['admin___queue___promote']['requestBody']['content']['application/json']; +export type AdminQueueStatsResponse = operations['admin___queue___stats']['responses']['200']['content']['application/json']; +export type AdminRelaysAddRequest = operations['admin___relays___add']['requestBody']['content']['application/json']; +export type AdminRelaysAddResponse = operations['admin___relays___add']['responses']['200']['content']['application/json']; +export type AdminRelaysListResponse = operations['admin___relays___list']['responses']['200']['content']['application/json']; +export type AdminRelaysRemoveRequest = operations['admin___relays___remove']['requestBody']['content']['application/json']; +export type AdminResetPasswordRequest = operations['admin___reset-password']['requestBody']['content']['application/json']; +export type AdminResetPasswordResponse = operations['admin___reset-password']['responses']['200']['content']['application/json']; +export type AdminResolveAbuseUserReportRequest = operations['admin___resolve-abuse-user-report']['requestBody']['content']['application/json']; +export type AdminSendEmailRequest = operations['admin___send-email']['requestBody']['content']['application/json']; +export type AdminServerInfoResponse = operations['admin___server-info']['responses']['200']['content']['application/json']; +export type AdminShowModerationLogsRequest = operations['admin___show-moderation-logs']['requestBody']['content']['application/json']; +export type AdminShowModerationLogsResponse = operations['admin___show-moderation-logs']['responses']['200']['content']['application/json']; +export type AdminShowUserRequest = operations['admin___show-user']['requestBody']['content']['application/json']; +export type AdminShowUserResponse = operations['admin___show-user']['responses']['200']['content']['application/json']; +export type AdminShowUsersRequest = operations['admin___show-users']['requestBody']['content']['application/json']; +export type AdminShowUsersResponse = operations['admin___show-users']['responses']['200']['content']['application/json']; +export type AdminSuspendUserRequest = operations['admin___suspend-user']['requestBody']['content']['application/json']; +export type AdminUnsuspendUserRequest = operations['admin___unsuspend-user']['requestBody']['content']['application/json']; +export type AdminUpdateMetaRequest = operations['admin___update-meta']['requestBody']['content']['application/json']; +export type AdminDeleteAccountRequest = operations['admin___delete-account']['requestBody']['content']['application/json']; +export type AdminUpdateUserNoteRequest = operations['admin___update-user-note']['requestBody']['content']['application/json']; +export type AdminRolesCreateRequest = operations['admin___roles___create']['requestBody']['content']['application/json']; +export type AdminRolesCreateResponse = operations['admin___roles___create']['responses']['200']['content']['application/json']; +export type AdminRolesDeleteRequest = operations['admin___roles___delete']['requestBody']['content']['application/json']; +export type AdminRolesListResponse = operations['admin___roles___list']['responses']['200']['content']['application/json']; +export type AdminRolesShowRequest = operations['admin___roles___show']['requestBody']['content']['application/json']; +export type AdminRolesShowResponse = operations['admin___roles___show']['responses']['200']['content']['application/json']; +export type AdminRolesUpdateRequest = operations['admin___roles___update']['requestBody']['content']['application/json']; +export type AdminRolesAssignRequest = operations['admin___roles___assign']['requestBody']['content']['application/json']; +export type AdminRolesUnassignRequest = operations['admin___roles___unassign']['requestBody']['content']['application/json']; +export type AdminRolesUpdateDefaultPoliciesRequest = operations['admin___roles___update-default-policies']['requestBody']['content']['application/json']; +export type AdminRolesUsersRequest = operations['admin___roles___users']['requestBody']['content']['application/json']; +export type AdminRolesUsersResponse = operations['admin___roles___users']['responses']['200']['content']['application/json']; export type AnnouncementsRequest = operations['announcements']['requestBody']['content']['application/json']; export type AnnouncementsResponse = operations['announcements']['responses']['200']['content']['application/json']; -export type AntennasCreateRequest = operations['antennas/create']['requestBody']['content']['application/json']; -export type AntennasCreateResponse = operations['antennas/create']['responses']['200']['content']['application/json']; -export type AntennasDeleteRequest = operations['antennas/delete']['requestBody']['content']['application/json']; -export type AntennasListResponse = operations['antennas/list']['responses']['200']['content']['application/json']; -export type AntennasNotesRequest = operations['antennas/notes']['requestBody']['content']['application/json']; -export type AntennasNotesResponse = operations['antennas/notes']['responses']['200']['content']['application/json']; -export type AntennasShowRequest = operations['antennas/show']['requestBody']['content']['application/json']; -export type AntennasShowResponse = operations['antennas/show']['responses']['200']['content']['application/json']; -export type AntennasUpdateRequest = operations['antennas/update']['requestBody']['content']['application/json']; -export type AntennasUpdateResponse = operations['antennas/update']['responses']['200']['content']['application/json']; -export type ApGetRequest = operations['ap/get']['requestBody']['content']['application/json']; -export type ApGetResponse = operations['ap/get']['responses']['200']['content']['application/json']; -export type ApShowRequest = operations['ap/show']['requestBody']['content']['application/json']; -export type ApShowResponse = operations['ap/show']['responses']['200']['content']['application/json']; -export type AppCreateRequest = operations['app/create']['requestBody']['content']['application/json']; -export type AppCreateResponse = operations['app/create']['responses']['200']['content']['application/json']; -export type AppShowRequest = operations['app/show']['requestBody']['content']['application/json']; -export type AppShowResponse = operations['app/show']['responses']['200']['content']['application/json']; -export type AuthAcceptRequest = operations['auth/accept']['requestBody']['content']['application/json']; -export type AuthSessionGenerateRequest = operations['auth/session/generate']['requestBody']['content']['application/json']; -export type AuthSessionGenerateResponse = operations['auth/session/generate']['responses']['200']['content']['application/json']; -export type AuthSessionShowRequest = operations['auth/session/show']['requestBody']['content']['application/json']; -export type AuthSessionShowResponse = operations['auth/session/show']['responses']['200']['content']['application/json']; -export type AuthSessionUserkeyRequest = operations['auth/session/userkey']['requestBody']['content']['application/json']; -export type AuthSessionUserkeyResponse = operations['auth/session/userkey']['responses']['200']['content']['application/json']; -export type BlockingCreateRequest = operations['blocking/create']['requestBody']['content']['application/json']; -export type BlockingCreateResponse = operations['blocking/create']['responses']['200']['content']['application/json']; -export type BlockingDeleteRequest = operations['blocking/delete']['requestBody']['content']['application/json']; -export type BlockingDeleteResponse = operations['blocking/delete']['responses']['200']['content']['application/json']; -export type BlockingListRequest = operations['blocking/list']['requestBody']['content']['application/json']; -export type BlockingListResponse = operations['blocking/list']['responses']['200']['content']['application/json']; -export type ChannelsCreateRequest = operations['channels/create']['requestBody']['content']['application/json']; -export type ChannelsCreateResponse = operations['channels/create']['responses']['200']['content']['application/json']; -export type ChannelsFeaturedResponse = operations['channels/featured']['responses']['200']['content']['application/json']; -export type ChannelsFollowRequest = operations['channels/follow']['requestBody']['content']['application/json']; -export type ChannelsFollowedRequest = operations['channels/followed']['requestBody']['content']['application/json']; -export type ChannelsFollowedResponse = operations['channels/followed']['responses']['200']['content']['application/json']; -export type ChannelsOwnedRequest = operations['channels/owned']['requestBody']['content']['application/json']; -export type ChannelsOwnedResponse = operations['channels/owned']['responses']['200']['content']['application/json']; -export type ChannelsShowRequest = operations['channels/show']['requestBody']['content']['application/json']; -export type ChannelsShowResponse = operations['channels/show']['responses']['200']['content']['application/json']; -export type ChannelsTimelineRequest = operations['channels/timeline']['requestBody']['content']['application/json']; -export type ChannelsTimelineResponse = operations['channels/timeline']['responses']['200']['content']['application/json']; -export type ChannelsUnfollowRequest = operations['channels/unfollow']['requestBody']['content']['application/json']; -export type ChannelsUpdateRequest = operations['channels/update']['requestBody']['content']['application/json']; -export type ChannelsUpdateResponse = operations['channels/update']['responses']['200']['content']['application/json']; -export type ChannelsFavoriteRequest = operations['channels/favorite']['requestBody']['content']['application/json']; -export type ChannelsUnfavoriteRequest = operations['channels/unfavorite']['requestBody']['content']['application/json']; -export type ChannelsMyFavoritesResponse = operations['channels/my-favorites']['responses']['200']['content']['application/json']; -export type ChannelsSearchRequest = operations['channels/search']['requestBody']['content']['application/json']; -export type ChannelsSearchResponse = operations['channels/search']['responses']['200']['content']['application/json']; -export type ChartsActiveUsersRequest = operations['charts/active-users']['requestBody']['content']['application/json']; -export type ChartsActiveUsersResponse = operations['charts/active-users']['responses']['200']['content']['application/json']; -export type ChartsApRequestRequest = operations['charts/ap-request']['requestBody']['content']['application/json']; -export type ChartsApRequestResponse = operations['charts/ap-request']['responses']['200']['content']['application/json']; -export type ChartsDriveRequest = operations['charts/drive']['requestBody']['content']['application/json']; -export type ChartsDriveResponse = operations['charts/drive']['responses']['200']['content']['application/json']; -export type ChartsFederationRequest = operations['charts/federation']['requestBody']['content']['application/json']; -export type ChartsFederationResponse = operations['charts/federation']['responses']['200']['content']['application/json']; -export type ChartsInstanceRequest = operations['charts/instance']['requestBody']['content']['application/json']; -export type ChartsInstanceResponse = operations['charts/instance']['responses']['200']['content']['application/json']; -export type ChartsNotesRequest = operations['charts/notes']['requestBody']['content']['application/json']; -export type ChartsNotesResponse = operations['charts/notes']['responses']['200']['content']['application/json']; -export type ChartsUserDriveRequest = operations['charts/user/drive']['requestBody']['content']['application/json']; -export type ChartsUserDriveResponse = operations['charts/user/drive']['responses']['200']['content']['application/json']; -export type ChartsUserFollowingRequest = operations['charts/user/following']['requestBody']['content']['application/json']; -export type ChartsUserFollowingResponse = operations['charts/user/following']['responses']['200']['content']['application/json']; -export type ChartsUserNotesRequest = operations['charts/user/notes']['requestBody']['content']['application/json']; -export type ChartsUserNotesResponse = operations['charts/user/notes']['responses']['200']['content']['application/json']; -export type ChartsUserPvRequest = operations['charts/user/pv']['requestBody']['content']['application/json']; -export type ChartsUserPvResponse = operations['charts/user/pv']['responses']['200']['content']['application/json']; -export type ChartsUserReactionsRequest = operations['charts/user/reactions']['requestBody']['content']['application/json']; -export type ChartsUserReactionsResponse = operations['charts/user/reactions']['responses']['200']['content']['application/json']; -export type ChartsUsersRequest = operations['charts/users']['requestBody']['content']['application/json']; -export type ChartsUsersResponse = operations['charts/users']['responses']['200']['content']['application/json']; -export type ClipsAddNoteRequest = operations['clips/add-note']['requestBody']['content']['application/json']; -export type ClipsRemoveNoteRequest = operations['clips/remove-note']['requestBody']['content']['application/json']; -export type ClipsCreateRequest = operations['clips/create']['requestBody']['content']['application/json']; -export type ClipsCreateResponse = operations['clips/create']['responses']['200']['content']['application/json']; -export type ClipsDeleteRequest = operations['clips/delete']['requestBody']['content']['application/json']; -export type ClipsListResponse = operations['clips/list']['responses']['200']['content']['application/json']; -export type ClipsNotesRequest = operations['clips/notes']['requestBody']['content']['application/json']; -export type ClipsNotesResponse = operations['clips/notes']['responses']['200']['content']['application/json']; -export type ClipsShowRequest = operations['clips/show']['requestBody']['content']['application/json']; -export type ClipsShowResponse = operations['clips/show']['responses']['200']['content']['application/json']; -export type ClipsUpdateRequest = operations['clips/update']['requestBody']['content']['application/json']; -export type ClipsUpdateResponse = operations['clips/update']['responses']['200']['content']['application/json']; -export type ClipsFavoriteRequest = operations['clips/favorite']['requestBody']['content']['application/json']; -export type ClipsUnfavoriteRequest = operations['clips/unfavorite']['requestBody']['content']['application/json']; -export type ClipsMyFavoritesResponse = operations['clips/my-favorites']['responses']['200']['content']['application/json']; +export type AntennasCreateRequest = operations['antennas___create']['requestBody']['content']['application/json']; +export type AntennasCreateResponse = operations['antennas___create']['responses']['200']['content']['application/json']; +export type AntennasDeleteRequest = operations['antennas___delete']['requestBody']['content']['application/json']; +export type AntennasListResponse = operations['antennas___list']['responses']['200']['content']['application/json']; +export type AntennasNotesRequest = operations['antennas___notes']['requestBody']['content']['application/json']; +export type AntennasNotesResponse = operations['antennas___notes']['responses']['200']['content']['application/json']; +export type AntennasShowRequest = operations['antennas___show']['requestBody']['content']['application/json']; +export type AntennasShowResponse = operations['antennas___show']['responses']['200']['content']['application/json']; +export type AntennasUpdateRequest = operations['antennas___update']['requestBody']['content']['application/json']; +export type AntennasUpdateResponse = operations['antennas___update']['responses']['200']['content']['application/json']; +export type ApGetRequest = operations['ap___get']['requestBody']['content']['application/json']; +export type ApGetResponse = operations['ap___get']['responses']['200']['content']['application/json']; +export type ApShowRequest = operations['ap___show']['requestBody']['content']['application/json']; +export type ApShowResponse = operations['ap___show']['responses']['200']['content']['application/json']; +export type AppCreateRequest = operations['app___create']['requestBody']['content']['application/json']; +export type AppCreateResponse = operations['app___create']['responses']['200']['content']['application/json']; +export type AppShowRequest = operations['app___show']['requestBody']['content']['application/json']; +export type AppShowResponse = operations['app___show']['responses']['200']['content']['application/json']; +export type AuthAcceptRequest = operations['auth___accept']['requestBody']['content']['application/json']; +export type AuthSessionGenerateRequest = operations['auth___session___generate']['requestBody']['content']['application/json']; +export type AuthSessionGenerateResponse = operations['auth___session___generate']['responses']['200']['content']['application/json']; +export type AuthSessionShowRequest = operations['auth___session___show']['requestBody']['content']['application/json']; +export type AuthSessionShowResponse = operations['auth___session___show']['responses']['200']['content']['application/json']; +export type AuthSessionUserkeyRequest = operations['auth___session___userkey']['requestBody']['content']['application/json']; +export type AuthSessionUserkeyResponse = operations['auth___session___userkey']['responses']['200']['content']['application/json']; +export type BlockingCreateRequest = operations['blocking___create']['requestBody']['content']['application/json']; +export type BlockingCreateResponse = operations['blocking___create']['responses']['200']['content']['application/json']; +export type BlockingDeleteRequest = operations['blocking___delete']['requestBody']['content']['application/json']; +export type BlockingDeleteResponse = operations['blocking___delete']['responses']['200']['content']['application/json']; +export type BlockingListRequest = operations['blocking___list']['requestBody']['content']['application/json']; +export type BlockingListResponse = operations['blocking___list']['responses']['200']['content']['application/json']; +export type ChannelsCreateRequest = operations['channels___create']['requestBody']['content']['application/json']; +export type ChannelsCreateResponse = operations['channels___create']['responses']['200']['content']['application/json']; +export type ChannelsFeaturedResponse = operations['channels___featured']['responses']['200']['content']['application/json']; +export type ChannelsFollowRequest = operations['channels___follow']['requestBody']['content']['application/json']; +export type ChannelsFollowedRequest = operations['channels___followed']['requestBody']['content']['application/json']; +export type ChannelsFollowedResponse = operations['channels___followed']['responses']['200']['content']['application/json']; +export type ChannelsOwnedRequest = operations['channels___owned']['requestBody']['content']['application/json']; +export type ChannelsOwnedResponse = operations['channels___owned']['responses']['200']['content']['application/json']; +export type ChannelsShowRequest = operations['channels___show']['requestBody']['content']['application/json']; +export type ChannelsShowResponse = operations['channels___show']['responses']['200']['content']['application/json']; +export type ChannelsTimelineRequest = operations['channels___timeline']['requestBody']['content']['application/json']; +export type ChannelsTimelineResponse = operations['channels___timeline']['responses']['200']['content']['application/json']; +export type ChannelsUnfollowRequest = operations['channels___unfollow']['requestBody']['content']['application/json']; +export type ChannelsUpdateRequest = operations['channels___update']['requestBody']['content']['application/json']; +export type ChannelsUpdateResponse = operations['channels___update']['responses']['200']['content']['application/json']; +export type ChannelsFavoriteRequest = operations['channels___favorite']['requestBody']['content']['application/json']; +export type ChannelsUnfavoriteRequest = operations['channels___unfavorite']['requestBody']['content']['application/json']; +export type ChannelsMyFavoritesResponse = operations['channels___my-favorites']['responses']['200']['content']['application/json']; +export type ChannelsSearchRequest = operations['channels___search']['requestBody']['content']['application/json']; +export type ChannelsSearchResponse = operations['channels___search']['responses']['200']['content']['application/json']; +export type ChartsActiveUsersRequest = operations['charts___active-users']['requestBody']['content']['application/json']; +export type ChartsActiveUsersResponse = operations['charts___active-users']['responses']['200']['content']['application/json']; +export type ChartsApRequestRequest = operations['charts___ap-request']['requestBody']['content']['application/json']; +export type ChartsApRequestResponse = operations['charts___ap-request']['responses']['200']['content']['application/json']; +export type ChartsDriveRequest = operations['charts___drive']['requestBody']['content']['application/json']; +export type ChartsDriveResponse = operations['charts___drive']['responses']['200']['content']['application/json']; +export type ChartsFederationRequest = operations['charts___federation']['requestBody']['content']['application/json']; +export type ChartsFederationResponse = operations['charts___federation']['responses']['200']['content']['application/json']; +export type ChartsInstanceRequest = operations['charts___instance']['requestBody']['content']['application/json']; +export type ChartsInstanceResponse = operations['charts___instance']['responses']['200']['content']['application/json']; +export type ChartsNotesRequest = operations['charts___notes']['requestBody']['content']['application/json']; +export type ChartsNotesResponse = operations['charts___notes']['responses']['200']['content']['application/json']; +export type ChartsUserDriveRequest = operations['charts___user___drive']['requestBody']['content']['application/json']; +export type ChartsUserDriveResponse = operations['charts___user___drive']['responses']['200']['content']['application/json']; +export type ChartsUserFollowingRequest = operations['charts___user___following']['requestBody']['content']['application/json']; +export type ChartsUserFollowingResponse = operations['charts___user___following']['responses']['200']['content']['application/json']; +export type ChartsUserNotesRequest = operations['charts___user___notes']['requestBody']['content']['application/json']; +export type ChartsUserNotesResponse = operations['charts___user___notes']['responses']['200']['content']['application/json']; +export type ChartsUserPvRequest = operations['charts___user___pv']['requestBody']['content']['application/json']; +export type ChartsUserPvResponse = operations['charts___user___pv']['responses']['200']['content']['application/json']; +export type ChartsUserReactionsRequest = operations['charts___user___reactions']['requestBody']['content']['application/json']; +export type ChartsUserReactionsResponse = operations['charts___user___reactions']['responses']['200']['content']['application/json']; +export type ChartsUsersRequest = operations['charts___users']['requestBody']['content']['application/json']; +export type ChartsUsersResponse = operations['charts___users']['responses']['200']['content']['application/json']; +export type ClipsAddNoteRequest = operations['clips___add-note']['requestBody']['content']['application/json']; +export type ClipsRemoveNoteRequest = operations['clips___remove-note']['requestBody']['content']['application/json']; +export type ClipsCreateRequest = operations['clips___create']['requestBody']['content']['application/json']; +export type ClipsCreateResponse = operations['clips___create']['responses']['200']['content']['application/json']; +export type ClipsDeleteRequest = operations['clips___delete']['requestBody']['content']['application/json']; +export type ClipsListResponse = operations['clips___list']['responses']['200']['content']['application/json']; +export type ClipsNotesRequest = operations['clips___notes']['requestBody']['content']['application/json']; +export type ClipsNotesResponse = operations['clips___notes']['responses']['200']['content']['application/json']; +export type ClipsShowRequest = operations['clips___show']['requestBody']['content']['application/json']; +export type ClipsShowResponse = operations['clips___show']['responses']['200']['content']['application/json']; +export type ClipsUpdateRequest = operations['clips___update']['requestBody']['content']['application/json']; +export type ClipsUpdateResponse = operations['clips___update']['responses']['200']['content']['application/json']; +export type ClipsFavoriteRequest = operations['clips___favorite']['requestBody']['content']['application/json']; +export type ClipsUnfavoriteRequest = operations['clips___unfavorite']['requestBody']['content']['application/json']; +export type ClipsMyFavoritesResponse = operations['clips___my-favorites']['responses']['200']['content']['application/json']; export type DriveResponse = operations['drive']['responses']['200']['content']['application/json']; -export type DriveFilesRequest = operations['drive/files']['requestBody']['content']['application/json']; -export type DriveFilesResponse = operations['drive/files']['responses']['200']['content']['application/json']; -export type DriveFilesAttachedNotesRequest = operations['drive/files/attached-notes']['requestBody']['content']['application/json']; -export type DriveFilesAttachedNotesResponse = operations['drive/files/attached-notes']['responses']['200']['content']['application/json']; -export type DriveFilesCheckExistenceRequest = operations['drive/files/check-existence']['requestBody']['content']['application/json']; -export type DriveFilesCheckExistenceResponse = operations['drive/files/check-existence']['responses']['200']['content']['application/json']; -export type DriveFilesCreateRequest = operations['drive/files/create']['requestBody']['content']['multipart/form-data']; -export type DriveFilesCreateResponse = operations['drive/files/create']['responses']['200']['content']['application/json']; -export type DriveFilesDeleteRequest = operations['drive/files/delete']['requestBody']['content']['application/json']; -export type DriveFilesFindByHashRequest = operations['drive/files/find-by-hash']['requestBody']['content']['application/json']; -export type DriveFilesFindByHashResponse = operations['drive/files/find-by-hash']['responses']['200']['content']['application/json']; -export type DriveFilesFindRequest = operations['drive/files/find']['requestBody']['content']['application/json']; -export type DriveFilesFindResponse = operations['drive/files/find']['responses']['200']['content']['application/json']; -export type DriveFilesShowRequest = operations['drive/files/show']['requestBody']['content']['application/json']; -export type DriveFilesShowResponse = operations['drive/files/show']['responses']['200']['content']['application/json']; -export type DriveFilesUpdateRequest = operations['drive/files/update']['requestBody']['content']['application/json']; -export type DriveFilesUpdateResponse = operations['drive/files/update']['responses']['200']['content']['application/json']; -export type DriveFilesUploadFromUrlRequest = operations['drive/files/upload-from-url']['requestBody']['content']['application/json']; -export type DriveFoldersRequest = operations['drive/folders']['requestBody']['content']['application/json']; -export type DriveFoldersResponse = operations['drive/folders']['responses']['200']['content']['application/json']; -export type DriveFoldersCreateRequest = operations['drive/folders/create']['requestBody']['content']['application/json']; -export type DriveFoldersCreateResponse = operations['drive/folders/create']['responses']['200']['content']['application/json']; -export type DriveFoldersDeleteRequest = operations['drive/folders/delete']['requestBody']['content']['application/json']; -export type DriveFoldersFindRequest = operations['drive/folders/find']['requestBody']['content']['application/json']; -export type DriveFoldersFindResponse = operations['drive/folders/find']['responses']['200']['content']['application/json']; -export type DriveFoldersShowRequest = operations['drive/folders/show']['requestBody']['content']['application/json']; -export type DriveFoldersShowResponse = operations['drive/folders/show']['responses']['200']['content']['application/json']; -export type DriveFoldersUpdateRequest = operations['drive/folders/update']['requestBody']['content']['application/json']; -export type DriveFoldersUpdateResponse = operations['drive/folders/update']['responses']['200']['content']['application/json']; -export type DriveStreamRequest = operations['drive/stream']['requestBody']['content']['application/json']; -export type DriveStreamResponse = operations['drive/stream']['responses']['200']['content']['application/json']; -export type EmailAddressAvailableRequest = operations['email-address/available']['requestBody']['content']['application/json']; -export type EmailAddressAvailableResponse = operations['email-address/available']['responses']['200']['content']['application/json']; +export type DriveFilesRequest = operations['drive___files']['requestBody']['content']['application/json']; +export type DriveFilesResponse = operations['drive___files']['responses']['200']['content']['application/json']; +export type DriveFilesAttachedNotesRequest = operations['drive___files___attached-notes']['requestBody']['content']['application/json']; +export type DriveFilesAttachedNotesResponse = operations['drive___files___attached-notes']['responses']['200']['content']['application/json']; +export type DriveFilesCheckExistenceRequest = operations['drive___files___check-existence']['requestBody']['content']['application/json']; +export type DriveFilesCheckExistenceResponse = operations['drive___files___check-existence']['responses']['200']['content']['application/json']; +export type DriveFilesCreateRequest = operations['drive___files___create']['requestBody']['content']['multipart/form-data']; +export type DriveFilesCreateResponse = operations['drive___files___create']['responses']['200']['content']['application/json']; +export type DriveFilesDeleteRequest = operations['drive___files___delete']['requestBody']['content']['application/json']; +export type DriveFilesFindByHashRequest = operations['drive___files___find-by-hash']['requestBody']['content']['application/json']; +export type DriveFilesFindByHashResponse = operations['drive___files___find-by-hash']['responses']['200']['content']['application/json']; +export type DriveFilesFindRequest = operations['drive___files___find']['requestBody']['content']['application/json']; +export type DriveFilesFindResponse = operations['drive___files___find']['responses']['200']['content']['application/json']; +export type DriveFilesShowRequest = operations['drive___files___show']['requestBody']['content']['application/json']; +export type DriveFilesShowResponse = operations['drive___files___show']['responses']['200']['content']['application/json']; +export type DriveFilesUpdateRequest = operations['drive___files___update']['requestBody']['content']['application/json']; +export type DriveFilesUpdateResponse = operations['drive___files___update']['responses']['200']['content']['application/json']; +export type DriveFilesUploadFromUrlRequest = operations['drive___files___upload-from-url']['requestBody']['content']['application/json']; +export type DriveFoldersRequest = operations['drive___folders']['requestBody']['content']['application/json']; +export type DriveFoldersResponse = operations['drive___folders']['responses']['200']['content']['application/json']; +export type DriveFoldersCreateRequest = operations['drive___folders___create']['requestBody']['content']['application/json']; +export type DriveFoldersCreateResponse = operations['drive___folders___create']['responses']['200']['content']['application/json']; +export type DriveFoldersDeleteRequest = operations['drive___folders___delete']['requestBody']['content']['application/json']; +export type DriveFoldersFindRequest = operations['drive___folders___find']['requestBody']['content']['application/json']; +export type DriveFoldersFindResponse = operations['drive___folders___find']['responses']['200']['content']['application/json']; +export type DriveFoldersShowRequest = operations['drive___folders___show']['requestBody']['content']['application/json']; +export type DriveFoldersShowResponse = operations['drive___folders___show']['responses']['200']['content']['application/json']; +export type DriveFoldersUpdateRequest = operations['drive___folders___update']['requestBody']['content']['application/json']; +export type DriveFoldersUpdateResponse = operations['drive___folders___update']['responses']['200']['content']['application/json']; +export type DriveStreamRequest = operations['drive___stream']['requestBody']['content']['application/json']; +export type DriveStreamResponse = operations['drive___stream']['responses']['200']['content']['application/json']; +export type EmailAddressAvailableRequest = operations['email-address___available']['requestBody']['content']['application/json']; +export type EmailAddressAvailableResponse = operations['email-address___available']['responses']['200']['content']['application/json']; export type EndpointRequest = operations['endpoint']['requestBody']['content']['application/json']; export type EndpointResponse = operations['endpoint']['responses']['200']['content']['application/json']; export type EndpointsResponse = operations['endpoints']['responses']['200']['content']['application/json']; -export type FederationFollowersRequest = operations['federation/followers']['requestBody']['content']['application/json']; -export type FederationFollowersResponse = operations['federation/followers']['responses']['200']['content']['application/json']; -export type FederationFollowingRequest = operations['federation/following']['requestBody']['content']['application/json']; -export type FederationFollowingResponse = operations['federation/following']['responses']['200']['content']['application/json']; -export type FederationInstancesRequest = operations['federation/instances']['requestBody']['content']['application/json']; -export type FederationInstancesResponse = operations['federation/instances']['responses']['200']['content']['application/json']; -export type FederationShowInstanceRequest = operations['federation/show-instance']['requestBody']['content']['application/json']; -export type FederationShowInstanceResponse = operations['federation/show-instance']['responses']['200']['content']['application/json']; -export type FederationUpdateRemoteUserRequest = operations['federation/update-remote-user']['requestBody']['content']['application/json']; -export type FederationUsersRequest = operations['federation/users']['requestBody']['content']['application/json']; -export type FederationUsersResponse = operations['federation/users']['responses']['200']['content']['application/json']; -export type FederationStatsRequest = operations['federation/stats']['requestBody']['content']['application/json']; -export type FederationStatsResponse = operations['federation/stats']['responses']['200']['content']['application/json']; -export type FollowingCreateRequest = operations['following/create']['requestBody']['content']['application/json']; -export type FollowingCreateResponse = operations['following/create']['responses']['200']['content']['application/json']; -export type FollowingDeleteRequest = operations['following/delete']['requestBody']['content']['application/json']; -export type FollowingDeleteResponse = operations['following/delete']['responses']['200']['content']['application/json']; -export type FollowingUpdateRequest = operations['following/update']['requestBody']['content']['application/json']; -export type FollowingUpdateResponse = operations['following/update']['responses']['200']['content']['application/json']; -export type FollowingUpdateAllRequest = operations['following/update-all']['requestBody']['content']['application/json']; -export type FollowingInvalidateRequest = operations['following/invalidate']['requestBody']['content']['application/json']; -export type FollowingInvalidateResponse = operations['following/invalidate']['responses']['200']['content']['application/json']; -export type FollowingRequestsAcceptRequest = operations['following/requests/accept']['requestBody']['content']['application/json']; -export type FollowingRequestsCancelRequest = operations['following/requests/cancel']['requestBody']['content']['application/json']; -export type FollowingRequestsCancelResponse = operations['following/requests/cancel']['responses']['200']['content']['application/json']; -export type FollowingRequestsListRequest = operations['following/requests/list']['requestBody']['content']['application/json']; -export type FollowingRequestsListResponse = operations['following/requests/list']['responses']['200']['content']['application/json']; -export type FollowingRequestsRejectRequest = operations['following/requests/reject']['requestBody']['content']['application/json']; -export type GalleryFeaturedRequest = operations['gallery/featured']['requestBody']['content']['application/json']; -export type GalleryFeaturedResponse = operations['gallery/featured']['responses']['200']['content']['application/json']; -export type GalleryPopularResponse = operations['gallery/popular']['responses']['200']['content']['application/json']; -export type GalleryPostsRequest = operations['gallery/posts']['requestBody']['content']['application/json']; -export type GalleryPostsResponse = operations['gallery/posts']['responses']['200']['content']['application/json']; -export type GalleryPostsCreateRequest = operations['gallery/posts/create']['requestBody']['content']['application/json']; -export type GalleryPostsCreateResponse = operations['gallery/posts/create']['responses']['200']['content']['application/json']; -export type GalleryPostsDeleteRequest = operations['gallery/posts/delete']['requestBody']['content']['application/json']; -export type GalleryPostsLikeRequest = operations['gallery/posts/like']['requestBody']['content']['application/json']; -export type GalleryPostsShowRequest = operations['gallery/posts/show']['requestBody']['content']['application/json']; -export type GalleryPostsShowResponse = operations['gallery/posts/show']['responses']['200']['content']['application/json']; -export type GalleryPostsUnlikeRequest = operations['gallery/posts/unlike']['requestBody']['content']['application/json']; -export type GalleryPostsUpdateRequest = operations['gallery/posts/update']['requestBody']['content']['application/json']; -export type GalleryPostsUpdateResponse = operations['gallery/posts/update']['responses']['200']['content']['application/json']; +export type FederationFollowersRequest = operations['federation___followers']['requestBody']['content']['application/json']; +export type FederationFollowersResponse = operations['federation___followers']['responses']['200']['content']['application/json']; +export type FederationFollowingRequest = operations['federation___following']['requestBody']['content']['application/json']; +export type FederationFollowingResponse = operations['federation___following']['responses']['200']['content']['application/json']; +export type FederationInstancesRequest = operations['federation___instances']['requestBody']['content']['application/json']; +export type FederationInstancesResponse = operations['federation___instances']['responses']['200']['content']['application/json']; +export type FederationShowInstanceRequest = operations['federation___show-instance']['requestBody']['content']['application/json']; +export type FederationShowInstanceResponse = operations['federation___show-instance']['responses']['200']['content']['application/json']; +export type FederationUpdateRemoteUserRequest = operations['federation___update-remote-user']['requestBody']['content']['application/json']; +export type FederationUsersRequest = operations['federation___users']['requestBody']['content']['application/json']; +export type FederationUsersResponse = operations['federation___users']['responses']['200']['content']['application/json']; +export type FederationStatsRequest = operations['federation___stats']['requestBody']['content']['application/json']; +export type FederationStatsResponse = operations['federation___stats']['responses']['200']['content']['application/json']; +export type FollowingCreateRequest = operations['following___create']['requestBody']['content']['application/json']; +export type FollowingCreateResponse = operations['following___create']['responses']['200']['content']['application/json']; +export type FollowingDeleteRequest = operations['following___delete']['requestBody']['content']['application/json']; +export type FollowingDeleteResponse = operations['following___delete']['responses']['200']['content']['application/json']; +export type FollowingUpdateRequest = operations['following___update']['requestBody']['content']['application/json']; +export type FollowingUpdateResponse = operations['following___update']['responses']['200']['content']['application/json']; +export type FollowingUpdateAllRequest = operations['following___update-all']['requestBody']['content']['application/json']; +export type FollowingInvalidateRequest = operations['following___invalidate']['requestBody']['content']['application/json']; +export type FollowingInvalidateResponse = operations['following___invalidate']['responses']['200']['content']['application/json']; +export type FollowingRequestsAcceptRequest = operations['following___requests___accept']['requestBody']['content']['application/json']; +export type FollowingRequestsCancelRequest = operations['following___requests___cancel']['requestBody']['content']['application/json']; +export type FollowingRequestsCancelResponse = operations['following___requests___cancel']['responses']['200']['content']['application/json']; +export type FollowingRequestsListRequest = operations['following___requests___list']['requestBody']['content']['application/json']; +export type FollowingRequestsListResponse = operations['following___requests___list']['responses']['200']['content']['application/json']; +export type FollowingRequestsRejectRequest = operations['following___requests___reject']['requestBody']['content']['application/json']; +export type GalleryFeaturedRequest = operations['gallery___featured']['requestBody']['content']['application/json']; +export type GalleryFeaturedResponse = operations['gallery___featured']['responses']['200']['content']['application/json']; +export type GalleryPopularResponse = operations['gallery___popular']['responses']['200']['content']['application/json']; +export type GalleryPostsRequest = operations['gallery___posts']['requestBody']['content']['application/json']; +export type GalleryPostsResponse = operations['gallery___posts']['responses']['200']['content']['application/json']; +export type GalleryPostsCreateRequest = operations['gallery___posts___create']['requestBody']['content']['application/json']; +export type GalleryPostsCreateResponse = operations['gallery___posts___create']['responses']['200']['content']['application/json']; +export type GalleryPostsDeleteRequest = operations['gallery___posts___delete']['requestBody']['content']['application/json']; +export type GalleryPostsLikeRequest = operations['gallery___posts___like']['requestBody']['content']['application/json']; +export type GalleryPostsShowRequest = operations['gallery___posts___show']['requestBody']['content']['application/json']; +export type GalleryPostsShowResponse = operations['gallery___posts___show']['responses']['200']['content']['application/json']; +export type GalleryPostsUnlikeRequest = operations['gallery___posts___unlike']['requestBody']['content']['application/json']; +export type GalleryPostsUpdateRequest = operations['gallery___posts___update']['requestBody']['content']['application/json']; +export type GalleryPostsUpdateResponse = operations['gallery___posts___update']['responses']['200']['content']['application/json']; export type GetOnlineUsersCountResponse = operations['get-online-users-count']['responses']['200']['content']['application/json']; export type GetAvatarDecorationsResponse = operations['get-avatar-decorations']['responses']['200']['content']['application/json']; -export type HashtagsListRequest = operations['hashtags/list']['requestBody']['content']['application/json']; -export type HashtagsListResponse = operations['hashtags/list']['responses']['200']['content']['application/json']; -export type HashtagsSearchRequest = operations['hashtags/search']['requestBody']['content']['application/json']; -export type HashtagsSearchResponse = operations['hashtags/search']['responses']['200']['content']['application/json']; -export type HashtagsShowRequest = operations['hashtags/show']['requestBody']['content']['application/json']; -export type HashtagsShowResponse = operations['hashtags/show']['responses']['200']['content']['application/json']; -export type HashtagsTrendResponse = operations['hashtags/trend']['responses']['200']['content']['application/json']; -export type HashtagsUsersRequest = operations['hashtags/users']['requestBody']['content']['application/json']; -export type HashtagsUsersResponse = operations['hashtags/users']['responses']['200']['content']['application/json']; +export type HashtagsListRequest = operations['hashtags___list']['requestBody']['content']['application/json']; +export type HashtagsListResponse = operations['hashtags___list']['responses']['200']['content']['application/json']; +export type HashtagsSearchRequest = operations['hashtags___search']['requestBody']['content']['application/json']; +export type HashtagsSearchResponse = operations['hashtags___search']['responses']['200']['content']['application/json']; +export type HashtagsShowRequest = operations['hashtags___show']['requestBody']['content']['application/json']; +export type HashtagsShowResponse = operations['hashtags___show']['responses']['200']['content']['application/json']; +export type HashtagsTrendResponse = operations['hashtags___trend']['responses']['200']['content']['application/json']; +export type HashtagsUsersRequest = operations['hashtags___users']['requestBody']['content']['application/json']; +export type HashtagsUsersResponse = operations['hashtags___users']['responses']['200']['content']['application/json']; export type IResponse = operations['i']['responses']['200']['content']['application/json']; -export type I2faDoneRequest = operations['i/2fa/done']['requestBody']['content']['application/json']; -export type I2faDoneResponse = operations['i/2fa/done']['responses']['200']['content']['application/json']; -export type I2faKeyDoneRequest = operations['i/2fa/key-done']['requestBody']['content']['application/json']; -export type I2faKeyDoneResponse = operations['i/2fa/key-done']['responses']['200']['content']['application/json']; -export type I2faPasswordLessRequest = operations['i/2fa/password-less']['requestBody']['content']['application/json']; -export type I2faRegisterKeyRequest = operations['i/2fa/register-key']['requestBody']['content']['application/json']; -export type I2faRegisterKeyResponse = operations['i/2fa/register-key']['responses']['200']['content']['application/json']; -export type I2faRegisterRequest = operations['i/2fa/register']['requestBody']['content']['application/json']; -export type I2faRegisterResponse = operations['i/2fa/register']['responses']['200']['content']['application/json']; -export type I2faUpdateKeyRequest = operations['i/2fa/update-key']['requestBody']['content']['application/json']; -export type I2faRemoveKeyRequest = operations['i/2fa/remove-key']['requestBody']['content']['application/json']; -export type I2faUnregisterRequest = operations['i/2fa/unregister']['requestBody']['content']['application/json']; -export type IAppsRequest = operations['i/apps']['requestBody']['content']['application/json']; -export type IAppsResponse = operations['i/apps']['responses']['200']['content']['application/json']; -export type IAuthorizedAppsRequest = operations['i/authorized-apps']['requestBody']['content']['application/json']; -export type IAuthorizedAppsResponse = operations['i/authorized-apps']['responses']['200']['content']['application/json']; -export type IClaimAchievementRequest = operations['i/claim-achievement']['requestBody']['content']['application/json']; -export type IChangePasswordRequest = operations['i/change-password']['requestBody']['content']['application/json']; -export type IDeleteAccountRequest = operations['i/delete-account']['requestBody']['content']['application/json']; -export type IExportFollowingRequest = operations['i/export-following']['requestBody']['content']['application/json']; -export type IFavoritesRequest = operations['i/favorites']['requestBody']['content']['application/json']; -export type IFavoritesResponse = operations['i/favorites']['responses']['200']['content']['application/json']; -export type IGalleryLikesRequest = operations['i/gallery/likes']['requestBody']['content']['application/json']; -export type IGalleryLikesResponse = operations['i/gallery/likes']['responses']['200']['content']['application/json']; -export type IGalleryPostsRequest = operations['i/gallery/posts']['requestBody']['content']['application/json']; -export type IGalleryPostsResponse = operations['i/gallery/posts']['responses']['200']['content']['application/json']; -export type IImportBlockingRequest = operations['i/import-blocking']['requestBody']['content']['application/json']; -export type IImportFollowingRequest = operations['i/import-following']['requestBody']['content']['application/json']; -export type IImportMutingRequest = operations['i/import-muting']['requestBody']['content']['application/json']; -export type IImportUserListsRequest = operations['i/import-user-lists']['requestBody']['content']['application/json']; -export type IImportAntennasRequest = operations['i/import-antennas']['requestBody']['content']['application/json']; -export type INotificationsRequest = operations['i/notifications']['requestBody']['content']['application/json']; -export type INotificationsResponse = operations['i/notifications']['responses']['200']['content']['application/json']; -export type INotificationsGroupedRequest = operations['i/notifications-grouped']['requestBody']['content']['application/json']; -export type INotificationsGroupedResponse = operations['i/notifications-grouped']['responses']['200']['content']['application/json']; -export type IPageLikesRequest = operations['i/page-likes']['requestBody']['content']['application/json']; -export type IPageLikesResponse = operations['i/page-likes']['responses']['200']['content']['application/json']; -export type IPagesRequest = operations['i/pages']['requestBody']['content']['application/json']; -export type IPagesResponse = operations['i/pages']['responses']['200']['content']['application/json']; -export type IPinRequest = operations['i/pin']['requestBody']['content']['application/json']; -export type IPinResponse = operations['i/pin']['responses']['200']['content']['application/json']; -export type IReadAnnouncementRequest = operations['i/read-announcement']['requestBody']['content']['application/json']; -export type IRegenerateTokenRequest = operations['i/regenerate-token']['requestBody']['content']['application/json']; -export type IRegistryGetAllRequest = operations['i/registry/get-all']['requestBody']['content']['application/json']; -export type IRegistryGetAllResponse = operations['i/registry/get-all']['responses']['200']['content']['application/json']; -export type IRegistryGetDetailRequest = operations['i/registry/get-detail']['requestBody']['content']['application/json']; -export type IRegistryGetDetailResponse = operations['i/registry/get-detail']['responses']['200']['content']['application/json']; -export type IRegistryGetRequest = operations['i/registry/get']['requestBody']['content']['application/json']; -export type IRegistryGetResponse = operations['i/registry/get']['responses']['200']['content']['application/json']; -export type IRegistryKeysWithTypeRequest = operations['i/registry/keys-with-type']['requestBody']['content']['application/json']; -export type IRegistryKeysWithTypeResponse = operations['i/registry/keys-with-type']['responses']['200']['content']['application/json']; -export type IRegistryKeysRequest = operations['i/registry/keys']['requestBody']['content']['application/json']; -export type IRegistryKeysResponse = operations['i/registry/keys']['responses']['200']['content']['application/json']; -export type IRegistryRemoveRequest = operations['i/registry/remove']['requestBody']['content']['application/json']; -export type IRegistryScopesWithDomainResponse = operations['i/registry/scopes-with-domain']['responses']['200']['content']['application/json']; -export type IRegistrySetRequest = operations['i/registry/set']['requestBody']['content']['application/json']; -export type IRevokeTokenRequest = operations['i/revoke-token']['requestBody']['content']['application/json']; -export type ISigninHistoryRequest = operations['i/signin-history']['requestBody']['content']['application/json']; -export type ISigninHistoryResponse = operations['i/signin-history']['responses']['200']['content']['application/json']; -export type IUnpinRequest = operations['i/unpin']['requestBody']['content']['application/json']; -export type IUnpinResponse = operations['i/unpin']['responses']['200']['content']['application/json']; -export type IUpdateEmailRequest = operations['i/update-email']['requestBody']['content']['application/json']; -export type IUpdateEmailResponse = operations['i/update-email']['responses']['200']['content']['application/json']; -export type IUpdateRequest = operations['i/update']['requestBody']['content']['application/json']; -export type IUpdateResponse = operations['i/update']['responses']['200']['content']['application/json']; -export type IUserGroupInvitesRequest = operations['i/user-group-invites']['requestBody']['content']['application/json']; -export type IUserGroupInvitesResponse = operations['i/user-group-invites']['responses']['200']['content']['application/json']; -export type IMoveRequest = operations['i/move']['requestBody']['content']['application/json']; -export type IMoveResponse = operations['i/move']['responses']['200']['content']['application/json']; -export type IWebhooksCreateRequest = operations['i/webhooks/create']['requestBody']['content']['application/json']; -export type IWebhooksCreateResponse = operations['i/webhooks/create']['responses']['200']['content']['application/json']; -export type IWebhooksListResponse = operations['i/webhooks/list']['responses']['200']['content']['application/json']; -export type IWebhooksShowRequest = operations['i/webhooks/show']['requestBody']['content']['application/json']; -export type IWebhooksShowResponse = operations['i/webhooks/show']['responses']['200']['content']['application/json']; -export type IWebhooksUpdateRequest = operations['i/webhooks/update']['requestBody']['content']['application/json']; -export type IWebhooksDeleteRequest = operations['i/webhooks/delete']['requestBody']['content']['application/json']; -export type InviteCreateResponse = operations['invite/create']['responses']['200']['content']['application/json']; -export type InviteDeleteRequest = operations['invite/delete']['requestBody']['content']['application/json']; -export type InviteListRequest = operations['invite/list']['requestBody']['content']['application/json']; -export type InviteListResponse = operations['invite/list']['responses']['200']['content']['application/json']; -export type InviteLimitResponse = operations['invite/limit']['responses']['200']['content']['application/json']; -export type MessagingHistoryRequest = operations['messaging/history']['requestBody']['content']['application/json']; -export type MessagingHistoryResponse = operations['messaging/history']['responses']['200']['content']['application/json']; -export type MessagingMessagesRequest = operations['messaging/messages']['requestBody']['content']['application/json']; -export type MessagingMessagesResponse = operations['messaging/messages']['responses']['200']['content']['application/json']; -export type MessagingMessagesCreateRequest = operations['messaging/messages/create']['requestBody']['content']['application/json']; -export type MessagingMessagesCreateResponse = operations['messaging/messages/create']['responses']['200']['content']['application/json']; -export type MessagingMessagesDeleteRequest = operations['messaging/messages/delete']['requestBody']['content']['application/json']; -export type MessagingMessagesReadRequest = operations['messaging/messages/read']['requestBody']['content']['application/json']; +export type I2faDoneRequest = operations['i___2fa___done']['requestBody']['content']['application/json']; +export type I2faDoneResponse = operations['i___2fa___done']['responses']['200']['content']['application/json']; +export type I2faKeyDoneRequest = operations['i___2fa___key-done']['requestBody']['content']['application/json']; +export type I2faKeyDoneResponse = operations['i___2fa___key-done']['responses']['200']['content']['application/json']; +export type I2faPasswordLessRequest = operations['i___2fa___password-less']['requestBody']['content']['application/json']; +export type I2faRegisterKeyRequest = operations['i___2fa___register-key']['requestBody']['content']['application/json']; +export type I2faRegisterKeyResponse = operations['i___2fa___register-key']['responses']['200']['content']['application/json']; +export type I2faRegisterRequest = operations['i___2fa___register']['requestBody']['content']['application/json']; +export type I2faRegisterResponse = operations['i___2fa___register']['responses']['200']['content']['application/json']; +export type I2faUpdateKeyRequest = operations['i___2fa___update-key']['requestBody']['content']['application/json']; +export type I2faRemoveKeyRequest = operations['i___2fa___remove-key']['requestBody']['content']['application/json']; +export type I2faUnregisterRequest = operations['i___2fa___unregister']['requestBody']['content']['application/json']; +export type IAppsRequest = operations['i___apps']['requestBody']['content']['application/json']; +export type IAppsResponse = operations['i___apps']['responses']['200']['content']['application/json']; +export type IAuthorizedAppsRequest = operations['i___authorized-apps']['requestBody']['content']['application/json']; +export type IAuthorizedAppsResponse = operations['i___authorized-apps']['responses']['200']['content']['application/json']; +export type IClaimAchievementRequest = operations['i___claim-achievement']['requestBody']['content']['application/json']; +export type IChangePasswordRequest = operations['i___change-password']['requestBody']['content']['application/json']; +export type IDeleteAccountRequest = operations['i___delete-account']['requestBody']['content']['application/json']; +export type IExportFollowingRequest = operations['i___export-following']['requestBody']['content']['application/json']; +export type IFavoritesRequest = operations['i___favorites']['requestBody']['content']['application/json']; +export type IFavoritesResponse = operations['i___favorites']['responses']['200']['content']['application/json']; +export type IGalleryLikesRequest = operations['i___gallery___likes']['requestBody']['content']['application/json']; +export type IGalleryLikesResponse = operations['i___gallery___likes']['responses']['200']['content']['application/json']; +export type IGalleryPostsRequest = operations['i___gallery___posts']['requestBody']['content']['application/json']; +export type IGalleryPostsResponse = operations['i___gallery___posts']['responses']['200']['content']['application/json']; +export type IImportBlockingRequest = operations['i___import-blocking']['requestBody']['content']['application/json']; +export type IImportFollowingRequest = operations['i___import-following']['requestBody']['content']['application/json']; +export type IImportMutingRequest = operations['i___import-muting']['requestBody']['content']['application/json']; +export type IImportUserListsRequest = operations['i___import-user-lists']['requestBody']['content']['application/json']; +export type IImportAntennasRequest = operations['i___import-antennas']['requestBody']['content']['application/json']; +export type INotificationsRequest = operations['i___notifications']['requestBody']['content']['application/json']; +export type INotificationsResponse = operations['i___notifications']['responses']['200']['content']['application/json']; +export type INotificationsGroupedRequest = operations['i___notifications-grouped']['requestBody']['content']['application/json']; +export type INotificationsGroupedResponse = operations['i___notifications-grouped']['responses']['200']['content']['application/json']; +export type IPageLikesRequest = operations['i___page-likes']['requestBody']['content']['application/json']; +export type IPageLikesResponse = operations['i___page-likes']['responses']['200']['content']['application/json']; +export type IPagesRequest = operations['i___pages']['requestBody']['content']['application/json']; +export type IPagesResponse = operations['i___pages']['responses']['200']['content']['application/json']; +export type IPinRequest = operations['i___pin']['requestBody']['content']['application/json']; +export type IPinResponse = operations['i___pin']['responses']['200']['content']['application/json']; +export type IReadAnnouncementRequest = operations['i___read-announcement']['requestBody']['content']['application/json']; +export type IRegenerateTokenRequest = operations['i___regenerate-token']['requestBody']['content']['application/json']; +export type IRegistryGetAllRequest = operations['i___registry___get-all']['requestBody']['content']['application/json']; +export type IRegistryGetAllResponse = operations['i___registry___get-all']['responses']['200']['content']['application/json']; +export type IRegistryGetDetailRequest = operations['i___registry___get-detail']['requestBody']['content']['application/json']; +export type IRegistryGetDetailResponse = operations['i___registry___get-detail']['responses']['200']['content']['application/json']; +export type IRegistryGetRequest = operations['i___registry___get']['requestBody']['content']['application/json']; +export type IRegistryGetResponse = operations['i___registry___get']['responses']['200']['content']['application/json']; +export type IRegistryKeysWithTypeRequest = operations['i___registry___keys-with-type']['requestBody']['content']['application/json']; +export type IRegistryKeysWithTypeResponse = operations['i___registry___keys-with-type']['responses']['200']['content']['application/json']; +export type IRegistryKeysRequest = operations['i___registry___keys']['requestBody']['content']['application/json']; +export type IRegistryKeysResponse = operations['i___registry___keys']['responses']['200']['content']['application/json']; +export type IRegistryRemoveRequest = operations['i___registry___remove']['requestBody']['content']['application/json']; +export type IRegistryScopesWithDomainResponse = operations['i___registry___scopes-with-domain']['responses']['200']['content']['application/json']; +export type IRegistrySetRequest = operations['i___registry___set']['requestBody']['content']['application/json']; +export type IRevokeTokenRequest = operations['i___revoke-token']['requestBody']['content']['application/json']; +export type ISigninHistoryRequest = operations['i___signin-history']['requestBody']['content']['application/json']; +export type ISigninHistoryResponse = operations['i___signin-history']['responses']['200']['content']['application/json']; +export type IUnpinRequest = operations['i___unpin']['requestBody']['content']['application/json']; +export type IUnpinResponse = operations['i___unpin']['responses']['200']['content']['application/json']; +export type IUpdateEmailRequest = operations['i___update-email']['requestBody']['content']['application/json']; +export type IUpdateEmailResponse = operations['i___update-email']['responses']['200']['content']['application/json']; +export type IUpdateRequest = operations['i___update']['requestBody']['content']['application/json']; +export type IUpdateResponse = operations['i___update']['responses']['200']['content']['application/json']; +export type IUserGroupInvitesRequest = operations['i___user-group-invites']['requestBody']['content']['application/json']; +export type IUserGroupInvitesResponse = operations['i___user-group-invites']['responses']['200']['content']['application/json']; +export type IMoveRequest = operations['i___move']['requestBody']['content']['application/json']; +export type IMoveResponse = operations['i___move']['responses']['200']['content']['application/json']; +export type IWebhooksCreateRequest = operations['i___webhooks___create']['requestBody']['content']['application/json']; +export type IWebhooksCreateResponse = operations['i___webhooks___create']['responses']['200']['content']['application/json']; +export type IWebhooksListResponse = operations['i___webhooks___list']['responses']['200']['content']['application/json']; +export type IWebhooksShowRequest = operations['i___webhooks___show']['requestBody']['content']['application/json']; +export type IWebhooksShowResponse = operations['i___webhooks___show']['responses']['200']['content']['application/json']; +export type IWebhooksUpdateRequest = operations['i___webhooks___update']['requestBody']['content']['application/json']; +export type IWebhooksDeleteRequest = operations['i___webhooks___delete']['requestBody']['content']['application/json']; +export type InviteCreateResponse = operations['invite___create']['responses']['200']['content']['application/json']; +export type InviteDeleteRequest = operations['invite___delete']['requestBody']['content']['application/json']; +export type InviteListRequest = operations['invite___list']['requestBody']['content']['application/json']; +export type InviteListResponse = operations['invite___list']['responses']['200']['content']['application/json']; +export type InviteLimitResponse = operations['invite___limit']['responses']['200']['content']['application/json']; +export type MessagingHistoryRequest = operations['messaging___history']['requestBody']['content']['application/json']; +export type MessagingHistoryResponse = operations['messaging___history']['responses']['200']['content']['application/json']; +export type MessagingMessagesRequest = operations['messaging___messages']['requestBody']['content']['application/json']; +export type MessagingMessagesResponse = operations['messaging___messages']['responses']['200']['content']['application/json']; +export type MessagingMessagesCreateRequest = operations['messaging___messages___create']['requestBody']['content']['application/json']; +export type MessagingMessagesCreateResponse = operations['messaging___messages___create']['responses']['200']['content']['application/json']; +export type MessagingMessagesDeleteRequest = operations['messaging___messages___delete']['requestBody']['content']['application/json']; +export type MessagingMessagesReadRequest = operations['messaging___messages___read']['requestBody']['content']['application/json']; export type MetaRequest = operations['meta']['requestBody']['content']['application/json']; export type MetaResponse = operations['meta']['responses']['200']['content']['application/json']; export type EmojisResponse = operations['emojis']['responses']['200']['content']['application/json']; export type EmojiRequest = operations['emoji']['requestBody']['content']['application/json']; export type EmojiResponse = operations['emoji']['responses']['200']['content']['application/json']; -export type MiauthGenTokenRequest = operations['miauth/gen-token']['requestBody']['content']['application/json']; -export type MiauthGenTokenResponse = operations['miauth/gen-token']['responses']['200']['content']['application/json']; -export type MuteCreateRequest = operations['mute/create']['requestBody']['content']['application/json']; -export type MuteDeleteRequest = operations['mute/delete']['requestBody']['content']['application/json']; -export type MuteListRequest = operations['mute/list']['requestBody']['content']['application/json']; -export type MuteListResponse = operations['mute/list']['responses']['200']['content']['application/json']; -export type RenoteMuteCreateRequest = operations['renote-mute/create']['requestBody']['content']['application/json']; -export type RenoteMuteDeleteRequest = operations['renote-mute/delete']['requestBody']['content']['application/json']; -export type RenoteMuteListRequest = operations['renote-mute/list']['requestBody']['content']['application/json']; -export type RenoteMuteListResponse = operations['renote-mute/list']['responses']['200']['content']['application/json']; -export type MyAppsRequest = operations['my/apps']['requestBody']['content']['application/json']; -export type MyAppsResponse = operations['my/apps']['responses']['200']['content']['application/json']; +export type MiauthGenTokenRequest = operations['miauth___gen-token']['requestBody']['content']['application/json']; +export type MiauthGenTokenResponse = operations['miauth___gen-token']['responses']['200']['content']['application/json']; +export type MuteCreateRequest = operations['mute___create']['requestBody']['content']['application/json']; +export type MuteDeleteRequest = operations['mute___delete']['requestBody']['content']['application/json']; +export type MuteListRequest = operations['mute___list']['requestBody']['content']['application/json']; +export type MuteListResponse = operations['mute___list']['responses']['200']['content']['application/json']; +export type RenoteMuteCreateRequest = operations['renote-mute___create']['requestBody']['content']['application/json']; +export type RenoteMuteDeleteRequest = operations['renote-mute___delete']['requestBody']['content']['application/json']; +export type RenoteMuteListRequest = operations['renote-mute___list']['requestBody']['content']['application/json']; +export type RenoteMuteListResponse = operations['renote-mute___list']['responses']['200']['content']['application/json']; +export type MyAppsRequest = operations['my___apps']['requestBody']['content']['application/json']; +export type MyAppsResponse = operations['my___apps']['responses']['200']['content']['application/json']; export type NotesRequest = operations['notes']['requestBody']['content']['application/json']; export type NotesResponse = operations['notes']['responses']['200']['content']['application/json']; -export type NotesChildrenRequest = operations['notes/children']['requestBody']['content']['application/json']; -export type NotesChildrenResponse = operations['notes/children']['responses']['200']['content']['application/json']; -export type NotesClipsRequest = operations['notes/clips']['requestBody']['content']['application/json']; -export type NotesClipsResponse = operations['notes/clips']['responses']['200']['content']['application/json']; -export type NotesConversationRequest = operations['notes/conversation']['requestBody']['content']['application/json']; -export type NotesConversationResponse = operations['notes/conversation']['responses']['200']['content']['application/json']; -export type NotesCreateRequest = operations['notes/create']['requestBody']['content']['application/json']; -export type NotesCreateResponse = operations['notes/create']['responses']['200']['content']['application/json']; -export type NotesDeleteRequest = operations['notes/delete']['requestBody']['content']['application/json']; -export type NotesUpdateRequest = operations['notes/update']['requestBody']['content']['application/json']; -export type NotesFavoritesCreateRequest = operations['notes/favorites/create']['requestBody']['content']['application/json']; -export type NotesFavoritesDeleteRequest = operations['notes/favorites/delete']['requestBody']['content']['application/json']; -export type NotesFeaturedRequest = operations['notes/featured']['requestBody']['content']['application/json']; -export type NotesFeaturedResponse = operations['notes/featured']['responses']['200']['content']['application/json']; -export type NotesGlobalTimelineRequest = operations['notes/global-timeline']['requestBody']['content']['application/json']; -export type NotesGlobalTimelineResponse = operations['notes/global-timeline']['responses']['200']['content']['application/json']; -export type NotesHybridTimelineRequest = operations['notes/hybrid-timeline']['requestBody']['content']['application/json']; -export type NotesHybridTimelineResponse = operations['notes/hybrid-timeline']['responses']['200']['content']['application/json']; -export type NotesLocalTimelineRequest = operations['notes/local-timeline']['requestBody']['content']['application/json']; -export type NotesLocalTimelineResponse = operations['notes/local-timeline']['responses']['200']['content']['application/json']; -export type NotesMentionsRequest = operations['notes/mentions']['requestBody']['content']['application/json']; -export type NotesMentionsResponse = operations['notes/mentions']['responses']['200']['content']['application/json']; -export type NotesPollsRecommendationRequest = operations['notes/polls/recommendation']['requestBody']['content']['application/json']; -export type NotesPollsRecommendationResponse = operations['notes/polls/recommendation']['responses']['200']['content']['application/json']; -export type NotesPollsVoteRequest = operations['notes/polls/vote']['requestBody']['content']['application/json']; -export type NotesEventsSearchRequest = operations['notes/events/search']['requestBody']['content']['application/json']; -export type NotesEventsSearchResponse = operations['notes/events/search']['responses']['200']['content']['application/json']; -export type NotesReactionsRequest = operations['notes/reactions']['requestBody']['content']['application/json']; -export type NotesReactionsResponse = operations['notes/reactions']['responses']['200']['content']['application/json']; -export type NotesReactionsCreateRequest = operations['notes/reactions/create']['requestBody']['content']['application/json']; -export type NotesReactionsDeleteRequest = operations['notes/reactions/delete']['requestBody']['content']['application/json']; -export type NotesRenotesRequest = operations['notes/renotes']['requestBody']['content']['application/json']; -export type NotesRenotesResponse = operations['notes/renotes']['responses']['200']['content']['application/json']; -export type NotesRepliesRequest = operations['notes/replies']['requestBody']['content']['application/json']; -export type NotesRepliesResponse = operations['notes/replies']['responses']['200']['content']['application/json']; -export type NotesSearchByTagRequest = operations['notes/search-by-tag']['requestBody']['content']['application/json']; -export type NotesSearchByTagResponse = operations['notes/search-by-tag']['responses']['200']['content']['application/json']; -export type NotesSearchRequest = operations['notes/search']['requestBody']['content']['application/json']; -export type NotesSearchResponse = operations['notes/search']['responses']['200']['content']['application/json']; -export type NotesShowRequest = operations['notes/show']['requestBody']['content']['application/json']; -export type NotesShowResponse = operations['notes/show']['responses']['200']['content']['application/json']; -export type NotesStateRequest = operations['notes/state']['requestBody']['content']['application/json']; -export type NotesStateResponse = operations['notes/state']['responses']['200']['content']['application/json']; -export type NotesThreadMutingCreateRequest = operations['notes/thread-muting/create']['requestBody']['content']['application/json']; -export type NotesThreadMutingDeleteRequest = operations['notes/thread-muting/delete']['requestBody']['content']['application/json']; -export type NotesTimelineRequest = operations['notes/timeline']['requestBody']['content']['application/json']; -export type NotesTimelineResponse = operations['notes/timeline']['responses']['200']['content']['application/json']; -export type NotesTranslateRequest = operations['notes/translate']['requestBody']['content']['application/json']; -export type NotesTranslateResponse = operations['notes/translate']['responses']['200']['content']['application/json']; -export type NotesUnrenoteRequest = operations['notes/unrenote']['requestBody']['content']['application/json']; -export type NotesUserListTimelineRequest = operations['notes/user-list-timeline']['requestBody']['content']['application/json']; -export type NotesUserListTimelineResponse = operations['notes/user-list-timeline']['responses']['200']['content']['application/json']; -export type NotificationsCreateRequest = operations['notifications/create']['requestBody']['content']['application/json']; +export type NotesChildrenRequest = operations['notes___children']['requestBody']['content']['application/json']; +export type NotesChildrenResponse = operations['notes___children']['responses']['200']['content']['application/json']; +export type NotesClipsRequest = operations['notes___clips']['requestBody']['content']['application/json']; +export type NotesClipsResponse = operations['notes___clips']['responses']['200']['content']['application/json']; +export type NotesConversationRequest = operations['notes___conversation']['requestBody']['content']['application/json']; +export type NotesConversationResponse = operations['notes___conversation']['responses']['200']['content']['application/json']; +export type NotesCreateRequest = operations['notes___create']['requestBody']['content']['application/json']; +export type NotesCreateResponse = operations['notes___create']['responses']['200']['content']['application/json']; +export type NotesDeleteRequest = operations['notes___delete']['requestBody']['content']['application/json']; +export type NotesUpdateRequest = operations['notes___update']['requestBody']['content']['application/json']; +export type NotesFavoritesCreateRequest = operations['notes___favorites___create']['requestBody']['content']['application/json']; +export type NotesFavoritesDeleteRequest = operations['notes___favorites___delete']['requestBody']['content']['application/json']; +export type NotesFeaturedRequest = operations['notes___featured']['requestBody']['content']['application/json']; +export type NotesFeaturedResponse = operations['notes___featured']['responses']['200']['content']['application/json']; +export type NotesGlobalTimelineRequest = operations['notes___global-timeline']['requestBody']['content']['application/json']; +export type NotesGlobalTimelineResponse = operations['notes___global-timeline']['responses']['200']['content']['application/json']; +export type NotesHybridTimelineRequest = operations['notes___hybrid-timeline']['requestBody']['content']['application/json']; +export type NotesHybridTimelineResponse = operations['notes___hybrid-timeline']['responses']['200']['content']['application/json']; +export type NotesLocalTimelineRequest = operations['notes___local-timeline']['requestBody']['content']['application/json']; +export type NotesLocalTimelineResponse = operations['notes___local-timeline']['responses']['200']['content']['application/json']; +export type NotesMentionsRequest = operations['notes___mentions']['requestBody']['content']['application/json']; +export type NotesMentionsResponse = operations['notes___mentions']['responses']['200']['content']['application/json']; +export type NotesPollsRecommendationRequest = operations['notes___polls___recommendation']['requestBody']['content']['application/json']; +export type NotesPollsRecommendationResponse = operations['notes___polls___recommendation']['responses']['200']['content']['application/json']; +export type NotesPollsVoteRequest = operations['notes___polls___vote']['requestBody']['content']['application/json']; +export type NotesEventsSearchRequest = operations['notes___events___search']['requestBody']['content']['application/json']; +export type NotesEventsSearchResponse = operations['notes___events___search']['responses']['200']['content']['application/json']; +export type NotesReactionsRequest = operations['notes___reactions']['requestBody']['content']['application/json']; +export type NotesReactionsResponse = operations['notes___reactions']['responses']['200']['content']['application/json']; +export type NotesReactionsCreateRequest = operations['notes___reactions___create']['requestBody']['content']['application/json']; +export type NotesReactionsDeleteRequest = operations['notes___reactions___delete']['requestBody']['content']['application/json']; +export type NotesRenotesRequest = operations['notes___renotes']['requestBody']['content']['application/json']; +export type NotesRenotesResponse = operations['notes___renotes']['responses']['200']['content']['application/json']; +export type NotesRepliesRequest = operations['notes___replies']['requestBody']['content']['application/json']; +export type NotesRepliesResponse = operations['notes___replies']['responses']['200']['content']['application/json']; +export type NotesSearchByTagRequest = operations['notes___search-by-tag']['requestBody']['content']['application/json']; +export type NotesSearchByTagResponse = operations['notes___search-by-tag']['responses']['200']['content']['application/json']; +export type NotesSearchRequest = operations['notes___search']['requestBody']['content']['application/json']; +export type NotesSearchResponse = operations['notes___search']['responses']['200']['content']['application/json']; +export type NotesShowRequest = operations['notes___show']['requestBody']['content']['application/json']; +export type NotesShowResponse = operations['notes___show']['responses']['200']['content']['application/json']; +export type NotesStateRequest = operations['notes___state']['requestBody']['content']['application/json']; +export type NotesStateResponse = operations['notes___state']['responses']['200']['content']['application/json']; +export type NotesThreadMutingCreateRequest = operations['notes___thread-muting___create']['requestBody']['content']['application/json']; +export type NotesThreadMutingDeleteRequest = operations['notes___thread-muting___delete']['requestBody']['content']['application/json']; +export type NotesTimelineRequest = operations['notes___timeline']['requestBody']['content']['application/json']; +export type NotesTimelineResponse = operations['notes___timeline']['responses']['200']['content']['application/json']; +export type NotesTranslateRequest = operations['notes___translate']['requestBody']['content']['application/json']; +export type NotesTranslateResponse = operations['notes___translate']['responses']['200']['content']['application/json']; +export type NotesUnrenoteRequest = operations['notes___unrenote']['requestBody']['content']['application/json']; +export type NotesUserListTimelineRequest = operations['notes___user-list-timeline']['requestBody']['content']['application/json']; +export type NotesUserListTimelineResponse = operations['notes___user-list-timeline']['responses']['200']['content']['application/json']; +export type NotificationsCreateRequest = operations['notifications___create']['requestBody']['content']['application/json']; export type PagePushRequest = operations['page-push']['requestBody']['content']['application/json']; -export type PagesCreateRequest = operations['pages/create']['requestBody']['content']['application/json']; -export type PagesCreateResponse = operations['pages/create']['responses']['200']['content']['application/json']; -export type PagesDeleteRequest = operations['pages/delete']['requestBody']['content']['application/json']; -export type PagesFeaturedResponse = operations['pages/featured']['responses']['200']['content']['application/json']; -export type PagesLikeRequest = operations['pages/like']['requestBody']['content']['application/json']; -export type PagesShowRequest = operations['pages/show']['requestBody']['content']['application/json']; -export type PagesShowResponse = operations['pages/show']['responses']['200']['content']['application/json']; -export type PagesUnlikeRequest = operations['pages/unlike']['requestBody']['content']['application/json']; -export type PagesUpdateRequest = operations['pages/update']['requestBody']['content']['application/json']; -export type FlashCreateRequest = operations['flash/create']['requestBody']['content']['application/json']; -export type FlashCreateResponse = operations['flash/create']['responses']['200']['content']['application/json']; -export type FlashDeleteRequest = operations['flash/delete']['requestBody']['content']['application/json']; -export type FlashFeaturedResponse = operations['flash/featured']['responses']['200']['content']['application/json']; -export type FlashGenTokenRequest = operations['flash/gen-token']['requestBody']['content']['application/json']; -export type FlashGenTokenResponse = operations['flash/gen-token']['responses']['200']['content']['application/json']; -export type FlashLikeRequest = operations['flash/like']['requestBody']['content']['application/json']; -export type FlashShowRequest = operations['flash/show']['requestBody']['content']['application/json']; -export type FlashShowResponse = operations['flash/show']['responses']['200']['content']['application/json']; -export type FlashUnlikeRequest = operations['flash/unlike']['requestBody']['content']['application/json']; -export type FlashUpdateRequest = operations['flash/update']['requestBody']['content']['application/json']; -export type FlashMyRequest = operations['flash/my']['requestBody']['content']['application/json']; -export type FlashMyResponse = operations['flash/my']['responses']['200']['content']['application/json']; -export type FlashMyLikesRequest = operations['flash/my-likes']['requestBody']['content']['application/json']; -export type FlashMyLikesResponse = operations['flash/my-likes']['responses']['200']['content']['application/json']; +export type PagesCreateRequest = operations['pages___create']['requestBody']['content']['application/json']; +export type PagesCreateResponse = operations['pages___create']['responses']['200']['content']['application/json']; +export type PagesDeleteRequest = operations['pages___delete']['requestBody']['content']['application/json']; +export type PagesFeaturedResponse = operations['pages___featured']['responses']['200']['content']['application/json']; +export type PagesLikeRequest = operations['pages___like']['requestBody']['content']['application/json']; +export type PagesShowRequest = operations['pages___show']['requestBody']['content']['application/json']; +export type PagesShowResponse = operations['pages___show']['responses']['200']['content']['application/json']; +export type PagesUnlikeRequest = operations['pages___unlike']['requestBody']['content']['application/json']; +export type PagesUpdateRequest = operations['pages___update']['requestBody']['content']['application/json']; +export type FlashCreateRequest = operations['flash___create']['requestBody']['content']['application/json']; +export type FlashCreateResponse = operations['flash___create']['responses']['200']['content']['application/json']; +export type FlashDeleteRequest = operations['flash___delete']['requestBody']['content']['application/json']; +export type FlashFeaturedResponse = operations['flash___featured']['responses']['200']['content']['application/json']; +export type FlashGenTokenRequest = operations['flash___gen-token']['requestBody']['content']['application/json']; +export type FlashGenTokenResponse = operations['flash___gen-token']['responses']['200']['content']['application/json']; +export type FlashLikeRequest = operations['flash___like']['requestBody']['content']['application/json']; +export type FlashShowRequest = operations['flash___show']['requestBody']['content']['application/json']; +export type FlashShowResponse = operations['flash___show']['responses']['200']['content']['application/json']; +export type FlashUnlikeRequest = operations['flash___unlike']['requestBody']['content']['application/json']; +export type FlashUpdateRequest = operations['flash___update']['requestBody']['content']['application/json']; +export type FlashMyRequest = operations['flash___my']['requestBody']['content']['application/json']; +export type FlashMyResponse = operations['flash___my']['responses']['200']['content']['application/json']; +export type FlashMyLikesRequest = operations['flash___my-likes']['requestBody']['content']['application/json']; +export type FlashMyLikesResponse = operations['flash___my-likes']['responses']['200']['content']['application/json']; export type PingResponse = operations['ping']['responses']['200']['content']['application/json']; export type PinnedUsersResponse = operations['pinned-users']['responses']['200']['content']['application/json']; -export type PromoReadRequest = operations['promo/read']['requestBody']['content']['application/json']; -export type RolesListResponse = operations['roles/list']['responses']['200']['content']['application/json']; -export type RolesShowRequest = operations['roles/show']['requestBody']['content']['application/json']; -export type RolesShowResponse = operations['roles/show']['responses']['200']['content']['application/json']; -export type RolesUsersRequest = operations['roles/users']['requestBody']['content']['application/json']; -export type RolesUsersResponse = operations['roles/users']['responses']['200']['content']['application/json']; -export type RolesNotesRequest = operations['roles/notes']['requestBody']['content']['application/json']; -export type RolesNotesResponse = operations['roles/notes']['responses']['200']['content']['application/json']; +export type PromoReadRequest = operations['promo___read']['requestBody']['content']['application/json']; +export type RolesListResponse = operations['roles___list']['responses']['200']['content']['application/json']; +export type RolesShowRequest = operations['roles___show']['requestBody']['content']['application/json']; +export type RolesShowResponse = operations['roles___show']['responses']['200']['content']['application/json']; +export type RolesUsersRequest = operations['roles___users']['requestBody']['content']['application/json']; +export type RolesUsersResponse = operations['roles___users']['responses']['200']['content']['application/json']; +export type RolesNotesRequest = operations['roles___notes']['requestBody']['content']['application/json']; +export type RolesNotesResponse = operations['roles___notes']['responses']['200']['content']['application/json']; export type RequestResetPasswordRequest = operations['request-reset-password']['requestBody']['content']['application/json']; export type ResetPasswordRequest = operations['reset-password']['requestBody']['content']['application/json']; export type ServerInfoResponse = operations['server-info']['responses']['200']['content']['application/json']; export type StatsResponse = operations['stats']['responses']['200']['content']['application/json']; -export type SwShowRegistrationRequest = operations['sw/show-registration']['requestBody']['content']['application/json']; -export type SwShowRegistrationResponse = operations['sw/show-registration']['responses']['200']['content']['application/json']; -export type SwUpdateRegistrationRequest = operations['sw/update-registration']['requestBody']['content']['application/json']; -export type SwUpdateRegistrationResponse = operations['sw/update-registration']['responses']['200']['content']['application/json']; -export type SwRegisterRequest = operations['sw/register']['requestBody']['content']['application/json']; -export type SwRegisterResponse = operations['sw/register']['responses']['200']['content']['application/json']; -export type SwUnregisterRequest = operations['sw/unregister']['requestBody']['content']['application/json']; +export type SwShowRegistrationRequest = operations['sw___show-registration']['requestBody']['content']['application/json']; +export type SwShowRegistrationResponse = operations['sw___show-registration']['responses']['200']['content']['application/json']; +export type SwUpdateRegistrationRequest = operations['sw___update-registration']['requestBody']['content']['application/json']; +export type SwUpdateRegistrationResponse = operations['sw___update-registration']['responses']['200']['content']['application/json']; +export type SwRegisterRequest = operations['sw___register']['requestBody']['content']['application/json']; +export type SwRegisterResponse = operations['sw___register']['responses']['200']['content']['application/json']; +export type SwUnregisterRequest = operations['sw___unregister']['requestBody']['content']['application/json']; export type TestRequest = operations['test']['requestBody']['content']['application/json']; export type TestResponse = operations['test']['responses']['200']['content']['application/json']; -export type UsernameAvailableRequest = operations['username/available']['requestBody']['content']['application/json']; -export type UsernameAvailableResponse = operations['username/available']['responses']['200']['content']['application/json']; +export type UsernameAvailableRequest = operations['username___available']['requestBody']['content']['application/json']; +export type UsernameAvailableResponse = operations['username___available']['responses']['200']['content']['application/json']; export type UsersRequest = operations['users']['requestBody']['content']['application/json']; export type UsersResponse = operations['users']['responses']['200']['content']['application/json']; -export type UsersClipsRequest = operations['users/clips']['requestBody']['content']['application/json']; -export type UsersClipsResponse = operations['users/clips']['responses']['200']['content']['application/json']; -export type UsersFollowersRequest = operations['users/followers']['requestBody']['content']['application/json']; -export type UsersFollowersResponse = operations['users/followers']['responses']['200']['content']['application/json']; -export type UsersFollowingRequest = operations['users/following']['requestBody']['content']['application/json']; -export type UsersFollowingResponse = operations['users/following']['responses']['200']['content']['application/json']; -export type UsersGalleryPostsRequest = operations['users/gallery/posts']['requestBody']['content']['application/json']; -export type UsersGalleryPostsResponse = operations['users/gallery/posts']['responses']['200']['content']['application/json']; -export type UsersGetFrequentlyRepliedUsersRequest = operations['users/get-frequently-replied-users']['requestBody']['content']['application/json']; -export type UsersGetFrequentlyRepliedUsersResponse = operations['users/get-frequently-replied-users']['responses']['200']['content']['application/json']; -export type UsersFeaturedNotesRequest = operations['users/featured-notes']['requestBody']['content']['application/json']; -export type UsersFeaturedNotesResponse = operations['users/featured-notes']['responses']['200']['content']['application/json']; -export type UsersGroupsCreateRequest = operations['users/groups/create']['requestBody']['content']['application/json']; -export type UsersGroupsCreateResponse = operations['users/groups/create']['responses']['200']['content']['application/json']; -export type UsersGroupsDeleteRequest = operations['users/groups/delete']['requestBody']['content']['application/json']; -export type UsersGroupsInvitationsAcceptRequest = operations['users/groups/invitations/accept']['requestBody']['content']['application/json']; -export type UsersGroupsInvitationsRejectRequest = operations['users/groups/invitations/reject']['requestBody']['content']['application/json']; -export type UsersGroupsInviteRequest = operations['users/groups/invite']['requestBody']['content']['application/json']; -export type UsersGroupsJoinedResponse = operations['users/groups/joined']['responses']['200']['content']['application/json']; -export type UsersGroupsLeaveRequest = operations['users/groups/leave']['requestBody']['content']['application/json']; -export type UsersGroupsOwnedResponse = operations['users/groups/owned']['responses']['200']['content']['application/json']; -export type UsersGroupsPullRequest = operations['users/groups/pull']['requestBody']['content']['application/json']; -export type UsersGroupsShowRequest = operations['users/groups/show']['requestBody']['content']['application/json']; -export type UsersGroupsShowResponse = operations['users/groups/show']['responses']['200']['content']['application/json']; -export type UsersGroupsTransferRequest = operations['users/groups/transfer']['requestBody']['content']['application/json']; -export type UsersGroupsTransferResponse = operations['users/groups/transfer']['responses']['200']['content']['application/json']; -export type UsersGroupsUpdateRequest = operations['users/groups/update']['requestBody']['content']['application/json']; -export type UsersGroupsUpdateResponse = operations['users/groups/update']['responses']['200']['content']['application/json']; -export type UsersListsCreateRequest = operations['users/lists/create']['requestBody']['content']['application/json']; -export type UsersListsCreateResponse = operations['users/lists/create']['responses']['200']['content']['application/json']; -export type UsersListsDeleteRequest = operations['users/lists/delete']['requestBody']['content']['application/json']; -export type UsersListsListRequest = operations['users/lists/list']['requestBody']['content']['application/json']; -export type UsersListsListResponse = operations['users/lists/list']['responses']['200']['content']['application/json']; -export type UsersListsPullRequest = operations['users/lists/pull']['requestBody']['content']['application/json']; -export type UsersListsPushRequest = operations['users/lists/push']['requestBody']['content']['application/json']; -export type UsersListsShowRequest = operations['users/lists/show']['requestBody']['content']['application/json']; -export type UsersListsShowResponse = operations['users/lists/show']['responses']['200']['content']['application/json']; -export type UsersListsFavoriteRequest = operations['users/lists/favorite']['requestBody']['content']['application/json']; -export type UsersListsUnfavoriteRequest = operations['users/lists/unfavorite']['requestBody']['content']['application/json']; -export type UsersListsUpdateRequest = operations['users/lists/update']['requestBody']['content']['application/json']; -export type UsersListsUpdateResponse = operations['users/lists/update']['responses']['200']['content']['application/json']; -export type UsersListsCreateFromPublicRequest = operations['users/lists/create-from-public']['requestBody']['content']['application/json']; -export type UsersListsCreateFromPublicResponse = operations['users/lists/create-from-public']['responses']['200']['content']['application/json']; -export type UsersListsUpdateMembershipRequest = operations['users/lists/update-membership']['requestBody']['content']['application/json']; -export type UsersListsGetMembershipsRequest = operations['users/lists/get-memberships']['requestBody']['content']['application/json']; -export type UsersListsGetMembershipsResponse = operations['users/lists/get-memberships']['responses']['200']['content']['application/json']; -export type UsersNotesRequest = operations['users/notes']['requestBody']['content']['application/json']; -export type UsersNotesResponse = operations['users/notes']['responses']['200']['content']['application/json']; -export type UsersPagesRequest = operations['users/pages']['requestBody']['content']['application/json']; -export type UsersPagesResponse = operations['users/pages']['responses']['200']['content']['application/json']; -export type UsersFlashsRequest = operations['users/flashs']['requestBody']['content']['application/json']; -export type UsersFlashsResponse = operations['users/flashs']['responses']['200']['content']['application/json']; -export type UsersReactionsRequest = operations['users/reactions']['requestBody']['content']['application/json']; -export type UsersReactionsResponse = operations['users/reactions']['responses']['200']['content']['application/json']; -export type UsersRecommendationRequest = operations['users/recommendation']['requestBody']['content']['application/json']; -export type UsersRecommendationResponse = operations['users/recommendation']['responses']['200']['content']['application/json']; -export type UsersRelationRequest = operations['users/relation']['requestBody']['content']['application/json']; -export type UsersRelationResponse = operations['users/relation']['responses']['200']['content']['application/json']; -export type UsersReportAbuseRequest = operations['users/report-abuse']['requestBody']['content']['application/json']; -export type UsersSearchByUsernameAndHostRequest = operations['users/search-by-username-and-host']['requestBody']['content']['application/json']; -export type UsersSearchByUsernameAndHostResponse = operations['users/search-by-username-and-host']['responses']['200']['content']['application/json']; -export type UsersSearchRequest = operations['users/search']['requestBody']['content']['application/json']; -export type UsersSearchResponse = operations['users/search']['responses']['200']['content']['application/json']; -export type UsersShowRequest = operations['users/show']['requestBody']['content']['application/json']; -export type UsersShowResponse = operations['users/show']['responses']['200']['content']['application/json']; -export type UsersStatsRequest = operations['users/stats']['requestBody']['content']['application/json']; -export type UsersStatsResponse = operations['users/stats']['responses']['200']['content']['application/json']; -export type UsersAchievementsRequest = operations['users/achievements']['requestBody']['content']['application/json']; -export type UsersAchievementsResponse = operations['users/achievements']['responses']['200']['content']['application/json']; -export type UsersUpdateMemoRequest = operations['users/update-memo']['requestBody']['content']['application/json']; -export type UsersTranslateRequest = operations['users/translate']['requestBody']['content']['application/json']; -export type UsersTranslateResponse = operations['users/translate']['responses']['200']['content']['application/json']; +export type UsersClipsRequest = operations['users___clips']['requestBody']['content']['application/json']; +export type UsersClipsResponse = operations['users___clips']['responses']['200']['content']['application/json']; +export type UsersFollowersRequest = operations['users___followers']['requestBody']['content']['application/json']; +export type UsersFollowersResponse = operations['users___followers']['responses']['200']['content']['application/json']; +export type UsersFollowingRequest = operations['users___following']['requestBody']['content']['application/json']; +export type UsersFollowingResponse = operations['users___following']['responses']['200']['content']['application/json']; +export type UsersGalleryPostsRequest = operations['users___gallery___posts']['requestBody']['content']['application/json']; +export type UsersGalleryPostsResponse = operations['users___gallery___posts']['responses']['200']['content']['application/json']; +export type UsersGetFrequentlyRepliedUsersRequest = operations['users___get-frequently-replied-users']['requestBody']['content']['application/json']; +export type UsersGetFrequentlyRepliedUsersResponse = operations['users___get-frequently-replied-users']['responses']['200']['content']['application/json']; +export type UsersFeaturedNotesRequest = operations['users___featured-notes']['requestBody']['content']['application/json']; +export type UsersFeaturedNotesResponse = operations['users___featured-notes']['responses']['200']['content']['application/json']; +export type UsersGroupsCreateRequest = operations['users___groups___create']['requestBody']['content']['application/json']; +export type UsersGroupsCreateResponse = operations['users___groups___create']['responses']['200']['content']['application/json']; +export type UsersGroupsDeleteRequest = operations['users___groups___delete']['requestBody']['content']['application/json']; +export type UsersGroupsInvitationsAcceptRequest = operations['users___groups___invitations___accept']['requestBody']['content']['application/json']; +export type UsersGroupsInvitationsRejectRequest = operations['users___groups___invitations___reject']['requestBody']['content']['application/json']; +export type UsersGroupsInviteRequest = operations['users___groups___invite']['requestBody']['content']['application/json']; +export type UsersGroupsJoinedResponse = operations['users___groups___joined']['responses']['200']['content']['application/json']; +export type UsersGroupsLeaveRequest = operations['users___groups___leave']['requestBody']['content']['application/json']; +export type UsersGroupsOwnedResponse = operations['users___groups___owned']['responses']['200']['content']['application/json']; +export type UsersGroupsPullRequest = operations['users___groups___pull']['requestBody']['content']['application/json']; +export type UsersGroupsShowRequest = operations['users___groups___show']['requestBody']['content']['application/json']; +export type UsersGroupsShowResponse = operations['users___groups___show']['responses']['200']['content']['application/json']; +export type UsersGroupsTransferRequest = operations['users___groups___transfer']['requestBody']['content']['application/json']; +export type UsersGroupsTransferResponse = operations['users___groups___transfer']['responses']['200']['content']['application/json']; +export type UsersGroupsUpdateRequest = operations['users___groups___update']['requestBody']['content']['application/json']; +export type UsersGroupsUpdateResponse = operations['users___groups___update']['responses']['200']['content']['application/json']; +export type UsersListsCreateRequest = operations['users___lists___create']['requestBody']['content']['application/json']; +export type UsersListsCreateResponse = operations['users___lists___create']['responses']['200']['content']['application/json']; +export type UsersListsDeleteRequest = operations['users___lists___delete']['requestBody']['content']['application/json']; +export type UsersListsListRequest = operations['users___lists___list']['requestBody']['content']['application/json']; +export type UsersListsListResponse = operations['users___lists___list']['responses']['200']['content']['application/json']; +export type UsersListsPullRequest = operations['users___lists___pull']['requestBody']['content']['application/json']; +export type UsersListsPushRequest = operations['users___lists___push']['requestBody']['content']['application/json']; +export type UsersListsShowRequest = operations['users___lists___show']['requestBody']['content']['application/json']; +export type UsersListsShowResponse = operations['users___lists___show']['responses']['200']['content']['application/json']; +export type UsersListsFavoriteRequest = operations['users___lists___favorite']['requestBody']['content']['application/json']; +export type UsersListsUnfavoriteRequest = operations['users___lists___unfavorite']['requestBody']['content']['application/json']; +export type UsersListsUpdateRequest = operations['users___lists___update']['requestBody']['content']['application/json']; +export type UsersListsUpdateResponse = operations['users___lists___update']['responses']['200']['content']['application/json']; +export type UsersListsCreateFromPublicRequest = operations['users___lists___create-from-public']['requestBody']['content']['application/json']; +export type UsersListsCreateFromPublicResponse = operations['users___lists___create-from-public']['responses']['200']['content']['application/json']; +export type UsersListsUpdateMembershipRequest = operations['users___lists___update-membership']['requestBody']['content']['application/json']; +export type UsersListsGetMembershipsRequest = operations['users___lists___get-memberships']['requestBody']['content']['application/json']; +export type UsersListsGetMembershipsResponse = operations['users___lists___get-memberships']['responses']['200']['content']['application/json']; +export type UsersNotesRequest = operations['users___notes']['requestBody']['content']['application/json']; +export type UsersNotesResponse = operations['users___notes']['responses']['200']['content']['application/json']; +export type UsersPagesRequest = operations['users___pages']['requestBody']['content']['application/json']; +export type UsersPagesResponse = operations['users___pages']['responses']['200']['content']['application/json']; +export type UsersFlashsRequest = operations['users___flashs']['requestBody']['content']['application/json']; +export type UsersFlashsResponse = operations['users___flashs']['responses']['200']['content']['application/json']; +export type UsersReactionsRequest = operations['users___reactions']['requestBody']['content']['application/json']; +export type UsersReactionsResponse = operations['users___reactions']['responses']['200']['content']['application/json']; +export type UsersRecommendationRequest = operations['users___recommendation']['requestBody']['content']['application/json']; +export type UsersRecommendationResponse = operations['users___recommendation']['responses']['200']['content']['application/json']; +export type UsersRelationRequest = operations['users___relation']['requestBody']['content']['application/json']; +export type UsersRelationResponse = operations['users___relation']['responses']['200']['content']['application/json']; +export type UsersReportAbuseRequest = operations['users___report-abuse']['requestBody']['content']['application/json']; +export type UsersSearchByUsernameAndHostRequest = operations['users___search-by-username-and-host']['requestBody']['content']['application/json']; +export type UsersSearchByUsernameAndHostResponse = operations['users___search-by-username-and-host']['responses']['200']['content']['application/json']; +export type UsersSearchRequest = operations['users___search']['requestBody']['content']['application/json']; +export type UsersSearchResponse = operations['users___search']['responses']['200']['content']['application/json']; +export type UsersShowRequest = operations['users___show']['requestBody']['content']['application/json']; +export type UsersShowResponse = operations['users___show']['responses']['200']['content']['application/json']; +export type UsersStatsRequest = operations['users___stats']['requestBody']['content']['application/json']; +export type UsersStatsResponse = operations['users___stats']['responses']['200']['content']['application/json']; +export type UsersAchievementsRequest = operations['users___achievements']['requestBody']['content']['application/json']; +export type UsersAchievementsResponse = operations['users___achievements']['responses']['200']['content']['application/json']; +export type UsersUpdateMemoRequest = operations['users___update-memo']['requestBody']['content']['application/json']; +export type UsersTranslateRequest = operations['users___translate']['requestBody']['content']['application/json']; +export type UsersTranslateResponse = operations['users___translate']['responses']['200']['content']['application/json']; export type FetchRssRequest = operations['fetch-rss']['requestBody']['content']['application/json']; export type FetchRssResponse = operations['fetch-rss']['responses']['200']['content']['application/json']; export type FetchExternalResourcesRequest = operations['fetch-external-resources']['requestBody']['content']['application/json']; export type FetchExternalResourcesResponse = operations['fetch-external-resources']['responses']['200']['content']['application/json']; export type RetentionResponse = operations['retention']['responses']['200']['content']['application/json']; -export type BubbleGameRegisterRequest = operations['bubble-game/register']['requestBody']['content']['application/json']; -export type BubbleGameRankingRequest = operations['bubble-game/ranking']['requestBody']['content']['application/json']; -export type BubbleGameRankingResponse = operations['bubble-game/ranking']['responses']['200']['content']['application/json']; -export type ReversiCancelMatchRequest = operations['reversi/cancel-match']['requestBody']['content']['application/json']; -export type ReversiGamesRequest = operations['reversi/games']['requestBody']['content']['application/json']; -export type ReversiGamesResponse = operations['reversi/games']['responses']['200']['content']['application/json']; -export type ReversiMatchRequest = operations['reversi/match']['requestBody']['content']['application/json']; -export type ReversiMatchResponse = operations['reversi/match']['responses']['200']['content']['application/json']; -export type ReversiInvitationsResponse = operations['reversi/invitations']['responses']['200']['content']['application/json']; -export type ReversiShowGameRequest = operations['reversi/show-game']['requestBody']['content']['application/json']; -export type ReversiShowGameResponse = operations['reversi/show-game']['responses']['200']['content']['application/json']; -export type ReversiSurrenderRequest = operations['reversi/surrender']['requestBody']['content']['application/json']; -export type ReversiVerifyRequest = operations['reversi/verify']['requestBody']['content']['application/json']; -export type ReversiVerifyResponse = operations['reversi/verify']['responses']['200']['content']['application/json']; +export type BubbleGameRegisterRequest = operations['bubble-game___register']['requestBody']['content']['application/json']; +export type BubbleGameRankingRequest = operations['bubble-game___ranking']['requestBody']['content']['application/json']; +export type BubbleGameRankingResponse = operations['bubble-game___ranking']['responses']['200']['content']['application/json']; +export type ReversiCancelMatchRequest = operations['reversi___cancel-match']['requestBody']['content']['application/json']; +export type ReversiGamesRequest = operations['reversi___games']['requestBody']['content']['application/json']; +export type ReversiGamesResponse = operations['reversi___games']['responses']['200']['content']['application/json']; +export type ReversiMatchRequest = operations['reversi___match']['requestBody']['content']['application/json']; +export type ReversiMatchResponse = operations['reversi___match']['responses']['200']['content']['application/json']; +export type ReversiInvitationsResponse = operations['reversi___invitations']['responses']['200']['content']['application/json']; +export type ReversiShowGameRequest = operations['reversi___show-game']['requestBody']['content']['application/json']; +export type ReversiShowGameResponse = operations['reversi___show-game']['responses']['200']['content']['application/json']; +export type ReversiSurrenderRequest = operations['reversi___surrender']['requestBody']['content']['application/json']; +export type ReversiVerifyRequest = operations['reversi___verify']['requestBody']['content']['application/json']; +export type ReversiVerifyResponse = operations['reversi___verify']['responses']['200']['content']['application/json']; diff --git a/packages/cherrypick-js/src/autogen/models.ts b/packages/cherrypick-js/src/autogen/models.ts index 8737a33b77..77be7dbf41 100644 --- a/packages/cherrypick-js/src/autogen/models.ts +++ b/packages/cherrypick-js/src/autogen/models.ts @@ -40,6 +40,7 @@ export type Signin = components['schemas']['Signin']; export type RoleCondFormulaLogics = components['schemas']['RoleCondFormulaLogics']; export type RoleCondFormulaValueNot = components['schemas']['RoleCondFormulaValueNot']; export type RoleCondFormulaValueIsLocalOrRemote = components['schemas']['RoleCondFormulaValueIsLocalOrRemote']; +export type RoleCondFormulaValueAssignedRole = components['schemas']['RoleCondFormulaValueAssignedRole']; export type RoleCondFormulaValueCreated = components['schemas']['RoleCondFormulaValueCreated']; export type RoleCondFormulaFollowersOrFollowingOrNotes = components['schemas']['RoleCondFormulaFollowersOrFollowingOrNotes']; export type RoleCondFormulaValue = components['schemas']['RoleCondFormulaValue']; @@ -48,3 +49,6 @@ export type Role = components['schemas']['Role']; export type RolePolicies = components['schemas']['RolePolicies']; export type ReversiGameLite = components['schemas']['ReversiGameLite']; export type ReversiGameDetailed = components['schemas']['ReversiGameDetailed']; +export type MetaLite = components['schemas']['MetaLite']; +export type MetaDetailedOnly = components['schemas']['MetaDetailedOnly']; +export type MetaDetailed = components['schemas']['MetaDetailed']; diff --git a/packages/cherrypick-js/src/autogen/types.ts b/packages/cherrypick-js/src/autogen/types.ts index 67f199ec60..558ff2c666 100644 --- a/packages/cherrypick-js/src/autogen/types.ts +++ b/packages/cherrypick-js/src/autogen/types.ts @@ -19,43 +19,47 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:meta* */ - post: operations['admin/meta']; + post: operations['admin___meta']; }; '/admin/abuse-report-resolver/create': { /** * admin/abuse-report-resolver/create * @description No description provided. * - * **Credential required**: *Yes* + * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *arr-create* */ - post: operations['admin/abuse-report-resolver/create']; + post: operations['admin___abuse-report-resolver___create']; }; '/admin/abuse-report-resolver/list': { /** * admin/abuse-report-resolver/list * @description No description provided. * - * **Credential required**: *Yes* + * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *arr-list* */ - post: operations['admin/abuse-report-resolver/list']; + post: operations['admin___abuse-report-resolver___list']; }; '/admin/abuse-report-resolver/delete': { /** * admin/abuse-report-resolver/delete * @description No description provided. * - * **Credential required**: *No* + * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. + * **Credential required**: *No* / **Permission**: *arr-delete* */ - post: operations['admin/abuse-report-resolver/delete']; + post: operations['admin___abuse-report-resolver___delete']; }; '/admin/abuse-report-resolver/update': { /** * admin/abuse-report-resolver/update * @description No description provided. * - * **Credential required**: *Yes* + * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *arr-update* */ - post: operations['admin/abuse-report-resolver/update']; + post: operations['admin___abuse-report-resolver___update']; }; '/admin/abuse-user-reports': { /** @@ -64,7 +68,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:abuse-user-reports* */ - post: operations['admin/abuse-user-reports']; + post: operations['admin___abuse-user-reports']; }; '/admin/accounts/create': { /** @@ -73,7 +77,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['admin/accounts/create']; + post: operations['admin___accounts___create']; }; '/admin/accounts/delete': { /** @@ -82,7 +86,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:account* */ - post: operations['admin/accounts/delete']; + post: operations['admin___accounts___delete']; }; '/admin/accounts/find-by-email': { /** @@ -91,7 +95,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:account* */ - post: operations['admin/accounts/find-by-email']; + post: operations['admin___accounts___find-by-email']; }; '/admin/ad/create': { /** @@ -100,7 +104,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:ad* */ - post: operations['admin/ad/create']; + post: operations['admin___ad___create']; }; '/admin/ad/delete': { /** @@ -109,7 +113,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:ad* */ - post: operations['admin/ad/delete']; + post: operations['admin___ad___delete']; }; '/admin/ad/list': { /** @@ -118,7 +122,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:ad* */ - post: operations['admin/ad/list']; + post: operations['admin___ad___list']; }; '/admin/ad/update': { /** @@ -127,7 +131,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:ad* */ - post: operations['admin/ad/update']; + post: operations['admin___ad___update']; }; '/admin/announcements/create': { /** @@ -136,7 +140,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:announcements* */ - post: operations['admin/announcements/create']; + post: operations['admin___announcements___create']; }; '/admin/announcements/delete': { /** @@ -145,7 +149,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:announcements* */ - post: operations['admin/announcements/delete']; + post: operations['admin___announcements___delete']; }; '/admin/announcements/list': { /** @@ -154,7 +158,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:announcements* */ - post: operations['admin/announcements/list']; + post: operations['admin___announcements___list']; }; '/admin/announcements/update': { /** @@ -163,7 +167,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:announcements* */ - post: operations['admin/announcements/update']; + post: operations['admin___announcements___update']; }; '/admin/avatar-decorations/create': { /** @@ -172,7 +176,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:avatar-decorations* */ - post: operations['admin/avatar-decorations/create']; + post: operations['admin___avatar-decorations___create']; }; '/admin/avatar-decorations/delete': { /** @@ -181,7 +185,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:avatar-decorations* */ - post: operations['admin/avatar-decorations/delete']; + post: operations['admin___avatar-decorations___delete']; }; '/admin/avatar-decorations/list': { /** @@ -190,7 +194,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:avatar-decorations* */ - post: operations['admin/avatar-decorations/list']; + post: operations['admin___avatar-decorations___list']; }; '/admin/avatar-decorations/update': { /** @@ -199,7 +203,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:avatar-decorations* */ - post: operations['admin/avatar-decorations/update']; + post: operations['admin___avatar-decorations___update']; }; '/admin/delete-all-files-of-a-user': { /** @@ -208,7 +212,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:delete-all-files-of-a-user* */ - post: operations['admin/delete-all-files-of-a-user']; + post: operations['admin___delete-all-files-of-a-user']; }; '/admin/unset-user-avatar': { /** @@ -217,7 +221,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-avatar* */ - post: operations['admin/unset-user-avatar']; + post: operations['admin___unset-user-avatar']; }; '/admin/unset-user-banner': { /** @@ -226,7 +230,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-banner* */ - post: operations['admin/unset-user-banner']; + post: operations['admin___unset-user-banner']; }; '/admin/drive/clean-remote-files': { /** @@ -235,7 +239,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:drive* */ - post: operations['admin/drive/clean-remote-files']; + post: operations['admin___drive___clean-remote-files']; }; '/admin/drive/cleanup': { /** @@ -244,7 +248,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:drive* */ - post: operations['admin/drive/cleanup']; + post: operations['admin___drive___cleanup']; }; '/admin/drive/files': { /** @@ -253,7 +257,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:drive* */ - post: operations['admin/drive/files']; + post: operations['admin___drive___files']; }; '/admin/drive/show-file': { /** @@ -262,7 +266,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:drive* */ - post: operations['admin/drive/show-file']; + post: operations['admin___drive___show-file']; }; '/admin/emoji/add-aliases-bulk': { /** @@ -271,7 +275,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - post: operations['admin/emoji/add-aliases-bulk']; + post: operations['admin___emoji___add-aliases-bulk']; }; '/admin/emoji/add': { /** @@ -280,7 +284,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - post: operations['admin/emoji/add']; + post: operations['admin___emoji___add']; }; '/admin/emoji/adds': { /** @@ -289,7 +293,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - post: operations['admin/emoji/adds']; + post: operations['admin___emoji___adds']; }; '/admin/emoji/copy': { /** @@ -298,7 +302,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - post: operations['admin/emoji/copy']; + post: operations['admin___emoji___copy']; }; '/admin/emoji/delete-bulk': { /** @@ -307,7 +311,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - post: operations['admin/emoji/delete-bulk']; + post: operations['admin___emoji___delete-bulk']; }; '/admin/emoji/delete': { /** @@ -316,7 +320,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - post: operations['admin/emoji/delete']; + post: operations['admin___emoji___delete']; }; '/admin/emoji/import-zip': { /** @@ -326,7 +330,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['admin/emoji/import-zip']; + post: operations['admin___emoji___import-zip']; }; '/admin/emoji/list-remote': { /** @@ -335,7 +339,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ - post: operations['admin/emoji/list-remote']; + post: operations['admin___emoji___list-remote']; }; '/admin/emoji/list': { /** @@ -344,7 +348,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ - post: operations['admin/emoji/list']; + post: operations['admin___emoji___list']; }; '/admin/emoji/remove-aliases-bulk': { /** @@ -353,7 +357,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - post: operations['admin/emoji/remove-aliases-bulk']; + post: operations['admin___emoji___remove-aliases-bulk']; }; '/admin/emoji/set-aliases-bulk': { /** @@ -362,7 +366,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - post: operations['admin/emoji/set-aliases-bulk']; + post: operations['admin___emoji___set-aliases-bulk']; }; '/admin/emoji/set-category-bulk': { /** @@ -371,7 +375,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - post: operations['admin/emoji/set-category-bulk']; + post: operations['admin___emoji___set-category-bulk']; }; '/admin/emoji/set-license-bulk': { /** @@ -380,7 +384,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - post: operations['admin/emoji/set-license-bulk']; + post: operations['admin___emoji___set-license-bulk']; }; '/admin/emoji/steal': { /** @@ -389,7 +393,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - post: operations['admin/emoji/steal']; + post: operations['admin___emoji___steal']; }; '/admin/emoji/update': { /** @@ -398,7 +402,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - post: operations['admin/emoji/update']; + post: operations['admin___emoji___update']; }; '/admin/federation/delete-all-files': { /** @@ -407,7 +411,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ - post: operations['admin/federation/delete-all-files']; + post: operations['admin___federation___delete-all-files']; }; '/admin/federation/refresh-remote-instance-metadata': { /** @@ -416,7 +420,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ - post: operations['admin/federation/refresh-remote-instance-metadata']; + post: operations['admin___federation___refresh-remote-instance-metadata']; }; '/admin/federation/remove-all-following': { /** @@ -425,7 +429,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ - post: operations['admin/federation/remove-all-following']; + post: operations['admin___federation___remove-all-following']; }; '/admin/federation/update-instance': { /** @@ -434,7 +438,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ - post: operations['admin/federation/update-instance']; + post: operations['admin___federation___update-instance']; }; '/admin/get-index-stats': { /** @@ -443,7 +447,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:index-stats* */ - post: operations['admin/get-index-stats']; + post: operations['admin___get-index-stats']; }; '/admin/get-table-stats': { /** @@ -452,7 +456,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:table-stats* */ - post: operations['admin/get-table-stats']; + post: operations['admin___get-table-stats']; }; '/admin/get-user-ips': { /** @@ -461,7 +465,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:user-ips* */ - post: operations['admin/get-user-ips']; + post: operations['admin___get-user-ips']; }; '/admin/invite/create': { /** @@ -470,7 +474,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:invite-codes* */ - post: operations['admin/invite/create']; + post: operations['admin___invite___create']; }; '/admin/invite/list': { /** @@ -479,7 +483,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:invite-codes* */ - post: operations['admin/invite/list']; + post: operations['admin___invite___list']; }; '/admin/invite/revoke': { /** @@ -488,7 +492,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:invite-codes* */ - post: operations['admin/invite/revoke']; + post: operations['admin___invite___revoke']; }; '/admin/promo/create': { /** @@ -497,7 +501,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:promo* */ - post: operations['admin/promo/create']; + post: operations['admin___promo___create']; }; '/admin/queue/clear': { /** @@ -506,7 +510,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:queue* */ - post: operations['admin/queue/clear']; + post: operations['admin___queue___clear']; }; '/admin/queue/deliver-delayed': { /** @@ -515,7 +519,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:queue* */ - post: operations['admin/queue/deliver-delayed']; + post: operations['admin___queue___deliver-delayed']; }; '/admin/queue/inbox-delayed': { /** @@ -524,7 +528,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:queue* */ - post: operations['admin/queue/inbox-delayed']; + post: operations['admin___queue___inbox-delayed']; }; '/admin/queue/promote': { /** @@ -533,7 +537,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:queue* */ - post: operations['admin/queue/promote']; + post: operations['admin___queue___promote']; }; '/admin/queue/stats': { /** @@ -542,7 +546,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ - post: operations['admin/queue/stats']; + post: operations['admin___queue___stats']; }; '/admin/relays/add': { /** @@ -551,7 +555,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:relays* */ - post: operations['admin/relays/add']; + post: operations['admin___relays___add']; }; '/admin/relays/list': { /** @@ -560,7 +564,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:relays* */ - post: operations['admin/relays/list']; + post: operations['admin___relays___list']; }; '/admin/relays/remove': { /** @@ -569,7 +573,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:relays* */ - post: operations['admin/relays/remove']; + post: operations['admin___relays___remove']; }; '/admin/reset-password': { /** @@ -578,7 +582,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:reset-password* */ - post: operations['admin/reset-password']; + post: operations['admin___reset-password']; }; '/admin/resolve-abuse-user-report': { /** @@ -587,7 +591,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* */ - post: operations['admin/resolve-abuse-user-report']; + post: operations['admin___resolve-abuse-user-report']; }; '/admin/send-email': { /** @@ -596,7 +600,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:send-email* */ - post: operations['admin/send-email']; + post: operations['admin___send-email']; }; '/admin/server-info': { /** @@ -605,7 +609,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:server-info* */ - post: operations['admin/server-info']; + post: operations['admin___server-info']; }; '/admin/show-moderation-logs': { /** @@ -614,7 +618,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:show-moderation-log* */ - post: operations['admin/show-moderation-logs']; + post: operations['admin___show-moderation-logs']; }; '/admin/show-user': { /** @@ -623,7 +627,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:show-user* */ - post: operations['admin/show-user']; + post: operations['admin___show-user']; }; '/admin/show-users': { /** @@ -632,7 +636,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:show-users* */ - post: operations['admin/show-users']; + post: operations['admin___show-users']; }; '/admin/suspend-user': { /** @@ -641,7 +645,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:suspend-user* */ - post: operations['admin/suspend-user']; + post: operations['admin___suspend-user']; }; '/admin/unsuspend-user': { /** @@ -650,7 +654,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:unsuspend-user* */ - post: operations['admin/unsuspend-user']; + post: operations['admin___unsuspend-user']; }; '/admin/update-meta': { /** @@ -659,7 +663,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:meta* */ - post: operations['admin/update-meta']; + post: operations['admin___update-meta']; }; '/admin/delete-account': { /** @@ -668,7 +672,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:delete-account* */ - post: operations['admin/delete-account']; + post: operations['admin___delete-account']; }; '/admin/update-user-note': { /** @@ -677,7 +681,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:user-note* */ - post: operations['admin/update-user-note']; + post: operations['admin___update-user-note']; }; '/admin/roles/create': { /** @@ -686,7 +690,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - post: operations['admin/roles/create']; + post: operations['admin___roles___create']; }; '/admin/roles/delete': { /** @@ -695,7 +699,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - post: operations['admin/roles/delete']; + post: operations['admin___roles___delete']; }; '/admin/roles/list': { /** @@ -704,7 +708,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:roles* */ - post: operations['admin/roles/list']; + post: operations['admin___roles___list']; }; '/admin/roles/show': { /** @@ -713,7 +717,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:admin:roles* */ - post: operations['admin/roles/show']; + post: operations['admin___roles___show']; }; '/admin/roles/update': { /** @@ -722,7 +726,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - post: operations['admin/roles/update']; + post: operations['admin___roles___update']; }; '/admin/roles/assign': { /** @@ -731,7 +735,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - post: operations['admin/roles/assign']; + post: operations['admin___roles___assign']; }; '/admin/roles/unassign': { /** @@ -740,7 +744,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - post: operations['admin/roles/unassign']; + post: operations['admin___roles___unassign']; }; '/admin/roles/update-default-policies': { /** @@ -749,7 +753,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - post: operations['admin/roles/update-default-policies']; + post: operations['admin___roles___update-default-policies']; }; '/admin/roles/users': { /** @@ -758,7 +762,7 @@ export type paths = { * * **Credential required**: *No* / **Permission**: *read:admin:roles* */ - post: operations['admin/roles/users']; + post: operations['admin___roles___users']; }; '/announcements': { /** @@ -776,7 +780,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['antennas/create']; + post: operations['antennas___create']; }; '/antennas/delete': { /** @@ -785,7 +789,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['antennas/delete']; + post: operations['antennas___delete']; }; '/antennas/list': { /** @@ -794,7 +798,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['antennas/list']; + post: operations['antennas___list']; }; '/antennas/notes': { /** @@ -803,7 +807,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['antennas/notes']; + post: operations['antennas___notes']; }; '/antennas/show': { /** @@ -812,7 +816,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['antennas/show']; + post: operations['antennas___show']; }; '/antennas/update': { /** @@ -821,7 +825,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['antennas/update']; + post: operations['antennas___update']; }; '/ap/get': { /** @@ -830,7 +834,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:federation* */ - post: operations['ap/get']; + post: operations['ap___get']; }; '/ap/show': { /** @@ -839,7 +843,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['ap/show']; + post: operations['ap___show']; }; '/app/create': { /** @@ -848,7 +852,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['app/create']; + post: operations['app___create']; }; '/app/show': { /** @@ -857,7 +861,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['app/show']; + post: operations['app___show']; }; '/auth/accept': { /** @@ -867,7 +871,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['auth/accept']; + post: operations['auth___accept']; }; '/auth/session/generate': { /** @@ -876,7 +880,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['auth/session/generate']; + post: operations['auth___session___generate']; }; '/auth/session/show': { /** @@ -885,7 +889,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['auth/session/show']; + post: operations['auth___session___show']; }; '/auth/session/userkey': { /** @@ -894,7 +898,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['auth/session/userkey']; + post: operations['auth___session___userkey']; }; '/blocking/create': { /** @@ -903,7 +907,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:blocks* */ - post: operations['blocking/create']; + post: operations['blocking___create']; }; '/blocking/delete': { /** @@ -912,7 +916,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:blocks* */ - post: operations['blocking/delete']; + post: operations['blocking___delete']; }; '/blocking/list': { /** @@ -921,7 +925,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:blocks* */ - post: operations['blocking/list']; + post: operations['blocking___list']; }; '/channels/create': { /** @@ -930,7 +934,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:channels* */ - post: operations['channels/create']; + post: operations['channels___create']; }; '/channels/featured': { /** @@ -939,7 +943,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['channels/featured']; + post: operations['channels___featured']; }; '/channels/follow': { /** @@ -948,7 +952,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:channels* */ - post: operations['channels/follow']; + post: operations['channels___follow']; }; '/channels/followed': { /** @@ -957,7 +961,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:channels* */ - post: operations['channels/followed']; + post: operations['channels___followed']; }; '/channels/owned': { /** @@ -966,7 +970,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:channels* */ - post: operations['channels/owned']; + post: operations['channels___owned']; }; '/channels/show': { /** @@ -975,7 +979,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['channels/show']; + post: operations['channels___show']; }; '/channels/timeline': { /** @@ -984,7 +988,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['channels/timeline']; + post: operations['channels___timeline']; }; '/channels/unfollow': { /** @@ -993,7 +997,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:channels* */ - post: operations['channels/unfollow']; + post: operations['channels___unfollow']; }; '/channels/update': { /** @@ -1002,7 +1006,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:channels* */ - post: operations['channels/update']; + post: operations['channels___update']; }; '/channels/favorite': { /** @@ -1011,7 +1015,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:channels* */ - post: operations['channels/favorite']; + post: operations['channels___favorite']; }; '/channels/unfavorite': { /** @@ -1020,7 +1024,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:channels* */ - post: operations['channels/unfavorite']; + post: operations['channels___unfavorite']; }; '/channels/my-favorites': { /** @@ -1029,7 +1033,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:channels* */ - post: operations['channels/my-favorites']; + post: operations['channels___my-favorites']; }; '/channels/search': { /** @@ -1038,7 +1042,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['channels/search']; + post: operations['channels___search']; }; '/charts/active-users': { /** @@ -1047,14 +1051,14 @@ export type paths = { * * **Credential required**: *No* */ - get: operations['charts/active-users']; + get: operations['charts___active-users']; /** * charts/active-users * @description No description provided. * * **Credential required**: *No* */ - post: operations['charts/active-users']; + post: operations['charts___active-users']; }; '/charts/ap-request': { /** @@ -1063,14 +1067,14 @@ export type paths = { * * **Credential required**: *No* */ - get: operations['charts/ap-request']; + get: operations['charts___ap-request']; /** * charts/ap-request * @description No description provided. * * **Credential required**: *No* */ - post: operations['charts/ap-request']; + post: operations['charts___ap-request']; }; '/charts/drive': { /** @@ -1079,14 +1083,14 @@ export type paths = { * * **Credential required**: *No* */ - get: operations['charts/drive']; + get: operations['charts___drive']; /** * charts/drive * @description No description provided. * * **Credential required**: *No* */ - post: operations['charts/drive']; + post: operations['charts___drive']; }; '/charts/federation': { /** @@ -1095,14 +1099,14 @@ export type paths = { * * **Credential required**: *No* */ - get: operations['charts/federation']; + get: operations['charts___federation']; /** * charts/federation * @description No description provided. * * **Credential required**: *No* */ - post: operations['charts/federation']; + post: operations['charts___federation']; }; '/charts/instance': { /** @@ -1111,14 +1115,14 @@ export type paths = { * * **Credential required**: *No* */ - get: operations['charts/instance']; + get: operations['charts___instance']; /** * charts/instance * @description No description provided. * * **Credential required**: *No* */ - post: operations['charts/instance']; + post: operations['charts___instance']; }; '/charts/notes': { /** @@ -1127,14 +1131,14 @@ export type paths = { * * **Credential required**: *No* */ - get: operations['charts/notes']; + get: operations['charts___notes']; /** * charts/notes * @description No description provided. * * **Credential required**: *No* */ - post: operations['charts/notes']; + post: operations['charts___notes']; }; '/charts/user/drive': { /** @@ -1143,14 +1147,14 @@ export type paths = { * * **Credential required**: *No* */ - get: operations['charts/user/drive']; + get: operations['charts___user___drive']; /** * charts/user/drive * @description No description provided. * * **Credential required**: *No* */ - post: operations['charts/user/drive']; + post: operations['charts___user___drive']; }; '/charts/user/following': { /** @@ -1159,14 +1163,14 @@ export type paths = { * * **Credential required**: *No* */ - get: operations['charts/user/following']; + get: operations['charts___user___following']; /** * charts/user/following * @description No description provided. * * **Credential required**: *No* */ - post: operations['charts/user/following']; + post: operations['charts___user___following']; }; '/charts/user/notes': { /** @@ -1175,14 +1179,14 @@ export type paths = { * * **Credential required**: *No* */ - get: operations['charts/user/notes']; + get: operations['charts___user___notes']; /** * charts/user/notes * @description No description provided. * * **Credential required**: *No* */ - post: operations['charts/user/notes']; + post: operations['charts___user___notes']; }; '/charts/user/pv': { /** @@ -1191,14 +1195,14 @@ export type paths = { * * **Credential required**: *No* */ - get: operations['charts/user/pv']; + get: operations['charts___user___pv']; /** * charts/user/pv * @description No description provided. * * **Credential required**: *No* */ - post: operations['charts/user/pv']; + post: operations['charts___user___pv']; }; '/charts/user/reactions': { /** @@ -1207,14 +1211,14 @@ export type paths = { * * **Credential required**: *No* */ - get: operations['charts/user/reactions']; + get: operations['charts___user___reactions']; /** * charts/user/reactions * @description No description provided. * * **Credential required**: *No* */ - post: operations['charts/user/reactions']; + post: operations['charts___user___reactions']; }; '/charts/users': { /** @@ -1223,14 +1227,14 @@ export type paths = { * * **Credential required**: *No* */ - get: operations['charts/users']; + get: operations['charts___users']; /** * charts/users * @description No description provided. * * **Credential required**: *No* */ - post: operations['charts/users']; + post: operations['charts___users']; }; '/clips/add-note': { /** @@ -1239,7 +1243,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['clips/add-note']; + post: operations['clips___add-note']; }; '/clips/remove-note': { /** @@ -1248,7 +1252,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['clips/remove-note']; + post: operations['clips___remove-note']; }; '/clips/create': { /** @@ -1257,7 +1261,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['clips/create']; + post: operations['clips___create']; }; '/clips/delete': { /** @@ -1266,7 +1270,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['clips/delete']; + post: operations['clips___delete']; }; '/clips/list': { /** @@ -1275,7 +1279,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['clips/list']; + post: operations['clips___list']; }; '/clips/notes': { /** @@ -1284,7 +1288,7 @@ export type paths = { * * **Credential required**: *No* / **Permission**: *read:account* */ - post: operations['clips/notes']; + post: operations['clips___notes']; }; '/clips/show': { /** @@ -1293,7 +1297,7 @@ export type paths = { * * **Credential required**: *No* / **Permission**: *read:account* */ - post: operations['clips/show']; + post: operations['clips___show']; }; '/clips/update': { /** @@ -1302,7 +1306,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['clips/update']; + post: operations['clips___update']; }; '/clips/favorite': { /** @@ -1311,7 +1315,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:clip-favorite* */ - post: operations['clips/favorite']; + post: operations['clips___favorite']; }; '/clips/unfavorite': { /** @@ -1320,7 +1324,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:clip-favorite* */ - post: operations['clips/unfavorite']; + post: operations['clips___unfavorite']; }; '/clips/my-favorites': { /** @@ -1329,7 +1333,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:clip-favorite* */ - post: operations['clips/my-favorites']; + post: operations['clips___my-favorites']; }; '/drive': { /** @@ -1347,7 +1351,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - post: operations['drive/files']; + post: operations['drive___files']; }; '/drive/files/attached-notes': { /** @@ -1356,7 +1360,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - post: operations['drive/files/attached-notes']; + post: operations['drive___files___attached-notes']; }; '/drive/files/check-existence': { /** @@ -1365,7 +1369,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - post: operations['drive/files/check-existence']; + post: operations['drive___files___check-existence']; }; '/drive/files/create': { /** @@ -1374,7 +1378,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:drive* */ - post: operations['drive/files/create']; + post: operations['drive___files___create']; }; '/drive/files/delete': { /** @@ -1383,7 +1387,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:drive* */ - post: operations['drive/files/delete']; + post: operations['drive___files___delete']; }; '/drive/files/find-by-hash': { /** @@ -1392,7 +1396,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - post: operations['drive/files/find-by-hash']; + post: operations['drive___files___find-by-hash']; }; '/drive/files/find': { /** @@ -1401,7 +1405,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - post: operations['drive/files/find']; + post: operations['drive___files___find']; }; '/drive/files/show': { /** @@ -1410,7 +1414,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - post: operations['drive/files/show']; + post: operations['drive___files___show']; }; '/drive/files/update': { /** @@ -1419,7 +1423,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:drive* */ - post: operations['drive/files/update']; + post: operations['drive___files___update']; }; '/drive/files/upload-from-url': { /** @@ -1428,7 +1432,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:drive* */ - post: operations['drive/files/upload-from-url']; + post: operations['drive___files___upload-from-url']; }; '/drive/folders': { /** @@ -1437,7 +1441,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - post: operations['drive/folders']; + post: operations['drive___folders']; }; '/drive/folders/create': { /** @@ -1446,7 +1450,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:drive* */ - post: operations['drive/folders/create']; + post: operations['drive___folders___create']; }; '/drive/folders/delete': { /** @@ -1455,7 +1459,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:drive* */ - post: operations['drive/folders/delete']; + post: operations['drive___folders___delete']; }; '/drive/folders/find': { /** @@ -1464,7 +1468,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - post: operations['drive/folders/find']; + post: operations['drive___folders___find']; }; '/drive/folders/show': { /** @@ -1473,7 +1477,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - post: operations['drive/folders/show']; + post: operations['drive___folders___show']; }; '/drive/folders/update': { /** @@ -1482,7 +1486,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:drive* */ - post: operations['drive/folders/update']; + post: operations['drive___folders___update']; }; '/drive/stream': { /** @@ -1491,7 +1495,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - post: operations['drive/stream']; + post: operations['drive___stream']; }; '/email-address/available': { /** @@ -1500,7 +1504,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['email-address/available']; + post: operations['email-address___available']; }; '/endpoint': { /** @@ -1537,7 +1541,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['federation/followers']; + post: operations['federation___followers']; }; '/federation/following': { /** @@ -1546,7 +1550,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['federation/following']; + post: operations['federation___following']; }; '/federation/instances': { /** @@ -1555,14 +1559,14 @@ export type paths = { * * **Credential required**: *No* */ - get: operations['federation/instances']; + get: operations['federation___instances']; /** * federation/instances * @description No description provided. * * **Credential required**: *No* */ - post: operations['federation/instances']; + post: operations['federation___instances']; }; '/federation/show-instance': { /** @@ -1571,7 +1575,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['federation/show-instance']; + post: operations['federation___show-instance']; }; '/federation/update-remote-user': { /** @@ -1580,7 +1584,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['federation/update-remote-user']; + post: operations['federation___update-remote-user']; }; '/federation/users': { /** @@ -1589,7 +1593,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['federation/users']; + post: operations['federation___users']; }; '/federation/stats': { /** @@ -1598,14 +1602,14 @@ export type paths = { * * **Credential required**: *No* */ - get: operations['federation/stats']; + get: operations['federation___stats']; /** * federation/stats * @description No description provided. * * **Credential required**: *No* */ - post: operations['federation/stats']; + post: operations['federation___stats']; }; '/following/create': { /** @@ -1614,7 +1618,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:following* */ - post: operations['following/create']; + post: operations['following___create']; }; '/following/delete': { /** @@ -1623,7 +1627,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:following* */ - post: operations['following/delete']; + post: operations['following___delete']; }; '/following/update': { /** @@ -1632,7 +1636,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:following* */ - post: operations['following/update']; + post: operations['following___update']; }; '/following/update-all': { /** @@ -1641,7 +1645,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:following* */ - post: operations['following/update-all']; + post: operations['following___update-all']; }; '/following/invalidate': { /** @@ -1650,7 +1654,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:following* */ - post: operations['following/invalidate']; + post: operations['following___invalidate']; }; '/following/requests/accept': { /** @@ -1659,7 +1663,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:following* */ - post: operations['following/requests/accept']; + post: operations['following___requests___accept']; }; '/following/requests/cancel': { /** @@ -1668,7 +1672,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:following* */ - post: operations['following/requests/cancel']; + post: operations['following___requests___cancel']; }; '/following/requests/list': { /** @@ -1677,7 +1681,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:following* */ - post: operations['following/requests/list']; + post: operations['following___requests___list']; }; '/following/requests/reject': { /** @@ -1686,7 +1690,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:following* */ - post: operations['following/requests/reject']; + post: operations['following___requests___reject']; }; '/gallery/featured': { /** @@ -1695,7 +1699,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['gallery/featured']; + post: operations['gallery___featured']; }; '/gallery/popular': { /** @@ -1704,7 +1708,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['gallery/popular']; + post: operations['gallery___popular']; }; '/gallery/posts': { /** @@ -1713,7 +1717,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['gallery/posts']; + post: operations['gallery___posts']; }; '/gallery/posts/create': { /** @@ -1722,7 +1726,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:gallery* */ - post: operations['gallery/posts/create']; + post: operations['gallery___posts___create']; }; '/gallery/posts/delete': { /** @@ -1731,7 +1735,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:gallery* */ - post: operations['gallery/posts/delete']; + post: operations['gallery___posts___delete']; }; '/gallery/posts/like': { /** @@ -1740,7 +1744,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:gallery-likes* */ - post: operations['gallery/posts/like']; + post: operations['gallery___posts___like']; }; '/gallery/posts/show': { /** @@ -1749,7 +1753,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['gallery/posts/show']; + post: operations['gallery___posts___show']; }; '/gallery/posts/unlike': { /** @@ -1758,7 +1762,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:gallery-likes* */ - post: operations['gallery/posts/unlike']; + post: operations['gallery___posts___unlike']; }; '/gallery/posts/update': { /** @@ -1767,7 +1771,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:gallery* */ - post: operations['gallery/posts/update']; + post: operations['gallery___posts___update']; }; '/get-online-users-count': { /** @@ -1801,7 +1805,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['hashtags/list']; + post: operations['hashtags___list']; }; '/hashtags/search': { /** @@ -1810,7 +1814,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['hashtags/search']; + post: operations['hashtags___search']; }; '/hashtags/show': { /** @@ -1819,7 +1823,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['hashtags/show']; + post: operations['hashtags___show']; }; '/hashtags/trend': { /** @@ -1828,14 +1832,14 @@ export type paths = { * * **Credential required**: *No* */ - get: operations['hashtags/trend']; + get: operations['hashtags___trend']; /** * hashtags/trend * @description No description provided. * * **Credential required**: *No* */ - post: operations['hashtags/trend']; + post: operations['hashtags___trend']; }; '/hashtags/users': { /** @@ -1844,7 +1848,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['hashtags/users']; + post: operations['hashtags___users']; }; '/i': { /** @@ -1863,7 +1867,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/2fa/done']; + post: operations['i___2fa___done']; }; '/i/2fa/key-done': { /** @@ -1873,7 +1877,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/2fa/key-done']; + post: operations['i___2fa___key-done']; }; '/i/2fa/password-less': { /** @@ -1883,7 +1887,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/2fa/password-less']; + post: operations['i___2fa___password-less']; }; '/i/2fa/register-key': { /** @@ -1893,7 +1897,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/2fa/register-key']; + post: operations['i___2fa___register-key']; }; '/i/2fa/register': { /** @@ -1903,7 +1907,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/2fa/register']; + post: operations['i___2fa___register']; }; '/i/2fa/update-key': { /** @@ -1913,7 +1917,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/2fa/update-key']; + post: operations['i___2fa___update-key']; }; '/i/2fa/remove-key': { /** @@ -1923,7 +1927,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/2fa/remove-key']; + post: operations['i___2fa___remove-key']; }; '/i/2fa/unregister': { /** @@ -1933,7 +1937,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/2fa/unregister']; + post: operations['i___2fa___unregister']; }; '/i/apps': { /** @@ -1943,7 +1947,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/apps']; + post: operations['i___apps']; }; '/i/authorized-apps': { /** @@ -1953,7 +1957,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/authorized-apps']; + post: operations['i___authorized-apps']; }; '/i/claim-achievement': { /** @@ -1962,7 +1966,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['i/claim-achievement']; + post: operations['i___claim-achievement']; }; '/i/change-password': { /** @@ -1972,7 +1976,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/change-password']; + post: operations['i___change-password']; }; '/i/delete-account': { /** @@ -1982,7 +1986,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/delete-account']; + post: operations['i___delete-account']; }; '/i/export-blocking': { /** @@ -1992,7 +1996,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/export-blocking']; + post: operations['i___export-blocking']; }; '/i/export-following': { /** @@ -2002,7 +2006,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/export-following']; + post: operations['i___export-following']; }; '/i/export-mute': { /** @@ -2012,7 +2016,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/export-mute']; + post: operations['i___export-mute']; }; '/i/export-notes': { /** @@ -2022,7 +2026,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/export-notes']; + post: operations['i___export-notes']; }; '/i/export-clips': { /** @@ -2032,7 +2036,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/export-clips']; + post: operations['i___export-clips']; }; '/i/export-favorites': { /** @@ -2042,7 +2046,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/export-favorites']; + post: operations['i___export-favorites']; }; '/i/export-user-lists': { /** @@ -2052,7 +2056,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/export-user-lists']; + post: operations['i___export-user-lists']; }; '/i/export-antennas': { /** @@ -2062,7 +2066,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/export-antennas']; + post: operations['i___export-antennas']; }; '/i/favorites': { /** @@ -2071,7 +2075,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:favorites* */ - post: operations['i/favorites']; + post: operations['i___favorites']; }; '/i/gallery/likes': { /** @@ -2080,7 +2084,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:gallery-likes* */ - post: operations['i/gallery/likes']; + post: operations['i___gallery___likes']; }; '/i/gallery/posts': { /** @@ -2089,7 +2093,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:gallery* */ - post: operations['i/gallery/posts']; + post: operations['i___gallery___posts']; }; '/i/import-blocking': { /** @@ -2099,7 +2103,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/import-blocking']; + post: operations['i___import-blocking']; }; '/i/import-following': { /** @@ -2109,7 +2113,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/import-following']; + post: operations['i___import-following']; }; '/i/import-muting': { /** @@ -2119,7 +2123,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/import-muting']; + post: operations['i___import-muting']; }; '/i/import-user-lists': { /** @@ -2129,7 +2133,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/import-user-lists']; + post: operations['i___import-user-lists']; }; '/i/import-antennas': { /** @@ -2139,7 +2143,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/import-antennas']; + post: operations['i___import-antennas']; }; '/i/notifications': { /** @@ -2148,7 +2152,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:notifications* */ - post: operations['i/notifications']; + post: operations['i___notifications']; }; '/i/notifications-grouped': { /** @@ -2157,7 +2161,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:notifications* */ - post: operations['i/notifications-grouped']; + post: operations['i___notifications-grouped']; }; '/i/page-likes': { /** @@ -2166,7 +2170,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:page-likes* */ - post: operations['i/page-likes']; + post: operations['i___page-likes']; }; '/i/pages': { /** @@ -2175,7 +2179,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:pages* */ - post: operations['i/pages']; + post: operations['i___pages']; }; '/i/pin': { /** @@ -2184,7 +2188,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['i/pin']; + post: operations['i___pin']; }; '/i/read-all-messaging-messages': { /** @@ -2193,7 +2197,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['i/read-all-messaging-messages']; + post: operations['i___read-all-messaging-messages']; }; '/i/read-all-unread-notes': { /** @@ -2202,7 +2206,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['i/read-all-unread-notes']; + post: operations['i___read-all-unread-notes']; }; '/i/read-announcement': { /** @@ -2211,7 +2215,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['i/read-announcement']; + post: operations['i___read-announcement']; }; '/i/regenerate-token': { /** @@ -2221,7 +2225,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/regenerate-token']; + post: operations['i___regenerate-token']; }; '/i/registry/get-all': { /** @@ -2230,7 +2234,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['i/registry/get-all']; + post: operations['i___registry___get-all']; }; '/i/registry/get-detail': { /** @@ -2239,7 +2243,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['i/registry/get-detail']; + post: operations['i___registry___get-detail']; }; '/i/registry/get': { /** @@ -2248,7 +2252,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['i/registry/get']; + post: operations['i___registry___get']; }; '/i/registry/keys-with-type': { /** @@ -2257,7 +2261,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['i/registry/keys-with-type']; + post: operations['i___registry___keys-with-type']; }; '/i/registry/keys': { /** @@ -2266,7 +2270,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['i/registry/keys']; + post: operations['i___registry___keys']; }; '/i/registry/remove': { /** @@ -2275,7 +2279,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['i/registry/remove']; + post: operations['i___registry___remove']; }; '/i/registry/scopes-with-domain': { /** @@ -2285,7 +2289,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/registry/scopes-with-domain']; + post: operations['i___registry___scopes-with-domain']; }; '/i/registry/set': { /** @@ -2294,7 +2298,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['i/registry/set']; + post: operations['i___registry___set']; }; '/i/revoke-token': { /** @@ -2304,7 +2308,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/revoke-token']; + post: operations['i___revoke-token']; }; '/i/signin-history': { /** @@ -2314,7 +2318,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/signin-history']; + post: operations['i___signin-history']; }; '/i/unpin': { /** @@ -2323,7 +2327,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['i/unpin']; + post: operations['i___unpin']; }; '/i/update-email': { /** @@ -2333,7 +2337,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/update-email']; + post: operations['i___update-email']; }; '/i/update': { /** @@ -2342,7 +2346,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['i/update']; + post: operations['i___update']; }; '/i/user-group-invites': { /** @@ -2351,7 +2355,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:user-groups* */ - post: operations['i/user-group-invites']; + post: operations['i___user-group-invites']; }; '/i/move': { /** @@ -2361,7 +2365,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i/move']; + post: operations['i___move']; }; '/i/webhooks/create': { /** @@ -2370,7 +2374,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['i/webhooks/create']; + post: operations['i___webhooks___create']; }; '/i/webhooks/list': { /** @@ -2379,7 +2383,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['i/webhooks/list']; + post: operations['i___webhooks___list']; }; '/i/webhooks/show': { /** @@ -2388,7 +2392,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['i/webhooks/show']; + post: operations['i___webhooks___show']; }; '/i/webhooks/update': { /** @@ -2397,7 +2401,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['i/webhooks/update']; + post: operations['i___webhooks___update']; }; '/i/webhooks/delete': { /** @@ -2406,7 +2410,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['i/webhooks/delete']; + post: operations['i___webhooks___delete']; }; '/invite/create': { /** @@ -2415,7 +2419,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:invite-codes* */ - post: operations['invite/create']; + post: operations['invite___create']; }; '/invite/delete': { /** @@ -2424,7 +2428,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:invite-codes* */ - post: operations['invite/delete']; + post: operations['invite___delete']; }; '/invite/list': { /** @@ -2433,7 +2437,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:invite-codes* */ - post: operations['invite/list']; + post: operations['invite___list']; }; '/invite/limit': { /** @@ -2442,7 +2446,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:invite-codes* */ - post: operations['invite/limit']; + post: operations['invite___limit']; }; '/messaging/history': { /** @@ -2451,7 +2455,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:messaging* */ - post: operations['messaging/history']; + post: operations['messaging___history']; }; '/messaging/messages': { /** @@ -2460,7 +2464,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:messaging* */ - post: operations['messaging/messages']; + post: operations['messaging___messages']; }; '/messaging/messages/create': { /** @@ -2469,7 +2473,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:messaging* */ - post: operations['messaging/messages/create']; + post: operations['messaging___messages___create']; }; '/messaging/messages/delete': { /** @@ -2478,7 +2482,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:messaging* */ - post: operations['messaging/messages/delete']; + post: operations['messaging___messages___delete']; }; '/messaging/messages/read': { /** @@ -2487,7 +2491,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:messaging* */ - post: operations['messaging/messages/read']; + post: operations['messaging___messages___read']; }; '/meta': { /** @@ -2538,7 +2542,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['miauth/gen-token']; + post: operations['miauth___gen-token']; }; '/mute/create': { /** @@ -2547,7 +2551,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:mutes* */ - post: operations['mute/create']; + post: operations['mute___create']; }; '/mute/delete': { /** @@ -2556,7 +2560,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:mutes* */ - post: operations['mute/delete']; + post: operations['mute___delete']; }; '/mute/list': { /** @@ -2565,7 +2569,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:mutes* */ - post: operations['mute/list']; + post: operations['mute___list']; }; '/renote-mute/create': { /** @@ -2574,7 +2578,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:mutes* */ - post: operations['renote-mute/create']; + post: operations['renote-mute___create']; }; '/renote-mute/delete': { /** @@ -2583,7 +2587,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:mutes* */ - post: operations['renote-mute/delete']; + post: operations['renote-mute___delete']; }; '/renote-mute/list': { /** @@ -2592,7 +2596,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:mutes* */ - post: operations['renote-mute/list']; + post: operations['renote-mute___list']; }; '/my/apps': { /** @@ -2601,7 +2605,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['my/apps']; + post: operations['my___apps']; }; '/notes': { /** @@ -2619,7 +2623,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['notes/children']; + post: operations['notes___children']; }; '/notes/clips': { /** @@ -2628,7 +2632,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['notes/clips']; + post: operations['notes___clips']; }; '/notes/conversation': { /** @@ -2637,7 +2641,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['notes/conversation']; + post: operations['notes___conversation']; }; '/notes/create': { /** @@ -2646,7 +2650,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:notes* */ - post: operations['notes/create']; + post: operations['notes___create']; }; '/notes/delete': { /** @@ -2655,7 +2659,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:notes* */ - post: operations['notes/delete']; + post: operations['notes___delete']; }; '/notes/update': { /** @@ -2664,7 +2668,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:notes* */ - post: operations['notes/update']; + post: operations['notes___update']; }; '/notes/favorites/create': { /** @@ -2673,7 +2677,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:favorites* */ - post: operations['notes/favorites/create']; + post: operations['notes___favorites___create']; }; '/notes/favorites/delete': { /** @@ -2682,7 +2686,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:favorites* */ - post: operations['notes/favorites/delete']; + post: operations['notes___favorites___delete']; }; '/notes/featured': { /** @@ -2691,14 +2695,14 @@ export type paths = { * * **Credential required**: *No* */ - get: operations['notes/featured']; + get: operations['notes___featured']; /** * notes/featured * @description No description provided. * * **Credential required**: *No* */ - post: operations['notes/featured']; + post: operations['notes___featured']; }; '/notes/global-timeline': { /** @@ -2707,7 +2711,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['notes/global-timeline']; + post: operations['notes___global-timeline']; }; '/notes/hybrid-timeline': { /** @@ -2716,7 +2720,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['notes/hybrid-timeline']; + post: operations['notes___hybrid-timeline']; }; '/notes/local-timeline': { /** @@ -2725,7 +2729,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['notes/local-timeline']; + post: operations['notes___local-timeline']; }; '/notes/mentions': { /** @@ -2734,7 +2738,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['notes/mentions']; + post: operations['notes___mentions']; }; '/notes/polls/recommendation': { /** @@ -2743,7 +2747,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['notes/polls/recommendation']; + post: operations['notes___polls___recommendation']; }; '/notes/polls/vote': { /** @@ -2752,7 +2756,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:votes* */ - post: operations['notes/polls/vote']; + post: operations['notes___polls___vote']; }; '/notes/events/search': { /** @@ -2761,7 +2765,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['notes/events/search']; + post: operations['notes___events___search']; }; '/notes/reactions': { /** @@ -2770,14 +2774,14 @@ export type paths = { * * **Credential required**: *No* */ - get: operations['notes/reactions']; + get: operations['notes___reactions']; /** * notes/reactions * @description No description provided. * * **Credential required**: *No* */ - post: operations['notes/reactions']; + post: operations['notes___reactions']; }; '/notes/reactions/create': { /** @@ -2786,7 +2790,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:reactions* */ - post: operations['notes/reactions/create']; + post: operations['notes___reactions___create']; }; '/notes/reactions/delete': { /** @@ -2795,7 +2799,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:reactions* */ - post: operations['notes/reactions/delete']; + post: operations['notes___reactions___delete']; }; '/notes/renotes': { /** @@ -2804,7 +2808,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['notes/renotes']; + post: operations['notes___renotes']; }; '/notes/replies': { /** @@ -2813,7 +2817,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['notes/replies']; + post: operations['notes___replies']; }; '/notes/search-by-tag': { /** @@ -2822,7 +2826,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['notes/search-by-tag']; + post: operations['notes___search-by-tag']; }; '/notes/search': { /** @@ -2831,7 +2835,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['notes/search']; + post: operations['notes___search']; }; '/notes/show': { /** @@ -2840,7 +2844,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['notes/show']; + post: operations['notes___show']; }; '/notes/state': { /** @@ -2849,7 +2853,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['notes/state']; + post: operations['notes___state']; }; '/notes/thread-muting/create': { /** @@ -2858,7 +2862,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['notes/thread-muting/create']; + post: operations['notes___thread-muting___create']; }; '/notes/thread-muting/delete': { /** @@ -2867,7 +2871,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['notes/thread-muting/delete']; + post: operations['notes___thread-muting___delete']; }; '/notes/timeline': { /** @@ -2876,7 +2880,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['notes/timeline']; + post: operations['notes___timeline']; }; '/notes/translate': { /** @@ -2885,7 +2889,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['notes/translate']; + post: operations['notes___translate']; }; '/notes/unrenote': { /** @@ -2894,7 +2898,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:notes* */ - post: operations['notes/unrenote']; + post: operations['notes___unrenote']; }; '/notes/user-list-timeline': { /** @@ -2903,7 +2907,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['notes/user-list-timeline']; + post: operations['notes___user-list-timeline']; }; '/notifications/create': { /** @@ -2912,7 +2916,16 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:notifications* */ - post: operations['notifications/create']; + post: operations['notifications___create']; + }; + '/notifications/flush': { + /** + * notifications/flush + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:notifications* + */ + post: operations['notifications___flush']; }; '/notifications/mark-all-as-read': { /** @@ -2921,7 +2934,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:notifications* */ - post: operations['notifications/mark-all-as-read']; + post: operations['notifications___mark-all-as-read']; }; '/notifications/test-notification': { /** @@ -2930,7 +2943,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:notifications* */ - post: operations['notifications/test-notification']; + post: operations['notifications___test-notification']; }; '/page-push': { /** @@ -2949,7 +2962,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:pages* */ - post: operations['pages/create']; + post: operations['pages___create']; }; '/pages/delete': { /** @@ -2958,7 +2971,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:pages* */ - post: operations['pages/delete']; + post: operations['pages___delete']; }; '/pages/featured': { /** @@ -2967,7 +2980,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['pages/featured']; + post: operations['pages___featured']; }; '/pages/like': { /** @@ -2976,7 +2989,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:page-likes* */ - post: operations['pages/like']; + post: operations['pages___like']; }; '/pages/show': { /** @@ -2985,7 +2998,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['pages/show']; + post: operations['pages___show']; }; '/pages/unlike': { /** @@ -2994,7 +3007,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:page-likes* */ - post: operations['pages/unlike']; + post: operations['pages___unlike']; }; '/pages/update': { /** @@ -3003,7 +3016,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:pages* */ - post: operations['pages/update']; + post: operations['pages___update']; }; '/flash/create': { /** @@ -3012,7 +3025,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:flash* */ - post: operations['flash/create']; + post: operations['flash___create']; }; '/flash/delete': { /** @@ -3021,7 +3034,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:flash* */ - post: operations['flash/delete']; + post: operations['flash___delete']; }; '/flash/featured': { /** @@ -3030,7 +3043,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['flash/featured']; + post: operations['flash___featured']; }; '/flash/gen-token': { /** @@ -3040,7 +3053,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['flash/gen-token']; + post: operations['flash___gen-token']; }; '/flash/like': { /** @@ -3049,7 +3062,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:flash-likes* */ - post: operations['flash/like']; + post: operations['flash___like']; }; '/flash/show': { /** @@ -3058,7 +3071,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['flash/show']; + post: operations['flash___show']; }; '/flash/unlike': { /** @@ -3067,7 +3080,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:flash-likes* */ - post: operations['flash/unlike']; + post: operations['flash___unlike']; }; '/flash/update': { /** @@ -3076,7 +3089,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:flash* */ - post: operations['flash/update']; + post: operations['flash___update']; }; '/flash/my': { /** @@ -3085,7 +3098,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:flash* */ - post: operations['flash/my']; + post: operations['flash___my']; }; '/flash/my-likes': { /** @@ -3094,7 +3107,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:flash-likes* */ - post: operations['flash/my-likes']; + post: operations['flash___my-likes']; }; '/ping': { /** @@ -3121,7 +3134,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['promo/read']; + post: operations['promo___read']; }; '/roles/list': { /** @@ -3130,7 +3143,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['roles/list']; + post: operations['roles___list']; }; '/roles/show': { /** @@ -3139,7 +3152,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['roles/show']; + post: operations['roles___show']; }; '/roles/users': { /** @@ -3148,7 +3161,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['roles/users']; + post: operations['roles___users']; }; '/roles/notes': { /** @@ -3157,7 +3170,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['roles/notes']; + post: operations['roles___notes']; }; '/request-reset-password': { /** @@ -3219,7 +3232,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['sw/show-registration']; + post: operations['sw___show-registration']; }; '/sw/update-registration': { /** @@ -3229,7 +3242,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['sw/update-registration']; + post: operations['sw___update-registration']; }; '/sw/register': { /** @@ -3239,7 +3252,7 @@ export type paths = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['sw/register']; + post: operations['sw___register']; }; '/sw/unregister': { /** @@ -3248,7 +3261,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['sw/unregister']; + post: operations['sw___unregister']; }; '/test': { /** @@ -3266,7 +3279,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['username/available']; + post: operations['username___available']; }; '/users': { /** @@ -3284,7 +3297,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['users/clips']; + post: operations['users___clips']; }; '/users/followers': { /** @@ -3293,7 +3306,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['users/followers']; + post: operations['users___followers']; }; '/users/following': { /** @@ -3302,7 +3315,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['users/following']; + post: operations['users___following']; }; '/users/gallery/posts': { /** @@ -3311,7 +3324,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['users/gallery/posts']; + post: operations['users___gallery___posts']; }; '/users/get-frequently-replied-users': { /** @@ -3320,7 +3333,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['users/get-frequently-replied-users']; + post: operations['users___get-frequently-replied-users']; }; '/users/featured-notes': { /** @@ -3329,14 +3342,14 @@ export type paths = { * * **Credential required**: *No* */ - get: operations['users/featured-notes']; + get: operations['users___featured-notes']; /** * users/featured-notes * @description No description provided. * * **Credential required**: *No* */ - post: operations['users/featured-notes']; + post: operations['users___featured-notes']; }; '/users/groups/create': { /** @@ -3345,7 +3358,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:user-groups* */ - post: operations['users/groups/create']; + post: operations['users___groups___create']; }; '/users/groups/delete': { /** @@ -3354,7 +3367,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:user-groups* */ - post: operations['users/groups/delete']; + post: operations['users___groups___delete']; }; '/users/groups/invitations/accept': { /** @@ -3363,7 +3376,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:user-groups* */ - post: operations['users/groups/invitations/accept']; + post: operations['users___groups___invitations___accept']; }; '/users/groups/invitations/reject': { /** @@ -3372,7 +3385,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:user-groups* */ - post: operations['users/groups/invitations/reject']; + post: operations['users___groups___invitations___reject']; }; '/users/groups/invite': { /** @@ -3381,7 +3394,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:user-groups* */ - post: operations['users/groups/invite']; + post: operations['users___groups___invite']; }; '/users/groups/joined': { /** @@ -3390,7 +3403,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:user-groups* */ - post: operations['users/groups/joined']; + post: operations['users___groups___joined']; }; '/users/groups/leave': { /** @@ -3399,7 +3412,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:user-groups* */ - post: operations['users/groups/leave']; + post: operations['users___groups___leave']; }; '/users/groups/owned': { /** @@ -3408,7 +3421,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:user-groups* */ - post: operations['users/groups/owned']; + post: operations['users___groups___owned']; }; '/users/groups/pull': { /** @@ -3417,7 +3430,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:user-groups* */ - post: operations['users/groups/pull']; + post: operations['users___groups___pull']; }; '/users/groups/show': { /** @@ -3426,7 +3439,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:user-groups* */ - post: operations['users/groups/show']; + post: operations['users___groups___show']; }; '/users/groups/transfer': { /** @@ -3435,7 +3448,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:user-groups* */ - post: operations['users/groups/transfer']; + post: operations['users___groups___transfer']; }; '/users/groups/update': { /** @@ -3444,7 +3457,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:user-groups* */ - post: operations['users/groups/update']; + post: operations['users___groups___update']; }; '/users/lists/create': { /** @@ -3453,7 +3466,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['users/lists/create']; + post: operations['users___lists___create']; }; '/users/lists/delete': { /** @@ -3462,7 +3475,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['users/lists/delete']; + post: operations['users___lists___delete']; }; '/users/lists/list': { /** @@ -3471,7 +3484,7 @@ export type paths = { * * **Credential required**: *No* / **Permission**: *read:account* */ - post: operations['users/lists/list']; + post: operations['users___lists___list']; }; '/users/lists/pull': { /** @@ -3480,7 +3493,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['users/lists/pull']; + post: operations['users___lists___pull']; }; '/users/lists/push': { /** @@ -3489,7 +3502,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['users/lists/push']; + post: operations['users___lists___push']; }; '/users/lists/show': { /** @@ -3498,7 +3511,7 @@ export type paths = { * * **Credential required**: *No* / **Permission**: *read:account* */ - post: operations['users/lists/show']; + post: operations['users___lists___show']; }; '/users/lists/favorite': { /** @@ -3507,7 +3520,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['users/lists/favorite']; + post: operations['users___lists___favorite']; }; '/users/lists/unfavorite': { /** @@ -3516,7 +3529,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['users/lists/unfavorite']; + post: operations['users___lists___unfavorite']; }; '/users/lists/update': { /** @@ -3525,7 +3538,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['users/lists/update']; + post: operations['users___lists___update']; }; '/users/lists/create-from-public': { /** @@ -3534,7 +3547,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['users/lists/create-from-public']; + post: operations['users___lists___create-from-public']; }; '/users/lists/update-membership': { /** @@ -3543,7 +3556,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['users/lists/update-membership']; + post: operations['users___lists___update-membership']; }; '/users/lists/get-memberships': { /** @@ -3552,7 +3565,7 @@ export type paths = { * * **Credential required**: *No* / **Permission**: *read:account* */ - post: operations['users/lists/get-memberships']; + post: operations['users___lists___get-memberships']; }; '/users/notes': { /** @@ -3561,7 +3574,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['users/notes']; + post: operations['users___notes']; }; '/users/pages': { /** @@ -3570,7 +3583,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['users/pages']; + post: operations['users___pages']; }; '/users/flashs': { /** @@ -3579,7 +3592,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['users/flashs']; + post: operations['users___flashs']; }; '/users/reactions': { /** @@ -3588,7 +3601,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['users/reactions']; + post: operations['users___reactions']; }; '/users/recommendation': { /** @@ -3597,7 +3610,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['users/recommendation']; + post: operations['users___recommendation']; }; '/users/relation': { /** @@ -3606,7 +3619,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['users/relation']; + post: operations['users___relation']; }; '/users/report-abuse': { /** @@ -3615,7 +3628,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:report-abuse* */ - post: operations['users/report-abuse']; + post: operations['users___report-abuse']; }; '/users/search-by-username-and-host': { /** @@ -3624,7 +3637,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['users/search-by-username-and-host']; + post: operations['users___search-by-username-and-host']; }; '/users/search': { /** @@ -3633,7 +3646,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['users/search']; + post: operations['users___search']; }; '/users/show': { /** @@ -3642,7 +3655,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['users/show']; + post: operations['users___show']; }; '/users/stats': { /** @@ -3651,7 +3664,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['users/stats']; + post: operations['users___stats']; }; '/users/achievements': { /** @@ -3660,7 +3673,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['users/achievements']; + post: operations['users___achievements']; }; '/users/update-memo': { /** @@ -3669,7 +3682,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['users/update-memo']; + post: operations['users___update-memo']; }; '/users/translate': { /** @@ -3678,7 +3691,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['users/translate']; + post: operations['users___translate']; }; '/fetch-rss': { /** @@ -3729,7 +3742,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['bubble-game/register']; + post: operations['bubble-game___register']; }; '/bubble-game/ranking': { /** @@ -3738,14 +3751,14 @@ export type paths = { * * **Credential required**: *No* */ - get: operations['bubble-game/ranking']; + get: operations['bubble-game___ranking']; /** * bubble-game/ranking * @description No description provided. * * **Credential required**: *No* */ - post: operations['bubble-game/ranking']; + post: operations['bubble-game___ranking']; }; '/reversi/cancel-match': { /** @@ -3754,7 +3767,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['reversi/cancel-match']; + post: operations['reversi___cancel-match']; }; '/reversi/games': { /** @@ -3763,7 +3776,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['reversi/games']; + post: operations['reversi___games']; }; '/reversi/match': { /** @@ -3772,7 +3785,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['reversi/match']; + post: operations['reversi___match']; }; '/reversi/invitations': { /** @@ -3781,7 +3794,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['reversi/invitations']; + post: operations['reversi___invitations']; }; '/reversi/show-game': { /** @@ -3790,7 +3803,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['reversi/show-game']; + post: operations['reversi___show-game']; }; '/reversi/surrender': { /** @@ -3799,7 +3812,7 @@ export type paths = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['reversi/surrender']; + post: operations['reversi___surrender']; }; '/reversi/verify': { /** @@ -3808,7 +3821,7 @@ export type paths = { * * **Credential required**: *No* */ - post: operations['reversi/verify']; + post: operations['reversi___verify']; }; }; @@ -3870,7 +3883,9 @@ export type components = { faviconUrl: string | null; themeColor: string | null; }; - emojis: Record; + emojis: { + [key: string]: string; + }; /** @enum {string} */ onlineStatus: 'unknown' | 'online' | 'active' | 'offline'; badgeRoles?: ({ @@ -3981,7 +3996,7 @@ export type components = { notificationRecieveConfig: { note?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -3990,7 +4005,7 @@ export type components = { }]>; follow?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -3999,7 +4014,7 @@ export type components = { }]>; mention?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -4008,7 +4023,7 @@ export type components = { }]>; reply?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -4017,7 +4032,7 @@ export type components = { }]>; renote?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -4026,7 +4041,7 @@ export type components = { }]>; quote?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -4035,7 +4050,7 @@ export type components = { }]>; reaction?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -4044,7 +4059,7 @@ export type components = { }]>; pollEnded?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -4053,7 +4068,7 @@ export type components = { }]>; receiveFollowRequest?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -4062,7 +4077,7 @@ export type components = { }]>; followRequestAccepted?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -4071,7 +4086,7 @@ export type components = { }]>; groupInvited?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -4080,7 +4095,7 @@ export type components = { }]>; roleAssigned?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -4089,7 +4104,7 @@ export type components = { }]>; achievementEarned?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -4098,7 +4113,7 @@ export type components = { }]>; app?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -4107,7 +4122,7 @@ export type components = { }]>; test?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -4311,6 +4326,7 @@ export type components = { reactions: { [key: string]: number; }; + reactionCount: number; renoteCount: number; repliesCount: number; uri?: string; @@ -4504,6 +4520,15 @@ export type components = { createdAt: string; /** @enum {string} */ type: 'test'; + } | { + /** Format: id */ + id: string; + /** Format: date-time */ + createdAt: string; + /** @enum {string} */ + type: 'groupInvited'; + /** Format: id */ + invitation: string; }; DriveFile: { /** @@ -4759,6 +4784,8 @@ export type components = { localOnly: boolean; notify: boolean; /** @default false */ + excludeBots: boolean; + /** @default false */ withReplies: boolean; withFile: boolean; isActive: boolean; @@ -4783,6 +4810,7 @@ export type components = { isPublic: boolean; favoritedCount: number; isFavorited?: boolean; + notesCount?: number; }; FederationInstance: { /** Format: id */ @@ -4817,6 +4845,7 @@ export type components = { infoUpdatedAt: string | null; /** Format: date-time */ latestRequestReceivedAt: string | null; + moderationNote?: string | null; }; GalleryPost: { /** @@ -4907,6 +4936,16 @@ export type components = { /** @enum {string} */ type: 'isLocal' | 'isRemote'; }; + RoleCondFormulaValueAssignedRole: { + id: string; + /** @enum {string} */ + type: 'roleAssignedTo'; + /** + * Format: id + * @example xxxxxxxxxx + */ + roleId: string; + }; RoleCondFormulaValueCreated: { id: string; /** @enum {string} */ @@ -4919,7 +4958,7 @@ export type components = { type: 'followersLessThanOrEq' | 'followersMoreThanOrEq' | 'followingLessThanOrEq' | 'followingMoreThanOrEq' | 'notesLessThanOrEq' | 'notesMoreThanOrEq'; value: number; }; - RoleCondFormulaValue: components['schemas']['RoleCondFormulaLogics'] | components['schemas']['RoleCondFormulaValueNot'] | components['schemas']['RoleCondFormulaValueIsLocalOrRemote'] | components['schemas']['RoleCondFormulaValueCreated'] | components['schemas']['RoleCondFormulaFollowersOrFollowingOrNotes']; + RoleCondFormulaValue: components['schemas']['RoleCondFormulaLogics'] | components['schemas']['RoleCondFormulaValueNot'] | components['schemas']['RoleCondFormulaValueIsLocalOrRemote'] | components['schemas']['RoleCondFormulaValueAssignedRole'] | components['schemas']['RoleCondFormulaValueCreated'] | components['schemas']['RoleCondFormulaFollowersOrFollowingOrNotes']; RoleLite: { /** * Format: id @@ -4968,6 +5007,7 @@ export type components = { gtlAvailable: boolean; ltlAvailable: boolean; canPublicNote: boolean; + mentionLimit: number; canInvite: boolean; inviteLimit: number; inviteLimitCycle: number; @@ -4989,6 +5029,7 @@ export type components = { userEachUserListsLimit: number; rateLimitFactor: number; avatarDecorationLimit: number; + canEditNote: boolean; }; ReversiGameLite: { /** Format: id */ @@ -5060,6 +5101,98 @@ export type components = { logs: number[][]; map: string[]; }; + MetaLite: { + maintainerName: string | null; + maintainerEmail: string | null; + version: string; + basedMisskeyVersion: string; + providesTarball: boolean; + name: string | null; + shortName: string | null; + /** + * Format: url + * @example https://cherrypick.example.com + */ + uri: string; + description: string | null; + langs: string[]; + tosUrl: string | null; + /** @default https://github.com/kokonect-link/cherrypick */ + repositoryUrl: string | null; + /** @default https://github.com/kokonect-link/cherrypick/issues/new */ + feedbackUrl: string | null; + defaultDarkTheme: string | null; + defaultLightTheme: string | null; + disableRegistration: boolean; + emailRequiredForSignup: boolean; + enableHcaptcha: boolean; + hcaptchaSiteKey: string | null; + enableMcaptcha: boolean; + mcaptchaSiteKey: string | null; + mcaptchaInstanceUrl: string | null; + enableRecaptcha: boolean; + recaptchaSiteKey: string | null; + enableTurnstile: boolean; + turnstileSiteKey: string | null; + swPublickey: string | null; + /** @default /assets/ai.png */ + mascotImageUrl: string; + bannerUrl: string | null; + serverErrorImageUrl: string | null; + infoImageUrl: string | null; + notFoundImageUrl: string | null; + iconUrl: string | null; + maxNoteTextLength: number; + ads: { + /** + * Format: id + * @example xxxxxxxxxx + */ + id: string; + /** Format: url */ + url: string; + place: string; + ratio: number; + /** Format: url */ + imageUrl: string; + dayOfWeek: number; + }[]; + /** @default 0 */ + notesPerOneAd: number; + enableEmail: boolean; + enableServiceWorker: boolean; + translatorAvailable: boolean; + mediaProxy: string; + enableUrlPreview: boolean; + backgroundImageUrl: string | null; + impressumUrl: string | null; + logoImageUrl: string | null; + privacyPolicyUrl: string | null; + serverRules: string[]; + themeColor: string | null; + policies: components['schemas']['RolePolicies']; + }; + MetaDetailedOnly: { + features?: { + registration: boolean; + emailRequiredForSignup: boolean; + localTimeline: boolean; + globalTimeline: boolean; + hcaptcha: boolean; + turnstile: boolean; + recaptcha: boolean; + objectStorage: boolean; + serviceWorker: boolean; + /** @default true */ + miauth?: boolean; + }; + proxyAccountName: string | null; + /** @example false */ + requireSetup: boolean; + cacheRemoteFiles: boolean; + cacheRemoteSensitiveFiles: boolean; + }; + MetaDetailed: components['schemas']['MetaLite'] & components['schemas']['MetaDetailedOnly']; }; responses: never; parameters: never; @@ -5080,7 +5213,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:meta* */ - 'admin/meta': { + admin___meta: { responses: { /** @description OK (with results) */ 200: { @@ -5197,11 +5330,21 @@ export type operations = { objectStorageRemoteS3ForcePathStyle: boolean; privacyPolicyUrl: string | null; repositoryUrl: string | null; + /** + * @deprecated + * @description [Deprecated] Use "urlPreviewSummaryProxyUrl" instead. + */ summalyProxy: string | null; themeColor: string | null; tosUrl: string | null; uri: string; version: string; + urlPreviewEnabled: boolean; + urlPreviewTimeout: number; + urlPreviewMaximumContentLength: number; + urlPreviewRequireContentLength: boolean; + urlPreviewUserAgent: string | null; + urlPreviewSummaryProxyUrl: string | null; doNotSendNotificationEmailsForAbuseReport: boolean; emailToReceiveAbuseReport: string | null; enableReceivePrerelease: boolean; @@ -5246,9 +5389,10 @@ export type operations = { * admin/abuse-report-resolver/create * @description No description provided. * - * **Credential required**: *Yes* + * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *arr-create* */ - 'admin/abuse-report-resolver/create': { + 'admin___abuse-report-resolver___create': { requestBody: { content: { 'application/json': { @@ -5312,9 +5456,10 @@ export type operations = { * admin/abuse-report-resolver/list * @description No description provided. * - * **Credential required**: *Yes* + * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *arr-list* */ - 'admin/abuse-report-resolver/list': { + 'admin___abuse-report-resolver___list': { requestBody: { content: { 'application/json': { @@ -5377,9 +5522,10 @@ export type operations = { * admin/abuse-report-resolver/delete * @description No description provided. * - * **Credential required**: *No* + * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. + * **Credential required**: *No* / **Permission**: *arr-delete* */ - 'admin/abuse-report-resolver/delete': { + 'admin___abuse-report-resolver___delete': { requestBody: { content: { 'application/json': { @@ -5429,9 +5575,10 @@ export type operations = { * admin/abuse-report-resolver/update * @description No description provided. * - * **Credential required**: *Yes* + * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *arr-update* */ - 'admin/abuse-report-resolver/update': { + 'admin___abuse-report-resolver___update': { requestBody: { content: { 'application/json': { @@ -5490,7 +5637,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:abuse-user-reports* */ - 'admin/abuse-user-reports': { + 'admin___abuse-user-reports': { requestBody: { content: { 'application/json': { @@ -5582,7 +5729,7 @@ export type operations = { * * **Credential required**: *No* */ - 'admin/accounts/create': { + admin___accounts___create: { requestBody: { content: { 'application/json': { @@ -5636,7 +5783,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:account* */ - 'admin/accounts/delete': { + admin___accounts___delete: { requestBody: { content: { 'application/json': { @@ -5688,7 +5835,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:account* */ - 'admin/accounts/find-by-email': { + 'admin___accounts___find-by-email': { requestBody: { content: { 'application/json': { @@ -5741,7 +5888,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:ad* */ - 'admin/ad/create': { + admin___ad___create: { requestBody: { content: { 'application/json': { @@ -5802,7 +5949,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:ad* */ - 'admin/ad/delete': { + admin___ad___delete: { requestBody: { content: { 'application/json': { @@ -5854,7 +6001,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:ad* */ - 'admin/ad/list': { + admin___ad___list: { requestBody: { content: { 'application/json': { @@ -5914,7 +6061,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:ad* */ - 'admin/ad/update': { + admin___ad___update: { requestBody: { content: { 'application/json': { @@ -5975,7 +6122,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:announcements* */ - 'admin/announcements/create': { + admin___announcements___create: { requestBody: { content: { 'application/json': { @@ -6064,7 +6211,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:announcements* */ - 'admin/announcements/delete': { + admin___announcements___delete: { requestBody: { content: { 'application/json': { @@ -6116,7 +6263,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:announcements* */ - 'admin/announcements/list': { + admin___announcements___list: { requestBody: { content: { 'application/json': { @@ -6190,7 +6337,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:announcements* */ - 'admin/announcements/update': { + admin___announcements___update: { requestBody: { content: { 'application/json': { @@ -6253,7 +6400,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:avatar-decorations* */ - 'admin/avatar-decorations/create': { + 'admin___avatar-decorations___create': { requestBody: { content: { 'application/json': { @@ -6307,7 +6454,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:avatar-decorations* */ - 'admin/avatar-decorations/delete': { + 'admin___avatar-decorations___delete': { requestBody: { content: { 'application/json': { @@ -6359,7 +6506,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:avatar-decorations* */ - 'admin/avatar-decorations/list': { + 'admin___avatar-decorations___list': { requestBody: { content: { 'application/json': { @@ -6433,7 +6580,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:avatar-decorations* */ - 'admin/avatar-decorations/update': { + 'admin___avatar-decorations___update': { requestBody: { content: { 'application/json': { @@ -6489,7 +6636,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:delete-all-files-of-a-user* */ - 'admin/delete-all-files-of-a-user': { + 'admin___delete-all-files-of-a-user': { requestBody: { content: { 'application/json': { @@ -6541,7 +6688,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-avatar* */ - 'admin/unset-user-avatar': { + 'admin___unset-user-avatar': { requestBody: { content: { 'application/json': { @@ -6593,7 +6740,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-banner* */ - 'admin/unset-user-banner': { + 'admin___unset-user-banner': { requestBody: { content: { 'application/json': { @@ -6645,7 +6792,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:drive* */ - 'admin/drive/clean-remote-files': { + 'admin___drive___clean-remote-files': { responses: { /** @description OK (without any results) */ 204: { @@ -6689,7 +6836,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:drive* */ - 'admin/drive/cleanup': { + admin___drive___cleanup: { responses: { /** @description OK (without any results) */ 204: { @@ -6733,7 +6880,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:drive* */ - 'admin/drive/files': { + admin___drive___files: { requestBody: { content: { 'application/json': { @@ -6804,7 +6951,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:drive* */ - 'admin/drive/show-file': { + 'admin___drive___show-file': { requestBody: { content: { 'application/json': { @@ -6913,7 +7060,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - 'admin/emoji/add-aliases-bulk': { + 'admin___emoji___add-aliases-bulk': { requestBody: { content: { 'application/json': { @@ -6965,7 +7112,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - 'admin/emoji/add': { + admin___emoji___add: { requestBody: { content: { 'application/json': { @@ -6983,9 +7130,11 @@ export type operations = { }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['EmojiDetailed']; + }; }; /** @description Client error */ 400: { @@ -7025,7 +7174,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - 'admin/emoji/adds': { + admin___emoji___adds: { requestBody: { content: { 'application/json': { @@ -7043,9 +7192,11 @@ export type operations = { }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['EmojiDetailed']; + }; }; /** @description Client error */ 400: { @@ -7085,7 +7236,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - 'admin/emoji/copy': { + admin___emoji___copy: { requestBody: { content: { 'application/json': { @@ -7142,7 +7293,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - 'admin/emoji/delete-bulk': { + 'admin___emoji___delete-bulk': { requestBody: { content: { 'application/json': { @@ -7193,7 +7344,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - 'admin/emoji/delete': { + admin___emoji___delete: { requestBody: { content: { 'application/json': { @@ -7246,7 +7397,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'admin/emoji/import-zip': { + 'admin___emoji___import-zip': { requestBody: { content: { 'application/json': { @@ -7298,7 +7449,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ - 'admin/emoji/list-remote': { + 'admin___emoji___list-remote': { requestBody: { content: { 'application/json': { @@ -7372,7 +7523,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ - 'admin/emoji/list': { + admin___emoji___list: { requestBody: { content: { 'application/json': { @@ -7441,7 +7592,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - 'admin/emoji/remove-aliases-bulk': { + 'admin___emoji___remove-aliases-bulk': { requestBody: { content: { 'application/json': { @@ -7493,7 +7644,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - 'admin/emoji/set-aliases-bulk': { + 'admin___emoji___set-aliases-bulk': { requestBody: { content: { 'application/json': { @@ -7545,7 +7696,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - 'admin/emoji/set-category-bulk': { + 'admin___emoji___set-category-bulk': { requestBody: { content: { 'application/json': { @@ -7598,7 +7749,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - 'admin/emoji/set-license-bulk': { + 'admin___emoji___set-license-bulk': { requestBody: { content: { 'application/json': { @@ -7651,7 +7802,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - 'admin/emoji/steal': { + admin___emoji___steal: { requestBody: { content: { 'application/json': { @@ -7708,18 +7859,18 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - 'admin/emoji/update': { + admin___emoji___update: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - id: string; - name: string; + id?: string; + name?: string; /** Format: misskey:id */ fileId?: string; /** @description Use `null` to reset the category. */ category?: string | null; - aliases: string[]; + aliases?: string[]; license?: string | null; isSensitive?: boolean; localOnly?: boolean; @@ -7770,7 +7921,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ - 'admin/federation/delete-all-files': { + 'admin___federation___delete-all-files': { requestBody: { content: { 'application/json': { @@ -7821,7 +7972,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ - 'admin/federation/refresh-remote-instance-metadata': { + 'admin___federation___refresh-remote-instance-metadata': { requestBody: { content: { 'application/json': { @@ -7872,7 +8023,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ - 'admin/federation/remove-all-following': { + 'admin___federation___remove-all-following': { requestBody: { content: { 'application/json': { @@ -7923,12 +8074,13 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ - 'admin/federation/update-instance': { + 'admin___federation___update-instance': { requestBody: { content: { 'application/json': { host: string; - isSuspended: boolean; + isSuspended?: boolean; + moderationNote?: string; }; }; }; @@ -7975,7 +8127,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:index-stats* */ - 'admin/get-index-stats': { + 'admin___get-index-stats': { responses: { /** @description OK (with results) */ 200: { @@ -8024,7 +8176,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:table-stats* */ - 'admin/get-table-stats': { + 'admin___get-table-stats': { responses: { /** @description OK (with results) */ 200: { @@ -8075,7 +8227,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:user-ips* */ - 'admin/get-user-ips': { + 'admin___get-user-ips': { requestBody: { content: { 'application/json': { @@ -8133,7 +8285,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:invite-codes* */ - 'admin/invite/create': { + admin___invite___create: { requestBody: { content: { 'application/json': { @@ -8188,7 +8340,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:invite-codes* */ - 'admin/invite/list': { + admin___invite___list: { requestBody: { content: { 'application/json': { @@ -8251,7 +8403,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:invite-codes* */ - 'admin/invite/revoke': { + admin___invite___revoke: { responses: { /** @description OK (without any results) */ 204: { @@ -8295,7 +8447,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:promo* */ - 'admin/promo/create': { + admin___promo___create: { requestBody: { content: { 'application/json': { @@ -8348,7 +8500,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:queue* */ - 'admin/queue/clear': { + admin___queue___clear: { responses: { /** @description OK (without any results) */ 204: { @@ -8392,7 +8544,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:queue* */ - 'admin/queue/deliver-delayed': { + 'admin___queue___deliver-delayed': { responses: { /** @description OK (with results) */ 200: { @@ -8438,7 +8590,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:queue* */ - 'admin/queue/inbox-delayed': { + 'admin___queue___inbox-delayed': { responses: { /** @description OK (with results) */ 200: { @@ -8484,7 +8636,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:queue* */ - 'admin/queue/promote': { + admin___queue___promote: { requestBody: { content: { 'application/json': { @@ -8536,7 +8688,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ - 'admin/queue/stats': { + admin___queue___stats: { responses: { /** @description OK (with results) */ 200: { @@ -8587,7 +8739,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:relays* */ - 'admin/relays/add': { + admin___relays___add: { requestBody: { content: { 'application/json': { @@ -8650,7 +8802,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:relays* */ - 'admin/relays/list': { + admin___relays___list: { responses: { /** @description OK (with results) */ 200: { @@ -8706,7 +8858,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:relays* */ - 'admin/relays/remove': { + admin___relays___remove: { requestBody: { content: { 'application/json': { @@ -8757,7 +8909,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:reset-password* */ - 'admin/reset-password': { + 'admin___reset-password': { requestBody: { content: { 'application/json': { @@ -8813,7 +8965,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* */ - 'admin/resolve-abuse-user-report': { + 'admin___resolve-abuse-user-report': { requestBody: { content: { 'application/json': { @@ -8867,7 +9019,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:send-email* */ - 'admin/send-email': { + 'admin___send-email': { requestBody: { content: { 'application/json': { @@ -8920,7 +9072,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:server-info* */ - 'admin/server-info': { + 'admin___server-info': { responses: { /** @description OK (with results) */ 200: { @@ -8990,7 +9142,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:show-moderation-log* */ - 'admin/show-moderation-logs': { + 'admin___show-moderation-logs': { requestBody: { content: { 'application/json': { @@ -9061,7 +9213,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:show-user* */ - 'admin/show-user': { + 'admin___show-user': { requestBody: { content: { 'application/json': { @@ -9090,7 +9242,7 @@ export type operations = { notificationRecieveConfig: { note?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -9099,7 +9251,7 @@ export type operations = { }]>; follow?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -9108,7 +9260,7 @@ export type operations = { }]>; mention?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -9117,7 +9269,7 @@ export type operations = { }]>; reply?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -9126,7 +9278,7 @@ export type operations = { }]>; renote?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -9135,7 +9287,7 @@ export type operations = { }]>; quote?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -9144,7 +9296,7 @@ export type operations = { }]>; reaction?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -9153,7 +9305,7 @@ export type operations = { }]>; pollEnded?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -9162,7 +9314,7 @@ export type operations = { }]>; receiveFollowRequest?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -9171,7 +9323,16 @@ export type operations = { }]>; followRequestAccepted?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + groupInvited?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -9180,7 +9341,7 @@ export type operations = { }]>; roleAssigned?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -9189,7 +9350,7 @@ export type operations = { }]>; achievementEarned?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -9198,7 +9359,7 @@ export type operations = { }]>; app?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -9207,7 +9368,7 @@ export type operations = { }]>; test?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -9270,7 +9431,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:show-users* */ - 'admin/show-users': { + 'admin___show-users': { requestBody: { content: { 'application/json': { @@ -9345,7 +9506,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:suspend-user* */ - 'admin/suspend-user': { + 'admin___suspend-user': { requestBody: { content: { 'application/json': { @@ -9397,7 +9558,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:unsuspend-user* */ - 'admin/unsuspend-user': { + 'admin___unsuspend-user': { requestBody: { content: { 'application/json': { @@ -9449,7 +9610,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:meta* */ - 'admin/update-meta': { + 'admin___update-meta': { requestBody: { content: { 'application/json': { @@ -9502,7 +9663,6 @@ export type operations = { maintainerName?: string | null; maintainerEmail?: string | null; langs?: string[]; - summalyProxy?: string | null; translatorType?: string | null; deeplAuthKey?: string | null; deeplIsPro?: boolean; @@ -9575,6 +9735,14 @@ export type operations = { perUserListTimelineCacheMax?: number; notesPerOneAd?: number; silencedHosts?: string[] | null; + /** @description [Deprecated] Use "urlPreviewSummaryProxyUrl" instead. */ + summalyProxy?: string | null; + urlPreviewEnabled?: boolean; + urlPreviewTimeout?: number; + urlPreviewMaximumContentLength?: number; + urlPreviewRequireContentLength?: boolean; + urlPreviewUserAgent?: string | null; + urlPreviewSummaryProxyUrl?: string | null; doNotSendNotificationEmailsForAbuseReport?: boolean; emailToReceiveAbuseReport?: string | null; enableReceivePrerelease?: boolean; @@ -9626,7 +9794,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:delete-account* */ - 'admin/delete-account': { + 'admin___delete-account': { requestBody: { content: { 'application/json': { @@ -9678,7 +9846,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:user-note* */ - 'admin/update-user-note': { + 'admin___update-user-note': { requestBody: { content: { 'application/json': { @@ -9731,7 +9899,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - 'admin/roles/create': { + admin___roles___create: { requestBody: { content: { 'application/json': { @@ -9799,7 +9967,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - 'admin/roles/delete': { + admin___roles___delete: { requestBody: { content: { 'application/json': { @@ -9851,7 +10019,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:roles* */ - 'admin/roles/list': { + admin___roles___list: { responses: { /** @description OK (with results) */ 200: { @@ -9897,7 +10065,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:admin:roles* */ - 'admin/roles/show': { + admin___roles___show: { requestBody: { content: { 'application/json': { @@ -9951,7 +10119,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - 'admin/roles/update': { + admin___roles___update: { requestBody: { content: { 'application/json': { @@ -10018,7 +10186,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - 'admin/roles/assign': { + admin___roles___assign: { requestBody: { content: { 'application/json': { @@ -10073,7 +10241,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - 'admin/roles/unassign': { + admin___roles___unassign: { requestBody: { content: { 'application/json': { @@ -10127,7 +10295,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - 'admin/roles/update-default-policies': { + 'admin___roles___update-default-policies': { requestBody: { content: { 'application/json': { @@ -10178,7 +10346,7 @@ export type operations = { * * **Credential required**: *No* / **Permission**: *read:admin:roles* */ - 'admin/roles/users': { + admin___roles___users: { requestBody: { content: { 'application/json': { @@ -10306,7 +10474,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'antennas/create': { + antennas___create: { requestBody: { content: { 'application/json': { @@ -10322,6 +10490,7 @@ export type operations = { users: string[]; caseSensitive: boolean; localOnly?: boolean; + excludeBots?: boolean; withReplies: boolean; withFile: boolean; notify: boolean; @@ -10373,7 +10542,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'antennas/delete': { + antennas___delete: { requestBody: { content: { 'application/json': { @@ -10425,7 +10594,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'antennas/list': { + antennas___list: { responses: { /** @description OK (with results) */ 200: { @@ -10471,7 +10640,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'antennas/notes': { + antennas___notes: { requestBody: { content: { 'application/json': { @@ -10533,7 +10702,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'antennas/show': { + antennas___show: { requestBody: { content: { 'application/json': { @@ -10587,27 +10756,28 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'antennas/update': { + antennas___update: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ antennaId: string; - name: string; + name?: string; /** @enum {string} */ - src: 'home' | 'all' | 'users' | 'list' | 'group' | 'users_blacklist'; + src?: 'home' | 'all' | 'users' | 'list' | 'group' | 'users_blacklist'; /** Format: misskey:id */ userListId?: string | null; /** Format: misskey:id */ userGroupId?: string | null; - keywords: string[][]; - excludeKeywords: string[][]; - users: string[]; - caseSensitive: boolean; + keywords?: string[][]; + excludeKeywords?: string[][]; + users?: string[]; + caseSensitive?: boolean; localOnly?: boolean; - withReplies: boolean; - withFile: boolean; - notify: boolean; + excludeBots?: boolean; + withReplies?: boolean; + withFile?: boolean; + notify?: boolean; }; }; }; @@ -10656,7 +10826,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:federation* */ - 'ap/get': { + ap___get: { requestBody: { content: { 'application/json': { @@ -10715,7 +10885,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'ap/show': { + ap___show: { requestBody: { content: { 'application/json': { @@ -10782,7 +10952,7 @@ export type operations = { * * **Credential required**: *No* */ - 'app/create': { + app___create: { requestBody: { content: { 'application/json': { @@ -10838,7 +11008,7 @@ export type operations = { * * **Credential required**: *No* */ - 'app/show': { + app___show: { requestBody: { content: { 'application/json': { @@ -10893,7 +11063,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'auth/accept': { + auth___accept: { requestBody: { content: { 'application/json': { @@ -10944,7 +11114,7 @@ export type operations = { * * **Credential required**: *No* */ - 'auth/session/generate': { + auth___session___generate: { requestBody: { content: { 'application/json': { @@ -11001,7 +11171,7 @@ export type operations = { * * **Credential required**: *No* */ - 'auth/session/show': { + auth___session___show: { requestBody: { content: { 'application/json': { @@ -11059,7 +11229,7 @@ export type operations = { * * **Credential required**: *No* */ - 'auth/session/userkey': { + auth___session___userkey: { requestBody: { content: { 'application/json': { @@ -11116,7 +11286,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:blocks* */ - 'blocking/create': { + blocking___create: { requestBody: { content: { 'application/json': { @@ -11176,7 +11346,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:blocks* */ - 'blocking/delete': { + blocking___delete: { requestBody: { content: { 'application/json': { @@ -11236,7 +11406,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:blocks* */ - 'blocking/list': { + blocking___list: { requestBody: { content: { 'application/json': { @@ -11294,7 +11464,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:channels* */ - 'channels/create': { + channels___create: { requestBody: { content: { 'application/json': { @@ -11359,7 +11529,7 @@ export type operations = { * * **Credential required**: *No* */ - 'channels/featured': { + channels___featured: { responses: { /** @description OK (with results) */ 200: { @@ -11405,7 +11575,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:channels* */ - 'channels/follow': { + channels___follow: { requestBody: { content: { 'application/json': { @@ -11457,7 +11627,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:channels* */ - 'channels/followed': { + channels___followed: { requestBody: { content: { 'application/json': { @@ -11515,7 +11685,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:channels* */ - 'channels/owned': { + channels___owned: { requestBody: { content: { 'application/json': { @@ -11573,7 +11743,7 @@ export type operations = { * * **Credential required**: *No* */ - 'channels/show': { + channels___show: { requestBody: { content: { 'application/json': { @@ -11627,7 +11797,7 @@ export type operations = { * * **Credential required**: *No* */ - 'channels/timeline': { + channels___timeline: { requestBody: { content: { 'application/json': { @@ -11691,7 +11861,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:channels* */ - 'channels/unfollow': { + channels___unfollow: { requestBody: { content: { 'application/json': { @@ -11743,7 +11913,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:channels* */ - 'channels/update': { + channels___update: { requestBody: { content: { 'application/json': { @@ -11806,7 +11976,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:channels* */ - 'channels/favorite': { + channels___favorite: { requestBody: { content: { 'application/json': { @@ -11858,7 +12028,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:channels* */ - 'channels/unfavorite': { + channels___unfavorite: { requestBody: { content: { 'application/json': { @@ -11910,7 +12080,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:channels* */ - 'channels/my-favorites': { + 'channels___my-favorites': { responses: { /** @description OK (with results) */ 200: { @@ -11956,7 +12126,7 @@ export type operations = { * * **Credential required**: *No* */ - 'channels/search': { + channels___search: { requestBody: { content: { 'application/json': { @@ -12020,7 +12190,7 @@ export type operations = { * * **Credential required**: *No* */ - 'charts/active-users': { + 'charts___active-users': { requestBody: { content: { 'application/json': { @@ -12088,7 +12258,7 @@ export type operations = { * * **Credential required**: *No* */ - 'charts/ap-request': { + 'charts___ap-request': { requestBody: { content: { 'application/json': { @@ -12150,7 +12320,7 @@ export type operations = { * * **Credential required**: *No* */ - 'charts/drive': { + charts___drive: { requestBody: { content: { 'application/json': { @@ -12221,7 +12391,7 @@ export type operations = { * * **Credential required**: *No* */ - 'charts/federation': { + charts___federation: { requestBody: { content: { 'application/json': { @@ -12288,7 +12458,7 @@ export type operations = { * * **Credential required**: *No* */ - 'charts/instance': { + charts___instance: { requestBody: { content: { 'application/json': { @@ -12386,7 +12556,7 @@ export type operations = { * * **Credential required**: *No* */ - 'charts/notes': { + charts___notes: { requestBody: { content: { 'application/json': { @@ -12467,7 +12637,7 @@ export type operations = { * * **Credential required**: *No* */ - 'charts/user/drive': { + charts___user___drive: { requestBody: { content: { 'application/json': { @@ -12534,7 +12704,7 @@ export type operations = { * * **Credential required**: *No* */ - 'charts/user/following': { + charts___user___following: { requestBody: { content: { 'application/json': { @@ -12619,7 +12789,7 @@ export type operations = { * * **Credential required**: *No* */ - 'charts/user/notes': { + charts___user___notes: { requestBody: { content: { 'application/json': { @@ -12689,7 +12859,7 @@ export type operations = { * * **Credential required**: *No* */ - 'charts/user/pv': { + charts___user___pv: { requestBody: { content: { 'application/json': { @@ -12758,7 +12928,7 @@ export type operations = { * * **Credential required**: *No* */ - 'charts/user/reactions': { + charts___user___reactions: { requestBody: { content: { 'application/json': { @@ -12825,7 +12995,7 @@ export type operations = { * * **Credential required**: *No* */ - 'charts/users': { + charts___users: { requestBody: { content: { 'application/json': { @@ -12894,7 +13064,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'clips/add-note': { + 'clips___add-note': { requestBody: { content: { 'application/json': { @@ -12954,7 +13124,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'clips/remove-note': { + 'clips___remove-note': { requestBody: { content: { 'application/json': { @@ -13008,7 +13178,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'clips/create': { + clips___create: { requestBody: { content: { 'application/json': { @@ -13064,7 +13234,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'clips/delete': { + clips___delete: { requestBody: { content: { 'application/json': { @@ -13116,7 +13286,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'clips/list': { + clips___list: { responses: { /** @description OK (with results) */ 200: { @@ -13162,7 +13332,7 @@ export type operations = { * * **Credential required**: *No* / **Permission**: *read:account* */ - 'clips/notes': { + clips___notes: { requestBody: { content: { 'application/json': { @@ -13222,7 +13392,7 @@ export type operations = { * * **Credential required**: *No* / **Permission**: *read:account* */ - 'clips/show': { + clips___show: { requestBody: { content: { 'application/json': { @@ -13276,7 +13446,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'clips/update': { + clips___update: { requestBody: { content: { 'application/json': { @@ -13333,7 +13503,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:clip-favorite* */ - 'clips/favorite': { + clips___favorite: { requestBody: { content: { 'application/json': { @@ -13385,7 +13555,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:clip-favorite* */ - 'clips/unfavorite': { + clips___unfavorite: { requestBody: { content: { 'application/json': { @@ -13437,7 +13607,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:clip-favorite* */ - 'clips/my-favorites': { + 'clips___my-favorites': { responses: { /** @description OK (with results) */ 200: { @@ -13532,7 +13702,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - 'drive/files': { + drive___files: { requestBody: { content: { 'application/json': { @@ -13598,7 +13768,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - 'drive/files/attached-notes': { + 'drive___files___attached-notes': { requestBody: { content: { 'application/json': { @@ -13658,7 +13828,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - 'drive/files/check-existence': { + 'drive___files___check-existence': { requestBody: { content: { 'application/json': { @@ -13711,7 +13881,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:drive* */ - 'drive/files/create': { + drive___files___create: { requestBody: { content: { 'multipart/form-data': { @@ -13787,7 +13957,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:drive* */ - 'drive/files/delete': { + drive___files___delete: { requestBody: { content: { 'application/json': { @@ -13839,7 +14009,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - 'drive/files/find-by-hash': { + 'drive___files___find-by-hash': { requestBody: { content: { 'application/json': { @@ -13892,7 +14062,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - 'drive/files/find': { + drive___files___find: { requestBody: { content: { 'application/json': { @@ -13950,7 +14120,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - 'drive/files/show': { + drive___files___show: { requestBody: { content: { 'application/json': { @@ -14005,7 +14175,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:drive* */ - 'drive/files/update': { + drive___files___update: { requestBody: { content: { 'application/json': { @@ -14064,7 +14234,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:drive* */ - 'drive/files/upload-from-url': { + 'drive___files___upload-from-url': { requestBody: { content: { 'application/json': { @@ -14134,7 +14304,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - 'drive/folders': { + drive___folders: { requestBody: { content: { 'application/json': { @@ -14197,7 +14367,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:drive* */ - 'drive/folders/create': { + drive___folders___create: { requestBody: { content: { 'application/json': { @@ -14259,7 +14429,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:drive* */ - 'drive/folders/delete': { + drive___folders___delete: { requestBody: { content: { 'application/json': { @@ -14311,7 +14481,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - 'drive/folders/find': { + drive___folders___find: { requestBody: { content: { 'application/json': { @@ -14369,7 +14539,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - 'drive/folders/show': { + drive___folders___show: { requestBody: { content: { 'application/json': { @@ -14423,7 +14593,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:drive* */ - 'drive/folders/update': { + drive___folders___update: { requestBody: { content: { 'application/json': { @@ -14480,7 +14650,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - 'drive/stream': { + drive___stream: { requestBody: { content: { 'application/json': { @@ -14539,7 +14709,7 @@ export type operations = { * * **Credential required**: *No* */ - 'email-address/available': { + 'email-address___available': { requestBody: { content: { 'application/json': { @@ -14754,7 +14924,7 @@ export type operations = { * * **Credential required**: *No* */ - 'federation/followers': { + federation___followers: { requestBody: { content: { 'application/json': { @@ -14813,7 +14983,7 @@ export type operations = { * * **Credential required**: *No* */ - 'federation/following': { + federation___following: { requestBody: { content: { 'application/json': { @@ -14872,7 +15042,7 @@ export type operations = { * * **Credential required**: *No* */ - 'federation/instances': { + federation___instances: { requestBody: { content: { 'application/json': { @@ -14939,7 +15109,7 @@ export type operations = { * * **Credential required**: *No* */ - 'federation/show-instance': { + 'federation___show-instance': { requestBody: { content: { 'application/json': { @@ -14996,7 +15166,7 @@ export type operations = { * * **Credential required**: *No* */ - 'federation/update-remote-user': { + 'federation___update-remote-user': { requestBody: { content: { 'application/json': { @@ -15048,7 +15218,7 @@ export type operations = { * * **Credential required**: *No* */ - 'federation/users': { + federation___users: { requestBody: { content: { 'application/json': { @@ -15107,7 +15277,7 @@ export type operations = { * * **Credential required**: *No* */ - 'federation/stats': { + federation___stats: { requestBody: { content: { 'application/json': { @@ -15166,7 +15336,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:following* */ - 'following/create': { + following___create: { requestBody: { content: { 'application/json': { @@ -15227,7 +15397,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:following* */ - 'following/delete': { + following___delete: { requestBody: { content: { 'application/json': { @@ -15287,7 +15457,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:following* */ - 'following/update': { + following___update: { requestBody: { content: { 'application/json': { @@ -15350,7 +15520,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:following* */ - 'following/update-all': { + 'following___update-all': { requestBody: { content: { 'application/json': { @@ -15409,7 +15579,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:following* */ - 'following/invalidate': { + following___invalidate: { requestBody: { content: { 'application/json': { @@ -15469,7 +15639,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:following* */ - 'following/requests/accept': { + following___requests___accept: { requestBody: { content: { 'application/json': { @@ -15521,7 +15691,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:following* */ - 'following/requests/cancel': { + following___requests___cancel: { requestBody: { content: { 'application/json': { @@ -15575,7 +15745,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:following* */ - 'following/requests/list': { + following___requests___list: { requestBody: { content: { 'application/json': { @@ -15638,7 +15808,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:following* */ - 'following/requests/reject': { + following___requests___reject: { requestBody: { content: { 'application/json': { @@ -15690,7 +15860,7 @@ export type operations = { * * **Credential required**: *No* */ - 'gallery/featured': { + gallery___featured: { requestBody: { content: { 'application/json': { @@ -15746,7 +15916,7 @@ export type operations = { * * **Credential required**: *No* */ - 'gallery/popular': { + gallery___popular: { responses: { /** @description OK (with results) */ 200: { @@ -15792,7 +15962,7 @@ export type operations = { * * **Credential required**: *No* */ - 'gallery/posts': { + gallery___posts: { requestBody: { content: { 'application/json': { @@ -15850,7 +16020,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:gallery* */ - 'gallery/posts/create': { + gallery___posts___create: { requestBody: { content: { 'application/json': { @@ -15913,7 +16083,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:gallery* */ - 'gallery/posts/delete': { + gallery___posts___delete: { requestBody: { content: { 'application/json': { @@ -15965,7 +16135,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:gallery-likes* */ - 'gallery/posts/like': { + gallery___posts___like: { requestBody: { content: { 'application/json': { @@ -16017,7 +16187,7 @@ export type operations = { * * **Credential required**: *No* */ - 'gallery/posts/show': { + gallery___posts___show: { requestBody: { content: { 'application/json': { @@ -16071,7 +16241,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:gallery-likes* */ - 'gallery/posts/unlike': { + gallery___posts___unlike: { requestBody: { content: { 'application/json': { @@ -16123,7 +16293,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:gallery* */ - 'gallery/posts/update': { + gallery___posts___update: { requestBody: { content: { 'application/json': { @@ -16292,7 +16462,7 @@ export type operations = { * * **Credential required**: *No* */ - 'hashtags/list': { + hashtags___list: { requestBody: { content: { 'application/json': { @@ -16354,7 +16524,7 @@ export type operations = { * * **Credential required**: *No* */ - 'hashtags/search': { + hashtags___search: { requestBody: { content: { 'application/json': { @@ -16411,7 +16581,7 @@ export type operations = { * * **Credential required**: *No* */ - 'hashtags/show': { + hashtags___show: { requestBody: { content: { 'application/json': { @@ -16464,7 +16634,7 @@ export type operations = { * * **Credential required**: *No* */ - 'hashtags/trend': { + hashtags___trend: { responses: { /** @description OK (with results) */ 200: { @@ -16514,7 +16684,7 @@ export type operations = { * * **Credential required**: *No* */ - 'hashtags/users': { + hashtags___users: { requestBody: { content: { 'application/json': { @@ -16628,7 +16798,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/2fa/done': { + i___2fa___done: { requestBody: { content: { 'application/json': { @@ -16684,7 +16854,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/2fa/key-done': { + 'i___2fa___key-done': { requestBody: { content: { 'application/json': { @@ -16744,7 +16914,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/2fa/password-less': { + 'i___2fa___password-less': { requestBody: { content: { 'application/json': { @@ -16796,7 +16966,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/2fa/register-key': { + 'i___2fa___register-key': { requestBody: { content: { 'application/json': { @@ -16885,7 +17055,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/2fa/register': { + i___2fa___register: { requestBody: { content: { 'application/json': { @@ -16946,7 +17116,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/2fa/update-key': { + 'i___2fa___update-key': { requestBody: { content: { 'application/json': { @@ -16999,7 +17169,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/2fa/remove-key': { + 'i___2fa___remove-key': { requestBody: { content: { 'application/json': { @@ -17053,7 +17223,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/2fa/unregister': { + i___2fa___unregister: { requestBody: { content: { 'application/json': { @@ -17106,7 +17276,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/apps': { + i___apps: { requestBody: { content: { 'application/json': { @@ -17170,7 +17340,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/authorized-apps': { + 'i___authorized-apps': { requestBody: { content: { 'application/json': { @@ -17238,7 +17408,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'i/claim-achievement': { + 'i___claim-achievement': { requestBody: { content: { 'application/json': { @@ -17291,7 +17461,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/change-password': { + 'i___change-password': { requestBody: { content: { 'application/json': { @@ -17345,7 +17515,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/delete-account': { + 'i___delete-account': { requestBody: { content: { 'application/json': { @@ -17398,7 +17568,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/export-blocking': { + 'i___export-blocking': { responses: { /** @description OK (without any results) */ 204: { @@ -17449,7 +17619,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/export-following': { + 'i___export-following': { requestBody: { content: { 'application/json': { @@ -17510,7 +17680,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/export-mute': { + 'i___export-mute': { responses: { /** @description OK (without any results) */ 204: { @@ -17561,7 +17731,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/export-notes': { + 'i___export-notes': { responses: { /** @description OK (without any results) */ 204: { @@ -17612,7 +17782,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/export-clips': { + 'i___export-clips': { responses: { /** @description OK (without any results) */ 204: { @@ -17663,7 +17833,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/export-favorites': { + 'i___export-favorites': { responses: { /** @description OK (without any results) */ 204: { @@ -17714,7 +17884,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/export-user-lists': { + 'i___export-user-lists': { responses: { /** @description OK (without any results) */ 204: { @@ -17765,7 +17935,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/export-antennas': { + 'i___export-antennas': { responses: { /** @description OK (without any results) */ 204: { @@ -17815,7 +17985,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:favorites* */ - 'i/favorites': { + i___favorites: { requestBody: { content: { 'application/json': { @@ -17873,7 +18043,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:gallery-likes* */ - 'i/gallery/likes': { + i___gallery___likes: { requestBody: { content: { 'application/json': { @@ -17935,7 +18105,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:gallery* */ - 'i/gallery/posts': { + i___gallery___posts: { requestBody: { content: { 'application/json': { @@ -17994,7 +18164,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/import-blocking': { + 'i___import-blocking': { requestBody: { content: { 'application/json': { @@ -18053,7 +18223,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/import-following': { + 'i___import-following': { requestBody: { content: { 'application/json': { @@ -18113,7 +18283,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/import-muting': { + 'i___import-muting': { requestBody: { content: { 'application/json': { @@ -18172,7 +18342,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/import-user-lists': { + 'i___import-user-lists': { requestBody: { content: { 'application/json': { @@ -18231,7 +18401,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/import-antennas': { + 'i___import-antennas': { requestBody: { content: { 'application/json': { @@ -18289,7 +18459,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:notifications* */ - 'i/notifications': { + i___notifications: { requestBody: { content: { 'application/json': { @@ -18301,8 +18471,8 @@ export type operations = { untilId?: string; /** @default true */ markAsRead?: boolean; - includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'groupInvited' | 'achievementEarned' | 'app' | 'test' | 'pollVote')[]; - excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'groupInvited' | 'achievementEarned' | 'app' | 'test' | 'pollVote')[]; + includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'pollVote')[]; + excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'pollVote')[]; }; }; }; @@ -18357,7 +18527,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:notifications* */ - 'i/notifications-grouped': { + 'i___notifications-grouped': { requestBody: { content: { 'application/json': { @@ -18369,8 +18539,8 @@ export type operations = { untilId?: string; /** @default true */ markAsRead?: boolean; - includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'groupInvited' | 'achievementEarned' | 'app' | 'test' | 'pollVote')[]; - excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'groupInvited' | 'achievementEarned' | 'app' | 'test' | 'pollVote')[]; + includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote')[]; + excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote')[]; }; }; }; @@ -18425,7 +18595,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:page-likes* */ - 'i/page-likes': { + 'i___page-likes': { requestBody: { content: { 'application/json': { @@ -18487,7 +18657,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:pages* */ - 'i/pages': { + i___pages: { requestBody: { content: { 'application/json': { @@ -18545,7 +18715,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'i/pin': { + i___pin: { requestBody: { content: { 'application/json': { @@ -18599,7 +18769,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'i/read-all-messaging-messages': { + 'i___read-all-messaging-messages': { responses: { /** @description OK (without any results) */ 204: { @@ -18643,7 +18813,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'i/read-all-unread-notes': { + 'i___read-all-unread-notes': { responses: { /** @description OK (without any results) */ 204: { @@ -18687,7 +18857,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'i/read-announcement': { + 'i___read-announcement': { requestBody: { content: { 'application/json': { @@ -18740,7 +18910,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/regenerate-token': { + 'i___regenerate-token': { requestBody: { content: { 'application/json': { @@ -18791,7 +18961,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'i/registry/get-all': { + 'i___registry___get-all': { requestBody: { content: { 'application/json': { @@ -18846,7 +19016,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'i/registry/get-detail': { + 'i___registry___get-detail': { requestBody: { content: { 'application/json': { @@ -18905,7 +19075,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'i/registry/get': { + i___registry___get: { requestBody: { content: { 'application/json': { @@ -18961,7 +19131,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'i/registry/keys-with-type': { + 'i___registry___keys-with-type': { requestBody: { content: { 'application/json': { @@ -19018,7 +19188,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'i/registry/keys': { + i___registry___keys: { requestBody: { content: { 'application/json': { @@ -19073,7 +19243,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'i/registry/remove': { + i___registry___remove: { requestBody: { content: { 'application/json': { @@ -19128,7 +19298,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/registry/scopes-with-domain': { + 'i___registry___scopes-with-domain': { responses: { /** @description OK (with results) */ 200: { @@ -19177,7 +19347,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'i/registry/set': { + i___registry___set: { requestBody: { content: { 'application/json': { @@ -19233,7 +19403,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/revoke-token': { + 'i___revoke-token': { requestBody: { content: { 'application/json': { @@ -19287,7 +19457,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/signin-history': { + 'i___signin-history': { requestBody: { content: { 'application/json': { @@ -19345,7 +19515,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'i/unpin': { + i___unpin: { requestBody: { content: { 'application/json': { @@ -19400,7 +19570,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/update-email': { + 'i___update-email': { requestBody: { content: { 'application/json': { @@ -19461,7 +19631,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'i/update': { + i___update: { requestBody: { content: { 'application/json': { @@ -19515,7 +19685,7 @@ export type operations = { notificationRecieveConfig?: { note?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -19524,7 +19694,7 @@ export type operations = { }]>; follow?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -19533,7 +19703,7 @@ export type operations = { }]>; mention?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -19542,7 +19712,7 @@ export type operations = { }]>; reply?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -19551,7 +19721,7 @@ export type operations = { }]>; renote?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -19560,7 +19730,7 @@ export type operations = { }]>; quote?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -19569,7 +19739,7 @@ export type operations = { }]>; reaction?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -19578,7 +19748,7 @@ export type operations = { }]>; pollEnded?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -19587,7 +19757,7 @@ export type operations = { }]>; receiveFollowRequest?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -19596,7 +19766,16 @@ export type operations = { }]>; followRequestAccepted?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + groupInvited?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -19605,7 +19784,7 @@ export type operations = { }]>; roleAssigned?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -19614,7 +19793,7 @@ export type operations = { }]>; achievementEarned?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -19623,7 +19802,7 @@ export type operations = { }]>; app?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -19632,7 +19811,7 @@ export type operations = { }]>; test?: OneOf<[{ /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'never'; + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; }, { /** @enum {string} */ type: 'list'; @@ -19696,7 +19875,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:user-groups* */ - 'i/user-group-invites': { + 'i___user-group-invites': { requestBody: { content: { 'application/json': { @@ -19759,7 +19938,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i/move': { + i___move: { requestBody: { content: { 'application/json': { @@ -19818,7 +19997,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'i/webhooks/create': { + i___webhooks___create: { requestBody: { content: { 'application/json': { @@ -19888,7 +20067,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'i/webhooks/list': { + i___webhooks___list: { responses: { /** @description OK (with results) */ 200: { @@ -19947,7 +20126,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'i/webhooks/show': { + i___webhooks___show: { requestBody: { content: { 'application/json': { @@ -20014,7 +20193,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'i/webhooks/update': { + i___webhooks___update: { requestBody: { content: { 'application/json': { @@ -20072,7 +20251,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'i/webhooks/delete': { + i___webhooks___delete: { requestBody: { content: { 'application/json': { @@ -20124,7 +20303,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:invite-codes* */ - 'invite/create': { + invite___create: { responses: { /** @description OK (with results) */ 200: { @@ -20170,7 +20349,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:invite-codes* */ - 'invite/delete': { + invite___delete: { requestBody: { content: { 'application/json': { @@ -20222,7 +20401,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:invite-codes* */ - 'invite/list': { + invite___list: { requestBody: { content: { 'application/json': { @@ -20280,7 +20459,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:invite-codes* */ - 'invite/limit': { + invite___limit: { responses: { /** @description OK (with results) */ 200: { @@ -20328,7 +20507,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:messaging* */ - 'messaging/history': { + messaging___history: { requestBody: { content: { 'application/json': { @@ -20384,7 +20563,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:messaging* */ - 'messaging/messages': { + messaging___messages: { requestBody: { content: { 'application/json': { @@ -20448,7 +20627,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:messaging* */ - 'messaging/messages/create': { + messaging___messages___create: { requestBody: { content: { 'application/json': { @@ -20513,7 +20692,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:messaging* */ - 'messaging/messages/delete': { + messaging___messages___delete: { requestBody: { content: { 'application/json': { @@ -20571,7 +20750,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:messaging* */ - 'messaging/messages/read': { + messaging___messages___read: { requestBody: { content: { 'application/json': { @@ -20636,92 +20815,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': { - maintainerName: string | null; - maintainerEmail: string | null; - version: string; - basedMisskeyVersion: string; - providesTarball: boolean; - name: string; - shortName: string | null; - /** - * Format: url - * @example https://cherrypick.example.com - */ - uri: string; - description: string | null; - langs: string[]; - tosUrl: string | null; - /** @default https://github.com/kokonect-link/cherrypick */ - repositoryUrl: string | null; - /** @default https://github.com/kokonect-link/cherrypick/issues/new */ - feedbackUrl: string | null; - defaultDarkTheme: string | null; - defaultLightTheme: string | null; - disableRegistration: boolean; - cacheRemoteFiles: boolean; - cacheRemoteSensitiveFiles: boolean; - emailRequiredForSignup: boolean; - enableHcaptcha: boolean; - hcaptchaSiteKey: string | null; - enableMcaptcha: boolean; - mcaptchaSiteKey: string | null; - mcaptchaInstanceUrl: string | null; - enableRecaptcha: boolean; - recaptchaSiteKey: string | null; - enableTurnstile: boolean; - turnstileSiteKey: string | null; - swPublickey: string | null; - /** @default /assets/ai.png */ - mascotImageUrl: string; - bannerUrl: string; - serverErrorImageUrl: string | null; - infoImageUrl: string | null; - notFoundImageUrl: string | null; - iconUrl: string | null; - maxNoteTextLength: number; - ads: { - /** - * Format: id - * @example xxxxxxxxxx - */ - id: string; - /** Format: url */ - url: string; - place: string; - ratio: number; - /** Format: url */ - imageUrl: string; - dayOfWeek: number; - }[]; - /** @default 0 */ - notesPerOneAd: number; - /** @example false */ - requireSetup: boolean; - enableEmail: boolean; - enableServiceWorker: boolean; - translatorAvailable: boolean; - proxyAccountName: string | null; - mediaProxy: string; - features?: { - registration: boolean; - localTimeline: boolean; - globalTimeline: boolean; - hcaptcha: boolean; - recaptcha: boolean; - objectStorage: boolean; - serviceWorker: boolean; - /** @default true */ - miauth?: boolean; - }; - backgroundImageUrl: string | null; - impressumUrl: string | null; - logoImageUrl: string | null; - privacyPolicyUrl: string | null; - serverRules: string[]; - themeColor: string | null; - policies: components['schemas']['RolePolicies']; - }; + 'application/json': components['schemas']['MetaLite'] | components['schemas']['MetaDetailed']; }; }; /** @description Client error */ @@ -20864,7 +20958,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'miauth/gen-token': { + 'miauth___gen-token': { requestBody: { content: { 'application/json': { @@ -20923,7 +21017,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:mutes* */ - 'mute/create': { + mute___create: { requestBody: { content: { 'application/json': { @@ -20983,7 +21077,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:mutes* */ - 'mute/delete': { + mute___delete: { requestBody: { content: { 'application/json': { @@ -21035,7 +21129,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:mutes* */ - 'mute/list': { + mute___list: { requestBody: { content: { 'application/json': { @@ -21093,7 +21187,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:mutes* */ - 'renote-mute/create': { + 'renote-mute___create': { requestBody: { content: { 'application/json': { @@ -21151,7 +21245,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:mutes* */ - 'renote-mute/delete': { + 'renote-mute___delete': { requestBody: { content: { 'application/json': { @@ -21203,7 +21297,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:mutes* */ - 'renote-mute/list': { + 'renote-mute___list': { requestBody: { content: { 'application/json': { @@ -21261,7 +21355,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'my/apps': { + my___apps: { requestBody: { content: { 'application/json': { @@ -21382,7 +21476,7 @@ export type operations = { * * **Credential required**: *No* */ - 'notes/children': { + notes___children: { requestBody: { content: { 'application/json': { @@ -21442,7 +21536,7 @@ export type operations = { * * **Credential required**: *No* */ - 'notes/clips': { + notes___clips: { requestBody: { content: { 'application/json': { @@ -21496,7 +21590,7 @@ export type operations = { * * **Credential required**: *No* */ - 'notes/conversation': { + notes___conversation: { requestBody: { content: { 'application/json': { @@ -21554,7 +21648,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:notes* */ - 'notes/create': { + notes___create: { requestBody: { content: { 'application/json': { @@ -21657,7 +21751,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:notes* */ - 'notes/delete': { + notes___delete: { requestBody: { content: { 'application/json': { @@ -21715,7 +21809,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:notes* */ - 'notes/update': { + notes___update: { requestBody: { content: { 'application/json': { @@ -21785,7 +21879,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:favorites* */ - 'notes/favorites/create': { + notes___favorites___create: { requestBody: { content: { 'application/json': { @@ -21843,7 +21937,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:favorites* */ - 'notes/favorites/delete': { + notes___favorites___delete: { requestBody: { content: { 'application/json': { @@ -21895,7 +21989,7 @@ export type operations = { * * **Credential required**: *No* */ - 'notes/featured': { + notes___featured: { requestBody: { content: { 'application/json': { @@ -21953,7 +22047,7 @@ export type operations = { * * **Credential required**: *No* */ - 'notes/global-timeline': { + 'notes___global-timeline': { requestBody: { content: { 'application/json': { @@ -22019,7 +22113,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'notes/hybrid-timeline': { + 'notes___hybrid-timeline': { requestBody: { content: { 'application/json': { @@ -22095,7 +22189,7 @@ export type operations = { * * **Credential required**: *No* */ - 'notes/local-timeline': { + 'notes___local-timeline': { requestBody: { content: { 'application/json': { @@ -22165,7 +22259,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'notes/mentions': { + notes___mentions: { requestBody: { content: { 'application/json': { @@ -22226,7 +22320,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'notes/polls/recommendation': { + notes___polls___recommendation: { requestBody: { content: { 'application/json': { @@ -22282,7 +22376,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:votes* */ - 'notes/polls/vote': { + notes___polls___vote: { requestBody: { content: { 'application/json': { @@ -22335,7 +22429,7 @@ export type operations = { * * **Credential required**: *No* */ - 'notes/events/search': { + notes___events___search: { requestBody: { content: { 'application/json': { @@ -22416,7 +22510,7 @@ export type operations = { * * **Credential required**: *No* */ - 'notes/reactions': { + notes___reactions: { requestBody: { content: { 'application/json': { @@ -22477,7 +22571,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:reactions* */ - 'notes/reactions/create': { + notes___reactions___create: { requestBody: { content: { 'application/json': { @@ -22530,7 +22624,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:reactions* */ - 'notes/reactions/delete': { + notes___reactions___delete: { requestBody: { content: { 'application/json': { @@ -22588,7 +22682,7 @@ export type operations = { * * **Credential required**: *No* */ - 'notes/renotes': { + notes___renotes: { requestBody: { content: { 'application/json': { @@ -22648,7 +22742,7 @@ export type operations = { * * **Credential required**: *No* */ - 'notes/replies': { + notes___replies: { requestBody: { content: { 'application/json': { @@ -22708,7 +22802,7 @@ export type operations = { * * **Credential required**: *No* */ - 'notes/search-by-tag': { + 'notes___search-by-tag': { requestBody: { content: { 'application/json': { @@ -22780,7 +22874,7 @@ export type operations = { * * **Credential required**: *No* */ - 'notes/search': { + notes___search: { requestBody: { content: { 'application/json': { @@ -22858,7 +22952,7 @@ export type operations = { * * **Credential required**: *No* */ - 'notes/show': { + notes___show: { requestBody: { content: { 'application/json': { @@ -22912,7 +23006,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'notes/state': { + notes___state: { requestBody: { content: { 'application/json': { @@ -22969,7 +23063,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'notes/thread-muting/create': { + 'notes___thread-muting___create': { requestBody: { content: { 'application/json': { @@ -23027,7 +23121,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'notes/thread-muting/delete': { + 'notes___thread-muting___delete': { requestBody: { content: { 'application/json': { @@ -23079,7 +23173,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'notes/timeline': { + notes___timeline: { requestBody: { content: { 'application/json': { @@ -23153,7 +23247,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'notes/translate': { + notes___translate: { requestBody: { content: { 'application/json': { @@ -23173,6 +23267,10 @@ export type operations = { }; }; }; + /** @description OK (without any results) */ + 204: { + content: never; + }; /** @description Client error */ 400: { content: { @@ -23211,7 +23309,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:notes* */ - 'notes/unrenote': { + notes___unrenote: { requestBody: { content: { 'application/json': { @@ -23269,7 +23367,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'notes/user-list-timeline': { + 'notes___user-list-timeline': { requestBody: { content: { 'application/json': { @@ -23348,7 +23446,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:notifications* */ - 'notifications/create': { + notifications___create: { requestBody: { content: { 'application/json': { @@ -23401,13 +23499,57 @@ export type operations = { }; }; }; + /** + * notifications/flush + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:notifications* + */ + notifications___flush: { + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; /** * notifications/mark-all-as-read * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:notifications* */ - 'notifications/mark-all-as-read': { + 'notifications___mark-all-as-read': { responses: { /** @description OK (without any results) */ 204: { @@ -23451,7 +23593,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:notifications* */ - 'notifications/test-notification': { + 'notifications___test-notification': { responses: { /** @description OK (without any results) */ 204: { @@ -23556,7 +23698,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:pages* */ - 'pages/create': { + pages___create: { requestBody: { content: { 'application/json': { @@ -23635,7 +23777,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:pages* */ - 'pages/delete': { + pages___delete: { requestBody: { content: { 'application/json': { @@ -23687,7 +23829,7 @@ export type operations = { * * **Credential required**: *No* */ - 'pages/featured': { + pages___featured: { responses: { /** @description OK (with results) */ 200: { @@ -23733,7 +23875,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:page-likes* */ - 'pages/like': { + pages___like: { requestBody: { content: { 'application/json': { @@ -23785,7 +23927,7 @@ export type operations = { * * **Credential required**: *No* */ - 'pages/show': { + pages___show: { requestBody: { content: { 'application/json': { @@ -23841,7 +23983,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:page-likes* */ - 'pages/unlike': { + pages___unlike: { requestBody: { content: { 'application/json': { @@ -23893,7 +24035,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:pages* */ - 'pages/update': { + pages___update: { requestBody: { content: { 'application/json': { @@ -23967,7 +24109,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:flash* */ - 'flash/create': { + flash___create: { requestBody: { content: { 'application/json': { @@ -23975,6 +24117,11 @@ export type operations = { summary: string; script: string; permissions: string[]; + /** + * @default public + * @enum {string} + */ + visibility?: 'public' | 'private'; }; }; }; @@ -24029,7 +24176,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:flash* */ - 'flash/delete': { + flash___delete: { requestBody: { content: { 'application/json': { @@ -24081,7 +24228,7 @@ export type operations = { * * **Credential required**: *No* */ - 'flash/featured': { + flash___featured: { responses: { /** @description OK (with results) */ 200: { @@ -24128,7 +24275,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'flash/gen-token': { + 'flash___gen-token': { requestBody: { content: { 'application/json': { @@ -24189,7 +24336,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:flash-likes* */ - 'flash/like': { + flash___like: { requestBody: { content: { 'application/json': { @@ -24241,7 +24388,7 @@ export type operations = { * * **Credential required**: *No* */ - 'flash/show': { + flash___show: { requestBody: { content: { 'application/json': { @@ -24295,7 +24442,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:flash-likes* */ - 'flash/unlike': { + flash___unlike: { requestBody: { content: { 'application/json': { @@ -24347,16 +24494,16 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:flash* */ - 'flash/update': { + flash___update: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ flashId: string; - title: string; - summary: string; - script: string; - permissions: string[]; + title?: string; + summary?: string; + script?: string; + permissions?: string[]; /** @enum {string} */ visibility?: 'public' | 'private'; }; @@ -24411,7 +24558,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:flash* */ - 'flash/my': { + flash___my: { requestBody: { content: { 'application/json': { @@ -24469,7 +24616,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:flash-likes* */ - 'flash/my-likes': { + 'flash___my-likes': { requestBody: { content: { 'application/json': { @@ -24625,7 +24772,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'promo/read': { + promo___read: { requestBody: { content: { 'application/json': { @@ -24677,7 +24824,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'roles/list': { + roles___list: { responses: { /** @description OK (with results) */ 200: { @@ -24723,7 +24870,7 @@ export type operations = { * * **Credential required**: *No* */ - 'roles/show': { + roles___show: { requestBody: { content: { 'application/json': { @@ -24777,7 +24924,7 @@ export type operations = { * * **Credential required**: *No* */ - 'roles/users': { + roles___users: { requestBody: { content: { 'application/json': { @@ -24841,7 +24988,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'roles/notes': { + roles___notes: { requestBody: { content: { 'application/json': { @@ -25171,7 +25318,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'sw/show-registration': { + 'sw___show-registration': { requestBody: { content: { 'application/json': { @@ -25233,7 +25380,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'sw/update-registration': { + 'sw___update-registration': { requestBody: { content: { 'application/json': { @@ -25292,7 +25439,7 @@ export type operations = { * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'sw/register': { + sw___register: { requestBody: { content: { 'application/json': { @@ -25356,7 +25503,7 @@ export type operations = { * * **Credential required**: *No* */ - 'sw/unregister': { + sw___unregister: { requestBody: { content: { 'application/json': { @@ -25475,7 +25622,7 @@ export type operations = { * * **Credential required**: *No* */ - 'username/available': { + username___available: { requestBody: { content: { 'application/json': { @@ -25603,7 +25750,7 @@ export type operations = { * * **Credential required**: *No* */ - 'users/clips': { + users___clips: { requestBody: { content: { 'application/json': { @@ -25663,7 +25810,7 @@ export type operations = { * * **Credential required**: *No* */ - 'users/followers': { + users___followers: { requestBody: { content: { 'application/json': { @@ -25726,7 +25873,7 @@ export type operations = { * * **Credential required**: *No* */ - 'users/following': { + users___following: { requestBody: { content: { 'application/json': { @@ -25790,7 +25937,7 @@ export type operations = { * * **Credential required**: *No* */ - 'users/gallery/posts': { + users___gallery___posts: { requestBody: { content: { 'application/json': { @@ -25850,7 +25997,7 @@ export type operations = { * * **Credential required**: *No* */ - 'users/get-frequently-replied-users': { + 'users___get-frequently-replied-users': { requestBody: { content: { 'application/json': { @@ -25909,7 +26056,7 @@ export type operations = { * * **Credential required**: *No* */ - 'users/featured-notes': { + 'users___featured-notes': { requestBody: { content: { 'application/json': { @@ -25967,7 +26114,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:user-groups* */ - 'users/groups/create': { + users___groups___create: { requestBody: { content: { 'application/json': { @@ -26026,7 +26173,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:user-groups* */ - 'users/groups/delete': { + users___groups___delete: { requestBody: { content: { 'application/json': { @@ -26078,7 +26225,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:user-groups* */ - 'users/groups/invitations/accept': { + users___groups___invitations___accept: { requestBody: { content: { 'application/json': { @@ -26130,7 +26277,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:user-groups* */ - 'users/groups/invitations/reject': { + users___groups___invitations___reject: { requestBody: { content: { 'application/json': { @@ -26182,7 +26329,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:user-groups* */ - 'users/groups/invite': { + users___groups___invite: { requestBody: { content: { 'application/json': { @@ -26236,7 +26383,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:user-groups* */ - 'users/groups/joined': { + users___groups___joined: { responses: { /** @description OK (with results) */ 200: { @@ -26282,7 +26429,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:user-groups* */ - 'users/groups/leave': { + users___groups___leave: { requestBody: { content: { 'application/json': { @@ -26334,7 +26481,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:user-groups* */ - 'users/groups/owned': { + users___groups___owned: { responses: { /** @description OK (with results) */ 200: { @@ -26380,7 +26527,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:user-groups* */ - 'users/groups/pull': { + users___groups___pull: { requestBody: { content: { 'application/json': { @@ -26434,7 +26581,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:user-groups* */ - 'users/groups/show': { + users___groups___show: { requestBody: { content: { 'application/json': { @@ -26488,7 +26635,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:user-groups* */ - 'users/groups/transfer': { + users___groups___transfer: { requestBody: { content: { 'application/json': { @@ -26544,7 +26691,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:user-groups* */ - 'users/groups/update': { + users___groups___update: { requestBody: { content: { 'application/json': { @@ -26599,7 +26746,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'users/lists/create': { + users___lists___create: { requestBody: { content: { 'application/json': { @@ -26652,7 +26799,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'users/lists/delete': { + users___lists___delete: { requestBody: { content: { 'application/json': { @@ -26704,7 +26851,7 @@ export type operations = { * * **Credential required**: *No* / **Permission**: *read:account* */ - 'users/lists/list': { + users___lists___list: { requestBody: { content: { 'application/json': { @@ -26758,7 +26905,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'users/lists/pull': { + users___lists___pull: { requestBody: { content: { 'application/json': { @@ -26812,7 +26959,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'users/lists/push': { + users___lists___push: { requestBody: { content: { 'application/json': { @@ -26872,7 +27019,7 @@ export type operations = { * * **Credential required**: *No* / **Permission**: *read:account* */ - 'users/lists/show': { + users___lists___show: { requestBody: { content: { 'application/json': { @@ -26928,7 +27075,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'users/lists/favorite': { + users___lists___favorite: { requestBody: { content: { 'application/json': { @@ -26980,7 +27127,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'users/lists/unfavorite': { + users___lists___unfavorite: { requestBody: { content: { 'application/json': { @@ -27032,7 +27179,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'users/lists/update': { + users___lists___update: { requestBody: { content: { 'application/json': { @@ -27088,7 +27235,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'users/lists/create-from-public': { + 'users___lists___create-from-public': { requestBody: { content: { 'application/json': { @@ -27143,7 +27290,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'users/lists/update-membership': { + 'users___lists___update-membership': { requestBody: { content: { 'application/json': { @@ -27198,7 +27345,7 @@ export type operations = { * * **Credential required**: *No* / **Permission**: *read:account* */ - 'users/lists/get-memberships': { + 'users___lists___get-memberships': { requestBody: { content: { 'application/json': { @@ -27269,7 +27416,7 @@ export type operations = { * * **Credential required**: *No* */ - 'users/notes': { + users___notes: { requestBody: { content: { 'application/json': { @@ -27343,7 +27490,7 @@ export type operations = { * * **Credential required**: *No* */ - 'users/pages': { + users___pages: { requestBody: { content: { 'application/json': { @@ -27403,7 +27550,7 @@ export type operations = { * * **Credential required**: *No* */ - 'users/flashs': { + users___flashs: { requestBody: { content: { 'application/json': { @@ -27463,7 +27610,7 @@ export type operations = { * * **Credential required**: *No* */ - 'users/reactions': { + users___reactions: { requestBody: { content: { 'application/json': { @@ -27525,7 +27672,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'users/recommendation': { + users___recommendation: { requestBody: { content: { 'application/json': { @@ -27581,7 +27728,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'users/relation': { + users___relation: { requestBody: { content: { 'application/json': { @@ -27656,7 +27803,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:report-abuse* */ - 'users/report-abuse': { + 'users___report-abuse': { requestBody: { content: { 'application/json': { @@ -27709,7 +27856,7 @@ export type operations = { * * **Credential required**: *No* */ - 'users/search-by-username-and-host': { + 'users___search-by-username-and-host': { requestBody: { content: { 'application/json': { @@ -27767,7 +27914,7 @@ export type operations = { * * **Credential required**: *No* */ - 'users/search': { + users___search: { requestBody: { content: { 'application/json': { @@ -27831,7 +27978,7 @@ export type operations = { * * **Credential required**: *No* */ - 'users/show': { + users___show: { requestBody: { content: { 'application/json': { @@ -27889,7 +28036,7 @@ export type operations = { * * **Credential required**: *No* */ - 'users/stats': { + users___stats: { requestBody: { content: { 'application/json': { @@ -27965,7 +28112,7 @@ export type operations = { * * **Credential required**: *No* */ - 'users/achievements': { + users___achievements: { requestBody: { content: { 'application/json': { @@ -28022,7 +28169,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'users/update-memo': { + 'users___update-memo': { requestBody: { content: { 'application/json': { @@ -28076,7 +28223,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'users/translate': { + users___translate: { requestBody: { content: { 'application/json': { @@ -28096,6 +28243,10 @@ export type operations = { }; }; }; + /** @description OK (without any results) */ + 204: { + content: never; + }; /** @description Client error */ 400: { content: { @@ -28306,7 +28457,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'bubble-game/register': { + 'bubble-game___register': { requestBody: { content: { 'application/json': { @@ -28367,7 +28518,7 @@ export type operations = { * * **Credential required**: *No* */ - 'bubble-game/ranking': { + 'bubble-game___ranking': { requestBody: { content: { 'application/json': { @@ -28425,7 +28576,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'reversi/cancel-match': { + 'reversi___cancel-match': { requestBody: { content: { 'application/json': { @@ -28477,7 +28628,7 @@ export type operations = { * * **Credential required**: *No* */ - 'reversi/games': { + reversi___games: { requestBody: { content: { 'application/json': { @@ -28537,7 +28688,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'reversi/match': { + reversi___match: { requestBody: { content: { 'application/json': { @@ -28599,7 +28750,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'reversi/invitations': { + reversi___invitations: { responses: { /** @description OK (with results) */ 200: { @@ -28645,7 +28796,7 @@ export type operations = { * * **Credential required**: *No* */ - 'reversi/show-game': { + 'reversi___show-game': { requestBody: { content: { 'application/json': { @@ -28699,7 +28850,7 @@ export type operations = { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'reversi/surrender': { + reversi___surrender: { requestBody: { content: { 'application/json': { @@ -28751,7 +28902,7 @@ export type operations = { * * **Credential required**: *No* */ - 'reversi/verify': { + reversi___verify: { requestBody: { content: { 'application/json': { diff --git a/packages/cherrypick-js/src/consts.ts b/packages/cherrypick-js/src/consts.ts index 0e446c1215..b690621e98 100644 --- a/packages/cherrypick-js/src/consts.ts +++ b/packages/cherrypick-js/src/consts.ts @@ -121,6 +121,7 @@ export const moderationLogTypes = [ 'resetPassword', 'suspendRemoteInstance', 'unsuspendRemoteInstance', + 'updateRemoteInstanceNote', 'markSensitiveDriveFile', 'unmarkSensitiveDriveFile', 'resolveAbuseReport', @@ -261,6 +262,12 @@ export type ModerationLogPayloads = { id: string; host: string; }; + updateRemoteInstanceNote: { + id: string; + host: string; + before: string | null; + after: string | null; + }; markSensitiveDriveFile: { fileId: string; fileUserId: string | null; diff --git a/packages/cherrypick-js/src/entities.ts b/packages/cherrypick-js/src/entities.ts index 772d2bbfa1..35503d6d6f 100644 --- a/packages/cherrypick-js/src/entities.ts +++ b/packages/cherrypick-js/src/entities.ts @@ -95,6 +95,9 @@ export type ModerationLog = { } | { type: 'unsuspendRemoteInstance'; info: ModerationLogPayloads['unsuspendRemoteInstance']; +} | { + type: 'updateRemoteInstanceNote'; + info: ModerationLogPayloads['updateRemoteInstanceNote']; } | { type: 'markSensitiveDriveFile'; info: ModerationLogPayloads['markSensitiveDriveFile']; diff --git a/packages/cherrypick-js/src/index.ts b/packages/cherrypick-js/src/index.ts index 54cae8ec03..28007a8ade 100644 --- a/packages/cherrypick-js/src/index.ts +++ b/packages/cherrypick-js/src/index.ts @@ -1,17 +1,20 @@ -import { Endpoints } from './api.types.js'; +import { type Endpoints } from './api.types.js'; import Stream, { Connection } from './streaming.js'; -import { Channels } from './streaming.types.js'; -import { Acct } from './acct.js'; +import { type Channels } from './streaming.types.js'; +import { type Acct } from './acct.js'; import * as consts from './consts.js'; -export { +export type { Endpoints, - Stream, - Connection as ChannelConnection, Channels, Acct, }; +export { + Stream, + Connection as ChannelConnection, +}; + export const permissions = consts.permissions; export const notificationTypes = consts.notificationTypes; export const noteVisibilities = consts.noteVisibilities; diff --git a/packages/cherrypick-js/src/streaming.types.ts b/packages/cherrypick-js/src/streaming.types.ts index a1e4108b28..6b997cad44 100644 --- a/packages/cherrypick-js/src/streaming.types.ts +++ b/packages/cherrypick-js/src/streaming.types.ts @@ -42,6 +42,7 @@ export type Channels = { unreadNotification: (payload: Notification) => void; unreadMention: (payload: Note['id']) => void; readAllUnreadMentions: () => void; + notificationFlushed: () => void; unreadSpecifiedNote: (payload: Note['id']) => void; readAllUnreadSpecifiedNotes: () => void; readAllMessagingMessages: () => void; diff --git a/packages/cherrypick-js/tsconfig.json b/packages/cherrypick-js/tsconfig.json index f56b65e868..6e34e332e0 100644 --- a/packages/cherrypick-js/tsconfig.json +++ b/packages/cherrypick-js/tsconfig.json @@ -6,7 +6,7 @@ "moduleResolution": "nodenext", "declaration": true, "declarationMap": true, - "sourceMap": true, + "sourceMap": false, "outDir": "./built/", "removeComments": true, "strict": true, diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx index ab8aa0537b..3763b3c933 100644 --- a/packages/frontend/.storybook/generate.tsx +++ b/packages/frontend/.storybook/generate.tsx @@ -401,7 +401,8 @@ function toStories(component: string): Promise { // glob('src/{components,pages,ui,widgets}/**/*.vue') (async () => { const globs = await Promise.all([ - glob('src/components/global/*.vue'), + glob('src/components/global/Mk*.vue'), + glob('src/components/global/RouterView.vue'), glob('src/components/Mk{A,B}*.vue'), glob('src/components/MkDigitalClock.vue'), glob('src/components/MkEvent.vue'), @@ -411,6 +412,7 @@ function toStories(component: string): Promise { glob('src/components/MkUserSetupDialog.*.vue'), glob('src/components/MkInviteCode.vue'), glob('src/pages/user/home.vue'), + glob('src/components/global/CP*.vue'), ]); const components = globs.flat(); await Promise.all(components.map(async (component) => { diff --git a/packages/frontend/.storybook/preview-head.html b/packages/frontend/.storybook/preview-head.html index 537ccf971a..4548dcedfe 100644 --- a/packages/frontend/.storybook/preview-head.html +++ b/packages/frontend/.storybook/preview-head.html @@ -1,3 +1,8 @@ + + diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 53fa567979..e82090b2c3 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -18,7 +18,6 @@ }, "dependencies": { "@discordapp/twemoji": "15.0.2", - "@fontsource/jetbrains-mono": "^5.0.12", "@github/webauthn-json": "2.1.1", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", "@misskey-dev/browser-image-resizer": "2024.1.0", @@ -28,22 +27,22 @@ "@syuilo/aiscript": "0.17.0", "@tabler/icons-webfont": "2.44.0", "@twemoji/parser": "15.0.0", - "@vitejs/plugin-vue": "5.0.3", - "@vue/compiler-sfc": "3.4.18", - "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.2", + "@vitejs/plugin-vue": "5.0.4", + "@vue/compiler-sfc": "3.4.21", + "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.4", "astring": "1.8.6", "autosize": "6.0.1", "broadcast-channel": "7.0.0", "buraha": "0.0.1", - "canvas-confetti": "1.6.1", - "chart.js": "4.4.1", + "canvas-confetti": "1.9.2", + "chart.js": "4.4.2", "chartjs-adapter-date-fns": "3.0.0", "chartjs-chart-matrix": "2.0.1", "chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-zoom": "2.0.1", "cherrypick-js": "workspace:*", "cherrypick-mfm-js": "0.24.0-cherrypick.4", - "chromatic": "10.6.1", + "chromatic": "11.0.0", "compare-versions": "6.1.0", "cropperjs": "2.0.0-beta.4", "date-fns": "2.30.0", @@ -58,18 +57,16 @@ "misskey-bubble-game": "workspace:*", "misskey-reversi": "workspace:*", "photoswipe": "5.4.3", - "pretendard": "^1.3.8", - "pretendard-jp": "^1.3.8", "prismjs": "1.29.0", "punycode": "2.3.1", - "rollup": "4.9.6", - "sanitize-html": "2.11.0", - "sass": "1.70.0", - "shiki": "1.0.0-beta.3", + "rollup": "4.12.0", + "sanitize-html": "2.12.1", + "sass": "1.71.1", + "shiki": "1.2.0", "strict-event-emitter-types": "2.0.0", "temml": "0.10.20", "textarea-caret": "3.1.0", - "three": "0.160.1", + "three": "0.162.0", "throttle-debounce": "5.0.0", "tinycolor2": "1.6.0", "tinyld": "^1.3.4", @@ -77,70 +74,70 @@ "tsconfig-paths": "4.2.0", "typescript": "5.3.3", "uuid": "9.0.1", - "v-code-diff": "1.7.2", - "vite": "5.1.0", - "vue": "3.4.18", + "v-code-diff": "1.9.0", + "vite": "5.1.4", + "vue": "3.4.21", "vue-prism-editor": "2.0.0-alpha.2", "vuedraggable": "next" }, "devDependencies": { "@misskey-dev/eslint-plugin": "1.0.0", "@misskey-dev/summaly": "5.0.3", - "@storybook/addon-actions": "8.0.0-beta.2", - "@storybook/addon-essentials": "8.0.0-beta.2", - "@storybook/addon-interactions": "8.0.0-beta.2", - "@storybook/addon-links": "8.0.0-beta.2", - "@storybook/addon-mdx-gfm": "8.0.0-beta.2", - "@storybook/addon-storysource": "8.0.0-beta.2", - "@storybook/blocks": "8.0.0-beta.2", - "@storybook/components": "8.0.0-beta.2", - "@storybook/core-events": "8.0.0-beta.2", - "@storybook/manager-api": "8.0.0-beta.2", - "@storybook/preview-api": "8.0.0-beta.2", - "@storybook/react": "8.0.0-beta.2", - "@storybook/react-vite": "8.0.0-beta.2", - "@storybook/test": "8.0.0-beta.2", - "@storybook/theming": "8.0.0-beta.2", - "@storybook/types": "8.0.0-beta.2", - "@storybook/vue3": "8.0.0-beta.2", - "@storybook/vue3-vite": "8.0.0-beta.2", + "@storybook/addon-actions": "8.0.0-beta.6", + "@storybook/addon-essentials": "8.0.0-beta.6", + "@storybook/addon-interactions": "8.0.0-beta.6", + "@storybook/addon-links": "8.0.0-beta.6", + "@storybook/addon-mdx-gfm": "8.0.0-beta.6", + "@storybook/addon-storysource": "8.0.0-beta.6", + "@storybook/blocks": "8.0.0-beta.6", + "@storybook/components": "8.0.0-beta.6", + "@storybook/core-events": "8.0.0-beta.6", + "@storybook/manager-api": "8.0.0-beta.6", + "@storybook/preview-api": "8.0.0-beta.6", + "@storybook/react": "8.0.0-beta.6", + "@storybook/react-vite": "8.0.0-beta.6", + "@storybook/test": "8.0.0-beta.6", + "@storybook/theming": "8.0.0-beta.6", + "@storybook/types": "8.0.0-beta.6", + "@storybook/vue3": "8.0.0-beta.6", + "@storybook/vue3-vite": "8.0.0-beta.6", "@testing-library/vue": "8.0.2", "@types/autosize": "^4.0.1", "@types/escape-regexp": "0.0.3", "@types/estree": "1.0.5", "@types/matter-js": "0.19.6", "@types/micromatch": "4.0.6", - "@types/node": "20.11.17", + "@types/node": "20.11.22", "@types/prismjs": "^1.26.0", - "@types/punycode": "2.1.3", - "@types/sanitize-html": "2.9.5", + "@types/punycode": "2.1.4", + "@types/sanitize-html": "2.11.0", "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@types/uuid": "9.0.8", "@types/ws": "8.5.10", - "@typescript-eslint/eslint-plugin": "6.18.1", - "@typescript-eslint/parser": "6.18.1", + "@typescript-eslint/eslint-plugin": "7.1.0", + "@typescript-eslint/parser": "7.1.0", "@vitest/coverage-v8": "0.34.6", - "@vue/runtime-core": "3.4.18", + "@vue/runtime-core": "3.4.21", "acorn": "8.11.3", "cross-env": "7.0.3", - "cypress": "13.6.4", - "eslint": "8.56.0", + "cypress": "13.6.6", + "eslint": "8.57.0", "eslint-plugin-import": "2.29.1", "eslint-plugin-storybook": "^0.6.13", - "eslint-plugin-vue": "9.20.1", + "eslint-plugin-vue": "9.22.0", "fast-glob": "3.3.2", - "happy-dom": "10.0.3", + "happy-dom": "13.6.2", "intersection-observer": "0.12.2", "micromatch": "4.0.5", "msw": "2.1.7", "msw-storybook-addon": "2.0.0-beta.1", - "nodemon": "3.0.3", + "nodemon": "3.1.0", "prettier": "3.2.5", "react": "18.2.0", "react-dom": "18.2.0", "start-server-and-test": "2.0.3", - "storybook": "8.0.0-beta.2", + "storybook": "8.0.0-beta.6", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", "vite-plugin-turbosnap": "1.0.3", "vitest": "0.34.6", diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts index a4b2526fda..173170b22f 100644 --- a/packages/frontend/src/account.ts +++ b/packages/frontend/src/account.ts @@ -329,7 +329,7 @@ export async function openAccountMenu(opts: { text: i18n.ts.profile, to: `/@${ $i.username }`, avatar: $i, - }, { type: 'divider' }, ...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises, { + }, { type: 'divider' as const }, ...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises, { type: 'parent' as const, icon: 'ti ti-plus', text: i18n.ts.addAccount, diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index e7340193c3..fe06585349 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -156,8 +156,11 @@ export async function common(createVue: () => App) { // NOTE: この処理は必ずクライアント更新チェック処理より後に来ること(テーマ再構築のため) watch(defaultStore.reactiveState.darkMode, (darkMode) => { applyTheme(darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme')); + document.documentElement.dataset.colorMode = darkMode ? 'dark' : 'light'; }, { immediate: miLocalStorage.getItem('theme') == null }); + document.documentElement.dataset.colorMode = defaultStore.state.darkMode ? 'dark' : 'light'; + const darkTheme = computed(ColdDeviceStorage.makeGetterSetter('darkTheme')); const lightTheme = computed(ColdDeviceStorage.makeGetterSetter('lightTheme')); diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index db10501b42..1e7ffe9a45 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -11,7 +11,7 @@ import { alert, confirm, popup, post, welcomeToast } 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 { instance } from '@/instance.js'; import { ColdDeviceStorage, defaultStore } from '@/store.js'; import { makeHotkey } from '@/scripts/hotkey.js'; import { reactionPicker } from '@/scripts/reaction-picker.js'; @@ -82,27 +82,31 @@ export async function mainBoot() { mainRouter.push('/search'); }, }; - - if (defaultStore.state.enableSeasonalScreenEffect) { - const month = new Date().getMonth() + 1; - if (defaultStore.state.hemisphere === 'S') { - // ▼南半球 - if (month === 7 || month === 8) { - const SnowfallEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect; - new SnowfallEffect({}).render(); - } - } else { - // ▼北半球 - if (month === 12 || month === 1) { - const SnowfallEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect; - new SnowfallEffect({}).render(); - } else if (month === 3 || month === 4) { - const SakuraEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect; - new SakuraEffect({ - sakura: true, - }).render(); + try { + if (defaultStore.state.enableSeasonalScreenEffect) { + const month = new Date().getMonth() + 1; + if (defaultStore.state.hemisphere === 'S') { + // ▼南半球 + if (month === 7 || month === 8) { + const SnowfallEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect; + new SnowfallEffect({}).render(); + } + } else { + // ▼北半球 + if (month === 12 || month === 1) { + const SnowfallEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect; + new SnowfallEffect({}).render(); + } else if (month === 3 || month === 4) { + const SakuraEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect; + new SakuraEffect({ + sakura: true, + }).render(); + } } - } + } + } catch (error) { + // console.error(error); + console.error('Failed to initialise the seasonal screen effect canvas context:', error); } if ($i) { @@ -194,14 +198,26 @@ export async function mainBoot() { if ($i.followersCount >= 500) claimAchievement('followers500'); if ($i.followersCount >= 1000) claimAchievement('followers1000'); - if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365) { - claimAchievement('passedSinceAccountCreated1'); - } - if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365 * 2) { - claimAchievement('passedSinceAccountCreated2'); - } - if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365 * 3) { + const createdAt = new Date($i.createdAt); + const createdAtThreeYearsLater = new Date($i.createdAt); + createdAtThreeYearsLater.setFullYear(createdAtThreeYearsLater.getFullYear() + 3); + if (now >= createdAtThreeYearsLater) { claimAchievement('passedSinceAccountCreated3'); + claimAchievement('passedSinceAccountCreated2'); + claimAchievement('passedSinceAccountCreated1'); + } else { + const createdAtTwoYearsLater = new Date($i.createdAt); + createdAtTwoYearsLater.setFullYear(createdAtTwoYearsLater.getFullYear() + 2); + if (now >= createdAtTwoYearsLater) { + claimAchievement('passedSinceAccountCreated2'); + claimAchievement('passedSinceAccountCreated1'); + } else { + const createdAtOneYearLater = new Date($i.createdAt); + createdAtOneYearLater.setFullYear(createdAtOneYearLater.getFullYear() + 1); + if (now >= createdAtOneYearLater) { + claimAchievement('passedSinceAccountCreated1'); + } + } } if (claimedAchievements.length >= 30) { @@ -236,18 +252,16 @@ export async function mainBoot() { const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt'); const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo'); - if (neverShowDonationInfo !== 'true' && (new Date($i.createdAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !location.pathname.startsWith('/miauth')) { + if (neverShowDonationInfo !== 'true' && (createdAt.getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !location.pathname.startsWith('/miauth')) { if (latestDonationInfoShownAt == null || (new Date(latestDonationInfoShownAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 30)))) { popup(defineAsyncComponent(() => import('@/components/MkDonation.vue')), {}, {}, 'closed'); } } - fetchInstance().then(() => { - const modifiedVersionMustProminentlyOfferInAgplV3Section13Read = miLocalStorage.getItem('modifiedVersionMustProminentlyOfferInAgplV3Section13Read'); - if (modifiedVersionMustProminentlyOfferInAgplV3Section13Read !== 'true' && instance.repositoryUrl !== 'https://github.com/kokonect-link/cherrypick') { - popup(defineAsyncComponent(() => import('@/components/MkSourceCodeAvailablePopup.vue')), {}, {}, 'closed'); - } - }); + const modifiedVersionMustProminentlyOfferInAgplV3Section13Read = miLocalStorage.getItem('modifiedVersionMustProminentlyOfferInAgplV3Section13Read'); + if (modifiedVersionMustProminentlyOfferInAgplV3Section13Read !== 'true' && instance.repositoryUrl !== 'https://github.com/kokonect-link/cherrypick') { + popup(defineAsyncComponent(() => import('@/components/MkSourceCodeAvailablePopup.vue')), {}, {}, 'closed'); + } if ('Notification' in window) { // 許可を得ていなかったらリクエスト diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index b4f8f447b1..d3f1595f6c 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
  1. - + @@ -62,18 +62,7 @@ import { i18n } from '@/i18n.js'; import { miLocalStorage } from '@/local-storage.js'; import { customEmojis } from '@/custom-emojis.js'; import { MFM_TAGS, MFM_PARAMS, HTML_TAGS } from '@/const.js'; - -type EmojiDef = { - emoji: string; - name: string; - url: string; - aliasOf?: string; -} | { - emoji: string; - name: string; - aliasOf?: string; - isCustomEmoji?: true; -}; +import { searchEmoji, EmojiDef } from '@/scripts/search-emoji.js'; const lib = emojilist.filter(x => x.category !== 'flags'); @@ -93,7 +82,7 @@ const emojiDb = computed(() => { unicodeEmojiDB.push({ emoji: emoji, name: k, - aliasOf: getEmojiName(emoji)!, + aliasOf: getEmojiName(emoji), url: char2path(emoji), }); } @@ -256,7 +245,7 @@ function exec() { return; } - emojis.value = emojiAutoComplete(props.q, emojiDb.value); + emojis.value = searchEmoji(props.q, emojiDb.value); } else if (props.type === 'mfmTag') { if (!props.q || props.q === '') { mfmTags.value = MFM_TAGS; @@ -281,87 +270,6 @@ function exec() { } } -type EmojiScore = { emoji: EmojiDef, score: number }; - -function emojiAutoComplete(query: string | null, emojiDb: EmojiDef[], max = 30): EmojiDef[] { - if (!query) { - return []; - } - - const matched = new Map(); - // 完全一致(エイリアス込み) - emojiDb.some(x => { - if (x.name === query && !matched.has(x.aliasOf ?? x.name)) { - matched.set(x.aliasOf ?? x.name, { emoji: x, score: query.length + 2 }); - } - return matched.size === max; - }); - - // 前方一致(エイリアスなし) - if (matched.size < max) { - emojiDb.some(x => { - if (x.name.startsWith(query) && !x.aliasOf) { - matched.set(x.name, { emoji: x, score: query.length + 1 }); - } - return matched.size === max; - }); - } - - // 前方一致(エイリアス込み) - if (matched.size < max) { - emojiDb.some(x => { - if (x.name.startsWith(query) && !matched.has(x.aliasOf ?? x.name)) { - matched.set(x.aliasOf ?? x.name, { emoji: x, score: query.length }); - } - return matched.size === max; - }); - } - - // 部分一致(エイリアス込み) - if (matched.size < max) { - emojiDb.some(x => { - if (x.name.includes(query) && !matched.has(x.aliasOf ?? x.name)) { - matched.set(x.aliasOf ?? x.name, { emoji: x, score: query.length - 1 }); - } - return matched.size === max; - }); - } - - // 簡易あいまい検索(3文字以上) - if (matched.size < max && query.length > 3) { - const queryChars = [...query]; - const hitEmojis = new Map(); - - for (const x of emojiDb) { - // 文字列の位置を進めながら、クエリの文字を順番に探す - - let pos = 0; - let hit = 0; - for (const c of queryChars) { - pos = x.name.indexOf(c, pos); - if (pos <= -1) break; - hit++; - } - - // 半分以上の文字が含まれていればヒットとする - if (hit > Math.ceil(queryChars.length / 2) && hit - 2 > (matched.get(x.aliasOf ?? x.name)?.score ?? 0)) { - hitEmojis.set(x.aliasOf ?? x.name, { emoji: x, score: hit - 2 }); - } - } - - // ヒットしたものを全部追加すると雑多になるので、先頭の6件程度だけにしておく(6件=オートコンプリートのポップアップのサイズ分) - [...hitEmojis.values()] - .sort((x, y) => y.score - x.score) - .slice(0, 6) - .forEach(it => matched.set(it.emoji.name, it)); - } - - return [...matched.values()] - .sort((x, y) => y.score - x.score) - .slice(0, max) - .map(it => it.emoji); -} - function onMousedown(event: Event) { if (!contains(rootEl.value, event.target) && (rootEl.value !== event.target)) props.close(); } diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue index f37892eb02..249f934bf0 100644 --- a/packages/frontend/src/components/MkButton.vue +++ b/packages/frontend/src/components/MkButton.vue @@ -23,6 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only v-else class="_button" :class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike }]" :to="to ?? '#'" + :behavior="linkBehavior" @mousedown="onMousedown" >
    @@ -45,6 +46,7 @@ const props = defineProps<{ inline?: boolean; link?: boolean; to?: string; + linkBehavior?: null | 'window' | 'browser'; autofocus?: boolean; wait?: boolean; danger?: boolean; diff --git a/packages/frontend/src/components/MkClipPreview.vue b/packages/frontend/src/components/MkClipPreview.vue index c51ad4356d..db47c109b2 100644 --- a/packages/frontend/src/components/MkClipPreview.vue +++ b/packages/frontend/src/components/MkClipPreview.vue @@ -4,37 +4,59 @@ SPDX-License-Identifier: AGPL-3.0-only --> diff --git a/packages/frontend/src/components/MkFormDialog.vue b/packages/frontend/src/components/MkFormDialog.vue index 0d8734799c..deedc5badb 100644 --- a/packages/frontend/src/components/MkFormDialog.vue +++ b/packages/frontend/src/components/MkFormDialog.vue @@ -21,37 +21,37 @@ SPDX-License-Identifier: AGPL-3.0-only
    - diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue index 0ab2e36b4f..6a9e1dc105 100644 --- a/packages/frontend/src/components/MkInput.vue +++ b/packages/frontend/src/components/MkInput.vue @@ -22,6 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only :autocomplete="autocomplete" :autocapitalize="autocapitalize" :spellcheck="spellcheck" + :inputmode="inputmode" :step="step" :list="id" :min="min" @@ -63,6 +64,7 @@ const props = defineProps<{ mfmAutocomplete?: boolean | SuggestionType[], autocapitalize?: string; spellcheck?: boolean; + inputmode?: 'none' | 'text' | 'search' | 'email' | 'url' | 'numeric' | 'tel' | 'decimal'; step?: any; datalist?: string[]; min?: number; diff --git a/packages/frontend/src/components/MkLink.vue b/packages/frontend/src/components/MkLink.vue index c115672646..d1c28b9227 100644 --- a/packages/frontend/src/components/MkLink.vue +++ b/packages/frontend/src/components/MkLink.vue @@ -19,6 +19,7 @@ import { defineAsyncComponent, ref } from 'vue'; import { url as local } from '@/config.js'; import { useTooltip } from '@/scripts/use-tooltip.js'; import * as os from '@/os.js'; +import { isEnabledUrlPreview } from '@/instance.js'; const props = withDefaults(defineProps<{ url: string; @@ -30,15 +31,17 @@ const self = props.url.startsWith(local); const attr = self ? 'to' : 'href'; const target = self ? null : '_blank'; -const el = ref(); +const el = ref(); -useTooltip(el, (showing) => { - os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), { - showing, - url: props.url, - source: el.value, - }, {}, 'closed'); -}); +if (isEnabledUrlPreview.value) { + useTooltip(el, (showing) => { + os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), { + showing, + url: props.url, + source: el.value instanceof HTMLElement ? el.value : el.value?.$el, + }, {}, 'closed'); + }); +} diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue index 07bb988c98..a17c1a8314 100644 --- a/packages/frontend/src/components/MkMediaImage.vue +++ b/packages/frontend/src/components/MkMediaImage.vue @@ -63,7 +63,7 @@ import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; -import { iAmModerator } from '@/account.js'; +import { $i, iAmModerator } from '@/account.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; const props = withDefaults(defineProps<{ @@ -139,6 +139,13 @@ function showMenu(ev: MouseEvent) { action: () => { os.apiWithDialog('drive/files/update', { fileId: props.image.id, isSensitive: true }); }, + }] : []), ...($i?.id === props.image.userId ? [{ + type: 'divider' as const, + }, { + type: 'link' as const, + text: i18n.ts._fileViewer.title, + icon: 'ti ti-info-circle', + to: `/my/drive/file/${props.image.id}`, }] : [])], ev.currentTarget ?? ev.target); } diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue index 2cd259d76d..343ed58d25 100644 --- a/packages/frontend/src/components/MkMediaVideo.vue +++ b/packages/frontend/src/components/MkMediaVideo.vue @@ -6,6 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only