Merge branch 'develop' into mahjong
This commit is contained in:
commit
f32b11ba12
252 changed files with 6666 additions and 4778 deletions
|
@ -1,5 +1,11 @@
|
||||||
|
# misskey settings
|
||||||
|
# MISSKEY_URL=https://example.tld/
|
||||||
|
|
||||||
# db settings
|
# db settings
|
||||||
POSTGRES_PASSWORD=example-misskey-pass
|
POSTGRES_PASSWORD=example-misskey-pass
|
||||||
|
# DATABASE_PASSWORD=${POSTGRES_PASSWORD}
|
||||||
POSTGRES_USER=example-misskey-user
|
POSTGRES_USER=example-misskey-user
|
||||||
|
# DATABASE_USER=${POSTGRES_USER}
|
||||||
POSTGRES_DB=misskey
|
POSTGRES_DB=misskey
|
||||||
|
# DATABASE_DB=${POSTGRES_DB}
|
||||||
DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}"
|
DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}"
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#───┘ URL └─────────────────────────────────────────────────────
|
#───┘ URL └─────────────────────────────────────────────────────
|
||||||
|
|
||||||
# Final accessible URL seen by a user.
|
# Final accessible URL seen by a user.
|
||||||
|
# You can set url from an environment variable instead.
|
||||||
url: https://example.tld/
|
url: https://example.tld/
|
||||||
|
|
||||||
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
|
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
|
||||||
|
@ -38,9 +39,11 @@ db:
|
||||||
port: 5432
|
port: 5432
|
||||||
|
|
||||||
# Database name
|
# Database name
|
||||||
|
# You can set db from an environment variable instead.
|
||||||
db: misskey
|
db: misskey
|
||||||
|
|
||||||
# Auth
|
# Auth
|
||||||
|
# You can set user and pass from environment variables instead.
|
||||||
user: example-misskey-user
|
user: example-misskey-user
|
||||||
pass: example-misskey-pass
|
pass: example-misskey-pass
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ node_modules/
|
||||||
packages/*/node_modules
|
packages/*/node_modules
|
||||||
redis/
|
redis/
|
||||||
files/
|
files/
|
||||||
misskey-assets/
|
|
||||||
fluent-emojis/
|
fluent-emojis/
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
|
|
8
.github/ISSUE_TEMPLATE/01_bug-report.yml
vendored
8
.github/ISSUE_TEMPLATE/01_bug-report.yml
vendored
|
@ -53,8 +53,8 @@ body:
|
||||||
Examples:
|
Examples:
|
||||||
* Model and OS of the device(s): MacBook Pro (14inch, 2021), macOS Ventura 13.4
|
* Model and OS of the device(s): MacBook Pro (14inch, 2021), macOS Ventura 13.4
|
||||||
* Browser: Chrome 113.0.5672.126
|
* Browser: Chrome 113.0.5672.126
|
||||||
* Server URL: misskey.io
|
* Server URL: misskey.example.com
|
||||||
* Misskey: 13.x.x
|
* Misskey: 2024.x.x
|
||||||
value: |
|
value: |
|
||||||
* Model and OS of the device(s):
|
* Model and OS of the device(s):
|
||||||
* Browser:
|
* Browser:
|
||||||
|
@ -74,11 +74,11 @@ body:
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
* Installation Method or Hosting Service: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment
|
* Installation Method or Hosting Service: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment
|
||||||
* Misskey: 13.x.x
|
* Misskey: 2024.x.x
|
||||||
* Node: 20.x.x
|
* Node: 20.x.x
|
||||||
* PostgreSQL: 15.x.x
|
* PostgreSQL: 15.x.x
|
||||||
* Redis: 7.x.x
|
* Redis: 7.x.x
|
||||||
* OS and Architecture: Ubuntu 22.04.2 LTS aarch64
|
* OS and Architecture: Ubuntu 24.04.2 LTS aarch64
|
||||||
value: |
|
value: |
|
||||||
* Installation Method or Hosting Service:
|
* Installation Method or Hosting Service:
|
||||||
* Misskey:
|
* Misskey:
|
||||||
|
|
5
.github/workflows/api-misskey-js.yml
vendored
5
.github/workflows/api-misskey-js.yml
vendored
|
@ -4,10 +4,11 @@ on:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
|
- .github/workflows/api-misskey-js.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
|
- .github/workflows/api-misskey-js.yml
|
||||||
jobs:
|
jobs:
|
||||||
report:
|
report:
|
||||||
|
|
||||||
|
@ -20,7 +21,7 @@ jobs:
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
2
.github/workflows/changelog-check.yml
vendored
2
.github/workflows/changelog-check.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
||||||
- name: Checkout head
|
- name: Checkout head
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.1
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ jobs:
|
||||||
|
|
||||||
- name: setup node
|
- name: setup node
|
||||||
id: setup-node
|
id: setup-node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
|
|
@ -6,12 +6,13 @@ on:
|
||||||
paths:
|
paths:
|
||||||
- packages/misskey-js/package.json
|
- packages/misskey-js/package.json
|
||||||
- package.json
|
- package.json
|
||||||
|
- .github/workflows/check-misskey-js-version.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ develop ]
|
branches: [ develop ]
|
||||||
paths:
|
paths:
|
||||||
- packages/misskey-js/package.json
|
- packages/misskey-js/package.json
|
||||||
- package.json
|
- package.json
|
||||||
|
- .github/workflows/check-misskey-js-version.yml
|
||||||
jobs:
|
jobs:
|
||||||
check-version:
|
check-version:
|
||||||
# ルートの package.json と packages/misskey-js/package.json のバージョンが一致しているかを確認する
|
# ルートの package.json と packages/misskey-js/package.json のバージョンが一致しているかを確認する
|
||||||
|
|
4
.github/workflows/get-api-diff.yml
vendored
4
.github/workflows/get-api-diff.yml
vendored
|
@ -9,7 +9,7 @@ on:
|
||||||
paths:
|
paths:
|
||||||
- packages/backend/**
|
- packages/backend/**
|
||||||
- .github/workflows/get-api-diff.yml
|
- .github/workflows/get-api-diff.yml
|
||||||
|
- .github/workflows/get-api-diff.yml
|
||||||
jobs:
|
jobs:
|
||||||
get-from-misskey:
|
get-from-misskey:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -34,7 +34,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
20
.github/workflows/lint.yml
vendored
20
.github/workflows/lint.yml
vendored
|
@ -11,6 +11,7 @@ on:
|
||||||
- packages/sw/**
|
- packages/sw/**
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
- packages/shared/eslint.config.js
|
- packages/shared/eslint.config.js
|
||||||
|
- .github/workflows/lint.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- packages/backend/**
|
- packages/backend/**
|
||||||
|
@ -18,7 +19,7 @@ on:
|
||||||
- packages/sw/**
|
- packages/sw/**
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
- packages/shared/eslint.config.js
|
- packages/shared/eslint.config.js
|
||||||
|
- .github/workflows/lint.yml
|
||||||
jobs:
|
jobs:
|
||||||
pnpm_install:
|
pnpm_install:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -28,7 +29,7 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: true
|
submodules: true
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4.0.2
|
- uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
@ -39,6 +40,8 @@ jobs:
|
||||||
needs: [pnpm_install]
|
needs: [pnpm_install]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
env:
|
||||||
|
eslint-cache-version: v1
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
workspace:
|
workspace:
|
||||||
|
@ -52,13 +55,20 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: true
|
submodules: true
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4.0.2
|
- uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- run: pnpm i --frozen-lockfile
|
- run: pnpm i --frozen-lockfile
|
||||||
- run: pnpm --filter ${{ matrix.workspace }} run eslint
|
- name: Restore eslint cache
|
||||||
|
uses: actions/cache@v4.0.2
|
||||||
|
with:
|
||||||
|
path: node_modules/.cache/eslint
|
||||||
|
key: eslint-${{ env.eslint-cache-version }}-${{ hashFiles('/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
eslint-${{ env.eslint-cache-version }}-${{ hashFiles('/pnpm-lock.yaml') }}-
|
||||||
|
- run: pnpm --filter ${{ matrix.workspace }} run eslint --cache --cache-location node_modules/.cache/eslint --cache-strategy content
|
||||||
|
|
||||||
typecheck:
|
typecheck:
|
||||||
needs: [pnpm_install]
|
needs: [pnpm_install]
|
||||||
|
@ -75,7 +85,7 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: true
|
submodules: true
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4.0.2
|
- uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
5
.github/workflows/locale.yml
vendored
5
.github/workflows/locale.yml
vendored
|
@ -4,10 +4,11 @@ on:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
- locales/**
|
- locales/**
|
||||||
|
- .github/workflows/locale.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- locales/**
|
- locales/**
|
||||||
|
- .github/workflows/locale.yml
|
||||||
jobs:
|
jobs:
|
||||||
locale_verify:
|
locale_verify:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -18,7 +19,7 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: true
|
submodules: true
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4.0.2
|
- uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
2
.github/workflows/on-release-created.yml
vendored
2
.github/workflows/on-release-created.yml
vendored
|
@ -26,7 +26,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
19
.github/workflows/release-edit-with-push.yml
vendored
19
.github/workflows/release-edit-with-push.yml
vendored
|
@ -3,10 +3,10 @@ name: "Release Manager: sync changelog with PR"
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- release/**
|
- develop
|
||||||
paths:
|
paths:
|
||||||
- 'CHANGELOG.md'
|
- 'CHANGELOG.md'
|
||||||
|
# - .github/workflows/release-edit-with-push.yml
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
@ -20,24 +20,29 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# headがrelease/かつopenのPRを1つ取得
|
# headが$GITHUB_REF_NAME, baseが$STABLE_BRANCHかつopenのPRを1つ取得
|
||||||
- name: Get PR
|
- name: Get PR
|
||||||
run: |
|
run: |
|
||||||
echo "pr_number=$(gh pr list --limit 1 --head "$GITHUB_REF_NAME" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT
|
echo "pr_number=$(gh pr list --limit 1 --search "head:$GITHUB_REF_NAME base:$STABLE_BRANCH is:open" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT
|
||||||
id: get_pr
|
id: get_pr
|
||||||
|
env:
|
||||||
|
STABLE_BRANCH: ${{ vars.STABLE_BRANCH }}
|
||||||
- name: Get target version
|
- name: Get target version
|
||||||
uses: misskey-dev/release-manager-actions/.github/actions/get-target-version@v1
|
if: steps.get_pr.outputs.pr_number != ''
|
||||||
|
uses: misskey-dev/release-manager-actions/.github/actions/get-target-version@v2
|
||||||
id: v
|
id: v
|
||||||
# CHANGELOG.mdの内容を取得
|
# CHANGELOG.mdの内容を取得
|
||||||
- name: Get changelog
|
- name: Get changelog
|
||||||
uses: misskey-dev/release-manager-actions/.github/actions/get-changelog@v1
|
if: steps.get_pr.outputs.pr_number != ''
|
||||||
|
uses: misskey-dev/release-manager-actions/.github/actions/get-changelog@v2
|
||||||
with:
|
with:
|
||||||
version: ${{ steps.v.outputs.target_version }}
|
version: ${{ steps.v.outputs.target_version }}
|
||||||
id: changelog
|
id: changelog
|
||||||
# PRのnotesを更新
|
# PRのnotesを更新
|
||||||
- name: Update PR
|
- name: Update PR
|
||||||
|
if: steps.get_pr.outputs.pr_number != ''
|
||||||
run: |
|
run: |
|
||||||
gh pr edit "$PR_NUMBER" --body "$CHANGELOG"
|
gh pr edit "$PR_NUMBER" --body "$CHANGELOG"
|
||||||
env:
|
env:
|
||||||
CHANGELOG: ${{ steps.changelog.outputs.changelog }}
|
|
||||||
PR_NUMBER: ${{ steps.get_pr.outputs.pr_number }}
|
PR_NUMBER: ${{ steps.get_pr.outputs.pr_number }}
|
||||||
|
CHANGELOG: ${{ steps.changelog.outputs.changelog }}
|
||||||
|
|
20
.github/workflows/release-with-dispatch.yml
vendored
20
.github/workflows/release-with-dispatch.yml
vendored
|
@ -33,18 +33,21 @@ jobs:
|
||||||
pr_number: ${{ steps.get_pr.outputs.pr_number }}
|
pr_number: ${{ steps.get_pr.outputs.pr_number }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# headがrelease/かつopenのPRを1つ取得
|
# headが$GITHUB_REF_NAME, baseが$STABLE_BRANCHかつopenのPRを1つ取得
|
||||||
- name: Get PRs
|
- name: Get PRs
|
||||||
run: |
|
run: |
|
||||||
echo "pr_number=$(gh pr list --limit 1 --search "head:release/ is:open" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT
|
echo "pr_number=$(gh pr list --limit 1 --search "head:$GITHUB_REF_NAME base:$STABLE_BRANCH is:open" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT
|
||||||
id: get_pr
|
id: get_pr
|
||||||
|
env:
|
||||||
|
STABLE_BRANCH: ${{ vars.STABLE_BRANCH }}
|
||||||
|
|
||||||
merge:
|
merge:
|
||||||
uses: misskey-dev/release-manager-actions/.github/workflows/merge.yml@v1
|
uses: misskey-dev/release-manager-actions/.github/workflows/merge.yml@v2
|
||||||
needs: get-pr
|
needs: get-pr
|
||||||
if: ${{ needs.get-pr.outputs.pr_number != '' && inputs.merge == true }}
|
if: ${{ needs.get-pr.outputs.pr_number != '' && inputs.merge == true }}
|
||||||
with:
|
with:
|
||||||
pr_number: ${{ needs.get-pr.outputs.pr_number }}
|
pr_number: ${{ needs.get-pr.outputs.pr_number }}
|
||||||
|
user: 'github-actions[bot]'
|
||||||
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
|
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
|
||||||
# Text to prepend to the changelog
|
# Text to prepend to the changelog
|
||||||
# The first line must be `## Unreleased`
|
# The first line must be `## Unreleased`
|
||||||
|
@ -65,15 +68,14 @@ jobs:
|
||||||
secrets:
|
secrets:
|
||||||
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
|
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
|
||||||
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
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:
|
create-prerelease:
|
||||||
uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v1
|
uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v2
|
||||||
needs: get-pr
|
needs: get-pr
|
||||||
if: ${{ needs.get-pr.outputs.pr_number != '' && inputs.merge != true }}
|
if: ${{ needs.get-pr.outputs.pr_number != '' && inputs.merge != true }}
|
||||||
with:
|
with:
|
||||||
pr_number: ${{ needs.get-pr.outputs.pr_number }}
|
pr_number: ${{ needs.get-pr.outputs.pr_number }}
|
||||||
|
user: 'github-actions[bot]'
|
||||||
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
|
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
|
||||||
use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }}
|
use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }}
|
||||||
indent: ${{ vars.INDENT }}
|
indent: ${{ vars.INDENT }}
|
||||||
|
@ -82,10 +84,11 @@ jobs:
|
||||||
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
create-target:
|
create-target:
|
||||||
uses: misskey-dev/release-manager-actions/.github/workflows/create-target.yml@v1
|
uses: misskey-dev/release-manager-actions/.github/workflows/create-target.yml@v2
|
||||||
needs: get-pr
|
needs: get-pr
|
||||||
if: ${{ needs.get-pr.outputs.pr_number == '' }}
|
if: ${{ needs.get-pr.outputs.pr_number == '' }}
|
||||||
with:
|
with:
|
||||||
|
user: 'github-actions[bot]'
|
||||||
# The script for version increment.
|
# The script for version increment.
|
||||||
# process.env.CURRENT_VERSION: The current version.
|
# process.env.CURRENT_VERSION: The current version.
|
||||||
#
|
#
|
||||||
|
@ -118,8 +121,7 @@ jobs:
|
||||||
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
|
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
|
||||||
use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }}
|
use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }}
|
||||||
indent: ${{ vars.INDENT }}
|
indent: ${{ vars.INDENT }}
|
||||||
|
stable_branch: ${{ vars.STABLE_BRANCH }}
|
||||||
secrets:
|
secrets:
|
||||||
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
|
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
|
||||||
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
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 }}
|
|
||||||
|
|
13
.github/workflows/release-with-ready.yml
vendored
13
.github/workflows/release-with-ready.yml
vendored
|
@ -16,23 +16,26 @@ jobs:
|
||||||
check:
|
check:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
ref: ${{ steps.get_pr.outputs.ref }}
|
head: ${{ steps.get_pr.outputs.head }}
|
||||||
|
base: ${{ steps.get_pr.outputs.base }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# PR情報を取得
|
# PR情報を取得
|
||||||
- name: Get PR
|
- name: Get PR
|
||||||
run: |
|
run: |
|
||||||
pr_json=$(gh pr view "$PR_NUMBER" --json isDraft,headRefName)
|
pr_json=$(gh pr view "$PR_NUMBER" --json isDraft,headRefName,baseRefName)
|
||||||
echo "ref=$(echo $pr_json | jq -r '.headRefName')" >> $GITHUB_OUTPUT
|
echo "head=$(echo $pr_json | jq -r '.headRefName')" >> $GITHUB_OUTPUT
|
||||||
|
echo "base=$(echo $pr_json | jq -r '.baseRefName')" >> $GITHUB_OUTPUT
|
||||||
id: get_pr
|
id: get_pr
|
||||||
env:
|
env:
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
release:
|
release:
|
||||||
uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v1
|
uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v2
|
||||||
needs: check
|
needs: check
|
||||||
if: startsWith(needs.check.outputs.ref, 'release/')
|
if: needs.check.outputs.head == github.event.repository.default_branch && needs.check.outputs.base == vars.STABLE_BRANCH
|
||||||
with:
|
with:
|
||||||
pr_number: ${{ github.event.pull_request.number }}
|
pr_number: ${{ github.event.pull_request.number }}
|
||||||
|
user: 'github-actions[bot]'
|
||||||
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
|
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
|
||||||
use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }}
|
use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }}
|
||||||
indent: ${{ vars.INDENT }}
|
indent: ${{ vars.INDENT }}
|
||||||
|
|
2
.github/workflows/storybook.yml
vendored
2
.github/workflows/storybook.yml
vendored
|
@ -36,7 +36,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js 20.x
|
- name: Use Node.js 20.x
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
7
.github/workflows/test-backend.yml
vendored
7
.github/workflows/test-backend.yml
vendored
|
@ -9,12 +9,13 @@ on:
|
||||||
- packages/backend/**
|
- packages/backend/**
|
||||||
# for permissions
|
# for permissions
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
|
- .github/workflows/test-backend.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- packages/backend/**
|
- packages/backend/**
|
||||||
# for permissions
|
# for permissions
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
|
- .github/workflows/test-backend.yml
|
||||||
jobs:
|
jobs:
|
||||||
unit:
|
unit:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -45,7 +46,7 @@ jobs:
|
||||||
- name: Install FFmpeg
|
- name: Install FFmpeg
|
||||||
uses: FedericoCarboni/setup-ffmpeg@v3
|
uses: FedericoCarboni/setup-ffmpeg@v3
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
@ -92,7 +93,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
8
.github/workflows/test-frontend.yml
vendored
8
.github/workflows/test-frontend.yml
vendored
|
@ -11,7 +11,7 @@ on:
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
# for e2e
|
# for e2e
|
||||||
- packages/backend/**
|
- packages/backend/**
|
||||||
|
- .github/workflows/test-frontend.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- packages/frontend/**
|
- packages/frontend/**
|
||||||
|
@ -19,7 +19,7 @@ on:
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
# for e2e
|
# for e2e
|
||||||
- packages/backend/**
|
- packages/backend/**
|
||||||
|
- .github/workflows/test-frontend.yml
|
||||||
jobs:
|
jobs:
|
||||||
vitest:
|
vitest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -35,7 +35,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
@ -90,7 +90,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
5
.github/workflows/test-misskey-js.yml
vendored
5
.github/workflows/test-misskey-js.yml
vendored
|
@ -8,11 +8,12 @@ on:
|
||||||
branches: [ develop ]
|
branches: [ develop ]
|
||||||
paths:
|
paths:
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
|
- .github/workflows/test-misskey-js.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ develop ]
|
branches: [ develop ]
|
||||||
paths:
|
paths:
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
|
- .github/workflows/test-misskey-js.yml
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@ jobs:
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
|
|
||||||
- name: Setup Node.js ${{ matrix.node-version }}
|
- name: Setup Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
2
.github/workflows/test-production.yml
vendored
2
.github/workflows/test-production.yml
vendored
|
@ -25,7 +25,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
5
.github/workflows/validate-api-json.yml
vendored
5
.github/workflows/validate-api-json.yml
vendored
|
@ -7,10 +7,11 @@ on:
|
||||||
- develop
|
- develop
|
||||||
paths:
|
paths:
|
||||||
- packages/backend/**
|
- packages/backend/**
|
||||||
|
- .github/workflows/validate-api-json.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- packages/backend/**
|
- packages/backend/**
|
||||||
|
- .github/workflows/validate-api-json.yml
|
||||||
jobs:
|
jobs:
|
||||||
validate-api-json:
|
validate-api-json:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -26,7 +27,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -59,6 +59,7 @@ ormconfig.json
|
||||||
temp
|
temp
|
||||||
/packages/frontend/src/**/*.stories.ts
|
/packages/frontend/src/**/*.stories.ts
|
||||||
tsdoc-metadata.json
|
tsdoc-metadata.json
|
||||||
|
misskey-assets
|
||||||
|
|
||||||
# blender backups
|
# blender backups
|
||||||
*.blend1
|
*.blend1
|
||||||
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,6 +1,3 @@
|
||||||
[submodule "misskey-assets"]
|
|
||||||
path = misskey-assets
|
|
||||||
url = https://github.com/misskey-dev/assets.git
|
|
||||||
[submodule "fluent-emojis"]
|
[submodule "fluent-emojis"]
|
||||||
path = fluent-emojis
|
path = fluent-emojis
|
||||||
url = https://github.com/misskey-dev/emojis.git
|
url = https://github.com/misskey-dev/emojis.git
|
||||||
|
|
56
CHANGELOG.md
56
CHANGELOG.md
|
@ -1,17 +1,50 @@
|
||||||
## Unreleased
|
## 2024.7.0
|
||||||
|
|
||||||
|
### Note
|
||||||
|
- デッキUIの新着ノートをサウンドで通知する機能の追加(v2024.5.0)に伴い、以前から動作しなくなっていたクライアント設定内の「アンテナ受信」「チャンネル通知」サウンドを削除しました。
|
||||||
|
- Streaming APIにて入力が不正な場合にはそのメッセージを無視するようになりました。 #14251
|
||||||
|
|
||||||
### General
|
### General
|
||||||
- Feat: 通報を受けた際、または解決した際に、予め登録した宛先に通知を飛ばせるように(mail or webhook) #13705
|
- Feat: 通報を受けた際、または解決した際に、予め登録した宛先に通知を飛ばせるように(mail or webhook) #13705
|
||||||
|
- Feat: ユーザーのアイコン/バナーの変更可否をロールで設定可能に
|
||||||
|
- 変更不可となっていても、設定済みのものを解除してデフォルト画像に戻すことは出来ます
|
||||||
- Fix: 配信停止したインスタンス一覧が見れなくなる問題を修正
|
- Fix: 配信停止したインスタンス一覧が見れなくなる問題を修正
|
||||||
- Fix: Dockerコンテナの立ち上げ時に`pnpm`のインストールで固まることがある問題
|
- Fix: Dockerコンテナの立ち上げ時に`pnpm`のインストールで固まることがある問題
|
||||||
|
- Fix: デフォルトテーマに無効なテーマコードを入力するとUIが使用できなくなる問題を修正
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
|
- Enhance: 内蔵APIドキュメントのデザイン・パフォーマンスを改善
|
||||||
|
- Enhance: 非ログイン時に他サーバーに遷移するアクションを追加
|
||||||
|
- Enhance: 非ログイン時のハイライトTLのデザインを改善
|
||||||
|
- Enhance: フロントエンドのアクセシビリティ改善
|
||||||
|
(Based on https://github.com/taiyme/misskey/pull/226)
|
||||||
|
- Enhance: サーバー情報ページ・お問い合わせページを改善
|
||||||
|
(Cherry-picked from https://github.com/taiyme/misskey/pull/238)
|
||||||
|
- Enhance: AiScriptを0.19.0にアップデート
|
||||||
|
- Enhance: Allow negative delay for MFM animation elements (`tada`, `jelly`, `twitch`, `shake`, `spin`, `jump`, `bounce`, `rainbow`)
|
||||||
|
- Enhance: センシティブなメディアを開く際に確認ダイアログを出せるように
|
||||||
- Fix: `/about#federation` ページなどで各インスタンスのチャートが表示されなくなっていた問題を修正
|
- Fix: `/about#federation` ページなどで各インスタンスのチャートが表示されなくなっていた問題を修正
|
||||||
- Fix: ユーザーページの追加情報のラベルを投稿者のサーバーの絵文字で表示する (#13968)
|
- Fix: ユーザーページの追加情報のラベルを投稿者のサーバーの絵文字で表示する (#13968)
|
||||||
- Fix: リバーシの対局を正しく共有できないことがある問題を修正
|
- Fix: リバーシの対局を正しく共有できないことがある問題を修正
|
||||||
- Fix: コントロールパネルでベースロールのポリシーを編集してもUI上では変更が反映されない問題を修正
|
- Fix: コントロールパネルでベースロールのポリシーを編集してもUI上では変更が反映されない問題を修正
|
||||||
- Fix: アンテナの編集画面のボタンに隙間を追加
|
- Fix: アンテナの編集画面のボタンに隙間を追加
|
||||||
- Fix: テーマプレビューが見れない問題を修正
|
- Fix: テーマプレビューが見れない問題を修正
|
||||||
|
- Fix: ショートカットキーが連打できる問題を修正
|
||||||
|
(Cherry-picked from https://github.com/taiyme/misskey/pull/234)
|
||||||
|
- Fix: MkSignin.vueのcredentialRequestからReactivityを削除(ProxyがPasskey認証処理に渡ることを避けるため)
|
||||||
|
- Fix: 「アニメーション画像を再生しない」がオンのときでもサーバーのバナー画像・背景画像がアニメーションしてしまう問題を修正
|
||||||
|
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/574)
|
||||||
|
- Fix: Twitchの埋め込みが開けない問題を修正
|
||||||
|
- Fix: 子メニューの高さがウィンドウからはみ出ることがある問題を修正
|
||||||
|
- Fix: 個人宛てのダイアログ形式のお知らせが即時表示されない問題を修正
|
||||||
|
- Fix: 一部の画像がセンシティブ指定されているときに画面に何も表示されないことがあるのを修正
|
||||||
|
- Fix: リアクションしたユーザー一覧のユーザー名がはみ出る問題を修正
|
||||||
|
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/672)
|
||||||
|
- Fix: `/share`ページにおいて絵文字ピッカーを開くことができない問題を修正
|
||||||
|
- Fix: deck uiの通知音が重なる問題 (#14029)
|
||||||
|
- Fix: ダイレクト投稿の"削除して編集"において、宛先が保持されていなかった問題を修正
|
||||||
|
- Fix: 投稿フォームへのURL貼り付けによる引用が下書きに保存されていなかった問題を修正
|
||||||
|
- Fix: "削除して編集"や下書きにおいて、リアクションの受け入れ設定が保持/保存されていなかった問題を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Feat: レートリミット制限に引っかかったときに`Retry-After`ヘッダーを返すように (#13949)
|
- Feat: レートリミット制限に引っかかったときに`Retry-After`ヘッダーを返すように (#13949)
|
||||||
|
@ -21,7 +54,8 @@
|
||||||
- Enhance: エンドポイント`gallery/posts/update`の必須項目を`postId`のみに
|
- Enhance: エンドポイント`gallery/posts/update`の必須項目を`postId`のみに
|
||||||
- Enhance: エンドポイント`i/webhook/update`の必須項目を`webhookId`のみに
|
- Enhance: エンドポイント`i/webhook/update`の必須項目を`webhookId`のみに
|
||||||
- Enhance: エンドポイント`admin/ad/update`の必須項目を`id`のみに
|
- Enhance: エンドポイント`admin/ad/update`の必須項目を`id`のみに
|
||||||
- Fix: チャート生成時にinstance.suspentionStateに置き換えられたinstance.isSuspendedが参照されてしまう問題を修正
|
- Enhance: `default.yml`内の`url`, `db.db`, `db.user`, `db.pass`を環境変数から読み込めるように
|
||||||
|
- Fix: チャート生成時にinstance.suspensionStateに置き換えられたinstance.isSuspendedが参照されてしまう問題を修正
|
||||||
- Fix: ユーザーのフィードページのMFMをHTMLに展開するように (#14006)
|
- Fix: ユーザーのフィードページのMFMをHTMLに展開するように (#14006)
|
||||||
- Fix: アンテナ・クリップ・リスト・ウェブフックがロールポリシーの上限より一つ多く作れてしまうのを修正 (#14036)
|
- Fix: アンテナ・クリップ・リスト・ウェブフックがロールポリシーの上限より一つ多く作れてしまうのを修正 (#14036)
|
||||||
- Fix: notRespondingSinceが実装される前に不通になったインスタンスが自動的に配信停止にならない (#14059)
|
- Fix: notRespondingSinceが実装される前に不通になったインスタンスが自動的に配信停止にならない (#14059)
|
||||||
|
@ -31,9 +65,27 @@
|
||||||
- Fix: リノートにリアクションできないように
|
- Fix: リノートにリアクションできないように
|
||||||
- Fix: ユーザー名の前後に空白文字列がある場合は省略するように
|
- Fix: ユーザー名の前後に空白文字列がある場合は省略するように
|
||||||
- Fix: プロフィール編集時に名前を空白文字列のみにできる問題を修正
|
- Fix: プロフィール編集時に名前を空白文字列のみにできる問題を修正
|
||||||
|
- Fix: ユーザ名のサジェスト時に表示される内容と順番を調整(以下の順番になります) #14149
|
||||||
|
1. フォロー中かつアクティブなユーザ
|
||||||
|
2. フォロー中かつ非アクティブなユーザ
|
||||||
|
3. フォローしていないアクティブなユーザ
|
||||||
|
4. フォローしていない非アクティブなユーザ
|
||||||
|
|
||||||
|
また、自分自身のアカウントもサジェストされるようになりました。
|
||||||
|
- Fix: 一般ユーザーから見たユーザーのバッジの一覧に公開されていないものが含まれることがある問題を修正
|
||||||
|
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/652)
|
||||||
|
- Fix: ユーザーのリアクション一覧でミュート/ブロックが機能していなかった問題を修正
|
||||||
|
- Fix: FTT有効時にリモートユーザーのノートがHTLにキャッシュされる問題を修正
|
||||||
|
- Fix: 一部の通知がローカル上のリモートユーザーに対して行われていた問題を修正
|
||||||
|
- Fix: エラーメッセージの誤字を修正 (#14213)
|
||||||
|
- Fix: ソーシャルタイムラインにローカルタイムラインに表示される自分へのリプライが表示されない問題を修正
|
||||||
|
- Fix: リノートのミュートが適用されるまでに時間がかかることがある問題を修正
|
||||||
|
(Cherry-picked from https://github.com/Type4ny-Project/Type4ny/commit/e9601029b52e0ad43d9131b555b614e56c84ebc1)
|
||||||
|
- Fix: Steaming APIが不正なデータを受けた場合の動作が不安定である問題 #14251
|
||||||
|
|
||||||
### Misskey.js
|
### Misskey.js
|
||||||
- Feat: `/drive/files/create` のリクエストに対応(`multipart/form-data`に対応)
|
- Feat: `/drive/files/create` のリクエストに対応(`multipart/form-data`に対応)
|
||||||
|
- Feat: `/admin/role/create` のロールポリシーの型を修正
|
||||||
|
|
||||||
## 2024.5.0
|
## 2024.5.0
|
||||||
|
|
||||||
|
|
101
CONTRIBUTING.md
101
CONTRIBUTING.md
|
@ -1,7 +1,7 @@
|
||||||
# Contribution guide
|
# Contribution guide
|
||||||
We're glad you're interested in contributing Misskey! In this document you will find the information you need to contribute to the project.
|
We're glad you're interested in contributing Misskey! In this document you will find the information you need to contribute to the project.
|
||||||
|
|
||||||
> **Note**
|
> [!NOTE]
|
||||||
> This project uses Japanese as its major language, **but you do not need to translate and write the Issues/PRs in Japanese.**
|
> This project uses Japanese as its major language, **but you do not need to translate and write the Issues/PRs in Japanese.**
|
||||||
> Also, you might receive comments on your Issue/PR in Japanese, but you do not need to reply to them in Japanese as well.\
|
> Also, you might receive comments on your Issue/PR in Japanese, but you do not need to reply to them in Japanese as well.\
|
||||||
> The accuracy of machine translation into Japanese is not high, so it will be easier for us to understand if you write it in the original language.
|
> The accuracy of machine translation into Japanese is not high, so it will be easier for us to understand if you write it in the original language.
|
||||||
|
@ -17,16 +17,31 @@ Before creating an issue, please check the following:
|
||||||
- Issues should only be used to feature requests, suggestions, and bug tracking.
|
- Issues should only be used to feature requests, suggestions, and bug tracking.
|
||||||
- Please ask questions or troubleshooting in [GitHub Discussions](https://github.com/misskey-dev/misskey/discussions) or [Discord](https://discord.gg/Wp8gVStHW3).
|
- Please ask questions or troubleshooting in [GitHub Discussions](https://github.com/misskey-dev/misskey/discussions) or [Discord](https://discord.gg/Wp8gVStHW3).
|
||||||
|
|
||||||
> **Warning**
|
> [!WARNING]
|
||||||
> Do not close issues that are about to be resolved. It should remain open until a commit that actually resolves it is merged.
|
> Do not close issues that are about to be resolved. It should remain open until a commit that actually resolves it is merged.
|
||||||
|
|
||||||
## Before implementation
|
### Recommended discussing before implementation
|
||||||
|
We welcome your proposal.
|
||||||
|
|
||||||
When you want to add a feature or fix a bug, **first have the design and policy reviewed in an Issue** (if it is not there, please make one). Without this step, there is a high possibility that the PR will not be merged even if it is implemented.
|
When you want to add a feature or fix a bug, **first have the design and policy reviewed in an Issue** (if it is not there, please make one). Without this step, there is a high possibility that the PR will not be merged even if it is implemented.
|
||||||
|
|
||||||
At this point, you also need to clarify the goals of the PR you will create, and make sure that the other members of the team are aware of them.
|
At this point, you also need to clarify the goals of the PR you will create, and make sure that the other members of the team are aware of them.
|
||||||
PRs that do not have a clear set of do's and don'ts tend to be bloated and difficult to review.
|
PRs that do not have a clear set of do's and don'ts tend to be bloated and difficult to review.
|
||||||
|
|
||||||
Also, when you start implementation, assign yourself to the Issue (if you cannot do it yourself, ask another member to assign you). By expressing your intention to work the Issue, you can prevent conflicts in the work.
|
Also, when you start implementation, assign yourself to the Issue (if you cannot do it yourself, ask Committer to assign you).
|
||||||
|
By expressing your intention to work on the Issue, you can prevent conflicts in the work.
|
||||||
|
|
||||||
|
To the Committers: you should not assign someone on it before the Final Decision.
|
||||||
|
|
||||||
|
### How issues are triaged
|
||||||
|
|
||||||
|
The Committers may:
|
||||||
|
* close an issue that is not reproducible on latest stable release,
|
||||||
|
* merge an issue into another issue,
|
||||||
|
* split an issue into multiple issues,
|
||||||
|
* or re-open that has been closed for some reason which is not applicable anymore.
|
||||||
|
|
||||||
|
@syuilo reserves the Final Decision rights including whether the project will implement feature and how to implement, these rights are not always exercised.
|
||||||
|
|
||||||
## Well-known branches
|
## Well-known branches
|
||||||
- **`master`** branch is tracking the latest release and used for production purposes.
|
- **`master`** branch is tracking the latest release and used for production purposes.
|
||||||
|
@ -37,14 +52,14 @@ Also, when you start implementation, assign yourself to the Issue (if you cannot
|
||||||
## Creating a PR
|
## Creating a PR
|
||||||
Thank you for your PR! Before creating a PR, please check the following:
|
Thank you for your PR! Before creating a PR, please check the following:
|
||||||
- If possible, prefix the title with a keyword that identifies the type of this PR, as shown below.
|
- If possible, prefix the title with a keyword that identifies the type of this PR, as shown below.
|
||||||
- `fix` / `refactor` / `feat` / `enhance` / `perf` / `chore` etc
|
- `fix` / `refactor` / `feat` / `enhance` / `perf` / `chore` etc
|
||||||
- Also, make sure that the granularity of this PR is appropriate. Please do not include more than one type of change or interest in a single PR.
|
- Also, make sure that the granularity of this PR is appropriate. Please do not include more than one type of change or interest in a single PR.
|
||||||
- If there is an Issue which will be resolved by this PR, please include a reference to the Issue in the text.
|
- If there is an Issue which will be resolved by this PR, please include a reference to the Issue in the text.
|
||||||
- Please add the summary of the changes to [`CHANGELOG.md`](/CHANGELOG.md). However, this is not necessary for changes that do not affect the users, such as refactoring.
|
- Please add the summary of the changes to [`CHANGELOG.md`](/CHANGELOG.md). However, this is not necessary for changes that do not affect the users, such as refactoring.
|
||||||
- Check if there are any documents that need to be created or updated due to this change.
|
- Check if there are any documents that need to be created or updated due to this change.
|
||||||
- If you have added a feature or fixed a bug, please add a test case if possible.
|
- If you have added a feature or fixed a bug, please add a test case if possible.
|
||||||
- Please make sure that tests and Lint are passed in advance.
|
- Please make sure that tests and Lint are passed in advance.
|
||||||
- You can run it with `pnpm test` and `pnpm lint`. [See more info](#testing)
|
- You can run it with `pnpm test` and `pnpm lint`. [See more info](#testing)
|
||||||
- If this PR includes UI changes, please attach a screenshot in the text.
|
- If this PR includes UI changes, please attach a screenshot in the text.
|
||||||
|
|
||||||
Thanks for your cooperation 🤗
|
Thanks for your cooperation 🤗
|
||||||
|
@ -54,8 +69,8 @@ Be willing to comment on the good points and not just the things you want fixed
|
||||||
|
|
||||||
### Review perspective
|
### Review perspective
|
||||||
- Scope
|
- Scope
|
||||||
- Are the goals of the PR clear?
|
- Are the goals of the PR clear?
|
||||||
- Is the granularity of the PR appropriate?
|
- Is the granularity of the PR appropriate?
|
||||||
- Security
|
- Security
|
||||||
- Does merging this PR create a vulnerability?
|
- Does merging this PR create a vulnerability?
|
||||||
- Performance
|
- Performance
|
||||||
|
@ -77,7 +92,7 @@ An actual domain will be assigned so you can test the federation.
|
||||||
|
|
||||||
## Release
|
## Release
|
||||||
### Release Instructions
|
### Release Instructions
|
||||||
1. Commit version changes in the `develop` branch ([package.json](https://github.com/misskey-dev/misskey/blob/develop/package.json))
|
1. Commit version changes in the `develop` branch ([package.json](package.json))
|
||||||
2. Create a release PR.
|
2. Create a release PR.
|
||||||
- Into `master` from `develop` branch.
|
- Into `master` from `develop` branch.
|
||||||
- The title must be in the format `Release: x.y.z`.
|
- The title must be in the format `Release: x.y.z`.
|
||||||
|
@ -88,7 +103,7 @@ An actual domain will be assigned so you can test the federation.
|
||||||
- The target branch must be `master`
|
- The target branch must be `master`
|
||||||
- The tag name must be the version
|
- The tag name must be the version
|
||||||
|
|
||||||
> **Note**
|
> [!NOTE]
|
||||||
> Why this instruction is necessary:
|
> Why this instruction is necessary:
|
||||||
> - To perform final QA checks
|
> - To perform final QA checks
|
||||||
> - To distribute responsibility
|
> - To distribute responsibility
|
||||||
|
@ -106,12 +121,42 @@ If your language is not listed in Crowdin, please open an issue.
|
||||||
![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg)
|
![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg)
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
During development, it is useful to use the
|
### Setup
|
||||||
|
Before developing, you have to set up environment. Misskey requires Redis, PostgreSQL, and FFmpeg.
|
||||||
|
|
||||||
|
You would want to install Meilisearch to experiment related features. Technically, meilisearch is not strict requirement, but some features and tests require it.
|
||||||
|
|
||||||
|
There are a few ways to proceed.
|
||||||
|
|
||||||
|
#### Use system-wide software
|
||||||
|
You could install them in system-wide (such as from package manager).
|
||||||
|
|
||||||
|
#### Use `docker compose`
|
||||||
|
You could obtain middleware container by typing `docker compose -f $PROJECT_ROOT/compose.local-db.yml up -d`.
|
||||||
|
|
||||||
|
#### Use Devcontainer
|
||||||
|
Devcontainer also has necessary setting. This method can be done by connecting from VSCode.
|
||||||
|
|
||||||
|
Instead of running `pnpm` locally, you can use Dev Container to set up your development environment.
|
||||||
|
To use Dev Container, open the project directory on VSCode with Dev Containers installed.
|
||||||
|
**Note:** If you are using Windows, please clone the repository with WSL. Using Git for Windows will result in broken files due to the difference in how newlines are handled.
|
||||||
|
|
||||||
|
It will run the following command automatically inside the container.
|
||||||
|
``` bash
|
||||||
|
git submodule update --init
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
cp .devcontainer/devcontainer.yml .config/default.yml
|
||||||
|
pnpm build
|
||||||
|
pnpm migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
After finishing the migration, you can proceed.
|
||||||
|
|
||||||
|
### Start developing
|
||||||
|
During development, it is useful to use the
|
||||||
```
|
```
|
||||||
pnpm dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
command.
|
command.
|
||||||
|
|
||||||
- Server-side source files and automatically builds them if they are modified. Automatically start the server process(es).
|
- Server-side source files and automatically builds them if they are modified. Automatically start the server process(es).
|
||||||
|
@ -135,26 +180,6 @@ MK_DEV_PREFER=backend pnpm dev
|
||||||
- To change the port of Vite, specify with `VITE_PORT` environment variable.
|
- To change the port of Vite, specify with `VITE_PORT` environment variable.
|
||||||
- HMR may not work in some environments such as Windows.
|
- HMR may not work in some environments such as Windows.
|
||||||
|
|
||||||
### Dev Container
|
|
||||||
Instead of running `pnpm` locally, you can use Dev Container to set up your development environment.
|
|
||||||
To use Dev Container, open the project directory on VSCode with Dev Containers installed.
|
|
||||||
**Note:** If you are using Windows, please clone the repository with WSL. Using Git for Windows will result in broken files due to the difference in how newlines are handled.
|
|
||||||
|
|
||||||
It will run the following command automatically inside the container.
|
|
||||||
``` bash
|
|
||||||
git submodule update --init
|
|
||||||
pnpm install --frozen-lockfile
|
|
||||||
cp .devcontainer/devcontainer.yml .config/default.yml
|
|
||||||
pnpm build
|
|
||||||
pnpm migrate
|
|
||||||
```
|
|
||||||
|
|
||||||
After finishing the migration, run the `pnpm dev` command to start the development server.
|
|
||||||
|
|
||||||
``` bash
|
|
||||||
pnpm dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
- Test codes are located in [`/packages/backend/test`](/packages/backend/test).
|
- Test codes are located in [`/packages/backend/test`](/packages/backend/test).
|
||||||
|
|
||||||
|
@ -165,7 +190,7 @@ cp .github/misskey/test.yml .config/
|
||||||
```
|
```
|
||||||
Prepare DB/Redis for testing.
|
Prepare DB/Redis for testing.
|
||||||
```
|
```
|
||||||
docker compose -f packages/backend/test/compose.yaml up
|
docker compose -f packages/backend/test/compose.yml up
|
||||||
```
|
```
|
||||||
Alternatively, prepare an empty (data can be erased) DB and edit `.config/test.yml`.
|
Alternatively, prepare an empty (data can be erased) DB and edit `.config/test.yml`.
|
||||||
|
|
||||||
|
@ -204,7 +229,7 @@ niraxは、Misskeyで使用しているオリジナルのフロントエンド
|
||||||
### ルート定義
|
### ルート定義
|
||||||
ルート定義は、以下の形式のオブジェクトの配列です。
|
ルート定義は、以下の形式のオブジェクトの配列です。
|
||||||
|
|
||||||
``` ts
|
```ts
|
||||||
{
|
{
|
||||||
name?: string;
|
name?: string;
|
||||||
path: string;
|
path: string;
|
||||||
|
@ -217,7 +242,7 @@ niraxは、Misskeyで使用しているオリジナルのフロントエンド
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Warning**
|
> [!WARNING]
|
||||||
> 現状、ルートは定義された順に評価されます。
|
> 現状、ルートは定義された順に評価されます。
|
||||||
> たとえば、`/foo/:id`ルート定義の次に`/foo/bar`ルート定義がされていた場合、後者がマッチすることはありません。
|
> たとえば、`/foo/:id`ルート定義の次に`/foo/bar`ルート定義がされていた場合、後者がマッチすることはありません。
|
||||||
|
|
||||||
|
@ -279,7 +304,7 @@ export const Default = {
|
||||||
parameters: {
|
parameters: {
|
||||||
layout: 'centered',
|
layout: 'centered',
|
||||||
},
|
},
|
||||||
} satisfies StoryObj<typeof MkAvatar>;
|
} satisfies StoryObj<typeof MyComponent>;
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to opt-out from the automatic generation, create a `MyComponent.stories.impl.ts` file and add the following line to the file.
|
If you want to opt-out from the automatic generation, create a `MyComponent.stories.impl.ts` file and add the following line to the file.
|
||||||
|
@ -517,7 +542,7 @@ https://github.com/misskey-dev/misskey/pull/10082
|
||||||
|
|
||||||
テキストhogeおよびfugaについて、片方を必須としつつ両方の指定もありうる場合:
|
テキストhogeおよびfugaについて、片方を必須としつつ両方の指定もありうる場合:
|
||||||
|
|
||||||
```
|
```ts
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
|
|
|
@ -17,6 +17,8 @@ services:
|
||||||
networks:
|
networks:
|
||||||
- internal_network
|
- internal_network
|
||||||
- external_network
|
- external_network
|
||||||
|
# env_file:
|
||||||
|
# - .config/docker.env
|
||||||
volumes:
|
volumes:
|
||||||
- ./files:/misskey/files
|
- ./files:/misskey/files
|
||||||
- ./.config:/misskey/.config:ro
|
- ./.config:/misskey/.config:ro
|
||||||
|
|
46
locales/index.d.ts
vendored
46
locales/index.d.ts
vendored
|
@ -736,6 +736,22 @@ export interface Locale extends ILocale {
|
||||||
* リモートで表示
|
* リモートで表示
|
||||||
*/
|
*/
|
||||||
"showOnRemote": string;
|
"showOnRemote": string;
|
||||||
|
/**
|
||||||
|
* リモートで続行
|
||||||
|
*/
|
||||||
|
"continueOnRemote": string;
|
||||||
|
/**
|
||||||
|
* Misskey Hubからサーバーを選択
|
||||||
|
*/
|
||||||
|
"chooseServerOnMisskeyHub": string;
|
||||||
|
/**
|
||||||
|
* サーバーのドメインを直接指定
|
||||||
|
*/
|
||||||
|
"specifyServerHost": string;
|
||||||
|
/**
|
||||||
|
* ドメインを入力してください
|
||||||
|
*/
|
||||||
|
"inputHostName": string;
|
||||||
/**
|
/**
|
||||||
* 全般
|
* 全般
|
||||||
*/
|
*/
|
||||||
|
@ -1921,9 +1937,13 @@ export interface Locale extends ILocale {
|
||||||
*/
|
*/
|
||||||
"onlyOneFileCanBeAttached": string;
|
"onlyOneFileCanBeAttached": string;
|
||||||
/**
|
/**
|
||||||
* 続行する前に、サインアップまたはサインインが必要です
|
* 続行する前に、登録またはログインが必要です
|
||||||
*/
|
*/
|
||||||
"signinRequired": string;
|
"signinRequired": string;
|
||||||
|
/**
|
||||||
|
* 続行するには、お使いのサーバーに移動するか、このサーバーに登録・ログインする必要があります
|
||||||
|
*/
|
||||||
|
"signinOrContinueOnRemote": string;
|
||||||
/**
|
/**
|
||||||
* 招待
|
* 招待
|
||||||
*/
|
*/
|
||||||
|
@ -4984,6 +5004,18 @@ export interface Locale extends ILocale {
|
||||||
* お問い合わせ
|
* お問い合わせ
|
||||||
*/
|
*/
|
||||||
"inquiry": string;
|
"inquiry": string;
|
||||||
|
/**
|
||||||
|
* もう一度お試しください。
|
||||||
|
*/
|
||||||
|
"tryAgain": string;
|
||||||
|
/**
|
||||||
|
* センシティブなメディアを表示するとき確認する
|
||||||
|
*/
|
||||||
|
"confirmWhenRevealingSensitiveMedia": string;
|
||||||
|
/**
|
||||||
|
* センシティブなメディアです。表示しますか?
|
||||||
|
*/
|
||||||
|
"sensitiveMediaRevealConfirm": string;
|
||||||
"_delivery": {
|
"_delivery": {
|
||||||
/**
|
/**
|
||||||
* 配信状態
|
* 配信状態
|
||||||
|
@ -6594,6 +6626,10 @@ export interface Locale extends ILocale {
|
||||||
* ファイルにNSFWを常に付与
|
* ファイルにNSFWを常に付与
|
||||||
*/
|
*/
|
||||||
"alwaysMarkNsfw": string;
|
"alwaysMarkNsfw": string;
|
||||||
|
/**
|
||||||
|
* アイコンとバナーの更新を許可
|
||||||
|
*/
|
||||||
|
"canUpdateBioMedia": string;
|
||||||
/**
|
/**
|
||||||
* ノートのピン留めの最大数
|
* ノートのピン留めの最大数
|
||||||
*/
|
*/
|
||||||
|
@ -7515,14 +7551,6 @@ export interface Locale extends ILocale {
|
||||||
* 通知
|
* 通知
|
||||||
*/
|
*/
|
||||||
"notification": string;
|
"notification": string;
|
||||||
/**
|
|
||||||
* アンテナ受信
|
|
||||||
*/
|
|
||||||
"antenna": string;
|
|
||||||
/**
|
|
||||||
* チャンネル通知
|
|
||||||
*/
|
|
||||||
"channel": string;
|
|
||||||
/**
|
/**
|
||||||
* リアクション選択時
|
* リアクション選択時
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -180,6 +180,10 @@ addAccount: "アカウントを追加"
|
||||||
reloadAccountsList: "アカウントリストの情報を更新"
|
reloadAccountsList: "アカウントリストの情報を更新"
|
||||||
loginFailed: "ログインに失敗しました"
|
loginFailed: "ログインに失敗しました"
|
||||||
showOnRemote: "リモートで表示"
|
showOnRemote: "リモートで表示"
|
||||||
|
continueOnRemote: "リモートで続行"
|
||||||
|
chooseServerOnMisskeyHub: "Misskey Hubからサーバーを選択"
|
||||||
|
specifyServerHost: "サーバーのドメインを直接指定"
|
||||||
|
inputHostName: "ドメインを入力してください"
|
||||||
general: "全般"
|
general: "全般"
|
||||||
wallpaper: "壁紙"
|
wallpaper: "壁紙"
|
||||||
setWallpaper: "壁紙を設定"
|
setWallpaper: "壁紙を設定"
|
||||||
|
@ -476,7 +480,8 @@ attachAsFileQuestion: "クリップボードのテキストが長いです。テ
|
||||||
noMessagesYet: "まだチャットはありません"
|
noMessagesYet: "まだチャットはありません"
|
||||||
newMessageExists: "新しいメッセージがあります"
|
newMessageExists: "新しいメッセージがあります"
|
||||||
onlyOneFileCanBeAttached: "メッセージに添付できるファイルはひとつです"
|
onlyOneFileCanBeAttached: "メッセージに添付できるファイルはひとつです"
|
||||||
signinRequired: "続行する前に、サインアップまたはサインインが必要です"
|
signinRequired: "続行する前に、登録またはログインが必要です"
|
||||||
|
signinOrContinueOnRemote: "続行するには、お使いのサーバーに移動するか、このサーバーに登録・ログインする必要があります"
|
||||||
invitations: "招待"
|
invitations: "招待"
|
||||||
invitationCode: "招待コード"
|
invitationCode: "招待コード"
|
||||||
checking: "確認しています"
|
checking: "確認しています"
|
||||||
|
@ -1242,6 +1247,9 @@ keepOriginalFilenameDescription: "この設定をオフにすると、アップ
|
||||||
noDescription: "説明文はありません"
|
noDescription: "説明文はありません"
|
||||||
alwaysConfirmFollow: "フォローの際常に確認する"
|
alwaysConfirmFollow: "フォローの際常に確認する"
|
||||||
inquiry: "お問い合わせ"
|
inquiry: "お問い合わせ"
|
||||||
|
tryAgain: "もう一度お試しください。"
|
||||||
|
confirmWhenRevealingSensitiveMedia: "センシティブなメディアを表示するとき確認する"
|
||||||
|
sensitiveMediaRevealConfirm: "センシティブなメディアです。表示しますか?"
|
||||||
|
|
||||||
_delivery:
|
_delivery:
|
||||||
status: "配信状態"
|
status: "配信状態"
|
||||||
|
@ -1705,6 +1713,7 @@ _role:
|
||||||
canManageAvatarDecorations: "アバターデコレーションの管理"
|
canManageAvatarDecorations: "アバターデコレーションの管理"
|
||||||
driveCapacity: "ドライブ容量"
|
driveCapacity: "ドライブ容量"
|
||||||
alwaysMarkNsfw: "ファイルにNSFWを常に付与"
|
alwaysMarkNsfw: "ファイルにNSFWを常に付与"
|
||||||
|
canUpdateBioMedia: "アイコンとバナーの更新を許可"
|
||||||
pinMax: "ノートのピン留めの最大数"
|
pinMax: "ノートのピン留めの最大数"
|
||||||
antennaMax: "アンテナの作成可能数"
|
antennaMax: "アンテナの作成可能数"
|
||||||
wordMuteMax: "ワードミュートの最大文字数"
|
wordMuteMax: "ワードミュートの最大文字数"
|
||||||
|
@ -1971,8 +1980,6 @@ _sfx:
|
||||||
note: "ノート"
|
note: "ノート"
|
||||||
noteMy: "ノート(自分)"
|
noteMy: "ノート(自分)"
|
||||||
notification: "通知"
|
notification: "通知"
|
||||||
antenna: "アンテナ受信"
|
|
||||||
channel: "チャンネル通知"
|
|
||||||
reaction: "リアクション選択時"
|
reaction: "リアクション選択時"
|
||||||
|
|
||||||
_soundSettings:
|
_soundSettings:
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 0179793ec891856d6f37a3be16ba4c22f67a81b5
|
|
30
package.json
30
package.json
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2024.5.0",
|
"version": "2024.7.0-beta.3",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/misskey-dev/misskey.git"
|
"url": "https://github.com/misskey-dev/misskey.git"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.0.6",
|
"packageManager": "pnpm@9.6.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/frontend",
|
"packages/frontend",
|
||||||
"packages/backend",
|
"packages/backend",
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
"build-assets": "node ./scripts/build-assets.mjs",
|
"build-assets": "node ./scripts/build-assets.mjs",
|
||||||
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
|
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
|
||||||
"build-storybook": "pnpm --filter frontend build-storybook",
|
"build-storybook": "pnpm --filter frontend build-storybook",
|
||||||
"build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
|
"build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
|
||||||
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
|
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
|
||||||
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
||||||
"init": "pnpm migrate",
|
"init": "pnpm migrate",
|
||||||
|
@ -52,24 +52,24 @@
|
||||||
"cssnano": "6.1.2",
|
"cssnano": "6.1.2",
|
||||||
"execa": "8.0.1",
|
"execa": "8.0.1",
|
||||||
"fast-glob": "3.3.2",
|
"fast-glob": "3.3.2",
|
||||||
"ignore-walk": "6.0.4",
|
"ignore-walk": "6.0.5",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"postcss": "8.4.38",
|
"postcss": "8.4.40",
|
||||||
"tar": "6.2.1",
|
"tar": "6.2.1",
|
||||||
"terser": "5.31.1",
|
"terser": "5.31.3",
|
||||||
"typescript": "5.5.3",
|
"typescript": "5.5.4",
|
||||||
"esbuild": "0.22.0",
|
"esbuild": "0.23.0",
|
||||||
"glob": "10.3.12"
|
"glob": "11.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/eslint-plugin": "2.0.2",
|
"@misskey-dev/eslint-plugin": "2.0.2",
|
||||||
"@types/node": "20.14.9",
|
"@types/node": "20.14.12",
|
||||||
"@typescript-eslint/eslint-plugin": "7.15.0",
|
"@typescript-eslint/eslint-plugin": "7.17.0",
|
||||||
"@typescript-eslint/parser": "7.15.0",
|
"@typescript-eslint/parser": "7.17.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "13.13.0",
|
"cypress": "13.13.1",
|
||||||
"eslint": "9.6.0",
|
"eslint": "9.8.0",
|
||||||
"globals": "15.7.0",
|
"globals": "15.8.0",
|
||||||
"ncp": "2.0.0",
|
"ncp": "2.0.0",
|
||||||
"start-server-and-test": "2.0.4"
|
"start-server-and-test": "2.0.4"
|
||||||
},
|
},
|
||||||
|
|
20
packages/backend/assets/api-doc.html
Normal file
20
packages/backend/assets/api-doc.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Misskey API</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script
|
||||||
|
id="api-reference"
|
||||||
|
data-url="/api.json"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,24 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Misskey API</title>
|
|
||||||
<!-- needed for adaptive design -->
|
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
|
||||||
|
|
||||||
<!--
|
|
||||||
ReDoc doesn't change outer page styles
|
|
||||||
-->
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<redoc spec-url="/api.json" expand-responses="200" expand-single-schema-field="true"></redoc>
|
|
||||||
<script src="https://cdn.redoc.ly/redoc/v2.1.3/bundles/redoc.standalone.js" integrity="sha256-u4DgqzYXoArvNF/Ymw3puKexfOC6lYfw0sfmeliBJ1I=" crossorigin="anonymous"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -4,7 +4,7 @@ import sharedConfig from '../shared/eslint.config.js';
|
||||||
export default [
|
export default [
|
||||||
...sharedConfig,
|
...sharedConfig,
|
||||||
{
|
{
|
||||||
ignores: ['**/node_modules', 'built', '@types/**/*'],
|
ignores: ['**/node_modules', 'built', '@types/**/*', 'migration'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.ts', '**/*.tsx'],
|
files: ['**/*.ts', '**/*.tsx'],
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
"test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e",
|
"test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e",
|
||||||
"test-and-coverage": "pnpm jest-and-coverage",
|
"test-and-coverage": "pnpm jest-and-coverage",
|
||||||
"test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e",
|
"test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e",
|
||||||
"generate-api-json": "pnpm build && node ./scripts/generate_api_json.js"
|
"generate-api-json": "node ./scripts/generate_api_json.js"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@swc/core-android-arm64": "1.3.11",
|
"@swc/core-android-arm64": "1.3.11",
|
||||||
|
@ -65,11 +65,11 @@
|
||||||
"utf-8-validate": "6.0.3"
|
"utf-8-validate": "6.0.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "3.600.0",
|
"@aws-sdk/client-s3": "3.620.0",
|
||||||
"@aws-sdk/lib-storage": "3.600.0",
|
"@aws-sdk/lib-storage": "3.620.0",
|
||||||
"@bull-board/api": "5.20.5",
|
"@bull-board/api": "5.21.1",
|
||||||
"@bull-board/fastify": "5.20.5",
|
"@bull-board/fastify": "5.21.1",
|
||||||
"@bull-board/ui": "5.20.5",
|
"@bull-board/ui": "5.21.1",
|
||||||
"@discordapp/twemoji": "15.0.3",
|
"@discordapp/twemoji": "15.0.3",
|
||||||
"@fastify/accepts": "4.3.0",
|
"@fastify/accepts": "4.3.0",
|
||||||
"@fastify/cookie": "9.3.1",
|
"@fastify/cookie": "9.3.1",
|
||||||
|
@ -86,22 +86,22 @@
|
||||||
"@nestjs/core": "10.3.10",
|
"@nestjs/core": "10.3.10",
|
||||||
"@nestjs/testing": "10.3.10",
|
"@nestjs/testing": "10.3.10",
|
||||||
"@peertube/http-signature": "1.7.0",
|
"@peertube/http-signature": "1.7.0",
|
||||||
"@sentry/node": "8.13.0",
|
"@sentry/node": "8.20.0",
|
||||||
"@sentry/profiling-node": "8.13.0",
|
"@sentry/profiling-node": "8.20.0",
|
||||||
"@simplewebauthn/server": "10.0.0",
|
"@simplewebauthn/server": "10.0.1",
|
||||||
"@sinonjs/fake-timers": "11.2.2",
|
"@sinonjs/fake-timers": "11.2.2",
|
||||||
"@smithy/node-http-handler": "2.5.0",
|
"@smithy/node-http-handler": "2.5.0",
|
||||||
"@swc/cli": "0.3.12",
|
"@swc/cli": "0.3.12",
|
||||||
"@swc/core": "1.6.6",
|
"@swc/core": "1.6.6",
|
||||||
"@twemoji/parser": "15.1.1",
|
"@twemoji/parser": "15.1.1",
|
||||||
"accepts": "1.3.8",
|
"accepts": "1.3.8",
|
||||||
"ajv": "8.16.0",
|
"ajv": "8.17.1",
|
||||||
"archiver": "7.0.1",
|
"archiver": "7.0.1",
|
||||||
"async-mutex": "0.5.0",
|
"async-mutex": "0.5.0",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
"body-parser": "1.20.2",
|
"body-parser": "1.20.2",
|
||||||
"bullmq": "5.8.3",
|
"bullmq": "5.10.4",
|
||||||
"cacheable-lookup": "7.0.0",
|
"cacheable-lookup": "7.0.0",
|
||||||
"cbor": "9.0.2",
|
"cbor": "9.0.2",
|
||||||
"chalk": "5.3.0",
|
"chalk": "5.3.0",
|
||||||
|
@ -115,10 +115,10 @@
|
||||||
"fastify": "4.28.1",
|
"fastify": "4.28.1",
|
||||||
"fastify-raw-body": "4.3.0",
|
"fastify-raw-body": "4.3.0",
|
||||||
"feed": "4.2.2",
|
"feed": "4.2.2",
|
||||||
"file-type": "19.0.0",
|
"file-type": "19.3.0",
|
||||||
"fluent-ffmpeg": "2.1.3",
|
"fluent-ffmpeg": "2.1.3",
|
||||||
"form-data": "4.0.0",
|
"form-data": "4.0.0",
|
||||||
"got": "14.4.1",
|
"got": "14.4.2",
|
||||||
"happy-dom": "10.0.3",
|
"happy-dom": "10.0.3",
|
||||||
"hpagent": "1.2.0",
|
"hpagent": "1.2.0",
|
||||||
"htmlescape": "1.1.1",
|
"htmlescape": "1.1.1",
|
||||||
|
@ -128,7 +128,7 @@
|
||||||
"ipaddr.js": "2.2.0",
|
"ipaddr.js": "2.2.0",
|
||||||
"is-svg": "5.0.1",
|
"is-svg": "5.0.1",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"jsdom": "24.1.0",
|
"jsdom": "24.1.1",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"jsonld": "8.3.2",
|
"jsonld": "8.3.2",
|
||||||
"jsrsasign": "11.1.0",
|
"jsrsasign": "11.1.0",
|
||||||
|
@ -178,11 +178,11 @@
|
||||||
"tsc-alias": "1.8.10",
|
"tsc-alias": "1.8.10",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typeorm": "0.3.20",
|
"typeorm": "0.3.20",
|
||||||
"typescript": "5.5.3",
|
"typescript": "5.5.4",
|
||||||
"ulid": "2.3.0",
|
"ulid": "2.3.0",
|
||||||
"vary": "1.1.2",
|
"vary": "1.1.2",
|
||||||
"web-push": "3.6.7",
|
"web-push": "3.6.7",
|
||||||
"ws": "8.17.1",
|
"ws": "8.18.0",
|
||||||
"xev": "3.0.2"
|
"xev": "3.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -202,11 +202,11 @@
|
||||||
"@types/jest": "29.5.12",
|
"@types/jest": "29.5.12",
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/jsdom": "21.1.7",
|
"@types/jsdom": "21.1.7",
|
||||||
"@types/jsonld": "1.5.14",
|
"@types/jsonld": "1.5.15",
|
||||||
"@types/jsrsasign": "10.5.14",
|
"@types/jsrsasign": "10.5.14",
|
||||||
"@types/mime-types": "2.1.4",
|
"@types/mime-types": "2.1.4",
|
||||||
"@types/ms": "0.7.34",
|
"@types/ms": "0.7.34",
|
||||||
"@types/node": "20.14.9",
|
"@types/node": "20.14.12",
|
||||||
"@types/nodemailer": "6.4.15",
|
"@types/nodemailer": "6.4.15",
|
||||||
"@types/oauth": "0.9.5",
|
"@types/oauth": "0.9.5",
|
||||||
"@types/oauth2orize": "1.11.5",
|
"@types/oauth2orize": "1.11.5",
|
||||||
|
@ -226,18 +226,18 @@
|
||||||
"@types/tmp": "0.2.6",
|
"@types/tmp": "0.2.6",
|
||||||
"@types/vary": "1.1.3",
|
"@types/vary": "1.1.3",
|
||||||
"@types/web-push": "3.6.3",
|
"@types/web-push": "3.6.3",
|
||||||
"@types/ws": "8.5.10",
|
"@types/ws": "8.5.11",
|
||||||
"@typescript-eslint/eslint-plugin": "7.15.0",
|
"@typescript-eslint/eslint-plugin": "7.17.0",
|
||||||
"@typescript-eslint/parser": "7.15.0",
|
"@typescript-eslint/parser": "7.17.0",
|
||||||
"aws-sdk-client-mock": "4.0.1",
|
"aws-sdk-client-mock": "4.0.1",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint-plugin-import": "2.29.1",
|
"eslint-plugin-import": "2.29.1",
|
||||||
"execa": "9.2.0",
|
"execa": "9.3.0",
|
||||||
"fkill": "9.0.0",
|
"fkill": "9.0.0",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-mock": "29.7.0",
|
"jest-mock": "29.7.0",
|
||||||
"nodemon": "3.1.4",
|
"nodemon": "3.1.4",
|
||||||
"pid-port": "1.0.0",
|
"pid-port": "1.0.0",
|
||||||
"simple-oauth2": "5.0.1"
|
"simple-oauth2": "5.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,34 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { loadConfig } from '../built/config.js'
|
import { execa } from 'execa';
|
||||||
import { genOpenapiSpec } from '../built/server/api/openapi/gen-spec.js'
|
import { writeFileSync, existsSync } from "node:fs";
|
||||||
import { writeFileSync } from "node:fs";
|
|
||||||
|
|
||||||
const config = loadConfig();
|
async function main() {
|
||||||
const spec = genOpenapiSpec(config, true);
|
if (!process.argv.includes('--no-build')) {
|
||||||
|
await execa('pnpm', ['run', 'build'], {
|
||||||
|
stdout: process.stdout,
|
||||||
|
stderr: process.stderr,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
writeFileSync('./built/api.json', JSON.stringify(spec), 'utf-8');
|
if (!existsSync('./built')) {
|
||||||
|
throw new Error('`built` directory does not exist.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {import('../src/config.js')} */
|
||||||
|
const { loadConfig } = await import('../built/config.js');
|
||||||
|
|
||||||
|
/** @type {import('../src/server/api/openapi/gen-spec.js')} */
|
||||||
|
const { genOpenapiSpec } = await import('../built/server/api/openapi/gen-spec.js');
|
||||||
|
|
||||||
|
const config = loadConfig();
|
||||||
|
const spec = genOpenapiSpec(config, true);
|
||||||
|
|
||||||
|
writeFileSync('./built/api.json', JSON.stringify(spec), 'utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(e => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
|
@ -23,7 +23,7 @@ type RedisOptionsSource = Partial<RedisOptions> & {
|
||||||
* 設定ファイルの型
|
* 設定ファイルの型
|
||||||
*/
|
*/
|
||||||
type Source = {
|
type Source = {
|
||||||
url: string;
|
url?: string;
|
||||||
port?: number;
|
port?: number;
|
||||||
socket?: string;
|
socket?: string;
|
||||||
chmodSocket?: string;
|
chmodSocket?: string;
|
||||||
|
@ -31,9 +31,9 @@ type Source = {
|
||||||
db: {
|
db: {
|
||||||
host: string;
|
host: string;
|
||||||
port: number;
|
port: number;
|
||||||
db: string;
|
db?: string;
|
||||||
user: string;
|
user?: string;
|
||||||
pass: string;
|
pass?: string;
|
||||||
disableCache?: boolean;
|
disableCache?: boolean;
|
||||||
extra?: { [x: string]: string };
|
extra?: { [x: string]: string };
|
||||||
};
|
};
|
||||||
|
@ -202,13 +202,17 @@ export function loadConfig(): Config {
|
||||||
: { 'src/_boot_.ts': { file: 'src/_boot_.ts' } };
|
: { 'src/_boot_.ts': { file: 'src/_boot_.ts' } };
|
||||||
const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source;
|
const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source;
|
||||||
|
|
||||||
const url = tryCreateUrl(config.url);
|
const url = tryCreateUrl(config.url ?? process.env.MISSKEY_URL ?? '');
|
||||||
const version = meta.version;
|
const version = meta.version;
|
||||||
const host = url.host;
|
const host = url.host;
|
||||||
const hostname = url.hostname;
|
const hostname = url.hostname;
|
||||||
const scheme = url.protocol.replace(/:$/, '');
|
const scheme = url.protocol.replace(/:$/, '');
|
||||||
const wsScheme = scheme.replace('http', 'ws');
|
const wsScheme = scheme.replace('http', 'ws');
|
||||||
|
|
||||||
|
const dbDb = config.db.db ?? process.env.DATABASE_DB ?? '';
|
||||||
|
const dbUser = config.db.user ?? process.env.DATABASE_USER ?? '';
|
||||||
|
const dbPass = config.db.pass ?? process.env.DATABASE_PASSWORD ?? '';
|
||||||
|
|
||||||
const externalMediaProxy = config.mediaProxy ?
|
const externalMediaProxy = config.mediaProxy ?
|
||||||
config.mediaProxy.endsWith('/') ? config.mediaProxy.substring(0, config.mediaProxy.length - 1) : config.mediaProxy
|
config.mediaProxy.endsWith('/') ? config.mediaProxy.substring(0, config.mediaProxy.length - 1) : config.mediaProxy
|
||||||
: null;
|
: null;
|
||||||
|
@ -231,7 +235,7 @@ export function loadConfig(): Config {
|
||||||
apiUrl: `${scheme}://${host}/api`,
|
apiUrl: `${scheme}://${host}/api`,
|
||||||
authUrl: `${scheme}://${host}/auth`,
|
authUrl: `${scheme}://${host}/auth`,
|
||||||
driveUrl: `${scheme}://${host}/files`,
|
driveUrl: `${scheme}://${host}/files`,
|
||||||
db: config.db,
|
db: { ...config.db, db: dbDb, user: dbUser, pass: dbPass },
|
||||||
dbReplications: config.dbReplications,
|
dbReplications: config.dbReplications,
|
||||||
dbSlaves: config.dbSlaves,
|
dbSlaves: config.dbSlaves,
|
||||||
meilisearch: config.meilisearch,
|
meilisearch: config.meilisearch,
|
||||||
|
@ -259,7 +263,7 @@ export function loadConfig(): Config {
|
||||||
deliverJobMaxAttempts: config.deliverJobMaxAttempts,
|
deliverJobMaxAttempts: config.deliverJobMaxAttempts,
|
||||||
inboxJobMaxAttempts: config.inboxJobMaxAttempts,
|
inboxJobMaxAttempts: config.inboxJobMaxAttempts,
|
||||||
proxyRemoteFiles: config.proxyRemoteFiles,
|
proxyRemoteFiles: config.proxyRemoteFiles,
|
||||||
signToActivityPubGet: config.signToActivityPubGet,
|
signToActivityPubGet: config.signToActivityPubGet ?? true,
|
||||||
mediaProxy: externalMediaProxy ?? internalMediaProxy,
|
mediaProxy: externalMediaProxy ?? internalMediaProxy,
|
||||||
externalMediaProxyEnabled: externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy,
|
externalMediaProxyEnabled: externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy,
|
||||||
videoThumbnailGenerator: config.videoThumbnailGenerator ?
|
videoThumbnailGenerator: config.videoThumbnailGenerator ?
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// dummy
|
||||||
export const MAX_NOTE_TEXT_LENGTH = 3000;
|
export const MAX_NOTE_TEXT_LENGTH = 3000;
|
||||||
|
|
||||||
export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min
|
export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
} from '@/core/entities/AbuseReportNotificationRecipientEntityService.js';
|
} from '@/core/entities/AbuseReportNotificationRecipientEntityService.js';
|
||||||
import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
|
import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
|
||||||
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
||||||
|
import { UserSearchService } from '@/core/UserSearchService.js';
|
||||||
import { AccountMoveService } from './AccountMoveService.js';
|
import { AccountMoveService } from './AccountMoveService.js';
|
||||||
import { AccountUpdateService } from './AccountUpdateService.js';
|
import { AccountUpdateService } from './AccountUpdateService.js';
|
||||||
import { AiService } from './AiService.js';
|
import { AiService } from './AiService.js';
|
||||||
|
@ -60,6 +61,7 @@ import { UserFollowingService } from './UserFollowingService.js';
|
||||||
import { UserKeypairService } from './UserKeypairService.js';
|
import { UserKeypairService } from './UserKeypairService.js';
|
||||||
import { UserListService } from './UserListService.js';
|
import { UserListService } from './UserListService.js';
|
||||||
import { UserMutingService } from './UserMutingService.js';
|
import { UserMutingService } from './UserMutingService.js';
|
||||||
|
import { UserRenoteMutingService } from './UserRenoteMutingService.js';
|
||||||
import { UserSuspendService } from './UserSuspendService.js';
|
import { UserSuspendService } from './UserSuspendService.js';
|
||||||
import { UserAuthService } from './UserAuthService.js';
|
import { UserAuthService } from './UserAuthService.js';
|
||||||
import { VideoProcessingService } from './VideoProcessingService.js';
|
import { VideoProcessingService } from './VideoProcessingService.js';
|
||||||
|
@ -203,6 +205,8 @@ const $UserFollowingService: Provider = { provide: 'UserFollowingService', useEx
|
||||||
const $UserKeypairService: Provider = { provide: 'UserKeypairService', useExisting: UserKeypairService };
|
const $UserKeypairService: Provider = { provide: 'UserKeypairService', useExisting: UserKeypairService };
|
||||||
const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService };
|
const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService };
|
||||||
const $UserMutingService: Provider = { provide: 'UserMutingService', useExisting: UserMutingService };
|
const $UserMutingService: Provider = { provide: 'UserMutingService', useExisting: UserMutingService };
|
||||||
|
const $UserRenoteMutingService: Provider = { provide: 'UserRenoteMutingService', useExisting: UserRenoteMutingService };
|
||||||
|
const $UserSearchService: Provider = { provide: 'UserSearchService', useExisting: UserSearchService };
|
||||||
const $UserSuspendService: Provider = { provide: 'UserSuspendService', useExisting: UserSuspendService };
|
const $UserSuspendService: Provider = { provide: 'UserSuspendService', useExisting: UserSuspendService };
|
||||||
const $UserAuthService: Provider = { provide: 'UserAuthService', useExisting: UserAuthService };
|
const $UserAuthService: Provider = { provide: 'UserAuthService', useExisting: UserAuthService };
|
||||||
const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', useExisting: VideoProcessingService };
|
const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', useExisting: VideoProcessingService };
|
||||||
|
@ -350,6 +354,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
UserKeypairService,
|
UserKeypairService,
|
||||||
UserListService,
|
UserListService,
|
||||||
UserMutingService,
|
UserMutingService,
|
||||||
|
UserRenoteMutingService,
|
||||||
|
UserSearchService,
|
||||||
UserSuspendService,
|
UserSuspendService,
|
||||||
UserAuthService,
|
UserAuthService,
|
||||||
VideoProcessingService,
|
VideoProcessingService,
|
||||||
|
@ -493,6 +499,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$UserKeypairService,
|
$UserKeypairService,
|
||||||
$UserListService,
|
$UserListService,
|
||||||
$UserMutingService,
|
$UserMutingService,
|
||||||
|
$UserRenoteMutingService,
|
||||||
|
$UserSearchService,
|
||||||
$UserSuspendService,
|
$UserSuspendService,
|
||||||
$UserAuthService,
|
$UserAuthService,
|
||||||
$VideoProcessingService,
|
$VideoProcessingService,
|
||||||
|
@ -637,6 +645,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
UserKeypairService,
|
UserKeypairService,
|
||||||
UserListService,
|
UserListService,
|
||||||
UserMutingService,
|
UserMutingService,
|
||||||
|
UserRenoteMutingService,
|
||||||
|
UserSearchService,
|
||||||
UserSuspendService,
|
UserSuspendService,
|
||||||
UserAuthService,
|
UserAuthService,
|
||||||
VideoProcessingService,
|
VideoProcessingService,
|
||||||
|
@ -779,6 +789,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$UserKeypairService,
|
$UserKeypairService,
|
||||||
$UserListService,
|
$UserListService,
|
||||||
$UserMutingService,
|
$UserMutingService,
|
||||||
|
$UserRenoteMutingService,
|
||||||
|
$UserSearchService,
|
||||||
$UserSuspendService,
|
$UserSuspendService,
|
||||||
$UserAuthService,
|
$UserAuthService,
|
||||||
$VideoProcessingService,
|
$VideoProcessingService,
|
||||||
|
|
|
@ -40,6 +40,7 @@ export class FederatedInstanceService implements OnApplicationShutdown {
|
||||||
firstRetrievedAt: new Date(parsed.firstRetrievedAt),
|
firstRetrievedAt: new Date(parsed.firstRetrievedAt),
|
||||||
latestRequestReceivedAt: parsed.latestRequestReceivedAt ? new Date(parsed.latestRequestReceivedAt) : null,
|
latestRequestReceivedAt: parsed.latestRequestReceivedAt ? new Date(parsed.latestRequestReceivedAt) : null,
|
||||||
infoUpdatedAt: parsed.infoUpdatedAt ? new Date(parsed.infoUpdatedAt) : null,
|
infoUpdatedAt: parsed.infoUpdatedAt ? new Date(parsed.infoUpdatedAt) : null,
|
||||||
|
notRespondingSince: parsed.notRespondingSince ? new Date(parsed.notRespondingSince) : null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -256,6 +256,10 @@ type SerializedAll<T> = {
|
||||||
[K in keyof T]: Serialized<T[K]>;
|
[K in keyof T]: Serialized<T[K]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type UndefinedAsNullAll<T> = {
|
||||||
|
[K in keyof T]: T[K] extends undefined ? null : T[K];
|
||||||
|
}
|
||||||
|
|
||||||
export interface InternalEventTypes {
|
export interface InternalEventTypes {
|
||||||
userChangeSuspendedState: { id: MiUser['id']; isSuspended: MiUser['isSuspended']; };
|
userChangeSuspendedState: { id: MiUser['id']; isSuspended: MiUser['isSuspended']; };
|
||||||
userChangeDeletedState: { id: MiUser['id']; isDeleted: MiUser['isDeleted']; };
|
userChangeDeletedState: { id: MiUser['id']; isDeleted: MiUser['isDeleted']; };
|
||||||
|
@ -294,43 +298,45 @@ export interface InternalEventTypes {
|
||||||
userListMemberRemoved: { userListId: MiUserList['id']; memberId: MiUser['id']; };
|
userListMemberRemoved: { userListId: MiUserList['id']; memberId: MiUser['id']; };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EventTypesToEventPayload<T> = EventUnionFromDictionary<UndefinedAsNullAll<SerializedAll<T>>>;
|
||||||
|
|
||||||
// name/messages(spec) pairs dictionary
|
// name/messages(spec) pairs dictionary
|
||||||
export type GlobalEvents = {
|
export type GlobalEvents = {
|
||||||
internal: {
|
internal: {
|
||||||
name: 'internal';
|
name: 'internal';
|
||||||
payload: EventUnionFromDictionary<SerializedAll<InternalEventTypes>>;
|
payload: EventTypesToEventPayload<InternalEventTypes>;
|
||||||
};
|
};
|
||||||
broadcast: {
|
broadcast: {
|
||||||
name: 'broadcast';
|
name: 'broadcast';
|
||||||
payload: EventUnionFromDictionary<SerializedAll<BroadcastTypes>>;
|
payload: EventTypesToEventPayload<BroadcastTypes>;
|
||||||
};
|
};
|
||||||
main: {
|
main: {
|
||||||
name: `mainStream:${MiUser['id']}`;
|
name: `mainStream:${MiUser['id']}`;
|
||||||
payload: EventUnionFromDictionary<SerializedAll<MainEventTypes>>;
|
payload: EventTypesToEventPayload<MainEventTypes>;
|
||||||
};
|
};
|
||||||
drive: {
|
drive: {
|
||||||
name: `driveStream:${MiUser['id']}`;
|
name: `driveStream:${MiUser['id']}`;
|
||||||
payload: EventUnionFromDictionary<SerializedAll<DriveEventTypes>>;
|
payload: EventTypesToEventPayload<DriveEventTypes>;
|
||||||
};
|
};
|
||||||
note: {
|
note: {
|
||||||
name: `noteStream:${MiNote['id']}`;
|
name: `noteStream:${MiNote['id']}`;
|
||||||
payload: EventUnionFromDictionary<SerializedAll<NoteStreamEventTypes>>;
|
payload: EventTypesToEventPayload<NoteStreamEventTypes>;
|
||||||
};
|
};
|
||||||
userList: {
|
userList: {
|
||||||
name: `userListStream:${MiUserList['id']}`;
|
name: `userListStream:${MiUserList['id']}`;
|
||||||
payload: EventUnionFromDictionary<SerializedAll<UserListEventTypes>>;
|
payload: EventTypesToEventPayload<UserListEventTypes>;
|
||||||
};
|
};
|
||||||
roleTimeline: {
|
roleTimeline: {
|
||||||
name: `roleTimelineStream:${MiRole['id']}`;
|
name: `roleTimelineStream:${MiRole['id']}`;
|
||||||
payload: EventUnionFromDictionary<SerializedAll<RoleTimelineEventTypes>>;
|
payload: EventTypesToEventPayload<RoleTimelineEventTypes>;
|
||||||
};
|
};
|
||||||
antenna: {
|
antenna: {
|
||||||
name: `antennaStream:${MiAntenna['id']}`;
|
name: `antennaStream:${MiAntenna['id']}`;
|
||||||
payload: EventUnionFromDictionary<SerializedAll<AntennaEventTypes>>;
|
payload: EventTypesToEventPayload<AntennaEventTypes>;
|
||||||
};
|
};
|
||||||
admin: {
|
admin: {
|
||||||
name: `adminStream:${MiUser['id']}`;
|
name: `adminStream:${MiUser['id']}`;
|
||||||
payload: EventUnionFromDictionary<SerializedAll<AdminEventTypes>>;
|
payload: EventTypesToEventPayload<AdminEventTypes>;
|
||||||
};
|
};
|
||||||
notes: {
|
notes: {
|
||||||
name: 'notesStream';
|
name: 'notesStream';
|
||||||
|
@ -338,11 +344,11 @@ export type GlobalEvents = {
|
||||||
};
|
};
|
||||||
reversi: {
|
reversi: {
|
||||||
name: `reversiStream:${MiUser['id']}`;
|
name: `reversiStream:${MiUser['id']}`;
|
||||||
payload: EventUnionFromDictionary<SerializedAll<ReversiEventTypes>>;
|
payload: EventTypesToEventPayload<ReversiEventTypes>;
|
||||||
};
|
};
|
||||||
reversiGame: {
|
reversiGame: {
|
||||||
name: `reversiGameStream:${MiReversiGame['id']}`;
|
name: `reversiGameStream:${MiReversiGame['id']}`;
|
||||||
payload: EventUnionFromDictionary<SerializedAll<ReversiGameEventTypes>>;
|
payload: EventTypesToEventPayload<ReversiGameEventTypes>;
|
||||||
};
|
};
|
||||||
mahjongRoom: {
|
mahjongRoom: {
|
||||||
name: `mahjongRoomStream:${string}`;
|
name: `mahjongRoomStream:${string}`;
|
||||||
|
|
|
@ -933,10 +933,13 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) { // 自分自身のHTL
|
// 自分自身のHTL
|
||||||
this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r);
|
if (note.userHost == null) {
|
||||||
if (note.fileIds.length > 0) {
|
if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) {
|
||||||
this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
|
this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r);
|
||||||
|
if (note.fileIds.length > 0) {
|
||||||
|
this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ export type RolePolicies = {
|
||||||
canHideAds: boolean;
|
canHideAds: boolean;
|
||||||
driveCapacityMb: number;
|
driveCapacityMb: number;
|
||||||
alwaysMarkNsfw: boolean;
|
alwaysMarkNsfw: boolean;
|
||||||
|
canUpdateBioMedia: boolean;
|
||||||
pinLimit: number;
|
pinLimit: number;
|
||||||
antennaLimit: number;
|
antennaLimit: number;
|
||||||
wordMuteLimit: number;
|
wordMuteLimit: number;
|
||||||
|
@ -75,6 +76,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
||||||
canHideAds: false,
|
canHideAds: false,
|
||||||
driveCapacityMb: 100,
|
driveCapacityMb: 100,
|
||||||
alwaysMarkNsfw: false,
|
alwaysMarkNsfw: false,
|
||||||
|
canUpdateBioMedia: true,
|
||||||
pinLimit: 5,
|
pinLimit: 5,
|
||||||
antennaLimit: 5,
|
antennaLimit: 5,
|
||||||
wordMuteLimit: 200,
|
wordMuteLimit: 200,
|
||||||
|
@ -376,6 +378,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
|
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
|
||||||
driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)),
|
driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)),
|
||||||
alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)),
|
alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)),
|
||||||
|
canUpdateBioMedia: calc('canUpdateBioMedia', vs => vs.some(v => v === true)),
|
||||||
pinLimit: calc('pinLimit', vs => Math.max(...vs)),
|
pinLimit: calc('pinLimit', vs => Math.max(...vs)),
|
||||||
antennaLimit: calc('antennaLimit', vs => Math.max(...vs)),
|
antennaLimit: calc('antennaLimit', vs => Math.max(...vs)),
|
||||||
wordMuteLimit: calc('wordMuteLimit', vs => Math.max(...vs)),
|
wordMuteLimit: calc('wordMuteLimit', vs => Math.max(...vs)),
|
||||||
|
@ -502,14 +505,15 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
|
|
||||||
this.globalEventService.publishInternalEvent('userRoleAssigned', created);
|
this.globalEventService.publishInternalEvent('userRoleAssigned', created);
|
||||||
|
|
||||||
if (role.isPublic) {
|
const user = await this.usersRepository.findOneByOrFail({ id: userId });
|
||||||
|
|
||||||
|
if (role.isPublic && user.host === null) {
|
||||||
this.notificationService.createNotification(userId, 'roleAssigned', {
|
this.notificationService.createNotification(userId, 'roleAssigned', {
|
||||||
roleId: roleId,
|
roleId: roleId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (moderator) {
|
if (moderator) {
|
||||||
const user = await this.usersRepository.findOneByOrFail({ id: userId });
|
|
||||||
this.moderationLogService.log(moderator, 'assignRole', {
|
this.moderationLogService.log(moderator, 'assignRole', {
|
||||||
roleId: roleId,
|
roleId: roleId,
|
||||||
roleName: role.name,
|
roleName: role.name,
|
||||||
|
|
|
@ -279,8 +279,10 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
});
|
});
|
||||||
|
|
||||||
// 通知を作成
|
// 通知を作成
|
||||||
this.notificationService.createNotification(follower.id, 'followRequestAccepted', {
|
if (follower.host === null) {
|
||||||
}, followee.id);
|
this.notificationService.createNotification(follower.id, 'followRequestAccepted', {
|
||||||
|
}, followee.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alreadyFollowed) return;
|
if (alreadyFollowed) return;
|
||||||
|
|
52
packages/backend/src/core/UserRenoteMutingService.ts
Normal file
52
packages/backend/src/core/UserRenoteMutingService.ts
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project , Type4ny-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { In } from 'typeorm';
|
||||||
|
import type { RenoteMutingsRepository } from '@/models/_.js';
|
||||||
|
import type { MiRenoteMuting } from '@/models/RenoteMuting.js';
|
||||||
|
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import type { MiUser } from '@/models/User.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserRenoteMutingService {
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.renoteMutingsRepository)
|
||||||
|
private renoteMutingsRepository: RenoteMutingsRepository,
|
||||||
|
|
||||||
|
private idService: IdService,
|
||||||
|
private cacheService: CacheService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise<void> {
|
||||||
|
await this.renoteMutingsRepository.insert({
|
||||||
|
id: this.idService.gen(),
|
||||||
|
muterId: user.id,
|
||||||
|
muteeId: target.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.cacheService.renoteMutingsCache.refresh(user.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async unmute(mutings: MiRenoteMuting[]): Promise<void> {
|
||||||
|
if (mutings.length === 0) return;
|
||||||
|
|
||||||
|
await this.renoteMutingsRepository.delete({
|
||||||
|
id: In(mutings.map(m => m.id)),
|
||||||
|
});
|
||||||
|
|
||||||
|
const muterIds = [...new Set(mutings.map(m => m.muterId))];
|
||||||
|
for (const muterId of muterIds) {
|
||||||
|
await this.cacheService.renoteMutingsCache.refresh(muterId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
205
packages/backend/src/core/UserSearchService.ts
Normal file
205
packages/backend/src/core/UserSearchService.ts
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { Brackets, SelectQueryBuilder } from 'typeorm';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { type FollowingsRepository, MiUser, type UsersRepository } from '@/models/_.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
|
||||||
|
import type { Config } from '@/config.js';
|
||||||
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
import { Packed } from '@/misc/json-schema.js';
|
||||||
|
|
||||||
|
function defaultActiveThreshold() {
|
||||||
|
return new Date(Date.now() - 1000 * 60 * 60 * 24 * 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserSearchService {
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.config)
|
||||||
|
private config: Config,
|
||||||
|
@Inject(DI.usersRepository)
|
||||||
|
private usersRepository: UsersRepository,
|
||||||
|
@Inject(DI.followingsRepository)
|
||||||
|
private followingsRepository: FollowingsRepository,
|
||||||
|
private userEntityService: UserEntityService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ユーザ名とホスト名によるユーザ検索を行う.
|
||||||
|
*
|
||||||
|
* - 検索結果には優先順位がつけられており、以下の順序で検索が行われる.
|
||||||
|
* 1. フォローしているユーザのうち、一定期間以内(※)に更新されたユーザ
|
||||||
|
* 2. フォローしているユーザのうち、一定期間以内に更新されていないユーザ
|
||||||
|
* 3. フォローしていないユーザのうち、一定期間以内に更新されたユーザ
|
||||||
|
* 4. フォローしていないユーザのうち、一定期間以内に更新されていないユーザ
|
||||||
|
* - ログインしていない場合は、以下の順序で検索が行われる.
|
||||||
|
* 1. 一定期間以内に更新されたユーザ
|
||||||
|
* 2. 一定期間以内に更新されていないユーザ
|
||||||
|
* - それぞれの検索結果はユーザ名の昇順でソートされる.
|
||||||
|
* - 動作的には先に登場した検索結果の登場位置が優先される(条件的にユーザIDが重複することはないが).
|
||||||
|
* (1で既にヒットしていた場合、2, 3, 4でヒットしても無視される)
|
||||||
|
* - ユーザ名とホスト名の検索条件はそれぞれ前方一致で検索される.
|
||||||
|
* - ユーザ名の検索は大文字小文字を区別しない.
|
||||||
|
* - ホスト名の検索は大文字小文字を区別しない.
|
||||||
|
* - 検索結果は最大で {@link opts.limit} 件までとなる.
|
||||||
|
*
|
||||||
|
* ※一定期間とは {@link params.activeThreshold} で指定された日時から現在までの期間を指す.
|
||||||
|
*
|
||||||
|
* @param params 検索条件.
|
||||||
|
* @param opts 関数の動作を制御するオプション.
|
||||||
|
* @param me 検索を実行するユーザの情報. 未ログインの場合は指定しない.
|
||||||
|
* @see {@link UserSearchService#buildSearchUserQueries}
|
||||||
|
* @see {@link UserSearchService#buildSearchUserNoLoginQueries}
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
|
public async search(
|
||||||
|
params: {
|
||||||
|
username?: string | null,
|
||||||
|
host?: string | null,
|
||||||
|
activeThreshold?: Date,
|
||||||
|
},
|
||||||
|
opts?: {
|
||||||
|
limit?: number,
|
||||||
|
detail?: boolean,
|
||||||
|
},
|
||||||
|
me?: MiUser | null,
|
||||||
|
): Promise<Packed<'User'>[]> {
|
||||||
|
const queries = me ? this.buildSearchUserQueries(me, params) : this.buildSearchUserNoLoginQueries(params);
|
||||||
|
|
||||||
|
let resultSet = new Set<MiUser['id']>();
|
||||||
|
const limit = opts?.limit ?? 10;
|
||||||
|
for (const query of queries) {
|
||||||
|
const ids = await query
|
||||||
|
.select('user.id')
|
||||||
|
.limit(limit - resultSet.size)
|
||||||
|
.orderBy('user.usernameLower', 'ASC')
|
||||||
|
.getRawMany<{ user_id: MiUser['id'] }>()
|
||||||
|
.then(res => res.map(x => x.user_id));
|
||||||
|
|
||||||
|
resultSet = new Set([...resultSet, ...ids]);
|
||||||
|
if (resultSet.size >= limit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.userEntityService.packMany<'UserLite' | 'UserDetailed'>(
|
||||||
|
[...resultSet].slice(0, limit),
|
||||||
|
me,
|
||||||
|
{ schema: opts?.detail ? 'UserDetailed' : 'UserLite' },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ログイン済みユーザによる検索実行時のクエリ一覧を構築する.
|
||||||
|
* @param me
|
||||||
|
* @param params
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
|
private buildSearchUserQueries(
|
||||||
|
me: MiUser,
|
||||||
|
params: {
|
||||||
|
username?: string | null,
|
||||||
|
host?: string | null,
|
||||||
|
activeThreshold?: Date,
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
// デフォルト30日以内に更新されたユーザーをアクティブユーザーとする
|
||||||
|
const activeThreshold = params.activeThreshold ?? defaultActiveThreshold();
|
||||||
|
|
||||||
|
const followingUserQuery = this.followingsRepository.createQueryBuilder('following')
|
||||||
|
.select('following.followeeId')
|
||||||
|
.where('following.followerId = :followerId', { followerId: me.id });
|
||||||
|
|
||||||
|
const activeFollowingUsersQuery = this.generateUserQueryBuilder(params)
|
||||||
|
.andWhere(`user.id IN (${followingUserQuery.getQuery()})`)
|
||||||
|
.andWhere('user.updatedAt > :activeThreshold', { activeThreshold });
|
||||||
|
activeFollowingUsersQuery.setParameters(followingUserQuery.getParameters());
|
||||||
|
|
||||||
|
const inactiveFollowingUsersQuery = this.generateUserQueryBuilder(params)
|
||||||
|
.andWhere(`user.id IN (${followingUserQuery.getQuery()})`)
|
||||||
|
.andWhere(new Brackets(qb => {
|
||||||
|
qb
|
||||||
|
.where('user.updatedAt IS NULL')
|
||||||
|
.orWhere('user.updatedAt <= :activeThreshold', { activeThreshold });
|
||||||
|
}));
|
||||||
|
inactiveFollowingUsersQuery.setParameters(followingUserQuery.getParameters());
|
||||||
|
|
||||||
|
// 自分自身がヒットするとしたらここ
|
||||||
|
const activeUserQuery = this.generateUserQueryBuilder(params)
|
||||||
|
.andWhere(`user.id NOT IN (${followingUserQuery.getQuery()})`)
|
||||||
|
.andWhere('user.updatedAt > :activeThreshold', { activeThreshold });
|
||||||
|
activeUserQuery.setParameters(followingUserQuery.getParameters());
|
||||||
|
|
||||||
|
const inactiveUserQuery = this.generateUserQueryBuilder(params)
|
||||||
|
.andWhere(`user.id NOT IN (${followingUserQuery.getQuery()})`)
|
||||||
|
.andWhere('user.updatedAt <= :activeThreshold', { activeThreshold });
|
||||||
|
inactiveUserQuery.setParameters(followingUserQuery.getParameters());
|
||||||
|
|
||||||
|
return [activeFollowingUsersQuery, inactiveFollowingUsersQuery, activeUserQuery, inactiveUserQuery];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ログインしていないユーザによる検索実行時のクエリ一覧を構築する.
|
||||||
|
* @param params
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
|
private buildSearchUserNoLoginQueries(params: {
|
||||||
|
username?: string | null,
|
||||||
|
host?: string | null,
|
||||||
|
activeThreshold?: Date,
|
||||||
|
}) {
|
||||||
|
// デフォルト30日以内に更新されたユーザーをアクティブユーザーとする
|
||||||
|
const activeThreshold = params.activeThreshold ?? defaultActiveThreshold();
|
||||||
|
|
||||||
|
const activeUserQuery = this.generateUserQueryBuilder(params)
|
||||||
|
.andWhere(new Brackets(qb => {
|
||||||
|
qb
|
||||||
|
.where('user.updatedAt IS NULL')
|
||||||
|
.orWhere('user.updatedAt > :activeThreshold', { activeThreshold });
|
||||||
|
}));
|
||||||
|
|
||||||
|
const inactiveUserQuery = this.generateUserQueryBuilder(params)
|
||||||
|
.andWhere('user.updatedAt <= :activeThreshold', { activeThreshold });
|
||||||
|
|
||||||
|
return [activeUserQuery, inactiveUserQuery];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ユーザ検索クエリで共通する抽出条件をあらかじめ設定したクエリビルダを生成する.
|
||||||
|
* @param params
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
|
private generateUserQueryBuilder(params: {
|
||||||
|
username?: string | null,
|
||||||
|
host?: string | null,
|
||||||
|
}): SelectQueryBuilder<MiUser> {
|
||||||
|
const userQuery = this.usersRepository.createQueryBuilder('user');
|
||||||
|
|
||||||
|
if (params.username) {
|
||||||
|
userQuery.andWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(params.username.toLowerCase()) + '%' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.host) {
|
||||||
|
if (params.host === this.config.hostname || params.host === '.') {
|
||||||
|
userQuery.andWhere('user.host IS NULL');
|
||||||
|
} else {
|
||||||
|
userQuery.andWhere('user.host LIKE :host', {
|
||||||
|
host: sqlLikeEscape(params.host.toLowerCase()) + '%',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userQuery.andWhere('user.isSuspended = FALSE');
|
||||||
|
|
||||||
|
return userQuery;
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ export class ApMfmService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public getNoteHtml(note: MiNote, apAppend?: string) {
|
public getNoteHtml(note: Pick<MiNote, 'text' | 'mentionedRemoteUsers'>, apAppend?: string) {
|
||||||
let noMisskeyContent = false;
|
let noMisskeyContent = false;
|
||||||
const srcMfm = (note.text ?? '') + (apAppend ?? '');
|
const srcMfm = (note.text ?? '') + (apAppend ?? '');
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ import { StatusError } from '@/misc/status-error.js';
|
||||||
import type { UtilityService } from '@/core/UtilityService.js';
|
import type { UtilityService } from '@/core/UtilityService.js';
|
||||||
import type { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import type { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||||
import type { AccountMoveService } from '@/core/AccountMoveService.js';
|
import type { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||||
|
@ -100,6 +101,8 @@ export class ApPersonService implements OnModuleInit {
|
||||||
|
|
||||||
@Inject(DI.followingsRepository)
|
@Inject(DI.followingsRepository)
|
||||||
private followingsRepository: FollowingsRepository,
|
private followingsRepository: FollowingsRepository,
|
||||||
|
|
||||||
|
private roleService: RoleService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,6 +241,11 @@ export class ApPersonService implements OnModuleInit {
|
||||||
return this.apImageService.resolveImage(user, img).catch(() => null);
|
return this.apImageService.resolveImage(user, img).catch(() => null);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
if (((avatar != null && avatar.id != null) || (banner != null && banner.id != null))
|
||||||
|
&& !(await this.roleService.getUserPolicies(user.id)).canUpdateBioMedia) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
we don't want to return nulls on errors! if the database fields
|
we don't want to return nulls on errors! if the database fields
|
||||||
are already null, nothing changes; if the database has old
|
are already null, nothing changes; if the database has old
|
||||||
|
|
|
@ -74,10 +74,10 @@ export class ApQuestionService {
|
||||||
|
|
||||||
//#region このサーバーに既に登録されているか
|
//#region このサーバーに既に登録されているか
|
||||||
const note = await this.notesRepository.findOneBy({ uri });
|
const note = await this.notesRepository.findOneBy({ uri });
|
||||||
if (note == null) throw new Error('Question is not registed');
|
if (note == null) throw new Error('Question is not registered');
|
||||||
|
|
||||||
const poll = await this.pollsRepository.findOneBy({ noteId: note.id });
|
const poll = await this.pollsRepository.findOneBy({ noteId: note.id });
|
||||||
if (poll == null) throw new Error('Question is not registed');
|
if (poll == null) throw new Error('Question is not registered');
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
// resolve new Question object
|
// resolve new Question object
|
||||||
|
|
|
@ -50,6 +50,22 @@ export class MetaEntityService {
|
||||||
}))
|
}))
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
|
// クライアントの手間を減らすためあらかじめJSONに変換しておく
|
||||||
|
let defaultLightTheme = null;
|
||||||
|
let defaultDarkTheme = null;
|
||||||
|
if (instance.defaultLightTheme) {
|
||||||
|
try {
|
||||||
|
defaultLightTheme = JSON.stringify(JSON5.parse(instance.defaultLightTheme));
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (instance.defaultDarkTheme) {
|
||||||
|
try {
|
||||||
|
defaultDarkTheme = JSON.stringify(JSON5.parse(instance.defaultDarkTheme));
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const packed: Packed<'MetaLite'> = {
|
const packed: Packed<'MetaLite'> = {
|
||||||
maintainerName: instance.maintainerName,
|
maintainerName: instance.maintainerName,
|
||||||
maintainerEmail: instance.maintainerEmail,
|
maintainerEmail: instance.maintainerEmail,
|
||||||
|
@ -90,9 +106,8 @@ export class MetaEntityService {
|
||||||
backgroundImageUrl: instance.backgroundImageUrl,
|
backgroundImageUrl: instance.backgroundImageUrl,
|
||||||
logoImageUrl: instance.logoImageUrl,
|
logoImageUrl: instance.logoImageUrl,
|
||||||
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
|
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
|
||||||
// クライアントの手間を減らすためあらかじめJSONに変換しておく
|
defaultLightTheme,
|
||||||
defaultLightTheme: instance.defaultLightTheme ? JSON.stringify(JSON5.parse(instance.defaultLightTheme)) : null,
|
defaultDarkTheme,
|
||||||
defaultDarkTheme: instance.defaultDarkTheme ? JSON.stringify(JSON5.parse(instance.defaultDarkTheme)) : null,
|
|
||||||
ads: ads.map(ad => ({
|
ads: ads.map(ad => ({
|
||||||
id: ad.id,
|
id: ad.id,
|
||||||
url: ad.url,
|
url: ad.url,
|
||||||
|
|
|
@ -501,11 +501,15 @@ export class UserEntityService implements OnModuleInit {
|
||||||
emojis: this.customEmojiService.populateEmojis(user.emojis, user.host),
|
emojis: this.customEmojiService.populateEmojis(user.emojis, user.host),
|
||||||
onlineStatus: this.getOnlineStatus(user),
|
onlineStatus: this.getOnlineStatus(user),
|
||||||
// パフォーマンス上の理由でローカルユーザーのみ
|
// パフォーマンス上の理由でローカルユーザーのみ
|
||||||
badgeRoles: user.host == null ? this.roleService.getUserBadgeRoles(user.id).then(rs => rs.sort((a, b) => b.displayOrder - a.displayOrder).map(r => ({
|
badgeRoles: user.host == null ? this.roleService.getUserBadgeRoles(user.id).then((rs) => rs
|
||||||
name: r.name,
|
.filter((r) => r.isPublic || iAmModerator)
|
||||||
iconUrl: r.iconUrl,
|
.sort((a, b) => b.displayOrder - a.displayOrder)
|
||||||
displayOrder: r.displayOrder,
|
.map((r) => ({
|
||||||
}))) : undefined,
|
name: r.name,
|
||||||
|
iconUrl: r.iconUrl,
|
||||||
|
displayOrder: r.displayOrder,
|
||||||
|
}))
|
||||||
|
) : undefined,
|
||||||
|
|
||||||
...(isDetailed ? {
|
...(isDetailed ? {
|
||||||
url: profile!.url,
|
url: profile!.url,
|
||||||
|
|
|
@ -4,6 +4,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function isUserRelated(note: any, userIds: Set<string>, ignoreAuthor = false): boolean {
|
export function isUserRelated(note: any, userIds: Set<string>, ignoreAuthor = false): boolean {
|
||||||
|
if (!note) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (userIds.has(note.userId) && !ignoreAuthor) {
|
if (userIds.has(note.userId) && !ignoreAuthor) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
8
packages/backend/src/misc/json-value.ts
Normal file
8
packages/backend/src/misc/json-value.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type JsonValue = JsonArray | JsonObject | string | number | boolean | null;
|
||||||
|
export type JsonObject = {[K in string]?: JsonValue};
|
||||||
|
export type JsonArray = JsonValue[];
|
|
@ -204,6 +204,7 @@ export const packedNoteSchema = {
|
||||||
reactionAcceptance: {
|
reactionAcceptance: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
|
enum: ['likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'],
|
||||||
},
|
},
|
||||||
reactionEmojis: {
|
reactionEmojis: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
|
|
@ -228,6 +228,10 @@ export const packedRolePoliciesSchema = {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
canUpdateBioMedia: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
pinLimit: {
|
pinLimit: {
|
||||||
type: 'integer',
|
type: 'integer',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { PollVotesRepository, NotesRepository } from '@/models/_.js';
|
import type { PollVotesRepository, NotesRepository } from '@/models/_.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import { NotificationService } from '@/core/NotificationService.js';
|
import { NotificationService } from '@/core/NotificationService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
|
@ -24,6 +25,7 @@ export class EndedPollNotificationProcessorService {
|
||||||
@Inject(DI.pollVotesRepository)
|
@Inject(DI.pollVotesRepository)
|
||||||
private pollVotesRepository: PollVotesRepository,
|
private pollVotesRepository: PollVotesRepository,
|
||||||
|
|
||||||
|
private cacheService: CacheService,
|
||||||
private notificationService: NotificationService,
|
private notificationService: NotificationService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
|
@ -47,9 +49,12 @@ export class EndedPollNotificationProcessorService {
|
||||||
const userIds = [...new Set([note.userId, ...votes.map(v => v.userId)])];
|
const userIds = [...new Set([note.userId, ...votes.map(v => v.userId)])];
|
||||||
|
|
||||||
for (const userId of userIds) {
|
for (const userId of userIds) {
|
||||||
this.notificationService.createNotification(userId, 'pollEnded', {
|
const profile = await this.cacheService.userProfileCache.fetch(userId);
|
||||||
noteId: note.id,
|
if (profile.userHost === null) {
|
||||||
});
|
this.notificationService.createNotification(userId, 'pollEnded', {
|
||||||
|
noteId: note.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { UserFollowingService } from '@/core/UserFollowingService.js';
|
||||||
import { AccountUpdateService } from '@/core/AccountUpdateService.js';
|
import { AccountUpdateService } from '@/core/AccountUpdateService.js';
|
||||||
import { HashtagService } from '@/core/HashtagService.js';
|
import { HashtagService } from '@/core/HashtagService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RolePolicies, RoleService } from '@/core/RoleService.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
||||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||||
|
@ -256,6 +256,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
const profileUpdates = {} as Partial<MiUserProfile>;
|
const profileUpdates = {} as Partial<MiUserProfile>;
|
||||||
|
|
||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||||
|
let policies: RolePolicies | null = null;
|
||||||
|
|
||||||
if (ps.name !== undefined) {
|
if (ps.name !== undefined) {
|
||||||
if (ps.name === null) {
|
if (ps.name === null) {
|
||||||
|
@ -296,14 +297,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps.mutedWords !== undefined) {
|
if (ps.mutedWords !== undefined) {
|
||||||
checkMuteWordCount(ps.mutedWords, (await this.roleService.getUserPolicies(user.id)).wordMuteLimit);
|
policies ??= await this.roleService.getUserPolicies(user.id);
|
||||||
|
checkMuteWordCount(ps.mutedWords, policies.wordMuteLimit);
|
||||||
validateMuteWordRegex(ps.mutedWords);
|
validateMuteWordRegex(ps.mutedWords);
|
||||||
|
|
||||||
profileUpdates.mutedWords = ps.mutedWords;
|
profileUpdates.mutedWords = ps.mutedWords;
|
||||||
profileUpdates.enableWordMute = ps.mutedWords.length > 0;
|
profileUpdates.enableWordMute = ps.mutedWords.length > 0;
|
||||||
}
|
}
|
||||||
if (ps.hardMutedWords !== undefined) {
|
if (ps.hardMutedWords !== undefined) {
|
||||||
checkMuteWordCount(ps.hardMutedWords, (await this.roleService.getUserPolicies(user.id)).wordMuteLimit);
|
policies ??= await this.roleService.getUserPolicies(user.id);
|
||||||
|
checkMuteWordCount(ps.hardMutedWords, policies.wordMuteLimit);
|
||||||
validateMuteWordRegex(ps.hardMutedWords);
|
validateMuteWordRegex(ps.hardMutedWords);
|
||||||
profileUpdates.hardMutedWords = ps.hardMutedWords;
|
profileUpdates.hardMutedWords = ps.hardMutedWords;
|
||||||
}
|
}
|
||||||
|
@ -322,13 +325,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
|
if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
|
||||||
if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
|
if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
|
||||||
if (typeof ps.alwaysMarkNsfw === 'boolean') {
|
if (typeof ps.alwaysMarkNsfw === 'boolean') {
|
||||||
if ((await roleService.getUserPolicies(user.id)).alwaysMarkNsfw) throw new ApiError(meta.errors.restrictedByRole);
|
policies ??= await this.roleService.getUserPolicies(user.id);
|
||||||
|
if (policies.alwaysMarkNsfw) throw new ApiError(meta.errors.restrictedByRole);
|
||||||
profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
|
profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
|
||||||
}
|
}
|
||||||
if (typeof ps.autoSensitive === 'boolean') profileUpdates.autoSensitive = ps.autoSensitive;
|
if (typeof ps.autoSensitive === 'boolean') profileUpdates.autoSensitive = ps.autoSensitive;
|
||||||
if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes;
|
if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes;
|
||||||
|
|
||||||
if (ps.avatarId) {
|
if (ps.avatarId) {
|
||||||
|
policies ??= await this.roleService.getUserPolicies(user.id);
|
||||||
|
if (!policies.canUpdateBioMedia) throw new ApiError(meta.errors.restrictedByRole);
|
||||||
|
|
||||||
const avatar = await this.driveFilesRepository.findOneBy({ id: ps.avatarId });
|
const avatar = await this.driveFilesRepository.findOneBy({ id: ps.avatarId });
|
||||||
|
|
||||||
if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar);
|
if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar);
|
||||||
|
@ -344,6 +351,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps.bannerId) {
|
if (ps.bannerId) {
|
||||||
|
policies ??= await this.roleService.getUserPolicies(user.id);
|
||||||
|
if (!policies.canUpdateBioMedia) throw new ApiError(meta.errors.restrictedByRole);
|
||||||
|
|
||||||
const banner = await this.driveFilesRepository.findOneBy({ id: ps.bannerId });
|
const banner = await this.driveFilesRepository.findOneBy({ id: ps.bannerId });
|
||||||
|
|
||||||
if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner);
|
if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner);
|
||||||
|
@ -359,14 +369,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps.avatarDecorations) {
|
if (ps.avatarDecorations) {
|
||||||
|
policies ??= await this.roleService.getUserPolicies(user.id);
|
||||||
const decorations = await this.avatarDecorationService.getAll(true);
|
const decorations = await this.avatarDecorationService.getAll(true);
|
||||||
const [myRoles, myPolicies] = await Promise.all([this.roleService.getUserRoles(user.id), this.roleService.getUserPolicies(user.id)]);
|
const myRoles = await this.roleService.getUserRoles(user.id);
|
||||||
const allRoles = await this.roleService.getRoles();
|
const allRoles = await this.roleService.getRoles();
|
||||||
const decorationIds = decorations
|
const decorationIds = decorations
|
||||||
.filter(d => d.roleIdsThatCanBeUsedThisDecoration.filter(roleId => allRoles.some(r => r.id === roleId)).length === 0 || myRoles.some(r => d.roleIdsThatCanBeUsedThisDecoration.includes(r.id)))
|
.filter(d => d.roleIdsThatCanBeUsedThisDecoration.filter(roleId => allRoles.some(r => r.id === roleId)).length === 0 || myRoles.some(r => d.roleIdsThatCanBeUsedThisDecoration.includes(r.id)))
|
||||||
.map(d => d.id);
|
.map(d => d.id);
|
||||||
|
|
||||||
if (ps.avatarDecorations.length > myPolicies.avatarDecorationLimit) throw new ApiError(meta.errors.restrictedByRole);
|
if (ps.avatarDecorations.length > policies.avatarDecorationLimit) throw new ApiError(meta.errors.restrictedByRole);
|
||||||
|
|
||||||
updates.avatarDecorations = ps.avatarDecorations.filter(d => decorationIds.includes(d.id)).map(d => ({
|
updates.avatarDecorations = ps.avatarDecorations.filter(d => decorationIds.includes(d.id)).map(d => ({
|
||||||
id: d.id,
|
id: d.id,
|
||||||
|
|
|
@ -139,6 +139,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
timelineConfig = [
|
timelineConfig = [
|
||||||
`homeTimeline:${me.id}`,
|
`homeTimeline:${me.id}`,
|
||||||
'localTimeline',
|
'localTimeline',
|
||||||
|
`localTimelineWithReplyTo:${me.id}`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,11 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
|
||||||
import type { RenoteMutingsRepository } from '@/models/_.js';
|
|
||||||
import type { MiRenoteMuting } from '@/models/RenoteMuting.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { GetterService } from '@/server/api/GetterService.js';
|
import { GetterService } from '@/server/api/GetterService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
import { UserRenoteMutingService } from "@/core/UserRenoteMutingService.js";
|
||||||
|
import type { RenoteMutingsRepository } from '@/models/_.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['account'],
|
tags: ['account'],
|
||||||
|
@ -62,7 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private renoteMutingsRepository: RenoteMutingsRepository,
|
private renoteMutingsRepository: RenoteMutingsRepository,
|
||||||
|
|
||||||
private getterService: GetterService,
|
private getterService: GetterService,
|
||||||
private idService: IdService,
|
private userRenoteMutingService: UserRenoteMutingService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const muter = me;
|
const muter = me;
|
||||||
|
@ -79,21 +78,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if already muting
|
// Check if already muting
|
||||||
const exist = await this.renoteMutingsRepository.findOneBy({
|
const exist = await this.renoteMutingsRepository.exists({
|
||||||
muterId: muter.id,
|
where: {
|
||||||
muteeId: mutee.id,
|
muterId: muter.id,
|
||||||
|
muteeId: mutee.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exist != null) {
|
if (exist === true) {
|
||||||
throw new ApiError(meta.errors.alreadyMuting);
|
throw new ApiError(meta.errors.alreadyMuting);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create mute
|
// Create mute
|
||||||
await this.renoteMutingsRepository.insert({
|
await this.userRenoteMutingService.mute(muter, mutee);
|
||||||
id: this.idService.gen(),
|
|
||||||
muterId: muter.id,
|
|
||||||
muteeId: mutee.id,
|
|
||||||
} as MiRenoteMuting);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,11 @@
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import type { RenoteMutingsRepository } from '@/models/_.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { GetterService } from '@/server/api/GetterService.js';
|
import { GetterService } from '@/server/api/GetterService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
import { UserRenoteMutingService } from "@/core/UserRenoteMutingService.js";
|
||||||
|
import type { RenoteMutingsRepository } from '@/models/_.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['account'],
|
tags: ['account'],
|
||||||
|
@ -53,6 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private renoteMutingsRepository: RenoteMutingsRepository,
|
private renoteMutingsRepository: RenoteMutingsRepository,
|
||||||
|
|
||||||
private getterService: GetterService,
|
private getterService: GetterService,
|
||||||
|
private userRenoteMutingService: UserRenoteMutingService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const muter = me;
|
const muter = me;
|
||||||
|
@ -79,9 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete mute
|
// Delete mute
|
||||||
await this.renoteMutingsRepository.delete({
|
await this.userRenoteMutingService.unmute([exist]);
|
||||||
id: exist.id,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { DI } from '@/di-symbols.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -74,6 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const userIdsWhoBlockingMe = me ? await this.cacheService.userBlockedCache.fetch(me.id) : new Set<string>();
|
||||||
const iAmModerator = me ? await this.roleService.isModerator(me) : false; // Moderators can see reactions of all users
|
const iAmModerator = me ? await this.roleService.isModerator(me) : false; // Moderators can see reactions of all users
|
||||||
if (!iAmModerator) {
|
if (!iAmModerator) {
|
||||||
const user = await this.cacheService.findUserById(ps.userId);
|
const user = await this.cacheService.findUserById(ps.userId);
|
||||||
|
@ -85,8 +87,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
if ((me == null || me.id !== ps.userId) && !profile.publicReactions) {
|
if ((me == null || me.id !== ps.userId) && !profile.publicReactions) {
|
||||||
throw new ApiError(meta.errors.reactionsNotPublic);
|
throw new ApiError(meta.errors.reactionsNotPublic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// early return if me is blocked by requesting user
|
||||||
|
if (userIdsWhoBlockingMe.has(ps.userId)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userIdsWhoMeMuting = me ? await this.cacheService.userMutingsCache.fetch(me.id) : new Set<string>();
|
||||||
|
|
||||||
const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'),
|
const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'),
|
||||||
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||||
.andWhere('reaction.userId = :userId', { userId: ps.userId })
|
.andWhere('reaction.userId = :userId', { userId: ps.userId })
|
||||||
|
@ -94,9 +103,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
this.queryService.generateVisibilityQuery(query, me);
|
this.queryService.generateVisibilityQuery(query, me);
|
||||||
|
|
||||||
const reactions = await query
|
const reactions = (await query
|
||||||
.limit(ps.limit)
|
.limit(ps.limit)
|
||||||
.getMany();
|
.getMany()).filter(reaction => {
|
||||||
|
if (reaction.note?.userId === ps.userId) return true; // we can see reactions to note of requesting user
|
||||||
|
if (me && isUserRelated(reaction.note, userIdsWhoBlockingMe)) return false;
|
||||||
|
if (me && isUserRelated(reaction.note, userIdsWhoMeMuting)) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
return await this.noteReactionEntityService.packMany(reactions, me, { withNote: true });
|
return await this.noteReactionEntityService.packMany(reactions, me, { withNote: true });
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,15 +3,9 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Brackets } from 'typeorm';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import type { UsersRepository, FollowingsRepository } from '@/models/_.js';
|
|
||||||
import type { Config } from '@/config.js';
|
|
||||||
import type { MiUser } from '@/models/User.js';
|
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserSearchService } from '@/core/UserSearchService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
|
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['users'],
|
tags: ['users'],
|
||||||
|
@ -49,89 +43,16 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
private userSearchService: UserSearchService,
|
||||||
private config: Config,
|
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
|
||||||
private usersRepository: UsersRepository,
|
|
||||||
|
|
||||||
@Inject(DI.followingsRepository)
|
|
||||||
private followingsRepository: FollowingsRepository,
|
|
||||||
|
|
||||||
private userEntityService: UserEntityService,
|
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, (ps, me) => {
|
||||||
const setUsernameAndHostQuery = (query = this.usersRepository.createQueryBuilder('user')) => {
|
return this.userSearchService.search({
|
||||||
if (ps.username) {
|
username: ps.username,
|
||||||
query.andWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.username.toLowerCase()) + '%' });
|
host: ps.host,
|
||||||
}
|
}, {
|
||||||
|
limit: ps.limit,
|
||||||
if (ps.host) {
|
detail: ps.detail,
|
||||||
if (ps.host === this.config.hostname || ps.host === '.') {
|
}, me);
|
||||||
query.andWhere('user.host IS NULL');
|
|
||||||
} else {
|
|
||||||
query.andWhere('user.host LIKE :host', {
|
|
||||||
host: sqlLikeEscape(ps.host.toLowerCase()) + '%',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return query;
|
|
||||||
};
|
|
||||||
|
|
||||||
const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日
|
|
||||||
|
|
||||||
let users: MiUser[] = [];
|
|
||||||
|
|
||||||
if (me) {
|
|
||||||
const followingQuery = this.followingsRepository.createQueryBuilder('following')
|
|
||||||
.select('following.followeeId')
|
|
||||||
.where('following.followerId = :followerId', { followerId: me.id });
|
|
||||||
|
|
||||||
const query = setUsernameAndHostQuery()
|
|
||||||
.andWhere(`user.id IN (${ followingQuery.getQuery() })`)
|
|
||||||
.andWhere('user.id != :meId', { meId: me.id })
|
|
||||||
.andWhere('user.isSuspended = FALSE')
|
|
||||||
.andWhere(new Brackets(qb => {
|
|
||||||
qb
|
|
||||||
.where('user.updatedAt IS NULL')
|
|
||||||
.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
|
|
||||||
}));
|
|
||||||
|
|
||||||
query.setParameters(followingQuery.getParameters());
|
|
||||||
|
|
||||||
users = await query
|
|
||||||
.orderBy('user.usernameLower', 'ASC')
|
|
||||||
.limit(ps.limit)
|
|
||||||
.getMany();
|
|
||||||
|
|
||||||
if (users.length < ps.limit) {
|
|
||||||
const otherQuery = setUsernameAndHostQuery()
|
|
||||||
.andWhere(`user.id NOT IN (${ followingQuery.getQuery() })`)
|
|
||||||
.andWhere('user.isSuspended = FALSE')
|
|
||||||
.andWhere('user.updatedAt IS NOT NULL');
|
|
||||||
|
|
||||||
otherQuery.setParameters(followingQuery.getParameters());
|
|
||||||
|
|
||||||
const otherUsers = await otherQuery
|
|
||||||
.orderBy('user.updatedAt', 'DESC')
|
|
||||||
.limit(ps.limit - users.length)
|
|
||||||
.getMany();
|
|
||||||
|
|
||||||
users = users.concat(otherUsers);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const query = setUsernameAndHostQuery()
|
|
||||||
.andWhere('user.isSuspended = FALSE')
|
|
||||||
.andWhere('user.updatedAt IS NOT NULL');
|
|
||||||
|
|
||||||
users = await query
|
|
||||||
.orderBy('user.updatedAt', 'DESC')
|
|
||||||
.limit(ps.limit - users.length)
|
|
||||||
.getMany();
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.userEntityService.packMany(users, me, { schema: ps.detail ? 'UserDetailed' : 'UserLite' });
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ export class OpenApiServerService {
|
||||||
public createServer(fastify: FastifyInstance, _options: FastifyPluginOptions, done: (err?: Error) => void) {
|
public createServer(fastify: FastifyInstance, _options: FastifyPluginOptions, done: (err?: Error) => void) {
|
||||||
fastify.get('/api-doc', async (_request, reply) => {
|
fastify.get('/api-doc', async (_request, reply) => {
|
||||||
reply.header('Cache-Control', 'public, max-age=86400');
|
reply.header('Cache-Control', 'public, max-age=86400');
|
||||||
return await reply.sendFile('/redoc.html', staticAssets);
|
return await reply.sendFile('/api-doc.html', staticAssets);
|
||||||
});
|
});
|
||||||
fastify.get('/api.json', (_request, reply) => {
|
fastify.get('/api.json', (_request, reply) => {
|
||||||
reply.header('Cache-Control', 'public, max-age=600');
|
reply.header('Cache-Control', 'public, max-age=600');
|
||||||
|
|
|
@ -15,7 +15,6 @@ export function genOpenapiSpec(config: Config, includeSelfRef = false) {
|
||||||
info: {
|
info: {
|
||||||
version: config.version,
|
version: config.version,
|
||||||
title: 'Misskey API',
|
title: 'Misskey API',
|
||||||
'x-logo': { url: '/static-assets/api-doc.png' },
|
|
||||||
},
|
},
|
||||||
|
|
||||||
externalDocs: {
|
externalDocs: {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { CacheService } from '@/core/CacheService.js';
|
||||||
import { MiFollowing, MiUserProfile } from '@/models/_.js';
|
import { MiFollowing, MiUserProfile } from '@/models/_.js';
|
||||||
import type { StreamEventEmitter, GlobalEvents } from '@/core/GlobalEventService.js';
|
import type { StreamEventEmitter, GlobalEvents } from '@/core/GlobalEventService.js';
|
||||||
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
|
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
|
||||||
|
import type { JsonObject } from '@/misc/json-value.js';
|
||||||
import type { ChannelsService } from './ChannelsService.js';
|
import type { ChannelsService } from './ChannelsService.js';
|
||||||
import type { EventEmitter } from 'events';
|
import type { EventEmitter } from 'events';
|
||||||
import type Channel from './channel.js';
|
import type Channel from './channel.js';
|
||||||
|
@ -28,7 +29,7 @@ export default class Connection {
|
||||||
private wsConnection: WebSocket.WebSocket;
|
private wsConnection: WebSocket.WebSocket;
|
||||||
public subscriber: StreamEventEmitter;
|
public subscriber: StreamEventEmitter;
|
||||||
private channels: Channel[] = [];
|
private channels: Channel[] = [];
|
||||||
private subscribingNotes: any = {};
|
private subscribingNotes: Partial<Record<string, number>> = {};
|
||||||
private cachedNotes: Packed<'Note'>[] = [];
|
private cachedNotes: Packed<'Note'>[] = [];
|
||||||
public userProfile: MiUserProfile | null = null;
|
public userProfile: MiUserProfile | null = null;
|
||||||
public following: Record<string, Pick<MiFollowing, 'withReplies'> | undefined> = {};
|
public following: Record<string, Pick<MiFollowing, 'withReplies'> | undefined> = {};
|
||||||
|
@ -101,7 +102,7 @@ export default class Connection {
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
private async onWsConnectionMessage(data: WebSocket.RawData) {
|
private async onWsConnectionMessage(data: WebSocket.RawData) {
|
||||||
let obj: Record<string, any>;
|
let obj: JsonObject;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
obj = JSON.parse(data.toString());
|
obj = JSON.parse(data.toString());
|
||||||
|
@ -111,6 +112,8 @@ export default class Connection {
|
||||||
|
|
||||||
const { type, body } = obj;
|
const { type, body } = obj;
|
||||||
|
|
||||||
|
if (typeof body !== 'object' || body === null || Array.isArray(body)) return;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'readNotification': this.onReadNotification(body); break;
|
case 'readNotification': this.onReadNotification(body); break;
|
||||||
case 'subNote': this.onSubscribeNote(body); break;
|
case 'subNote': this.onSubscribeNote(body); break;
|
||||||
|
@ -151,7 +154,7 @@ export default class Connection {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private readNote(body: any) {
|
private readNote(body: JsonObject) {
|
||||||
const id = body.id;
|
const id = body.id;
|
||||||
|
|
||||||
const note = this.cachedNotes.find(n => n.id === id);
|
const note = this.cachedNotes.find(n => n.id === id);
|
||||||
|
@ -163,7 +166,7 @@ export default class Connection {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private onReadNotification(payload: any) {
|
private onReadNotification(payload: JsonObject) {
|
||||||
this.notificationService.readAllNotification(this.user!.id);
|
this.notificationService.readAllNotification(this.user!.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,16 +174,14 @@ export default class Connection {
|
||||||
* 投稿購読要求時
|
* 投稿購読要求時
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
private onSubscribeNote(payload: any) {
|
private onSubscribeNote(payload: JsonObject) {
|
||||||
if (!payload.id) return;
|
if (!payload.id || typeof payload.id !== 'string') return;
|
||||||
|
|
||||||
if (this.subscribingNotes[payload.id] == null) {
|
const current = this.subscribingNotes[payload.id] ?? 0;
|
||||||
this.subscribingNotes[payload.id] = 0;
|
const updated = current + 1;
|
||||||
}
|
this.subscribingNotes[payload.id] = updated;
|
||||||
|
|
||||||
this.subscribingNotes[payload.id]++;
|
if (updated === 1) {
|
||||||
|
|
||||||
if (this.subscribingNotes[payload.id] === 1) {
|
|
||||||
this.subscriber.on(`noteStream:${payload.id}`, this.onNoteStreamMessage);
|
this.subscriber.on(`noteStream:${payload.id}`, this.onNoteStreamMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,11 +190,14 @@ export default class Connection {
|
||||||
* 投稿購読解除要求時
|
* 投稿購読解除要求時
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
private onUnsubscribeNote(payload: any) {
|
private onUnsubscribeNote(payload: JsonObject) {
|
||||||
if (!payload.id) return;
|
if (!payload.id || typeof payload.id !== 'string') return;
|
||||||
|
|
||||||
this.subscribingNotes[payload.id]--;
|
const current = this.subscribingNotes[payload.id];
|
||||||
if (this.subscribingNotes[payload.id] <= 0) {
|
if (current == null) return;
|
||||||
|
const updated = current - 1;
|
||||||
|
this.subscribingNotes[payload.id] = updated;
|
||||||
|
if (updated <= 0) {
|
||||||
delete this.subscribingNotes[payload.id];
|
delete this.subscribingNotes[payload.id];
|
||||||
this.subscriber.off(`noteStream:${payload.id}`, this.onNoteStreamMessage);
|
this.subscriber.off(`noteStream:${payload.id}`, this.onNoteStreamMessage);
|
||||||
}
|
}
|
||||||
|
@ -212,17 +216,22 @@ export default class Connection {
|
||||||
* チャンネル接続要求時
|
* チャンネル接続要求時
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
private onChannelConnectRequested(payload: any) {
|
private onChannelConnectRequested(payload: JsonObject) {
|
||||||
const { channel, id, params, pong } = payload;
|
const { channel, id, params, pong } = payload;
|
||||||
this.connectChannel(id, params, channel, pong);
|
if (typeof id !== 'string') return;
|
||||||
|
if (typeof channel !== 'string') return;
|
||||||
|
if (typeof pong !== 'boolean' && typeof pong !== 'undefined' && pong !== null) return;
|
||||||
|
if (typeof params !== 'undefined' && (typeof params !== 'object' || params === null || Array.isArray(params))) return;
|
||||||
|
this.connectChannel(id, params, channel, pong ?? undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* チャンネル切断要求時
|
* チャンネル切断要求時
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
private onChannelDisconnectRequested(payload: any) {
|
private onChannelDisconnectRequested(payload: JsonObject) {
|
||||||
const { id } = payload;
|
const { id } = payload;
|
||||||
|
if (typeof id !== 'string') return;
|
||||||
this.disconnectChannel(id);
|
this.disconnectChannel(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,7 +239,7 @@ export default class Connection {
|
||||||
* クライアントにメッセージ送信
|
* クライアントにメッセージ送信
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public sendMessageToWs(type: string, payload: any) {
|
public sendMessageToWs(type: string, payload: JsonObject) {
|
||||||
this.wsConnection.send(JSON.stringify({
|
this.wsConnection.send(JSON.stringify({
|
||||||
type: type,
|
type: type,
|
||||||
body: payload,
|
body: payload,
|
||||||
|
@ -241,7 +250,7 @@ export default class Connection {
|
||||||
* チャンネルに接続
|
* チャンネルに接続
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public connectChannel(id: string, params: any, channel: string, pong = false) {
|
public connectChannel(id: string, params: JsonObject | undefined, channel: string, pong = false) {
|
||||||
const channelService = this.channelsService.getChannelService(channel);
|
const channelService = this.channelsService.getChannelService(channel);
|
||||||
|
|
||||||
if (channelService.requireCredential && this.user == null) {
|
if (channelService.requireCredential && this.user == null) {
|
||||||
|
@ -288,7 +297,11 @@ export default class Connection {
|
||||||
* @param data メッセージ
|
* @param data メッセージ
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
private onChannelMessageRequested(data: any) {
|
private onChannelMessageRequested(data: JsonObject) {
|
||||||
|
if (typeof data.id !== 'string') return;
|
||||||
|
if (typeof data.type !== 'string') return;
|
||||||
|
if (typeof data.body === 'undefined') return;
|
||||||
|
|
||||||
const channel = this.channels.find(c => c.id === data.id);
|
const channel = this.channels.find(c => c.id === data.id);
|
||||||
if (channel != null && channel.onMessage != null) {
|
if (channel != null && channel.onMessage != null) {
|
||||||
channel.onMessage(data.type, data.body);
|
channel.onMessage(data.type, data.body);
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { isInstanceMuted } from '@/misc/is-instance-muted.js';
|
||||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
import type { JsonObject, JsonValue } from '@/misc/json-value.js';
|
||||||
import type Connection from './Connection.js';
|
import type Connection from './Connection.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -81,10 +82,12 @@ export default abstract class Channel {
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public send(payload: { type: string, body: JsonValue }): void
|
||||||
|
public send(type: string, payload: JsonValue): void
|
||||||
@bindThis
|
@bindThis
|
||||||
public send(typeOrPayload: any, payload?: any) {
|
public send(typeOrPayload: { type: string, body: JsonValue } | string, payload?: JsonValue) {
|
||||||
const type = payload === undefined ? typeOrPayload.type : typeOrPayload;
|
const type = payload === undefined ? (typeOrPayload as { type: string, body: JsonValue }).type : (typeOrPayload as string);
|
||||||
const body = payload === undefined ? typeOrPayload.body : payload;
|
const body = payload === undefined ? (typeOrPayload as { type: string, body: JsonValue }).body : payload;
|
||||||
|
|
||||||
this.connection.sendMessageToWs('channel', {
|
this.connection.sendMessageToWs('channel', {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
@ -93,11 +96,11 @@ export default abstract class Channel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract init(params: any): void;
|
public abstract init(params: JsonObject): void;
|
||||||
|
|
||||||
public dispose?(): void;
|
public dispose?(): void;
|
||||||
|
|
||||||
public onMessage?(type: string, body: any): void;
|
public onMessage?(type: string, body: JsonValue): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MiChannelService<T extends boolean> = {
|
export type MiChannelService<T extends boolean> = {
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import type { JsonObject } from '@/misc/json-value.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
class AdminChannel extends Channel {
|
class AdminChannel extends Channel {
|
||||||
|
@ -14,7 +15,7 @@ class AdminChannel extends Channel {
|
||||||
public static kind = 'read:admin:stream';
|
public static kind = 'read:admin:stream';
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async init(params: any) {
|
public async init(params: JsonObject) {
|
||||||
// Subscribe admin stream
|
// Subscribe admin stream
|
||||||
this.subscriber.on(`adminStream:${this.user!.id}`, data => {
|
this.subscriber.on(`adminStream:${this.user!.id}`, data => {
|
||||||
this.send(data);
|
this.send(data);
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { Injectable } from '@nestjs/common';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||||
|
import type { JsonObject } from '@/misc/json-value.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
class AntennaChannel extends Channel {
|
class AntennaChannel extends Channel {
|
||||||
|
@ -27,8 +28,9 @@ class AntennaChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async init(params: any) {
|
public async init(params: JsonObject) {
|
||||||
this.antennaId = params.antennaId as string;
|
if (typeof params.antennaId !== 'string') return;
|
||||||
|
this.antennaId = params.antennaId;
|
||||||
|
|
||||||
// Subscribe stream
|
// Subscribe stream
|
||||||
this.subscriber.on(`antennaStream:${this.antennaId}`, this.onEvent);
|
this.subscriber.on(`antennaStream:${this.antennaId}`, this.onEvent);
|
||||||
|
|
|
@ -8,6 +8,7 @@ import type { Packed } from '@/misc/json-schema.js';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||||
|
import type { JsonObject } from '@/misc/json-value.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
class ChannelChannel extends Channel {
|
class ChannelChannel extends Channel {
|
||||||
|
@ -27,8 +28,9 @@ class ChannelChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async init(params: any) {
|
public async init(params: JsonObject) {
|
||||||
this.channelId = params.channelId as string;
|
if (typeof params.channelId !== 'string') return;
|
||||||
|
this.channelId = params.channelId;
|
||||||
|
|
||||||
// Subscribe stream
|
// Subscribe stream
|
||||||
this.subscriber.on('notesStream', this.onNote);
|
this.subscriber.on('notesStream', this.onNote);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import type { JsonObject } from '@/misc/json-value.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
class DriveChannel extends Channel {
|
class DriveChannel extends Channel {
|
||||||
|
@ -14,7 +15,7 @@ class DriveChannel extends Channel {
|
||||||
public static kind = 'read:account';
|
public static kind = 'read:account';
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async init(params: any) {
|
public async init(params: JsonObject) {
|
||||||
// Subscribe drive stream
|
// Subscribe drive stream
|
||||||
this.subscriber.on(`driveStream:${this.user!.id}`, data => {
|
this.subscriber.on(`driveStream:${this.user!.id}`, data => {
|
||||||
this.send(data);
|
this.send(data);
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||||
|
import type { JsonObject } from '@/misc/json-value.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
class GlobalTimelineChannel extends Channel {
|
class GlobalTimelineChannel extends Channel {
|
||||||
|
@ -32,12 +33,12 @@ class GlobalTimelineChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async init(params: any) {
|
public async init(params: JsonObject) {
|
||||||
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
|
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
|
||||||
if (!policies.gtlAvailable) return;
|
if (!policies.gtlAvailable) return;
|
||||||
|
|
||||||
this.withRenotes = params.withRenotes ?? true;
|
this.withRenotes = !!(params.withRenotes ?? true);
|
||||||
this.withFiles = params.withFiles ?? false;
|
this.withFiles = !!(params.withFiles ?? false);
|
||||||
|
|
||||||
// Subscribe events
|
// Subscribe events
|
||||||
this.subscriber.on('notesStream', this.onNote);
|
this.subscriber.on('notesStream', this.onNote);
|
||||||
|
|
|
@ -9,6 +9,7 @@ import type { Packed } from '@/misc/json-schema.js';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||||
|
import type { JsonObject } from '@/misc/json-value.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
class HashtagChannel extends Channel {
|
class HashtagChannel extends Channel {
|
||||||
|
@ -28,11 +29,11 @@ class HashtagChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async init(params: any) {
|
public async init(params: JsonObject) {
|
||||||
|
if (!Array.isArray(params.q)) return;
|
||||||
|
if (!params.q.every(x => Array.isArray(x) && x.every(y => typeof y === 'string'))) return;
|
||||||
this.q = params.q;
|
this.q = params.q;
|
||||||
|
|
||||||
if (this.q == null) return;
|
|
||||||
|
|
||||||
// Subscribe stream
|
// Subscribe stream
|
||||||
this.subscriber.on('notesStream', this.onNote);
|
this.subscriber.on('notesStream', this.onNote);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import type { Packed } from '@/misc/json-schema.js';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||||
|
import type { JsonObject } from '@/misc/json-value.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
class HomeTimelineChannel extends Channel {
|
class HomeTimelineChannel extends Channel {
|
||||||
|
@ -29,9 +30,9 @@ class HomeTimelineChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async init(params: any) {
|
public async init(params: JsonObject) {
|
||||||
this.withRenotes = params.withRenotes ?? true;
|
this.withRenotes = !!(params.withRenotes ?? true);
|
||||||
this.withFiles = params.withFiles ?? false;
|
this.withFiles = !!(params.withFiles ?? false);
|
||||||
|
|
||||||
this.subscriber.on('notesStream', this.onNote);
|
this.subscriber.on('notesStream', this.onNote);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||||
|
import type { JsonObject } from '@/misc/json-value.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
class HybridTimelineChannel extends Channel {
|
class HybridTimelineChannel extends Channel {
|
||||||
|
@ -34,13 +35,13 @@ class HybridTimelineChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async init(params: any): Promise<void> {
|
public async init(params: JsonObject): Promise<void> {
|
||||||
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
|
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
|
||||||
if (!policies.ltlAvailable) return;
|
if (!policies.ltlAvailable) return;
|
||||||
|
|
||||||
this.withRenotes = params.withRenotes ?? true;
|
this.withRenotes = !!(params.withRenotes ?? true);
|
||||||
this.withReplies = params.withReplies ?? false;
|
this.withReplies = !!(params.withReplies ?? false);
|
||||||
this.withFiles = params.withFiles ?? false;
|
this.withFiles = !!(params.withFiles ?? false);
|
||||||
|
|
||||||
// Subscribe events
|
// Subscribe events
|
||||||
this.subscriber.on('notesStream', this.onNote);
|
this.subscriber.on('notesStream', this.onNote);
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { isQuotePacked, isRenotePacked } from '@/misc/is-renote.js';
|
import { isQuotePacked, isRenotePacked } from '@/misc/is-renote.js';
|
||||||
|
import type { JsonObject } from '@/misc/json-value.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
class LocalTimelineChannel extends Channel {
|
class LocalTimelineChannel extends Channel {
|
||||||
|
@ -33,13 +34,13 @@ class LocalTimelineChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async init(params: any) {
|
public async init(params: JsonObject) {
|
||||||
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
|
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
|
||||||
if (!policies.ltlAvailable) return;
|
if (!policies.ltlAvailable) return;
|
||||||
|
|
||||||
this.withRenotes = params.withRenotes ?? true;
|
this.withRenotes = !!(params.withRenotes ?? true);
|
||||||
this.withReplies = params.withReplies ?? false;
|
this.withReplies = !!(params.withReplies ?? false);
|
||||||
this.withFiles = params.withFiles ?? false;
|
this.withFiles = !!(params.withFiles ?? false);
|
||||||
|
|
||||||
// Subscribe events
|
// Subscribe events
|
||||||
this.subscriber.on('notesStream', this.onNote);
|
this.subscriber.on('notesStream', this.onNote);
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { Injectable } from '@nestjs/common';
|
||||||
import { isInstanceMuted, isUserFromMutedInstance } from '@/misc/is-instance-muted.js';
|
import { isInstanceMuted, isUserFromMutedInstance } from '@/misc/is-instance-muted.js';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import type { JsonObject } from '@/misc/json-value.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
class MainChannel extends Channel {
|
class MainChannel extends Channel {
|
||||||
|
@ -25,7 +26,7 @@ class MainChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async init(params: any) {
|
public async init(params: JsonObject) {
|
||||||
// Subscribe main stream channel
|
// Subscribe main stream channel
|
||||||
this.subscriber.on(`mainStream:${this.user!.id}`, async data => {
|
this.subscriber.on(`mainStream:${this.user!.id}`, async data => {
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
import Xev from 'xev';
|
import Xev from 'xev';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import type { JsonObject, JsonValue } from '@/misc/json-value.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
const ev = new Xev();
|
const ev = new Xev();
|
||||||
|
@ -22,19 +23,22 @@ class QueueStatsChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async init(params: any) {
|
public async init(params: JsonObject) {
|
||||||
ev.addListener('queueStats', this.onStats);
|
ev.addListener('queueStats', this.onStats);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private onStats(stats: any) {
|
private onStats(stats: JsonObject) {
|
||||||
this.send('stats', stats);
|
this.send('stats', stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public onMessage(type: string, body: any) {
|
public onMessage(type: string, body: JsonValue) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'requestLog':
|
case 'requestLog':
|
||||||
|
if (typeof body !== 'object' || body === null || Array.isArray(body)) return;
|
||||||
|
if (typeof body.id !== 'string') return;
|
||||||
|
if (typeof body.length !== 'number') return;
|
||||||
ev.once(`queueStatsLog:${body.id}`, statsLog => {
|
ev.once(`queueStatsLog:${body.id}`, statsLog => {
|
||||||
this.send('statsLog', statsLog);
|
this.send('statsLog', statsLog);
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { DI } from '@/di-symbols.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { ReversiService } from '@/core/ReversiService.js';
|
import { ReversiService } from '@/core/ReversiService.js';
|
||||||
import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
|
import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
|
||||||
|
import type { JsonObject, JsonValue } from '@/misc/json-value.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
class ReversiGameChannel extends Channel {
|
class ReversiGameChannel extends Channel {
|
||||||
|
@ -28,25 +29,41 @@ class ReversiGameChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async init(params: any) {
|
public async init(params: JsonObject) {
|
||||||
this.gameId = params.gameId as string;
|
if (typeof params.gameId !== 'string') return;
|
||||||
|
this.gameId = params.gameId;
|
||||||
|
|
||||||
this.subscriber.on(`reversiGameStream:${this.gameId}`, this.send);
|
this.subscriber.on(`reversiGameStream:${this.gameId}`, this.send);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public onMessage(type: string, body: any) {
|
public onMessage(type: string, body: JsonValue) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'ready': this.ready(body); break;
|
case 'ready':
|
||||||
case 'updateSettings': this.updateSettings(body.key, body.value); break;
|
if (typeof body !== 'boolean') return;
|
||||||
case 'cancel': this.cancelGame(); break;
|
this.ready(body);
|
||||||
case 'putStone': this.putStone(body.pos, body.id); break;
|
break;
|
||||||
|
case 'updateSettings':
|
||||||
|
if (typeof body !== 'object' || body === null || Array.isArray(body)) return;
|
||||||
|
if (typeof body.key !== 'string') return;
|
||||||
|
if (typeof body.value !== 'object' || body.value === null || Array.isArray(body.value)) return;
|
||||||
|
this.updateSettings(body.key, body.value);
|
||||||
|
break;
|
||||||
|
case 'cancel':
|
||||||
|
this.cancelGame();
|
||||||
|
break;
|
||||||
|
case 'putStone':
|
||||||
|
if (typeof body !== 'object' || body === null || Array.isArray(body)) return;
|
||||||
|
if (typeof body.pos !== 'number') return;
|
||||||
|
if (typeof body.id !== 'string') return;
|
||||||
|
this.putStone(body.pos, body.id);
|
||||||
|
break;
|
||||||
case 'claimTimeIsUp': this.claimTimeIsUp(); break;
|
case 'claimTimeIsUp': this.claimTimeIsUp(); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async updateSettings(key: string, value: any) {
|
private async updateSettings(key: string, value: JsonObject) {
|
||||||
if (this.user == null) return;
|
if (this.user == null) return;
|
||||||
|
|
||||||
this.reversiService.updateSettings(this.gameId!, this.user, key, value);
|
this.reversiService.updateSettings(this.gameId!, this.user, key, value);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import type { JsonObject } from '@/misc/json-value.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
class ReversiChannel extends Channel {
|
class ReversiChannel extends Channel {
|
||||||
|
@ -21,7 +22,7 @@ class ReversiChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async init(params: any) {
|
public async init(params: JsonObject) {
|
||||||
this.subscriber.on(`reversiStream:${this.user!.id}`, this.send);
|
this.subscriber.on(`reversiStream:${this.user!.id}`, this.send);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||||
|
import type { JsonObject } from '@/misc/json-value.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
class RoleTimelineChannel extends Channel {
|
class RoleTimelineChannel extends Channel {
|
||||||
|
@ -28,8 +29,9 @@ class RoleTimelineChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async init(params: any) {
|
public async init(params: JsonObject) {
|
||||||
this.roleId = params.roleId as string;
|
if (typeof params.roleId !== 'string') return;
|
||||||
|
this.roleId = params.roleId;
|
||||||
|
|
||||||
this.subscriber.on(`roleTimelineStream:${this.roleId}`, this.onEvent);
|
this.subscriber.on(`roleTimelineStream:${this.roleId}`, this.onEvent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
import Xev from 'xev';
|
import Xev from 'xev';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import type { JsonObject, JsonValue } from '@/misc/json-value.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
const ev = new Xev();
|
const ev = new Xev();
|
||||||
|
@ -22,19 +23,20 @@ class ServerStatsChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async init(params: any) {
|
public async init(params: JsonObject) {
|
||||||
ev.addListener('serverStats', this.onStats);
|
ev.addListener('serverStats', this.onStats);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private onStats(stats: any) {
|
private onStats(stats: JsonObject) {
|
||||||
this.send('stats', stats);
|
this.send('stats', stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public onMessage(type: string, body: any) {
|
public onMessage(type: string, body: JsonValue) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'requestLog':
|
case 'requestLog':
|
||||||
|
if (typeof body !== 'object' || body === null || Array.isArray(body)) return;
|
||||||
ev.once(`serverStatsLog:${body.id}`, statsLog => {
|
ev.once(`serverStatsLog:${body.id}`, statsLog => {
|
||||||
this.send('statsLog', statsLog);
|
this.send('statsLog', statsLog);
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||||
|
import type { JsonObject } from '@/misc/json-value.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
class UserListChannel extends Channel {
|
class UserListChannel extends Channel {
|
||||||
|
@ -36,10 +37,11 @@ class UserListChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async init(params: any) {
|
public async init(params: JsonObject) {
|
||||||
this.listId = params.listId as string;
|
if (typeof params.listId !== 'string') return;
|
||||||
this.withFiles = params.withFiles ?? false;
|
this.listId = params.listId;
|
||||||
this.withRenotes = params.withRenotes ?? true;
|
this.withFiles = !!(params.withFiles ?? false);
|
||||||
|
this.withRenotes = !!(params.withRenotes ?? true);
|
||||||
|
|
||||||
// Check existence and owner
|
// Check existence and owner
|
||||||
const listExist = await this.userListsRepository.exists({
|
const listExist = await this.userListsRepository.exists({
|
||||||
|
|
|
@ -206,7 +206,7 @@ describe('2要素認証', () => {
|
||||||
username,
|
username,
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(usersShowResponse.status, 200);
|
assert.strictEqual(usersShowResponse.status, 200);
|
||||||
assert.strictEqual(usersShowResponse.body.twoFactorEnabled, true);
|
assert.strictEqual((usersShowResponse.body as unknown as { twoFactorEnabled: boolean }).twoFactorEnabled, true);
|
||||||
|
|
||||||
const signinResponse = await api('signin', {
|
const signinResponse = await api('signin', {
|
||||||
...signinParam(),
|
...signinParam(),
|
||||||
|
@ -248,7 +248,7 @@ describe('2要素認証', () => {
|
||||||
keyName,
|
keyName,
|
||||||
credentialId,
|
credentialId,
|
||||||
creationOptions: registerKeyResponse.body,
|
creationOptions: registerKeyResponse.body,
|
||||||
}) as any, alice);
|
} as any) as any, alice);
|
||||||
assert.strictEqual(keyDoneResponse.status, 200);
|
assert.strictEqual(keyDoneResponse.status, 200);
|
||||||
assert.strictEqual(keyDoneResponse.body.id, credentialId.toString('base64url'));
|
assert.strictEqual(keyDoneResponse.body.id, credentialId.toString('base64url'));
|
||||||
assert.strictEqual(keyDoneResponse.body.name, keyName);
|
assert.strictEqual(keyDoneResponse.body.name, keyName);
|
||||||
|
@ -257,22 +257,22 @@ describe('2要素認証', () => {
|
||||||
username,
|
username,
|
||||||
});
|
});
|
||||||
assert.strictEqual(usersShowResponse.status, 200);
|
assert.strictEqual(usersShowResponse.status, 200);
|
||||||
assert.strictEqual(usersShowResponse.body.securityKeys, true);
|
assert.strictEqual((usersShowResponse.body as unknown as { securityKeys: boolean }).securityKeys, true);
|
||||||
|
|
||||||
const signinResponse = await api('signin', {
|
const signinResponse = await api('signin', {
|
||||||
...signinParam(),
|
...signinParam(),
|
||||||
});
|
});
|
||||||
assert.strictEqual(signinResponse.status, 200);
|
assert.strictEqual(signinResponse.status, 200);
|
||||||
assert.strictEqual(signinResponse.body.i, undefined);
|
assert.strictEqual(signinResponse.body.i, undefined);
|
||||||
assert.notEqual(signinResponse.body.challenge, undefined);
|
assert.notEqual((signinResponse.body as unknown as { challenge: unknown | undefined }).challenge, undefined);
|
||||||
assert.notEqual(signinResponse.body.allowCredentials, undefined);
|
assert.notEqual((signinResponse.body as unknown as { allowCredentials: unknown | undefined }).allowCredentials, undefined);
|
||||||
assert.strictEqual(signinResponse.body.allowCredentials[0].id, credentialId.toString('base64url'));
|
assert.strictEqual((signinResponse.body as unknown as { allowCredentials: {id: string}[] }).allowCredentials[0].id, credentialId.toString('base64url'));
|
||||||
|
|
||||||
const signinResponse2 = await api('signin', signinWithSecurityKeyParam({
|
const signinResponse2 = await api('signin', signinWithSecurityKeyParam({
|
||||||
keyName,
|
keyName,
|
||||||
credentialId,
|
credentialId,
|
||||||
requestOptions: signinResponse.body,
|
requestOptions: signinResponse.body,
|
||||||
}));
|
} as any));
|
||||||
assert.strictEqual(signinResponse2.status, 200);
|
assert.strictEqual(signinResponse2.status, 200);
|
||||||
assert.notEqual(signinResponse2.body.i, undefined);
|
assert.notEqual(signinResponse2.body.i, undefined);
|
||||||
|
|
||||||
|
@ -307,7 +307,7 @@ describe('2要素認証', () => {
|
||||||
keyName,
|
keyName,
|
||||||
credentialId,
|
credentialId,
|
||||||
creationOptions: registerKeyResponse.body,
|
creationOptions: registerKeyResponse.body,
|
||||||
}) as any, alice);
|
} as any) as any, alice);
|
||||||
assert.strictEqual(keyDoneResponse.status, 200);
|
assert.strictEqual(keyDoneResponse.status, 200);
|
||||||
|
|
||||||
const passwordLessResponse = await api('i/2fa/password-less', {
|
const passwordLessResponse = await api('i/2fa/password-less', {
|
||||||
|
@ -319,7 +319,7 @@ describe('2要素認証', () => {
|
||||||
username,
|
username,
|
||||||
});
|
});
|
||||||
assert.strictEqual(usersShowResponse.status, 200);
|
assert.strictEqual(usersShowResponse.status, 200);
|
||||||
assert.strictEqual(usersShowResponse.body.usePasswordLessLogin, true);
|
assert.strictEqual((usersShowResponse.body as unknown as { usePasswordLessLogin: boolean }).usePasswordLessLogin, true);
|
||||||
|
|
||||||
const signinResponse = await api('signin', {
|
const signinResponse = await api('signin', {
|
||||||
...signinParam(),
|
...signinParam(),
|
||||||
|
@ -333,7 +333,7 @@ describe('2要素認証', () => {
|
||||||
keyName,
|
keyName,
|
||||||
credentialId,
|
credentialId,
|
||||||
requestOptions: signinResponse.body,
|
requestOptions: signinResponse.body,
|
||||||
}),
|
} as any),
|
||||||
password: '',
|
password: '',
|
||||||
});
|
});
|
||||||
assert.strictEqual(signinResponse2.status, 200);
|
assert.strictEqual(signinResponse2.status, 200);
|
||||||
|
@ -370,7 +370,7 @@ describe('2要素認証', () => {
|
||||||
keyName,
|
keyName,
|
||||||
credentialId,
|
credentialId,
|
||||||
creationOptions: registerKeyResponse.body,
|
creationOptions: registerKeyResponse.body,
|
||||||
}) as any, alice);
|
} as any) as any, alice);
|
||||||
assert.strictEqual(keyDoneResponse.status, 200);
|
assert.strictEqual(keyDoneResponse.status, 200);
|
||||||
|
|
||||||
const renamedKey = 'other-key';
|
const renamedKey = 'other-key';
|
||||||
|
@ -383,6 +383,7 @@ describe('2要素認証', () => {
|
||||||
const iResponse = await api('i', {
|
const iResponse = await api('i', {
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(iResponse.status, 200);
|
assert.strictEqual(iResponse.status, 200);
|
||||||
|
assert.ok(iResponse.body.securityKeysList);
|
||||||
const securityKeys = iResponse.body.securityKeysList.filter((s: { id: string; }) => s.id === credentialId.toString('base64url'));
|
const securityKeys = iResponse.body.securityKeysList.filter((s: { id: string; }) => s.id === credentialId.toString('base64url'));
|
||||||
assert.strictEqual(securityKeys.length, 1);
|
assert.strictEqual(securityKeys.length, 1);
|
||||||
assert.strictEqual(securityKeys[0].name, renamedKey);
|
assert.strictEqual(securityKeys[0].name, renamedKey);
|
||||||
|
@ -419,13 +420,14 @@ describe('2要素認証', () => {
|
||||||
keyName,
|
keyName,
|
||||||
credentialId,
|
credentialId,
|
||||||
creationOptions: registerKeyResponse.body,
|
creationOptions: registerKeyResponse.body,
|
||||||
}) as any, alice);
|
} as any) as any, alice);
|
||||||
assert.strictEqual(keyDoneResponse.status, 200);
|
assert.strictEqual(keyDoneResponse.status, 200);
|
||||||
|
|
||||||
// テストの実行順によっては複数残ってるので全部消す
|
// テストの実行順によっては複数残ってるので全部消す
|
||||||
const iResponse = await api('i', {
|
const iResponse = await api('i', {
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(iResponse.status, 200);
|
assert.strictEqual(iResponse.status, 200);
|
||||||
|
assert.ok(iResponse.body.securityKeysList);
|
||||||
for (const key of iResponse.body.securityKeysList) {
|
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),
|
token: otpToken(registerResponse.body.secret),
|
||||||
|
@ -439,7 +441,7 @@ describe('2要素認証', () => {
|
||||||
username,
|
username,
|
||||||
});
|
});
|
||||||
assert.strictEqual(usersShowResponse.status, 200);
|
assert.strictEqual(usersShowResponse.status, 200);
|
||||||
assert.strictEqual(usersShowResponse.body.securityKeys, false);
|
assert.strictEqual((usersShowResponse.body as unknown as { securityKeys: boolean }).securityKeys, false);
|
||||||
|
|
||||||
const signinResponse = await api('signin', {
|
const signinResponse = await api('signin', {
|
||||||
...signinParam(),
|
...signinParam(),
|
||||||
|
@ -470,7 +472,7 @@ describe('2要素認証', () => {
|
||||||
username,
|
username,
|
||||||
});
|
});
|
||||||
assert.strictEqual(usersShowResponse.status, 200);
|
assert.strictEqual(usersShowResponse.status, 200);
|
||||||
assert.strictEqual(usersShowResponse.body.twoFactorEnabled, true);
|
assert.strictEqual((usersShowResponse.body as unknown as { twoFactorEnabled: boolean }).twoFactorEnabled, true);
|
||||||
|
|
||||||
const unregisterResponse = await api('i/2fa/unregister', {
|
const unregisterResponse = await api('i/2fa/unregister', {
|
||||||
token: otpToken(registerResponse.body.secret),
|
token: otpToken(registerResponse.body.secret),
|
||||||
|
|
|
@ -410,21 +410,21 @@ describe('API visibility', () => {
|
||||||
test('[HTL] public-post が 自分が見れる', async () => {
|
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);
|
assert.strictEqual(res.status, 200);
|
||||||
const notes = res.body.filter((n: any) => n.id === pub.id);
|
const notes = res.body.filter(n => n.id === pub.id);
|
||||||
assert.strictEqual(notes[0].text, 'x');
|
assert.strictEqual(notes[0].text, 'x');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('[HTL] public-post が 非フォロワーから見れない', async () => {
|
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);
|
assert.strictEqual(res.status, 200);
|
||||||
const notes = res.body.filter((n: any) => n.id === pub.id);
|
const notes = res.body.filter(n => n.id === pub.id);
|
||||||
assert.strictEqual(notes.length, 0);
|
assert.strictEqual(notes.length, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('[HTL] followers-post が フォロワーから見れる', async () => {
|
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);
|
assert.strictEqual(res.status, 200);
|
||||||
const notes = res.body.filter((n: any) => n.id === fol.id);
|
const notes = res.body.filter(n => n.id === fol.id);
|
||||||
assert.strictEqual(notes[0].text, 'x');
|
assert.strictEqual(notes[0].text, 'x');
|
||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
@ -433,21 +433,21 @@ describe('API visibility', () => {
|
||||||
test('[replies] followers-reply が フォロワーから見れる', async () => {
|
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);
|
assert.strictEqual(res.status, 200);
|
||||||
const notes = res.body.filter((n: any) => n.id === folR.id);
|
const notes = res.body.filter(n => n.id === folR.id);
|
||||||
assert.strictEqual(notes[0].text, 'x');
|
assert.strictEqual(notes[0].text, 'x');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('[replies] followers-reply が 非フォロワー (リプライ先ではない) から見れない', async () => {
|
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);
|
assert.strictEqual(res.status, 200);
|
||||||
const notes = res.body.filter((n: any) => n.id === folR.id);
|
const notes = res.body.filter(n => n.id === folR.id);
|
||||||
assert.strictEqual(notes.length, 0);
|
assert.strictEqual(notes.length, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('[replies] followers-reply が 非フォロワー (リプライ先である) から見れる', async () => {
|
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);
|
assert.strictEqual(res.status, 200);
|
||||||
const notes = res.body.filter((n: any) => n.id === folR.id);
|
const notes = res.body.filter(n => n.id === folR.id);
|
||||||
assert.strictEqual(notes[0].text, 'x');
|
assert.strictEqual(notes[0].text, 'x');
|
||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
@ -456,14 +456,14 @@ describe('API visibility', () => {
|
||||||
test('[mentions] followers-reply が 非フォロワー (リプライ先である) から見れる', async () => {
|
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);
|
assert.strictEqual(res.status, 200);
|
||||||
const notes = res.body.filter((n: any) => n.id === folR.id);
|
const notes = res.body.filter(n => n.id === folR.id);
|
||||||
assert.strictEqual(notes[0].text, 'x');
|
assert.strictEqual(notes[0].text, 'x');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('[mentions] followers-mention が 非フォロワー (メンション先である) から見れる', async () => {
|
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);
|
assert.strictEqual(res.status, 200);
|
||||||
const notes = res.body.filter((n: any) => n.id === folM.id);
|
const notes = res.body.filter(n => n.id === folM.id);
|
||||||
assert.strictEqual(notes[0].text, '@target x');
|
assert.strictEqual(notes[0].text, '@target x');
|
||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { api, post, signup } from '../utils.js';
|
import { api, castAsError, post, signup } from '../utils.js';
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('Block', () => {
|
describe('Block', () => {
|
||||||
|
@ -33,7 +33,7 @@ describe('Block', () => {
|
||||||
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.status, 400);
|
||||||
assert.strictEqual(res.body.error.id, 'c4ab57cc-4e41-45e9-bfd9-584f61e35ce0');
|
assert.strictEqual(castAsError(res.body).error.id, 'c4ab57cc-4e41-45e9-bfd9-584f61e35ce0');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ブロックされているユーザーにリアクションできない', async () => {
|
test('ブロックされているユーザーにリアクションできない', async () => {
|
||||||
|
@ -42,7 +42,8 @@ describe('Block', () => {
|
||||||
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.status, 400);
|
||||||
assert.strictEqual(res.body.error.id, '20ef5475-9f38-4e4c-bd33-de6d979498ec');
|
assert.ok(res.body);
|
||||||
|
assert.strictEqual(castAsError(res.body).error.id, '20ef5475-9f38-4e4c-bd33-de6d979498ec');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ブロックされているユーザーに返信できない', async () => {
|
test('ブロックされているユーザーに返信できない', async () => {
|
||||||
|
@ -51,7 +52,8 @@ describe('Block', () => {
|
||||||
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.status, 400);
|
||||||
assert.strictEqual(res.body.error.id, 'b390d7e1-8a5e-46ed-b625-06271cafd3d3');
|
assert.ok(res.body);
|
||||||
|
assert.strictEqual(castAsError(res.body).error.id, 'b390d7e1-8a5e-46ed-b625-06271cafd3d3');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ブロックされているユーザーのノートをRenoteできない', async () => {
|
test('ブロックされているユーザーのノートをRenoteできない', async () => {
|
||||||
|
@ -60,7 +62,7 @@ describe('Block', () => {
|
||||||
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.status, 400);
|
||||||
assert.strictEqual(res.body.error.id, 'b390d7e1-8a5e-46ed-b625-06271cafd3d3');
|
assert.strictEqual(castAsError(res.body).error.id, 'b390d7e1-8a5e-46ed-b625-06271cafd3d3');
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: ユーザーリストに入れられないテスト
|
// TODO: ユーザーリストに入れられないテスト
|
||||||
|
|
|
@ -79,14 +79,14 @@ describe('クリップ', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteClip = async (parameters: Misskey.entities.ClipsDeleteRequest, request: Partial<ApiRequest<'clips/delete'>> = {}): Promise<void> => {
|
const deleteClip = async (parameters: Misskey.entities.ClipsDeleteRequest, request: Partial<ApiRequest<'clips/delete'>> = {}): Promise<void> => {
|
||||||
return await successfulApiCall({
|
await successfulApiCall({
|
||||||
endpoint: 'clips/delete',
|
endpoint: 'clips/delete',
|
||||||
parameters,
|
parameters,
|
||||||
user: alice,
|
user: alice,
|
||||||
...request,
|
...request,
|
||||||
}, {
|
}, {
|
||||||
status: 204,
|
status: 204,
|
||||||
}) as any as void;
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const show = async (parameters: Misskey.entities.ClipsShowRequest, request: Partial<ApiRequest<'clips/show'>> = {}): Promise<Misskey.entities.Clip> => {
|
const show = async (parameters: Misskey.entities.ClipsShowRequest, request: Partial<ApiRequest<'clips/show'>> = {}): Promise<Misskey.entities.Clip> => {
|
||||||
|
@ -454,25 +454,25 @@ describe('クリップ', () => {
|
||||||
let aliceClip: Misskey.entities.Clip;
|
let aliceClip: Misskey.entities.Clip;
|
||||||
|
|
||||||
const favorite = async (parameters: Misskey.entities.ClipsFavoriteRequest, request: Partial<ApiRequest<'clips/favorite'>> = {}): Promise<void> => {
|
const favorite = async (parameters: Misskey.entities.ClipsFavoriteRequest, request: Partial<ApiRequest<'clips/favorite'>> = {}): Promise<void> => {
|
||||||
return successfulApiCall({
|
await successfulApiCall({
|
||||||
endpoint: 'clips/favorite',
|
endpoint: 'clips/favorite',
|
||||||
parameters,
|
parameters,
|
||||||
user: alice,
|
user: alice,
|
||||||
...request,
|
...request,
|
||||||
}, {
|
}, {
|
||||||
status: 204,
|
status: 204,
|
||||||
}) as any as void;
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const unfavorite = async (parameters: Misskey.entities.ClipsUnfavoriteRequest, request: Partial<ApiRequest<'clips/unfavorite'>> = {}): Promise<void> => {
|
const unfavorite = async (parameters: Misskey.entities.ClipsUnfavoriteRequest, request: Partial<ApiRequest<'clips/unfavorite'>> = {}): Promise<void> => {
|
||||||
return successfulApiCall({
|
await successfulApiCall({
|
||||||
endpoint: 'clips/unfavorite',
|
endpoint: 'clips/unfavorite',
|
||||||
parameters,
|
parameters,
|
||||||
user: alice,
|
user: alice,
|
||||||
...request,
|
...request,
|
||||||
}, {
|
}, {
|
||||||
status: 204,
|
status: 204,
|
||||||
}) as any as void;
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const myFavorites = async (request: Partial<ApiRequest<'clips/my-favorites'>> = {}): Promise<Misskey.entities.Clip[]> => {
|
const myFavorites = async (request: Partial<ApiRequest<'clips/my-favorites'>> = {}): Promise<Misskey.entities.Clip[]> => {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import * as assert from 'assert';
|
||||||
// https://github.com/node-fetch/node-fetch/pull/1664
|
// https://github.com/node-fetch/node-fetch/pull/1664
|
||||||
import { Blob } from 'node-fetch';
|
import { Blob } from 'node-fetch';
|
||||||
import { MiUser } from '@/models/_.js';
|
import { MiUser } from '@/models/_.js';
|
||||||
import { api, initTestDb, post, signup, simpleGet, uploadFile } from '../utils.js';
|
import { api, castAsError, initTestDb, post, signup, simpleGet, uploadFile } from '../utils.js';
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('Endpoints', () => {
|
describe('Endpoints', () => {
|
||||||
|
@ -164,7 +164,7 @@ describe('Endpoints', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.id, alice.id);
|
assert.strictEqual((res.body as unknown as { id: string }).id, alice.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ユーザーが存在しなかったら怒る', async () => {
|
test('ユーザーが存在しなかったら怒る', async () => {
|
||||||
|
@ -285,7 +285,8 @@ describe('Endpoints', () => {
|
||||||
}, alice);
|
}, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
assert.strictEqual(res.status, 400);
|
||||||
assert.strictEqual(res.body.error.code, 'CANNOT_REACT_TO_RENOTE');
|
assert.ok(res.body);
|
||||||
|
assert.strictEqual(castAsError(res.body).error.code, 'CANNOT_REACT_TO_RENOTE');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('引用にリアクションできる', async () => {
|
test('引用にリアクションできる', async () => {
|
||||||
|
@ -1063,7 +1064,7 @@ describe('Endpoints', () => {
|
||||||
userId: bob.id,
|
userId: bob.id,
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(res1.status, 204);
|
assert.strictEqual(res1.status, 204);
|
||||||
assert.strictEqual(res2.body?.memo, memo);
|
assert.strictEqual((res2.body as unknown as { memo: string })?.memo, memo);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('自分に関するメモを更新できる', async () => {
|
test('自分に関するメモを更新できる', async () => {
|
||||||
|
@ -1078,7 +1079,7 @@ describe('Endpoints', () => {
|
||||||
userId: alice.id,
|
userId: alice.id,
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(res1.status, 204);
|
assert.strictEqual(res1.status, 204);
|
||||||
assert.strictEqual(res2.body?.memo, memo);
|
assert.strictEqual((res2.body as unknown as { memo: string })?.memo, memo);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('メモを削除できる', async () => {
|
test('メモを削除できる', async () => {
|
||||||
|
@ -1099,7 +1100,7 @@ describe('Endpoints', () => {
|
||||||
}, alice);
|
}, alice);
|
||||||
|
|
||||||
// memoには常に文字列かnullが入っている(5cac151)
|
// memoには常に文字列かnullが入っている(5cac151)
|
||||||
assert.strictEqual(res.body.memo, null);
|
assert.strictEqual((res.body as unknown as { memo: string | null }).memo, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('メモは個人ごとに独立して保存される', async () => {
|
test('メモは個人ごとに独立して保存される', async () => {
|
||||||
|
@ -1126,8 +1127,8 @@ describe('Endpoints', () => {
|
||||||
}, carol),
|
}, carol),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert.strictEqual(resAlice.body.memo, memoAliceToBob);
|
assert.strictEqual((resAlice.body as unknown as { memo: string }).memo, memoAliceToBob);
|
||||||
assert.strictEqual(resCarol.body.memo, memoCarolToBob);
|
assert.strictEqual((resCarol.body as unknown as { memo: string }).memo, memoCarolToBob);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -61,14 +61,14 @@ describe('export-clips', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('basic export', async () => {
|
test('basic export', async () => {
|
||||||
let res = await api('clips/create', {
|
const res1 = await api('clips/create', {
|
||||||
name: 'foo',
|
name: 'foo',
|
||||||
description: 'bar',
|
description: 'bar',
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res1.status, 200);
|
||||||
|
|
||||||
res = await api('i/export-clips', {}, alice);
|
const res2 = await api('i/export-clips', {}, alice);
|
||||||
assert.strictEqual(res.status, 204);
|
assert.strictEqual(res2.status, 204);
|
||||||
|
|
||||||
const exported = await pollFirstDriveFile();
|
const exported = await pollFirstDriveFile();
|
||||||
assert.strictEqual(exported[0].name, 'foo');
|
assert.strictEqual(exported[0].name, 'foo');
|
||||||
|
@ -77,7 +77,7 @@ describe('export-clips', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('export with notes', async () => {
|
test('export with notes', async () => {
|
||||||
let res = await api('clips/create', {
|
const res = await api('clips/create', {
|
||||||
name: 'foo',
|
name: 'foo',
|
||||||
description: 'bar',
|
description: 'bar',
|
||||||
}, alice);
|
}, alice);
|
||||||
|
@ -96,15 +96,15 @@ describe('export-clips', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const note of [note1, note2]) {
|
for (const note of [note1, note2]) {
|
||||||
res = await api('clips/add-note', {
|
const res2 = await api('clips/add-note', {
|
||||||
clipId: clip.id,
|
clipId: clip.id,
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(res.status, 204);
|
assert.strictEqual(res2.status, 204);
|
||||||
}
|
}
|
||||||
|
|
||||||
res = await api('i/export-clips', {}, alice);
|
const res3 = await api('i/export-clips', {}, alice);
|
||||||
assert.strictEqual(res.status, 204);
|
assert.strictEqual(res3.status, 204);
|
||||||
|
|
||||||
const exported = await pollFirstDriveFile();
|
const exported = await pollFirstDriveFile();
|
||||||
assert.strictEqual(exported[0].name, 'foo');
|
assert.strictEqual(exported[0].name, 'foo');
|
||||||
|
@ -116,19 +116,19 @@ describe('export-clips', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('multiple clips', async () => {
|
test('multiple clips', async () => {
|
||||||
let res = await api('clips/create', {
|
const res1 = await api('clips/create', {
|
||||||
name: 'kawaii',
|
name: 'kawaii',
|
||||||
description: 'kawaii',
|
description: 'kawaii',
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res1.status, 200);
|
||||||
const clip1 = res.body;
|
const clip1 = res1.body;
|
||||||
|
|
||||||
res = await api('clips/create', {
|
const res2 = await api('clips/create', {
|
||||||
name: 'yuri',
|
name: 'yuri',
|
||||||
description: 'yuri',
|
description: 'yuri',
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res2.status, 200);
|
||||||
const clip2 = res.body;
|
const clip2 = res2.body;
|
||||||
|
|
||||||
const note1 = await post(alice, {
|
const note1 = await post(alice, {
|
||||||
text: 'baz1',
|
text: 'baz1',
|
||||||
|
@ -138,20 +138,26 @@ describe('export-clips', () => {
|
||||||
text: 'baz2',
|
text: 'baz2',
|
||||||
});
|
});
|
||||||
|
|
||||||
res = await api('clips/add-note', {
|
{
|
||||||
clipId: clip1.id,
|
const res = await api('clips/add-note', {
|
||||||
noteId: note1.id,
|
clipId: clip1.id,
|
||||||
}, alice);
|
noteId: note1.id,
|
||||||
assert.strictEqual(res.status, 204);
|
}, alice);
|
||||||
|
assert.strictEqual(res.status, 204);
|
||||||
|
}
|
||||||
|
|
||||||
res = await api('clips/add-note', {
|
{
|
||||||
clipId: clip2.id,
|
const res = await api('clips/add-note', {
|
||||||
noteId: note2.id,
|
clipId: clip2.id,
|
||||||
}, alice);
|
noteId: note2.id,
|
||||||
assert.strictEqual(res.status, 204);
|
}, alice);
|
||||||
|
assert.strictEqual(res.status, 204);
|
||||||
|
}
|
||||||
|
|
||||||
res = await api('i/export-clips', {}, alice);
|
{
|
||||||
assert.strictEqual(res.status, 204);
|
const res = await api('i/export-clips', {}, alice);
|
||||||
|
assert.strictEqual(res.status, 204);
|
||||||
|
}
|
||||||
|
|
||||||
const exported = await pollFirstDriveFile();
|
const exported = await pollFirstDriveFile();
|
||||||
assert.strictEqual(exported[0].name, 'kawaii');
|
assert.strictEqual(exported[0].name, 'kawaii');
|
||||||
|
@ -163,7 +169,7 @@ describe('export-clips', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Clipping other user\'s note', async () => {
|
test('Clipping other user\'s note', async () => {
|
||||||
let res = await api('clips/create', {
|
const res = await api('clips/create', {
|
||||||
name: 'kawaii',
|
name: 'kawaii',
|
||||||
description: 'kawaii',
|
description: 'kawaii',
|
||||||
}, alice);
|
}, alice);
|
||||||
|
@ -175,14 +181,14 @@ describe('export-clips', () => {
|
||||||
visibility: 'followers',
|
visibility: 'followers',
|
||||||
});
|
});
|
||||||
|
|
||||||
res = await api('clips/add-note', {
|
const res2 = await api('clips/add-note', {
|
||||||
clipId: clip.id,
|
clipId: clip.id,
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(res.status, 204);
|
assert.strictEqual(res2.status, 204);
|
||||||
|
|
||||||
res = await api('i/export-clips', {}, alice);
|
const res3 = await api('i/export-clips', {}, alice);
|
||||||
assert.strictEqual(res.status, 204);
|
assert.strictEqual(res3.status, 204);
|
||||||
|
|
||||||
const exported = await pollFirstDriveFile();
|
const exported = await pollFirstDriveFile();
|
||||||
assert.strictEqual(exported[0].name, 'kawaii');
|
assert.strictEqual(exported[0].name, 'kawaii');
|
||||||
|
|
|
@ -13,14 +13,14 @@ import { loadConfig } from '@/config.js';
|
||||||
import { MiRepository, MiUser, UsersRepository, miRepository } from '@/models/_.js';
|
import { MiRepository, MiUser, UsersRepository, miRepository } from '@/models/_.js';
|
||||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||||
import { jobQueue } from '@/boot/common.js';
|
import { jobQueue } from '@/boot/common.js';
|
||||||
import { api, initTestDb, signup, successfulApiCall, uploadFile } from '../utils.js';
|
import { api, castAsError, initTestDb, signup, successfulApiCall, uploadFile } from '../utils.js';
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('Account Move', () => {
|
describe('Account Move', () => {
|
||||||
let jq: INestApplicationContext;
|
let jq: INestApplicationContext;
|
||||||
let url: URL;
|
let url: URL;
|
||||||
|
|
||||||
let root: any;
|
let root: misskey.entities.SignupResponse;
|
||||||
let alice: misskey.entities.SignupResponse;
|
let alice: misskey.entities.SignupResponse;
|
||||||
let bob: misskey.entities.SignupResponse;
|
let bob: misskey.entities.SignupResponse;
|
||||||
let carol: misskey.entities.SignupResponse;
|
let carol: misskey.entities.SignupResponse;
|
||||||
|
@ -93,8 +93,8 @@ describe('Account Move', () => {
|
||||||
}, bob);
|
}, bob);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
assert.strictEqual(res.status, 400);
|
||||||
assert.strictEqual(res.body.error.code, 'NO_SUCH_USER');
|
assert.strictEqual(castAsError(res.body).error.code, 'NO_SUCH_USER');
|
||||||
assert.strictEqual(res.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
|
assert.strictEqual(castAsError(res.body).error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Unable to add duplicated aliases to alsoKnownAs', async () => {
|
test('Unable to add duplicated aliases to alsoKnownAs', async () => {
|
||||||
|
@ -103,8 +103,8 @@ describe('Account Move', () => {
|
||||||
}, bob);
|
}, bob);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
assert.strictEqual(res.status, 400);
|
||||||
assert.strictEqual(res.body.error.code, 'INVALID_PARAM');
|
assert.strictEqual(castAsError(res.body).error.code, 'INVALID_PARAM');
|
||||||
assert.strictEqual(res.body.error.id, '3d81ceae-475f-4600-b2a8-2bc116157532');
|
assert.strictEqual(castAsError(res.body).error.id, '3d81ceae-475f-4600-b2a8-2bc116157532');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Unable to add itself', async () => {
|
test('Unable to add itself', async () => {
|
||||||
|
@ -113,8 +113,8 @@ describe('Account Move', () => {
|
||||||
}, bob);
|
}, bob);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
assert.strictEqual(res.status, 400);
|
||||||
assert.strictEqual(res.body.error.code, 'FORBIDDEN_TO_SET_YOURSELF');
|
assert.strictEqual(castAsError(res.body).error.code, 'FORBIDDEN_TO_SET_YOURSELF');
|
||||||
assert.strictEqual(res.body.error.id, '25c90186-4ab0-49c8-9bba-a1fa6c202ba4');
|
assert.strictEqual(castAsError(res.body).error.id, '25c90186-4ab0-49c8-9bba-a1fa6c202ba4');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Unable to add a nonexisting local account to alsoKnownAs', async () => {
|
test('Unable to add a nonexisting local account to alsoKnownAs', async () => {
|
||||||
|
@ -123,16 +123,16 @@ describe('Account Move', () => {
|
||||||
}, bob);
|
}, bob);
|
||||||
|
|
||||||
assert.strictEqual(res1.status, 400);
|
assert.strictEqual(res1.status, 400);
|
||||||
assert.strictEqual(res1.body.error.code, 'NO_SUCH_USER');
|
assert.strictEqual(castAsError(res1.body).error.code, 'NO_SUCH_USER');
|
||||||
assert.strictEqual(res1.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
|
assert.strictEqual(castAsError(res1.body).error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
|
||||||
|
|
||||||
const res2 = await api('i/update', {
|
const res2 = await api('i/update', {
|
||||||
alsoKnownAs: ['@alice', 'nonexist'],
|
alsoKnownAs: ['@alice', 'nonexist'],
|
||||||
}, bob);
|
}, bob);
|
||||||
|
|
||||||
assert.strictEqual(res2.status, 400);
|
assert.strictEqual(res2.status, 400);
|
||||||
assert.strictEqual(res2.body.error.code, 'NO_SUCH_USER');
|
assert.strictEqual(castAsError(res2.body).error.code, 'NO_SUCH_USER');
|
||||||
assert.strictEqual(res2.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
|
assert.strictEqual(castAsError(res2.body).error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Able to add two existing local account to alsoKnownAs', async () => {
|
test('Able to add two existing local account to alsoKnownAs', async () => {
|
||||||
|
@ -241,8 +241,8 @@ describe('Account Move', () => {
|
||||||
}, root);
|
}, root);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
assert.strictEqual(res.status, 400);
|
||||||
assert.strictEqual(res.body.error.code, 'NOT_ROOT_FORBIDDEN');
|
assert.strictEqual(castAsError(res.body).error.code, 'NOT_ROOT_FORBIDDEN');
|
||||||
assert.strictEqual(res.body.error.id, '4362e8dc-731f-4ad8-a694-be2a88922a24');
|
assert.strictEqual(castAsError(res.body).error.id, '4362e8dc-731f-4ad8-a694-be2a88922a24');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Unable to move to a nonexisting local account', async () => {
|
test('Unable to move to a nonexisting local account', async () => {
|
||||||
|
@ -251,8 +251,8 @@ describe('Account Move', () => {
|
||||||
}, alice);
|
}, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
assert.strictEqual(res.status, 400);
|
||||||
assert.strictEqual(res.body.error.code, 'NO_SUCH_USER');
|
assert.strictEqual(castAsError(res.body).error.code, 'NO_SUCH_USER');
|
||||||
assert.strictEqual(res.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
|
assert.strictEqual(castAsError(res.body).error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Unable to move if alsoKnownAs is invalid', async () => {
|
test('Unable to move if alsoKnownAs is invalid', async () => {
|
||||||
|
@ -261,8 +261,8 @@ describe('Account Move', () => {
|
||||||
}, alice);
|
}, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
assert.strictEqual(res.status, 400);
|
||||||
assert.strictEqual(res.body.error.code, 'DESTINATION_ACCOUNT_FORBIDS');
|
assert.strictEqual(castAsError(res.body).error.code, 'DESTINATION_ACCOUNT_FORBIDS');
|
||||||
assert.strictEqual(res.body.error.id, 'b5c90186-4ab0-49c8-9bba-a1f766282ba4');
|
assert.strictEqual(castAsError(res.body).error.id, 'b5c90186-4ab0-49c8-9bba-a1f766282ba4');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Relationships have been properly migrated', async () => {
|
test('Relationships have been properly migrated', async () => {
|
||||||
|
@ -279,36 +279,44 @@ describe('Account Move', () => {
|
||||||
userId: alice.id,
|
userId: alice.id,
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(aliceFollowings.status, 200);
|
assert.strictEqual(aliceFollowings.status, 200);
|
||||||
|
assert.ok(aliceFollowings);
|
||||||
assert.strictEqual(aliceFollowings.body.length, 3);
|
assert.strictEqual(aliceFollowings.body.length, 3);
|
||||||
|
|
||||||
const carolFollowings = await api('users/following', {
|
const carolFollowings = await api('users/following', {
|
||||||
userId: carol.id,
|
userId: carol.id,
|
||||||
}, carol);
|
}, carol);
|
||||||
assert.strictEqual(carolFollowings.status, 200);
|
assert.strictEqual(carolFollowings.status, 200);
|
||||||
|
assert.ok(carolFollowings);
|
||||||
assert.strictEqual(carolFollowings.body.length, 2);
|
assert.strictEqual(carolFollowings.body.length, 2);
|
||||||
assert.strictEqual(carolFollowings.body[0].followeeId, bob.id);
|
assert.strictEqual(carolFollowings.body[0].followeeId, bob.id);
|
||||||
assert.strictEqual(carolFollowings.body[1].followeeId, alice.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.status, 200);
|
||||||
|
assert.ok(blockings);
|
||||||
assert.strictEqual(blockings.body.length, 2);
|
assert.strictEqual(blockings.body.length, 2);
|
||||||
assert.strictEqual(blockings.body[0].blockeeId, bob.id);
|
assert.strictEqual(blockings.body[0].blockeeId, bob.id);
|
||||||
assert.strictEqual(blockings.body[1].blockeeId, alice.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.status, 200);
|
||||||
|
assert.ok(mutings);
|
||||||
assert.strictEqual(mutings.body.length, 2);
|
assert.strictEqual(mutings.body.length, 2);
|
||||||
assert.strictEqual(mutings.body[0].muteeId, bob.id);
|
assert.strictEqual(mutings.body[0].muteeId, bob.id);
|
||||||
assert.strictEqual(mutings.body[1].muteeId, alice.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.status, 200);
|
||||||
|
assert.ok(rootLists);
|
||||||
|
assert.ok(rootLists.body[0].userIds);
|
||||||
assert.strictEqual(rootLists.body[0].userIds.length, 2);
|
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 === bob.id));
|
||||||
assert.ok(rootLists.body[0].userIds.find((id: string) => id === alice.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.status, 200);
|
||||||
|
assert.ok(eveLists);
|
||||||
|
assert.ok(eveLists.body[0].userIds);
|
||||||
assert.strictEqual(eveLists.body[0].userIds.length, 1);
|
assert.strictEqual(eveLists.body[0].userIds.length, 1);
|
||||||
assert.ok(eveLists.body[0].userIds.find((id: string) => id === bob.id));
|
assert.ok(eveLists.body[0].userIds.find((id: string) => id === bob.id));
|
||||||
});
|
});
|
||||||
|
@ -347,8 +355,8 @@ describe('Account Move', () => {
|
||||||
}, bob);
|
}, bob);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
assert.strictEqual(res.status, 400);
|
||||||
assert.strictEqual(res.body.error.code, 'DESTINATION_ACCOUNT_FORBIDS');
|
assert.strictEqual(castAsError(res.body).error.code, 'DESTINATION_ACCOUNT_FORBIDS');
|
||||||
assert.strictEqual(res.body.error.id, 'b5c90186-4ab0-49c8-9bba-a1f766282ba4');
|
assert.strictEqual(castAsError(res.body).error.id, 'b5c90186-4ab0-49c8-9bba-a1f766282ba4');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Follow and follower counts are properly adjusted', async () => {
|
test('Follow and follower counts are properly adjusted', async () => {
|
||||||
|
@ -419,8 +427,9 @@ describe('Account Move', () => {
|
||||||
] as const)('Prohibit access after moving: %s', async (endpoint) => {
|
] as const)('Prohibit access after moving: %s', async (endpoint) => {
|
||||||
const res = await api(endpoint, {}, alice);
|
const res = await api(endpoint, {}, alice);
|
||||||
assert.strictEqual(res.status, 403);
|
assert.strictEqual(res.status, 403);
|
||||||
assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED');
|
assert.ok(res.body);
|
||||||
assert.strictEqual(res.body.error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31');
|
assert.strictEqual(castAsError(res.body).error.code, 'YOUR_ACCOUNT_MOVED');
|
||||||
|
assert.strictEqual(castAsError(res.body).error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Prohibit access after moving: /antennas/update', async () => {
|
test('Prohibit access after moving: /antennas/update', async () => {
|
||||||
|
@ -438,16 +447,19 @@ describe('Account Move', () => {
|
||||||
}, alice);
|
}, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 403);
|
assert.strictEqual(res.status, 403);
|
||||||
assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED');
|
assert.ok(res.body);
|
||||||
assert.strictEqual(res.body.error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31');
|
assert.strictEqual(castAsError(res.body).error.code, 'YOUR_ACCOUNT_MOVED');
|
||||||
|
assert.strictEqual(castAsError(res.body).error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Prohibit access after moving: /drive/files/create', async () => {
|
test('Prohibit access after moving: /drive/files/create', async () => {
|
||||||
|
// FIXME: 一旦逃げておく
|
||||||
const res = await uploadFile(alice);
|
const res = await uploadFile(alice);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 403);
|
assert.strictEqual(res.status, 403);
|
||||||
assert.strictEqual((res.body! as any as { error: misskey.api.APIError }).error.code, 'YOUR_ACCOUNT_MOVED');
|
assert.ok(res.body);
|
||||||
assert.strictEqual((res.body! as any as { error: misskey.api.APIError }).error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31');
|
assert.strictEqual(castAsError(res.body).error.code, 'YOUR_ACCOUNT_MOVED');
|
||||||
|
assert.strictEqual(castAsError(res.body).error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Prohibit updating alsoKnownAs after moving', async () => {
|
test('Prohibit updating alsoKnownAs after moving', async () => {
|
||||||
|
@ -456,8 +468,8 @@ describe('Account Move', () => {
|
||||||
}, alice);
|
}, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 403);
|
assert.strictEqual(res.status, 403);
|
||||||
assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED');
|
assert.strictEqual(castAsError(res.body).error.code, 'YOUR_ACCOUNT_MOVED');
|
||||||
assert.strictEqual(res.body.error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31');
|
assert.strictEqual(castAsError(res.body).error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -47,8 +47,8 @@ describe('Mute', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ミュートしているユーザーからメンションされても、hasUnreadMentions が true にならない', async () => {
|
test('ミュートしているユーザーからメンションされても、hasUnreadMentions が true にならない', async () => {
|
||||||
|
@ -92,9 +92,9 @@ describe('Mute', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
|
assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('タイムラインにミュートしているユーザーの投稿のRenoteが含まれない', async () => {
|
test('タイムラインにミュートしているユーザーの投稿のRenoteが含まれない', async () => {
|
||||||
|
@ -108,9 +108,9 @@ describe('Mute', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
|
assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -124,8 +124,8 @@ describe('Mute', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
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 => 'userId' in notification && notification.userId === bob.id), true);
|
||||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('通知にミュートしているユーザーからのリプライが含まれない', async () => {
|
test('通知にミュートしているユーザーからのリプライが含まれない', async () => {
|
||||||
|
@ -138,8 +138,8 @@ describe('Mute', () => {
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
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 => 'userId' in notification && notification.userId === bob.id), true);
|
||||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('通知にミュートしているユーザーからのリプライが含まれない', async () => {
|
test('通知にミュートしているユーザーからのリプライが含まれない', async () => {
|
||||||
|
@ -152,8 +152,8 @@ describe('Mute', () => {
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
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 => 'userId' in notification && notification.userId === bob.id), true);
|
||||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('通知にミュートしているユーザーからの引用リノートが含まれない', async () => {
|
test('通知にミュートしているユーザーからの引用リノートが含まれない', async () => {
|
||||||
|
@ -166,8 +166,8 @@ describe('Mute', () => {
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
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 => 'userId' in notification && notification.userId === bob.id), true);
|
||||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('通知にミュートしているユーザーからのリノートが含まれない', async () => {
|
test('通知にミュートしているユーザーからのリノートが含まれない', async () => {
|
||||||
|
@ -180,8 +180,8 @@ describe('Mute', () => {
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
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 => 'userId' in notification && notification.userId === bob.id), true);
|
||||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('通知にミュートしているユーザーからのフォロー通知が含まれない', async () => {
|
test('通知にミュートしているユーザーからのフォロー通知が含まれない', async () => {
|
||||||
|
@ -193,8 +193,8 @@ describe('Mute', () => {
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
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 => 'userId' in notification && notification.userId === bob.id), true);
|
||||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||||
|
|
||||||
await api('following/delete', { userId: alice.id }, bob);
|
await api('following/delete', { userId: alice.id }, bob);
|
||||||
await api('following/delete', { userId: alice.id }, carol);
|
await api('following/delete', { userId: alice.id }, carol);
|
||||||
|
@ -210,8 +210,8 @@ describe('Mute', () => {
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
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 => 'userId' in notification && notification.userId === bob.id), true);
|
||||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||||
|
|
||||||
await api('following/delete', { userId: alice.id }, bob);
|
await api('following/delete', { userId: alice.id }, bob);
|
||||||
await api('following/delete', { userId: alice.id }, carol);
|
await api('following/delete', { userId: alice.id }, carol);
|
||||||
|
@ -228,8 +228,8 @@ describe('Mute', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
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 => 'userId' in notification && notification.userId === bob.id), true);
|
||||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||||
});
|
});
|
||||||
test('通知にミュートしているユーザーからのリプライが含まれない', async () => {
|
test('通知にミュートしているユーザーからのリプライが含まれない', async () => {
|
||||||
const aliceNote = await post(alice, { text: 'hi' });
|
const aliceNote = await post(alice, { text: 'hi' });
|
||||||
|
@ -241,8 +241,8 @@ describe('Mute', () => {
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
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 => 'userId' in notification && notification.userId === bob.id), true);
|
||||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('通知にミュートしているユーザーからのリプライが含まれない', async () => {
|
test('通知にミュートしているユーザーからのリプライが含まれない', async () => {
|
||||||
|
@ -255,8 +255,8 @@ describe('Mute', () => {
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
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 => 'userId' in notification && notification.userId === bob.id), true);
|
||||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('通知にミュートしているユーザーからの引用リノートが含まれない', async () => {
|
test('通知にミュートしているユーザーからの引用リノートが含まれない', async () => {
|
||||||
|
@ -269,8 +269,8 @@ describe('Mute', () => {
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
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 => 'userId' in notification && notification.userId === bob.id), true);
|
||||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('通知にミュートしているユーザーからのリノートが含まれない', async () => {
|
test('通知にミュートしているユーザーからのリノートが含まれない', async () => {
|
||||||
|
@ -283,8 +283,8 @@ describe('Mute', () => {
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
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 => 'userId' in notification && notification.userId === bob.id), true);
|
||||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('通知にミュートしているユーザーからのフォロー通知が含まれない', async () => {
|
test('通知にミュートしているユーザーからのフォロー通知が含まれない', async () => {
|
||||||
|
@ -296,8 +296,8 @@ describe('Mute', () => {
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
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 => 'userId' in notification && notification.userId === bob.id), true);
|
||||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||||
|
|
||||||
await api('following/delete', { userId: alice.id }, bob);
|
await api('following/delete', { userId: alice.id }, bob);
|
||||||
await api('following/delete', { userId: alice.id }, carol);
|
await api('following/delete', { userId: alice.id }, carol);
|
||||||
|
@ -313,8 +313,8 @@ describe('Mute', () => {
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
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 => 'userId' in notification && notification.userId === bob.id), true);
|
||||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,16 +3,18 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { Repository } from "typeorm";
|
||||||
|
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { MiNote } from '@/models/Note.js';
|
import { MiNote } from '@/models/Note.js';
|
||||||
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||||
import { api, initTestDb, post, role, signup, uploadFile, uploadUrl } from '../utils.js';
|
import { api, castAsError, initTestDb, post, role, signup, uploadFile, uploadUrl } from '../utils.js';
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('Note', () => {
|
describe('Note', () => {
|
||||||
let Notes: any;
|
let Notes: Repository<MiNote>;
|
||||||
|
|
||||||
let root: misskey.entities.SignupResponse;
|
let root: misskey.entities.SignupResponse;
|
||||||
let alice: misskey.entities.SignupResponse;
|
let alice: misskey.entities.SignupResponse;
|
||||||
|
@ -61,8 +63,8 @@ describe('Note', () => {
|
||||||
}, alice);
|
}, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
assert.strictEqual(res.status, 400);
|
||||||
assert.strictEqual(res.body.error.code, 'NO_SUCH_FILE');
|
assert.strictEqual(castAsError(res.body).error.code, 'NO_SUCH_FILE');
|
||||||
assert.strictEqual(res.body.error.id, 'b6992544-63e7-67f0-fa7f-32444b1b5306');
|
assert.strictEqual(castAsError(res.body).error.id, 'b6992544-63e7-67f0-fa7f-32444b1b5306');
|
||||||
}, 1000 * 10);
|
}, 1000 * 10);
|
||||||
|
|
||||||
test('存在しないファイルで怒られる', async () => {
|
test('存在しないファイルで怒られる', async () => {
|
||||||
|
@ -72,8 +74,8 @@ describe('Note', () => {
|
||||||
}, alice);
|
}, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
assert.strictEqual(res.status, 400);
|
||||||
assert.strictEqual(res.body.error.code, 'NO_SUCH_FILE');
|
assert.strictEqual(castAsError(res.body).error.code, 'NO_SUCH_FILE');
|
||||||
assert.strictEqual(res.body.error.id, 'b6992544-63e7-67f0-fa7f-32444b1b5306');
|
assert.strictEqual(castAsError(res.body).error.id, 'b6992544-63e7-67f0-fa7f-32444b1b5306');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('不正なファイルIDで怒られる', async () => {
|
test('不正なファイルIDで怒られる', async () => {
|
||||||
|
@ -81,8 +83,8 @@ describe('Note', () => {
|
||||||
fileIds: ['kyoppie'],
|
fileIds: ['kyoppie'],
|
||||||
}, alice);
|
}, alice);
|
||||||
assert.strictEqual(res.status, 400);
|
assert.strictEqual(res.status, 400);
|
||||||
assert.strictEqual(res.body.error.code, 'NO_SUCH_FILE');
|
assert.strictEqual(castAsError(res.body).error.code, 'NO_SUCH_FILE');
|
||||||
assert.strictEqual(res.body.error.id, 'b6992544-63e7-67f0-fa7f-32444b1b5306');
|
assert.strictEqual(castAsError(res.body).error.id, 'b6992544-63e7-67f0-fa7f-32444b1b5306');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('返信できる', async () => {
|
test('返信できる', async () => {
|
||||||
|
@ -101,6 +103,7 @@ describe('Note', () => {
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.createdNote.text, alicePost.text);
|
assert.strictEqual(res.body.createdNote.text, alicePost.text);
|
||||||
assert.strictEqual(res.body.createdNote.replyId, alicePost.replyId);
|
assert.strictEqual(res.body.createdNote.replyId, alicePost.replyId);
|
||||||
|
assert.ok(res.body.createdNote.reply);
|
||||||
assert.strictEqual(res.body.createdNote.reply.text, bobPost.text);
|
assert.strictEqual(res.body.createdNote.reply.text, bobPost.text);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -118,6 +121,7 @@ describe('Note', () => {
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.createdNote.renoteId, alicePost.renoteId);
|
assert.strictEqual(res.body.createdNote.renoteId, alicePost.renoteId);
|
||||||
|
assert.ok(res.body.createdNote.renote);
|
||||||
assert.strictEqual(res.body.createdNote.renote.text, bobPost.text);
|
assert.strictEqual(res.body.createdNote.renote.text, bobPost.text);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -137,6 +141,7 @@ describe('Note', () => {
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.createdNote.text, alicePost.text);
|
assert.strictEqual(res.body.createdNote.text, alicePost.text);
|
||||||
assert.strictEqual(res.body.createdNote.renoteId, alicePost.renoteId);
|
assert.strictEqual(res.body.createdNote.renoteId, alicePost.renoteId);
|
||||||
|
assert.ok(res.body.createdNote.renote);
|
||||||
assert.strictEqual(res.body.createdNote.renote.text, bobPost.text);
|
assert.strictEqual(res.body.createdNote.renote.text, bobPost.text);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -218,7 +223,7 @@ describe('Note', () => {
|
||||||
}, bob);
|
}, bob);
|
||||||
|
|
||||||
assert.strictEqual(bobReply.status, 400);
|
assert.strictEqual(bobReply.status, 400);
|
||||||
assert.strictEqual(bobReply.body.error.code, 'CANNOT_REPLY_TO_AN_INVISIBLE_NOTE');
|
assert.strictEqual(castAsError(bobReply.body).error.code, 'CANNOT_REPLY_TO_AN_INVISIBLE_NOTE');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('visibility: specifiedなノートに対してvisibility: specifiedで返信できる', async () => {
|
test('visibility: specifiedなノートに対してvisibility: specifiedで返信できる', async () => {
|
||||||
|
@ -256,7 +261,7 @@ describe('Note', () => {
|
||||||
}, bob);
|
}, bob);
|
||||||
|
|
||||||
assert.strictEqual(bobReply.status, 400);
|
assert.strictEqual(bobReply.status, 400);
|
||||||
assert.strictEqual(bobReply.body.error.code, 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY');
|
assert.strictEqual(castAsError(bobReply.body).error.code, 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('文字数ぎりぎりで怒られない', async () => {
|
test('文字数ぎりぎりで怒られない', async () => {
|
||||||
|
@ -333,6 +338,7 @@ describe('Note', () => {
|
||||||
assert.strictEqual(res.body.createdNote.text, post.text);
|
assert.strictEqual(res.body.createdNote.text, post.text);
|
||||||
|
|
||||||
const noteDoc = await Notes.findOneBy({ id: res.body.createdNote.id });
|
const noteDoc = await Notes.findOneBy({ id: res.body.createdNote.id });
|
||||||
|
assert.ok(noteDoc);
|
||||||
assert.deepStrictEqual(noteDoc.mentions, [bob.id]);
|
assert.deepStrictEqual(noteDoc.mentions, [bob.id]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -345,6 +351,7 @@ describe('Note', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||||
|
assert.ok(res.body.createdNote.files);
|
||||||
assert.strictEqual(res.body.createdNote.files.length, 1);
|
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);
|
||||||
});
|
});
|
||||||
|
@ -363,8 +370,9 @@ describe('Note', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
const myNote = res.body.find((note: { id: string; files: { id: string }[] }) => note.id === createdNote.body.createdNote.id);
|
const myNote = res.body.find(note => note.id === createdNote.body.createdNote.id);
|
||||||
assert.notEqual(myNote, null);
|
assert.ok(myNote);
|
||||||
|
assert.ok(myNote.files);
|
||||||
assert.strictEqual(myNote.files.length, 1);
|
assert.strictEqual(myNote.files.length, 1);
|
||||||
assert.strictEqual(myNote.files[0].id, file.body!.id);
|
assert.strictEqual(myNote.files[0].id, file.body!.id);
|
||||||
});
|
});
|
||||||
|
@ -389,7 +397,9 @@ describe('Note', () => {
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
const myNote = res.body.find((note: { id: string }) => note.id === renoted.body.createdNote.id);
|
const myNote = res.body.find((note: { id: string }) => note.id === renoted.body.createdNote.id);
|
||||||
assert.notEqual(myNote, null);
|
assert.ok(myNote);
|
||||||
|
assert.ok(myNote.renote);
|
||||||
|
assert.ok(myNote.renote.files);
|
||||||
assert.strictEqual(myNote.renote.files.length, 1);
|
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);
|
||||||
});
|
});
|
||||||
|
@ -415,7 +425,9 @@ describe('Note', () => {
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
const myNote = res.body.find((note: { id: string }) => note.id === reply.body.createdNote.id);
|
const myNote = res.body.find((note: { id: string }) => note.id === reply.body.createdNote.id);
|
||||||
assert.notEqual(myNote, null);
|
assert.ok(myNote);
|
||||||
|
assert.ok(myNote.reply);
|
||||||
|
assert.ok(myNote.reply.files);
|
||||||
assert.strictEqual(myNote.reply.files.length, 1);
|
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);
|
||||||
});
|
});
|
||||||
|
@ -446,7 +458,10 @@ describe('Note', () => {
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
const myNote = res.body.find((note: { id: string }) => note.id === renoted.body.createdNote.id);
|
const myNote = res.body.find((note: { id: string }) => note.id === renoted.body.createdNote.id);
|
||||||
assert.notEqual(myNote, null);
|
assert.ok(myNote);
|
||||||
|
assert.ok(myNote.renote);
|
||||||
|
assert.ok(myNote.renote.reply);
|
||||||
|
assert.ok(myNote.renote.reply.files);
|
||||||
assert.strictEqual(myNote.renote.reply.files.length, 1);
|
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);
|
||||||
});
|
});
|
||||||
|
@ -474,7 +489,7 @@ describe('Note', () => {
|
||||||
priority: 0,
|
priority: 0,
|
||||||
value: true,
|
value: true,
|
||||||
},
|
},
|
||||||
} as any,
|
},
|
||||||
}, root);
|
}, root);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
|
@ -498,7 +513,7 @@ describe('Note', () => {
|
||||||
}, alice);
|
}, alice);
|
||||||
|
|
||||||
assert.strictEqual(liftnsfw.status, 400);
|
assert.strictEqual(liftnsfw.status, 400);
|
||||||
assert.strictEqual(liftnsfw.body.error.code, 'RESTRICTED_BY_ROLE');
|
assert.strictEqual(castAsError(liftnsfw.body).error.code, 'RESTRICTED_BY_ROLE');
|
||||||
|
|
||||||
const oldaddnsfw = await api('drive/files/update', {
|
const oldaddnsfw = await api('drive/files/update', {
|
||||||
fileId: file.body!.id,
|
fileId: file.body!.id,
|
||||||
|
@ -710,7 +725,7 @@ describe('Note', () => {
|
||||||
}, alice);
|
}, alice);
|
||||||
|
|
||||||
assert.strictEqual(note1.status, 400);
|
assert.strictEqual(note1.status, 400);
|
||||||
assert.strictEqual(note1.body.error.code, 'CONTAINS_PROHIBITED_WORDS');
|
assert.strictEqual(castAsError(note1.body).error.code, 'CONTAINS_PROHIBITED_WORDS');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('禁止ワードを含む投稿はエラーになる (正規表現)', async () => {
|
test('禁止ワードを含む投稿はエラーになる (正規表現)', async () => {
|
||||||
|
@ -727,7 +742,7 @@ describe('Note', () => {
|
||||||
}, alice);
|
}, alice);
|
||||||
|
|
||||||
assert.strictEqual(note2.status, 400);
|
assert.strictEqual(note2.status, 400);
|
||||||
assert.strictEqual(note2.body.error.code, 'CONTAINS_PROHIBITED_WORDS');
|
assert.strictEqual(castAsError(note2.body).error.code, 'CONTAINS_PROHIBITED_WORDS');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('禁止ワードを含む投稿はエラーになる (スペースアンド)', async () => {
|
test('禁止ワードを含む投稿はエラーになる (スペースアンド)', async () => {
|
||||||
|
@ -744,7 +759,7 @@ describe('Note', () => {
|
||||||
}, alice);
|
}, alice);
|
||||||
|
|
||||||
assert.strictEqual(note2.status, 400);
|
assert.strictEqual(note2.status, 400);
|
||||||
assert.strictEqual(note2.body.error.code, 'CONTAINS_PROHIBITED_WORDS');
|
assert.strictEqual(castAsError(note2.body).error.code, 'CONTAINS_PROHIBITED_WORDS');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('禁止ワードを含んでるリモートノートもエラーになる', async () => {
|
test('禁止ワードを含んでるリモートノートもエラーになる', async () => {
|
||||||
|
@ -786,7 +801,7 @@ describe('Note', () => {
|
||||||
priority: 1,
|
priority: 1,
|
||||||
value: 0,
|
value: 0,
|
||||||
},
|
},
|
||||||
} as any,
|
},
|
||||||
}, root);
|
}, root);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
|
@ -807,7 +822,7 @@ describe('Note', () => {
|
||||||
}, alice);
|
}, alice);
|
||||||
|
|
||||||
assert.strictEqual(note.status, 400);
|
assert.strictEqual(note.status, 400);
|
||||||
assert.strictEqual(note.body.error.code, 'CONTAINS_TOO_MANY_MENTIONS');
|
assert.strictEqual(castAsError(note.body).error.code, 'CONTAINS_TOO_MANY_MENTIONS');
|
||||||
|
|
||||||
await api('admin/roles/unassign', {
|
await api('admin/roles/unassign', {
|
||||||
userId: alice.id,
|
userId: alice.id,
|
||||||
|
@ -840,7 +855,7 @@ describe('Note', () => {
|
||||||
priority: 1,
|
priority: 1,
|
||||||
value: 0,
|
value: 0,
|
||||||
},
|
},
|
||||||
} as any,
|
},
|
||||||
}, root);
|
}, root);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
|
@ -863,7 +878,7 @@ describe('Note', () => {
|
||||||
}, alice);
|
}, alice);
|
||||||
|
|
||||||
assert.strictEqual(note.status, 400);
|
assert.strictEqual(note.status, 400);
|
||||||
assert.strictEqual(note.body.error.code, 'CONTAINS_TOO_MANY_MENTIONS');
|
assert.strictEqual(castAsError(note.body).error.code, 'CONTAINS_TOO_MANY_MENTIONS');
|
||||||
|
|
||||||
await api('admin/roles/unassign', {
|
await api('admin/roles/unassign', {
|
||||||
userId: alice.id,
|
userId: alice.id,
|
||||||
|
@ -896,7 +911,7 @@ describe('Note', () => {
|
||||||
priority: 1,
|
priority: 1,
|
||||||
value: 1,
|
value: 1,
|
||||||
},
|
},
|
||||||
} as any,
|
},
|
||||||
}, root);
|
}, root);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
|
@ -951,6 +966,7 @@ describe('Note', () => {
|
||||||
|
|
||||||
assert.strictEqual(deleteOneRes.status, 204);
|
assert.strictEqual(deleteOneRes.status, 204);
|
||||||
let mainNote = await Notes.findOneBy({ id: mainNoteRes.body.createdNote.id });
|
let mainNote = await Notes.findOneBy({ id: mainNoteRes.body.createdNote.id });
|
||||||
|
assert.ok(mainNote);
|
||||||
assert.strictEqual(mainNote.repliesCount, 1);
|
assert.strictEqual(mainNote.repliesCount, 1);
|
||||||
|
|
||||||
const deleteTwoRes = await api('notes/delete', {
|
const deleteTwoRes = await api('notes/delete', {
|
||||||
|
@ -959,6 +975,7 @@ describe('Note', () => {
|
||||||
|
|
||||||
assert.strictEqual(deleteTwoRes.status, 204);
|
assert.strictEqual(deleteTwoRes.status, 204);
|
||||||
mainNote = await Notes.findOneBy({ id: mainNoteRes.body.createdNote.id });
|
mainNote = await Notes.findOneBy({ id: mainNoteRes.body.createdNote.id });
|
||||||
|
assert.ok(mainNote);
|
||||||
assert.strictEqual(mainNote.repliesCount, 0);
|
assert.strictEqual(mainNote.repliesCount, 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -980,7 +997,7 @@ describe('Note', () => {
|
||||||
}, alice);
|
}, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
assert.strictEqual(res.status, 400);
|
||||||
assert.strictEqual(res.body.error.code, 'UNAVAILABLE');
|
assert.strictEqual(castAsError(res.body).error.code, 'UNAVAILABLE');
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
@ -992,7 +1009,7 @@ describe('Note', () => {
|
||||||
const res = await api('notes/translate', { noteId: 'foo', targetLang: 'ja' }, alice);
|
const res = await api('notes/translate', { noteId: 'foo', targetLang: 'ja' }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
assert.strictEqual(res.status, 400);
|
||||||
assert.strictEqual(res.body.error.code, 'NO_SUCH_NOTE');
|
assert.strictEqual(castAsError(res.body).error.code, 'NO_SUCH_NOTE');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('不可視なノートは翻訳できない', async () => {
|
test('不可視なノートは翻訳できない', async () => {
|
||||||
|
@ -1000,7 +1017,7 @@ describe('Note', () => {
|
||||||
const bobTranslateAttempt = await api('notes/translate', { noteId: aliceNote.id, targetLang: 'ja' }, bob);
|
const bobTranslateAttempt = await api('notes/translate', { noteId: aliceNote.id, targetLang: 'ja' }, bob);
|
||||||
|
|
||||||
assert.strictEqual(bobTranslateAttempt.status, 400);
|
assert.strictEqual(bobTranslateAttempt.status, 400);
|
||||||
assert.strictEqual(bobTranslateAttempt.body.error.code, 'CANNOT_TRANSLATE_INVISIBLE_NOTE');
|
assert.strictEqual(castAsError(bobTranslateAttempt.body).error.code, 'CANNOT_TRANSLATE_INVISIBLE_NOTE');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('text: null なノートを翻訳すると空のレスポンスが返ってくる', async () => {
|
test('text: null なノートを翻訳すると空のレスポンスが返ってくる', async () => {
|
||||||
|
@ -1016,7 +1033,7 @@ describe('Note', () => {
|
||||||
|
|
||||||
// NOTE: デフォルトでは登録されていないので落ちる
|
// NOTE: デフォルトでは登録されていないので落ちる
|
||||||
assert.strictEqual(res.status, 400);
|
assert.strictEqual(res.status, 400);
|
||||||
assert.strictEqual(res.body.error.code, 'UNAVAILABLE');
|
assert.strictEqual(castAsError(res.body).error.code, 'UNAVAILABLE');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -42,9 +42,9 @@ describe('Renote Mute', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolRenote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolRenote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('タイムラインにリノートミュートしているユーザーの引用が含まれる', async () => {
|
test('タイムラインにリノートミュートしているユーザーの引用が含まれる', async () => {
|
||||||
|
@ -59,9 +59,9 @@ describe('Renote Mute', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolRenote.id), true);
|
assert.strictEqual(res.body.some(note => note.id === carolRenote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
// #12956
|
// #12956
|
||||||
|
@ -76,8 +76,8 @@ describe('Renote Mute', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobRenote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ストリームにリノートミュートしているユーザーのリノートが流れない', async () => {
|
test('ストリームにリノートミュートしているユーザーのリノートが流れない', async () => {
|
||||||
|
|
|
@ -33,9 +33,9 @@ describe('Note thread mute', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolReply.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolReply.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolReplyWithoutMention.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolReplyWithoutMention.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ミュートしているスレッドからメンションされても、hasUnreadMentions が true にならない', async () => {
|
test('ミュートしているスレッドからメンションされても、hasUnreadMentions が true にならない', async () => {
|
||||||
|
@ -93,8 +93,8 @@ describe('Note thread mute', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.some((notification: any) => notification.note.id === carolReply.id), false);
|
assert.strictEqual(res.body.some(notification => 'note' in notification && notification.note.id === carolReply.id), false);
|
||||||
assert.strictEqual(res.body.some((notification: any) => notification.note.id === carolReplyWithoutMention.id), false);
|
assert.strictEqual(res.body.some(notification => 'note' in notification && notification.note.id === carolReplyWithoutMention.id), false);
|
||||||
|
|
||||||
// NOTE: bobの投稿はスレッドミュート前に行われたため通知に含まれていてもよい
|
// NOTE: bobの投稿はスレッドミュート前に行われたため通知に含まれていてもよい
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { setTimeout } from 'node:timers/promises';
|
import { setTimeout } from 'node:timers/promises';
|
||||||
import { Redis } from 'ioredis';
|
import { Redis } from 'ioredis';
|
||||||
import { loadConfig } from '@/config.js';
|
|
||||||
import { api, post, randomString, sendEnvUpdateRequest, signup, uploadUrl } from '../utils.js';
|
import { api, post, randomString, sendEnvUpdateRequest, signup, uploadUrl } from '../utils.js';
|
||||||
|
import { loadConfig } from '@/config.js';
|
||||||
|
|
||||||
function genHost() {
|
function genHost() {
|
||||||
return randomString() + '.example.com';
|
return randomString() + '.example.com';
|
||||||
|
@ -37,8 +37,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === aliceNote.id), true);
|
||||||
assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi');
|
assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'hi');
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('フォローしているユーザーのノートが含まれる', async () => {
|
test.concurrent('フォローしているユーザーのノートが含まれる', async () => {
|
||||||
|
@ -53,8 +53,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('フォローしているユーザーの visibility: followers なノートが含まれる', async () => {
|
test.concurrent('フォローしているユーザーの visibility: followers なノートが含まれる', async () => {
|
||||||
|
@ -69,9 +69,9 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi');
|
assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi');
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('withReplies: false でフォローしているユーザーの他人への返信が含まれない', async () => {
|
test.concurrent('withReplies: false でフォローしているユーザーの他人への返信が含まれない', async () => {
|
||||||
|
@ -86,8 +86,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('withReplies: true でフォローしているユーザーの他人への返信が含まれる', async () => {
|
test.concurrent('withReplies: true でフォローしているユーザーの他人への返信が含まれる', async () => {
|
||||||
|
@ -103,8 +103,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('withReplies: true でフォローしているユーザーの他人へのDM返信が含まれない', async () => {
|
test.concurrent('withReplies: true でフォローしているユーザーの他人へのDM返信が含まれない', async () => {
|
||||||
|
@ -120,8 +120,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('withReplies: true でフォローしているユーザーの他人の visibility: followers な投稿への返信が含まれない', async () => {
|
test.concurrent('withReplies: true でフォローしているユーザーの他人の visibility: followers な投稿への返信が含まれない', async () => {
|
||||||
|
@ -137,8 +137,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの visibility: followers な投稿への返信が含まれる', async () => {
|
test.concurrent('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの visibility: followers な投稿への返信が含まれる', async () => {
|
||||||
|
@ -156,9 +156,9 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
|
||||||
assert.strictEqual(res.body.find((note: any) => note.id === carolNote.id).text, 'hi');
|
assert.strictEqual(res.body.find(note => note.id === carolNote.id)?.text, 'hi');
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの投稿への visibility: specified な返信が含まれない', async () => {
|
test.concurrent('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの投稿への visibility: specified な返信が含まれない', async () => {
|
||||||
|
@ -175,8 +175,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('withReplies: false でフォローしているユーザーのそのユーザー自身への返信が含まれる', async () => {
|
test.concurrent('withReplies: false でフォローしているユーザーのそのユーザー自身への返信が含まれる', async () => {
|
||||||
|
@ -191,8 +191,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote1.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => {
|
test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => {
|
||||||
|
@ -207,8 +207,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === aliceNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('自分の他人への返信が含まれる', async () => {
|
test.concurrent('自分の他人への返信が含まれる', async () => {
|
||||||
|
@ -221,8 +221,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
|
assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('フォローしているユーザーの他人の投稿のリノートが含まれる', async () => {
|
test.concurrent('フォローしているユーザーの他人の投稿のリノートが含まれる', async () => {
|
||||||
|
@ -237,8 +237,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('[withRenotes: false] フォローしているユーザーの他人の投稿のリノートが含まれない', async () => {
|
test.concurrent('[withRenotes: false] フォローしているユーザーの他人の投稿のリノートが含まれない', async () => {
|
||||||
|
@ -255,8 +255,8 @@ describe('Timelines', () => {
|
||||||
withRenotes: false,
|
withRenotes: false,
|
||||||
}, alice);
|
}, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('[withRenotes: false] フォローしているユーザーの他人の投稿の引用が含まれる', async () => {
|
test.concurrent('[withRenotes: false] フォローしているユーザーの他人の投稿の引用が含まれる', async () => {
|
||||||
|
@ -273,8 +273,8 @@ describe('Timelines', () => {
|
||||||
withRenotes: false,
|
withRenotes: false,
|
||||||
}, alice);
|
}, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('フォローしているユーザーの他人への visibility: specified なノートが含まれない', async () => {
|
test.concurrent('フォローしているユーザーの他人への visibility: specified なノートが含まれない', async () => {
|
||||||
|
@ -288,7 +288,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('フォローしているユーザーが行ったミュートしているユーザーのリノートが含まれない', async () => {
|
test.concurrent('フォローしているユーザーが行ったミュートしているユーザーのリノートが含まれない', async () => {
|
||||||
|
@ -304,8 +304,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('withReplies: true でフォローしているユーザーが行ったミュートしているユーザーの投稿への返信が含まれない', async () => {
|
test.concurrent('withReplies: true でフォローしているユーザーが行ったミュートしているユーザーの投稿への返信が含まれない', async () => {
|
||||||
|
@ -322,8 +322,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => {
|
test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => {
|
||||||
|
@ -338,7 +338,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
|
test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
|
||||||
|
@ -353,7 +353,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('[withFiles: true] フォローしているユーザーのファイル付きノートのみ含まれる', async () => {
|
test.concurrent('[withFiles: true] フォローしているユーザーのファイル付きノートのみ含まれる', async () => {
|
||||||
|
@ -374,10 +374,10 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote1.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote1.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote1.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote2.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote2.id), false);
|
||||||
}, 1000 * 10);
|
}, 1000 * 10);
|
||||||
|
|
||||||
test.concurrent('フォローしているユーザーのチャンネル投稿が含まれない', async () => {
|
test.concurrent('フォローしているユーザーのチャンネル投稿が含まれない', async () => {
|
||||||
|
@ -392,7 +392,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('自分の visibility: specified なノートが含まれる', async () => {
|
test.concurrent('自分の visibility: specified なノートが含まれる', async () => {
|
||||||
|
@ -404,8 +404,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === aliceNote.id), true);
|
||||||
assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi');
|
assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'hi');
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('フォローしているユーザーの自身を visibleUserIds に指定した visibility: specified なノートが含まれる', async () => {
|
test.concurrent('フォローしているユーザーの自身を visibleUserIds に指定した visibility: specified なノートが含まれる', async () => {
|
||||||
|
@ -419,8 +419,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi');
|
assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi');
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('フォローしていないユーザーの自身を visibleUserIds に指定した visibility: specified なノートが含まれない', async () => {
|
test.concurrent('フォローしていないユーザーの自身を visibleUserIds に指定した visibility: specified なノートが含まれない', async () => {
|
||||||
|
@ -432,7 +432,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('フォローしているユーザーの自身を visibleUserIds に指定していない visibility: specified なノートが含まれない', async () => {
|
test.concurrent('フォローしているユーザーの自身を visibleUserIds に指定していない visibility: specified なノートが含まれない', async () => {
|
||||||
|
@ -446,7 +446,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('フォローしていないユーザーからの visibility: specified なノートに返信したときの自身のノートが含まれる', async () => {
|
test.concurrent('フォローしていないユーザーからの visibility: specified なノートに返信したときの自身のノートが含まれる', async () => {
|
||||||
|
@ -459,8 +459,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === aliceNote.id), true);
|
||||||
assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'ok');
|
assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'ok');
|
||||||
});
|
});
|
||||||
|
|
||||||
/* TODO
|
/* TODO
|
||||||
|
@ -474,8 +474,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'ok');
|
assert.strictEqual(res.body.find(note => note.id === bobNote.id).text, 'ok');
|
||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -490,7 +490,45 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.concurrent('FTT: ローカルユーザーの HTL にはプッシュされる', async () => {
|
||||||
|
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||||
|
|
||||||
|
await api('following/create', {
|
||||||
|
userId: alice.id,
|
||||||
|
}, bob);
|
||||||
|
|
||||||
|
const aliceNote = await post(alice, { text: 'I\'m Alice.' });
|
||||||
|
const bobNote = await post(bob, { text: 'I\'m Bob.' });
|
||||||
|
const carolNote = await post(carol, { text: 'I\'m Carol.' });
|
||||||
|
|
||||||
|
await waitForPushToTl();
|
||||||
|
|
||||||
|
// NOTE: notes/timeline だと DB へのフォールバックが効くので Redis を直接見て確かめる
|
||||||
|
assert.strictEqual(await redisForTimelines.exists(`list:homeTimeline:${bob.id}`), 1);
|
||||||
|
|
||||||
|
const bobHTL = await redisForTimelines.lrange(`list:homeTimeline:${bob.id}`, 0, -1);
|
||||||
|
assert.strictEqual(bobHTL.includes(aliceNote.id), true);
|
||||||
|
assert.strictEqual(bobHTL.includes(bobNote.id), true);
|
||||||
|
assert.strictEqual(bobHTL.includes(carolNote.id), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.concurrent('FTT: リモートユーザーの HTL にはプッシュされない', async () => {
|
||||||
|
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
|
||||||
|
|
||||||
|
await api('following/create', {
|
||||||
|
userId: alice.id,
|
||||||
|
}, bob);
|
||||||
|
|
||||||
|
await post(alice, { text: 'I\'m Alice.' });
|
||||||
|
await post(bob, { text: 'I\'m Bob.' });
|
||||||
|
|
||||||
|
await waitForPushToTl();
|
||||||
|
|
||||||
|
// NOTE: notes/timeline だと DB へのフォールバックが効くので Redis を直接見て確かめる
|
||||||
|
assert.strictEqual(await redisForTimelines.exists(`list:homeTimeline:${bob.id}`), 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -505,8 +543,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('他人の他人への返信が含まれない', async () => {
|
test.concurrent('他人の他人への返信が含まれない', async () => {
|
||||||
|
@ -519,8 +557,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('他人のその人自身への返信が含まれる', async () => {
|
test.concurrent('他人のその人自身への返信が含まれる', async () => {
|
||||||
|
@ -533,8 +571,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote1.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('チャンネル投稿が含まれない', async () => {
|
test.concurrent('チャンネル投稿が含まれない', async () => {
|
||||||
|
@ -547,7 +585,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('リモートユーザーのノートが含まれない', async () => {
|
test.concurrent('リモートユーザーのノートが含まれない', async () => {
|
||||||
|
@ -559,7 +597,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 含まれても良いと思うけど実装が面倒なので含まれない
|
// 含まれても良いと思うけど実装が面倒なので含まれない
|
||||||
|
@ -575,8 +613,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('ミュートしているユーザーのノートが含まれない', async () => {
|
test.concurrent('ミュートしているユーザーのノートが含まれない', async () => {
|
||||||
|
@ -591,8 +629,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('フォローしているユーザーが行ったミュートしているユーザーのリノートが含まれない', async () => {
|
test.concurrent('フォローしているユーザーが行ったミュートしているユーザーのリノートが含まれない', async () => {
|
||||||
|
@ -608,8 +646,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('withReplies: true でフォローしているユーザーが行ったミュートしているユーザーの投稿への返信が含まれない', async () => {
|
test.concurrent('withReplies: true でフォローしているユーザーが行ったミュートしているユーザーの投稿への返信が含まれない', async () => {
|
||||||
|
@ -626,8 +664,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => {
|
test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => {
|
||||||
|
@ -642,8 +680,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === aliceNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('[withReplies: true] 他人の他人への返信が含まれる', async () => {
|
test.concurrent('[withReplies: true] 他人の他人への返信が含まれる', async () => {
|
||||||
|
@ -656,7 +694,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
|
test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
|
||||||
|
@ -670,8 +708,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote1.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
|
||||||
}, 1000 * 10);
|
}, 1000 * 10);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -685,7 +723,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('ローカルユーザーの visibility: home なノートが含まれない', async () => {
|
test.concurrent('ローカルユーザーの visibility: home なノートが含まれない', async () => {
|
||||||
|
@ -697,7 +735,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('フォローしているローカルユーザーの visibility: home なノートが含まれる', async () => {
|
test.concurrent('フォローしているローカルユーザーの visibility: home なノートが含まれる', async () => {
|
||||||
|
@ -711,7 +749,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => {
|
test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => {
|
||||||
|
@ -726,8 +764,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === aliceNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('他人の他人への返信が含まれない', async () => {
|
test.concurrent('他人の他人への返信が含まれない', async () => {
|
||||||
|
@ -740,8 +778,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
|
assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('リモートユーザーのノートが含まれない', async () => {
|
test.concurrent('リモートユーザーのノートが含まれない', async () => {
|
||||||
|
@ -753,7 +791,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => {
|
test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => {
|
||||||
|
@ -768,7 +806,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
|
test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
|
||||||
|
@ -783,7 +821,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('[withReplies: true] 他人の他人への返信が含まれる', async () => {
|
test.concurrent('[withReplies: true] 他人の他人への返信が含まれる', async () => {
|
||||||
|
@ -796,7 +834,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
|
test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
|
||||||
|
@ -810,8 +848,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote1.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
|
||||||
}, 1000 * 10);
|
}, 1000 * 10);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -828,7 +866,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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.some(note => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('リスインしているフォローしていないユーザーの visibility: home なノートが含まれる', async () => {
|
test.concurrent('リスインしているフォローしていないユーザーの visibility: home なノートが含まれる', async () => {
|
||||||
|
@ -843,7 +881,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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.some(note => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('リスインしているフォローしていないユーザーの visibility: followers なノートが含まれない', async () => {
|
test.concurrent('リスインしているフォローしていないユーザーの visibility: followers なノートが含まれない', async () => {
|
||||||
|
@ -858,7 +896,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('リスインしているフォローしていないユーザーの他人への返信が含まれない', async () => {
|
test.concurrent('リスインしているフォローしていないユーザーの他人への返信が含まれない', async () => {
|
||||||
|
@ -874,7 +912,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('リスインしているフォローしていないユーザーのユーザー自身への返信が含まれる', async () => {
|
test.concurrent('リスインしているフォローしていないユーザーのユーザー自身への返信が含まれる', async () => {
|
||||||
|
@ -890,8 +928,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote1.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('withReplies: false でリスインしているフォローしていないユーザーからの自分への返信が含まれる', async () => {
|
test.concurrent('withReplies: false でリスインしているフォローしていないユーザーからの自分への返信が含まれる', async () => {
|
||||||
|
@ -908,7 +946,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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.some(note => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('withReplies: false でリスインしているフォローしていないユーザーの他人への返信が含まれない', async () => {
|
test.concurrent('withReplies: false でリスインしているフォローしていないユーザーの他人への返信が含まれない', async () => {
|
||||||
|
@ -925,7 +963,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('withReplies: true でリスインしているフォローしていないユーザーの他人への返信が含まれる', async () => {
|
test.concurrent('withReplies: true でリスインしているフォローしていないユーザーの他人への返信が含まれる', async () => {
|
||||||
|
@ -942,7 +980,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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.some(note => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('リスインしているフォローしているユーザーの visibility: home なノートが含まれる', async () => {
|
test.concurrent('リスインしているフォローしているユーザーの visibility: home なノートが含まれる', async () => {
|
||||||
|
@ -958,7 +996,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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.some(note => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('リスインしているフォローしているユーザーの visibility: followers なノートが含まれる', async () => {
|
test.concurrent('リスインしているフォローしているユーザーの visibility: followers なノートが含まれる', async () => {
|
||||||
|
@ -974,8 +1012,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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.some(note => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi');
|
assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi');
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('リスインしている自分の visibility: followers なノートが含まれる', async () => {
|
test.concurrent('リスインしている自分の visibility: followers なノートが含まれる', async () => {
|
||||||
|
@ -990,8 +1028,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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.some(note => note.id === aliceNote.id), true);
|
||||||
assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi');
|
assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'hi');
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('リスインしているユーザーのチャンネルノートが含まれない', async () => {
|
test.concurrent('リスインしているユーザーのチャンネルノートが含まれない', async () => {
|
||||||
|
@ -1007,7 +1045,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('[withFiles: true] リスインしているユーザーのファイル付きノートのみ含まれる', async () => {
|
test.concurrent('[withFiles: true] リスインしているユーザーのファイル付きノートのみ含まれる', async () => {
|
||||||
|
@ -1023,8 +1061,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote1.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
|
||||||
}, 1000 * 10);
|
}, 1000 * 10);
|
||||||
|
|
||||||
test.concurrent('リスインしているユーザーの自身宛ての visibility: specified なノートが含まれる', async () => {
|
test.concurrent('リスインしているユーザーの自身宛ての visibility: specified なノートが含まれる', async () => {
|
||||||
|
@ -1039,8 +1077,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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.some(note => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi');
|
assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi');
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('リスインしているユーザーの自身宛てではない visibility: specified なノートが含まれない', async () => {
|
test.concurrent('リスインしているユーザーの自身宛てではない visibility: specified なノートが含まれない', async () => {
|
||||||
|
@ -1056,7 +1094,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1070,7 +1108,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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.some(note => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('フォローしていないユーザーの visibility: followers なノートが含まれない', async () => {
|
test.concurrent('フォローしていないユーザーの visibility: followers なノートが含まれない', async () => {
|
||||||
|
@ -1082,7 +1120,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('フォローしているユーザーの visibility: followers なノートが含まれる', async () => {
|
test.concurrent('フォローしているユーザーの visibility: followers なノートが含まれる', async () => {
|
||||||
|
@ -1096,8 +1134,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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.some(note => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi');
|
assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi');
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('自身の visibility: followers なノートが含まれる', async () => {
|
test.concurrent('自身の visibility: followers なノートが含まれる', async () => {
|
||||||
|
@ -1109,8 +1147,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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.some(note => note.id === aliceNote.id), true);
|
||||||
assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi');
|
assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'hi');
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('チャンネル投稿が含まれない', async () => {
|
test.concurrent('チャンネル投稿が含まれない', async () => {
|
||||||
|
@ -1123,7 +1161,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('[withReplies: false] 他人への返信が含まれない', async () => {
|
test.concurrent('[withReplies: false] 他人への返信が含まれない', async () => {
|
||||||
|
@ -1137,8 +1175,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote1.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), false);
|
assert.strictEqual(res.body.some(note => note.id === bobNote2.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('[withReplies: true] 他人への返信が含まれる', async () => {
|
test.concurrent('[withReplies: true] 他人への返信が含まれる', async () => {
|
||||||
|
@ -1152,8 +1190,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote1.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('[withReplies: true] 他人への visibility: specified な返信が含まれない', async () => {
|
test.concurrent('[withReplies: true] 他人への visibility: specified な返信が含まれない', async () => {
|
||||||
|
@ -1167,8 +1205,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote1.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), false);
|
assert.strictEqual(res.body.some(note => note.id === bobNote2.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
|
test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
|
||||||
|
@ -1182,8 +1220,8 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote1.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
|
||||||
}, 1000 * 10);
|
}, 1000 * 10);
|
||||||
|
|
||||||
test.concurrent('[withChannelNotes: true] チャンネル投稿が含まれる', async () => {
|
test.concurrent('[withChannelNotes: true] チャンネル投稿が含まれる', async () => {
|
||||||
|
@ -1196,7 +1234,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('[withChannelNotes: true] 他人が取得した場合センシティブチャンネル投稿が含まれない', async () => {
|
test.concurrent('[withChannelNotes: true] 他人が取得した場合センシティブチャンネル投稿が含まれない', async () => {
|
||||||
|
@ -1209,7 +1247,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('[withChannelNotes: true] 自分が取得した場合センシティブチャンネル投稿が含まれる', async () => {
|
test.concurrent('[withChannelNotes: true] 自分が取得した場合センシティブチャンネル投稿が含まれる', async () => {
|
||||||
|
@ -1222,7 +1260,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('ミュートしているユーザーに関連する投稿が含まれない', async () => {
|
test.concurrent('ミュートしているユーザーに関連する投稿が含まれない', async () => {
|
||||||
|
@ -1237,7 +1275,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('ミュートしていても userId に指定したユーザーの投稿が含まれる', async () => {
|
test.concurrent('ミュートしていても userId に指定したユーザーの投稿が含まれる', async () => {
|
||||||
|
@ -1253,9 +1291,9 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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 => note.id === bobNote1.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote3.id), true);
|
assert.strictEqual(res.body.some(note => note.id === bobNote3.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('自身の visibility: specified なノートが含まれる', async () => {
|
test.concurrent('自身の visibility: specified なノートが含まれる', async () => {
|
||||||
|
@ -1267,7 +1305,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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);
|
assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.concurrent('visibleUserIds に指定されてない visibility: specified なノートが含まれない', async () => {
|
test.concurrent('visibleUserIds に指定されてない visibility: specified なノートが含まれない', async () => {
|
||||||
|
@ -1279,7 +1317,7 @@ describe('Timelines', () => {
|
||||||
|
|
||||||
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);
|
assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
/** @see https://github.com/misskey-dev/misskey/issues/14000 */
|
/** @see https://github.com/misskey-dev/misskey/issues/14000 */
|
||||||
|
|
|
@ -231,7 +231,7 @@ describe('ユーザー', () => {
|
||||||
rolePublic = await role(root, { isPublic: true, name: 'Public Role' });
|
rolePublic = await role(root, { isPublic: true, name: 'Public Role' });
|
||||||
await api('admin/roles/assign', { userId: userRolePublic.id, roleId: rolePublic.id }, root);
|
await api('admin/roles/assign', { userId: userRolePublic.id, roleId: rolePublic.id }, root);
|
||||||
userRoleBadge = await signup({ username: 'userRoleBadge' });
|
userRoleBadge = await signup({ username: 'userRoleBadge' });
|
||||||
roleBadge = await role(root, { asBadge: true, name: 'Badge Role' });
|
roleBadge = await role(root, { asBadge: true, name: 'Badge Role', isPublic: true });
|
||||||
await api('admin/roles/assign', { userId: userRoleBadge.id, roleId: roleBadge.id }, root);
|
await api('admin/roles/assign', { userId: userRoleBadge.id, roleId: roleBadge.id }, root);
|
||||||
userSilenced = await signup({ username: 'userSilenced' });
|
userSilenced = await signup({ username: 'userSilenced' });
|
||||||
await post(userSilenced, { text: 'test' });
|
await post(userSilenced, { text: 'test' });
|
||||||
|
@ -655,7 +655,16 @@ describe('ユーザー', () => {
|
||||||
iconUrl: roleBadge.iconUrl,
|
iconUrl: roleBadge.iconUrl,
|
||||||
displayOrder: roleBadge.displayOrder,
|
displayOrder: roleBadge.displayOrder,
|
||||||
}]);
|
}]);
|
||||||
assert.deepStrictEqual(response.roles, []); // バッヂだからといってrolesが取れるとは限らない
|
assert.deepStrictEqual(response.roles, [{
|
||||||
|
id: roleBadge.id,
|
||||||
|
name: roleBadge.name,
|
||||||
|
color: roleBadge.color,
|
||||||
|
iconUrl: roleBadge.iconUrl,
|
||||||
|
description: roleBadge.description,
|
||||||
|
isModerator: roleBadge.isModerator,
|
||||||
|
isAdministrator: roleBadge.isAdministrator,
|
||||||
|
displayOrder: roleBadge.displayOrder,
|
||||||
|
}]);
|
||||||
});
|
});
|
||||||
test('をID指定のリスト形式で取得することができる(空)', async () => {
|
test('をID指定のリスト形式で取得することができる(空)', async () => {
|
||||||
const parameters = { userIds: [] };
|
const parameters = { userIds: [] };
|
||||||
|
|
|
@ -23,10 +23,10 @@ describe('ApMfmService', () => {
|
||||||
|
|
||||||
describe('getNoteHtml', () => {
|
describe('getNoteHtml', () => {
|
||||||
test('Do not provide _misskey_content for simple text', () => {
|
test('Do not provide _misskey_content for simple text', () => {
|
||||||
const note: MiNote = {
|
const note = {
|
||||||
text: 'テキスト #タグ @mention 🍊 :emoji: https://example.com',
|
text: 'テキスト #タグ @mention 🍊 :emoji: https://example.com',
|
||||||
mentionedRemoteUsers: '[]',
|
mentionedRemoteUsers: '[]',
|
||||||
} as any;
|
};
|
||||||
|
|
||||||
const { content, noMisskeyContent } = apMfmService.getNoteHtml(note);
|
const { content, noMisskeyContent } = apMfmService.getNoteHtml(note);
|
||||||
|
|
||||||
|
@ -35,10 +35,10 @@ describe('ApMfmService', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Provide _misskey_content for MFM', () => {
|
test('Provide _misskey_content for MFM', () => {
|
||||||
const note: MiNote = {
|
const note = {
|
||||||
text: '$[tada foo]',
|
text: '$[tada foo]',
|
||||||
mentionedRemoteUsers: '[]',
|
mentionedRemoteUsers: '[]',
|
||||||
} as any;
|
};
|
||||||
|
|
||||||
const { content, noMisskeyContent } = apMfmService.getNoteHtml(note);
|
const { content, noMisskeyContent } = apMfmService.getNoteHtml(note);
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { ModuleMocker } from 'jest-mock';
|
||||||
import { Test } from '@nestjs/testing';
|
import { Test } from '@nestjs/testing';
|
||||||
import { afterAll, beforeAll, describe, test } from '@jest/globals';
|
import { afterAll, beforeAll, describe, test } from '@jest/globals';
|
||||||
import { GlobalModule } from '@/GlobalModule.js';
|
import { GlobalModule } from '@/GlobalModule.js';
|
||||||
import { FileInfoService } from '@/core/FileInfoService.js';
|
import { FileInfo, FileInfoService } from '@/core/FileInfoService.js';
|
||||||
//import { DI } from '@/di-symbols.js';
|
//import { DI } from '@/di-symbols.js';
|
||||||
import { AiService } from '@/core/AiService.js';
|
import { AiService } from '@/core/AiService.js';
|
||||||
import { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
|
@ -28,6 +28,15 @@ const moduleMocker = new ModuleMocker(global);
|
||||||
describe('FileInfoService', () => {
|
describe('FileInfoService', () => {
|
||||||
let app: TestingModule;
|
let app: TestingModule;
|
||||||
let fileInfoService: FileInfoService;
|
let fileInfoService: FileInfoService;
|
||||||
|
const strip = (fileInfo: FileInfo): Omit<Partial<FileInfo>, 'warnings' | 'blurhash' | 'sensitive' | 'porn'> => {
|
||||||
|
const fi: Partial<FileInfo> = fileInfo;
|
||||||
|
delete fi.warnings;
|
||||||
|
delete fi.sensitive;
|
||||||
|
delete fi.blurhash;
|
||||||
|
delete fi.porn;
|
||||||
|
|
||||||
|
return fi;
|
||||||
|
}
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await Test.createTestingModule({
|
app = await Test.createTestingModule({
|
||||||
|
@ -63,11 +72,7 @@ describe('FileInfoService', () => {
|
||||||
|
|
||||||
test('Empty file', async () => {
|
test('Empty file', async () => {
|
||||||
const path = `${resources}/emptyfile`;
|
const path = `${resources}/emptyfile`;
|
||||||
const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
|
const info = strip(await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }));
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
assert.deepStrictEqual(info, {
|
assert.deepStrictEqual(info, {
|
||||||
size: 0,
|
size: 0,
|
||||||
md5: 'd41d8cd98f00b204e9800998ecf8427e',
|
md5: 'd41d8cd98f00b204e9800998ecf8427e',
|
||||||
|
@ -84,11 +89,7 @@ describe('FileInfoService', () => {
|
||||||
describe('IMAGE', () => {
|
describe('IMAGE', () => {
|
||||||
test('Generic JPEG', async () => {
|
test('Generic JPEG', async () => {
|
||||||
const path = `${resources}/192.jpg`;
|
const path = `${resources}/192.jpg`;
|
||||||
const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
|
const info = strip(await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }));
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
assert.deepStrictEqual(info, {
|
assert.deepStrictEqual(info, {
|
||||||
size: 5131,
|
size: 5131,
|
||||||
md5: '8c9ed0677dd2b8f9f7472c3af247e5e3',
|
md5: '8c9ed0677dd2b8f9f7472c3af247e5e3',
|
||||||
|
@ -104,11 +105,7 @@ describe('FileInfoService', () => {
|
||||||
|
|
||||||
test('Generic APNG', async () => {
|
test('Generic APNG', async () => {
|
||||||
const path = `${resources}/anime.png`;
|
const path = `${resources}/anime.png`;
|
||||||
const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
|
const info = strip(await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }));
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
assert.deepStrictEqual(info, {
|
assert.deepStrictEqual(info, {
|
||||||
size: 1868,
|
size: 1868,
|
||||||
md5: '08189c607bea3b952704676bb3c979e0',
|
md5: '08189c607bea3b952704676bb3c979e0',
|
||||||
|
@ -124,11 +121,7 @@ describe('FileInfoService', () => {
|
||||||
|
|
||||||
test('Generic AGIF', async () => {
|
test('Generic AGIF', async () => {
|
||||||
const path = `${resources}/anime.gif`;
|
const path = `${resources}/anime.gif`;
|
||||||
const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
|
const info = strip(await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }));
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
assert.deepStrictEqual(info, {
|
assert.deepStrictEqual(info, {
|
||||||
size: 2248,
|
size: 2248,
|
||||||
md5: '32c47a11555675d9267aee1a86571e7e',
|
md5: '32c47a11555675d9267aee1a86571e7e',
|
||||||
|
@ -144,11 +137,7 @@ describe('FileInfoService', () => {
|
||||||
|
|
||||||
test('PNG with alpha', async () => {
|
test('PNG with alpha', async () => {
|
||||||
const path = `${resources}/with-alpha.png`;
|
const path = `${resources}/with-alpha.png`;
|
||||||
const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
|
const info = strip(await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }));
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
assert.deepStrictEqual(info, {
|
assert.deepStrictEqual(info, {
|
||||||
size: 3772,
|
size: 3772,
|
||||||
md5: 'f73535c3e1e27508885b69b10cf6e991',
|
md5: 'f73535c3e1e27508885b69b10cf6e991',
|
||||||
|
@ -164,11 +153,7 @@ describe('FileInfoService', () => {
|
||||||
|
|
||||||
test('Generic SVG', async () => {
|
test('Generic SVG', async () => {
|
||||||
const path = `${resources}/image.svg`;
|
const path = `${resources}/image.svg`;
|
||||||
const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
|
const info = strip(await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }));
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
assert.deepStrictEqual(info, {
|
assert.deepStrictEqual(info, {
|
||||||
size: 505,
|
size: 505,
|
||||||
md5: 'b6f52b4b021e7b92cdd04509c7267965',
|
md5: 'b6f52b4b021e7b92cdd04509c7267965',
|
||||||
|
@ -185,11 +170,7 @@ describe('FileInfoService', () => {
|
||||||
test('SVG with XML definition', async () => {
|
test('SVG with XML definition', async () => {
|
||||||
// https://github.com/misskey-dev/misskey/issues/4413
|
// https://github.com/misskey-dev/misskey/issues/4413
|
||||||
const path = `${resources}/with-xml-def.svg`;
|
const path = `${resources}/with-xml-def.svg`;
|
||||||
const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
|
const info = strip(await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }));
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
assert.deepStrictEqual(info, {
|
assert.deepStrictEqual(info, {
|
||||||
size: 544,
|
size: 544,
|
||||||
md5: '4b7a346cde9ccbeb267e812567e33397',
|
md5: '4b7a346cde9ccbeb267e812567e33397',
|
||||||
|
@ -205,11 +186,7 @@ describe('FileInfoService', () => {
|
||||||
|
|
||||||
test('Dimension limit', async () => {
|
test('Dimension limit', async () => {
|
||||||
const path = `${resources}/25000x25000.png`;
|
const path = `${resources}/25000x25000.png`;
|
||||||
const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
|
const info = strip(await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }));
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
assert.deepStrictEqual(info, {
|
assert.deepStrictEqual(info, {
|
||||||
size: 75933,
|
size: 75933,
|
||||||
md5: '268c5dde99e17cf8fe09f1ab3f97df56',
|
md5: '268c5dde99e17cf8fe09f1ab3f97df56',
|
||||||
|
@ -225,11 +202,7 @@ describe('FileInfoService', () => {
|
||||||
|
|
||||||
test('Rotate JPEG', async () => {
|
test('Rotate JPEG', async () => {
|
||||||
const path = `${resources}/rotate.jpg`;
|
const path = `${resources}/rotate.jpg`;
|
||||||
const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
|
const info = strip(await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }));
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
assert.deepStrictEqual(info, {
|
assert.deepStrictEqual(info, {
|
||||||
size: 12624,
|
size: 12624,
|
||||||
md5: '68d5b2d8d1d1acbbce99203e3ec3857e',
|
md5: '68d5b2d8d1d1acbbce99203e3ec3857e',
|
||||||
|
@ -247,11 +220,7 @@ describe('FileInfoService', () => {
|
||||||
describe('AUDIO', () => {
|
describe('AUDIO', () => {
|
||||||
test('MP3', async () => {
|
test('MP3', async () => {
|
||||||
const path = `${resources}/kick_gaba7.mp3`;
|
const path = `${resources}/kick_gaba7.mp3`;
|
||||||
const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
|
const info = strip(await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }));
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
delete info.width;
|
delete info.width;
|
||||||
delete info.height;
|
delete info.height;
|
||||||
delete info.orientation;
|
delete info.orientation;
|
||||||
|
@ -267,11 +236,7 @@ describe('FileInfoService', () => {
|
||||||
|
|
||||||
test('WAV', async () => {
|
test('WAV', async () => {
|
||||||
const path = `${resources}/kick_gaba7.wav`;
|
const path = `${resources}/kick_gaba7.wav`;
|
||||||
const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
|
const info = strip(await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }));
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
delete info.width;
|
delete info.width;
|
||||||
delete info.height;
|
delete info.height;
|
||||||
delete info.orientation;
|
delete info.orientation;
|
||||||
|
@ -287,11 +252,7 @@ describe('FileInfoService', () => {
|
||||||
|
|
||||||
test('AAC', async () => {
|
test('AAC', async () => {
|
||||||
const path = `${resources}/kick_gaba7.aac`;
|
const path = `${resources}/kick_gaba7.aac`;
|
||||||
const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
|
const info = strip(await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }));
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
delete info.width;
|
delete info.width;
|
||||||
delete info.height;
|
delete info.height;
|
||||||
delete info.orientation;
|
delete info.orientation;
|
||||||
|
@ -307,11 +268,7 @@ describe('FileInfoService', () => {
|
||||||
|
|
||||||
test('FLAC', async () => {
|
test('FLAC', async () => {
|
||||||
const path = `${resources}/kick_gaba7.flac`;
|
const path = `${resources}/kick_gaba7.flac`;
|
||||||
const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
|
const info = strip(await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }));
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
delete info.width;
|
delete info.width;
|
||||||
delete info.height;
|
delete info.height;
|
||||||
delete info.orientation;
|
delete info.orientation;
|
||||||
|
@ -327,11 +284,7 @@ describe('FileInfoService', () => {
|
||||||
|
|
||||||
test('MPEG-4 AUDIO (M4A)', async () => {
|
test('MPEG-4 AUDIO (M4A)', async () => {
|
||||||
const path = `${resources}/kick_gaba7.m4a`;
|
const path = `${resources}/kick_gaba7.m4a`;
|
||||||
const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
|
const info = strip(await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }));
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
delete info.width;
|
delete info.width;
|
||||||
delete info.height;
|
delete info.height;
|
||||||
delete info.orientation;
|
delete info.orientation;
|
||||||
|
@ -347,11 +300,7 @@ describe('FileInfoService', () => {
|
||||||
|
|
||||||
test('WEBM AUDIO', async () => {
|
test('WEBM AUDIO', async () => {
|
||||||
const path = `${resources}/kick_gaba7.webm`;
|
const path = `${resources}/kick_gaba7.webm`;
|
||||||
const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
|
const info = strip(await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }));
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
delete info.width;
|
delete info.width;
|
||||||
delete info.height;
|
delete info.height;
|
||||||
delete info.orientation;
|
delete info.orientation;
|
||||||
|
|
265
packages/backend/test/unit/UserSearchService.ts
Normal file
265
packages/backend/test/unit/UserSearchService.ts
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { describe, jest, test } from '@jest/globals';
|
||||||
|
import { In } from 'typeorm';
|
||||||
|
import { UserSearchService } from '@/core/UserSearchService.js';
|
||||||
|
import { FollowingsRepository, MiUser, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { GlobalModule } from '@/GlobalModule.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
|
||||||
|
describe('UserSearchService', () => {
|
||||||
|
let app: TestingModule;
|
||||||
|
let service: UserSearchService;
|
||||||
|
|
||||||
|
let usersRepository: UsersRepository;
|
||||||
|
let followingsRepository: FollowingsRepository;
|
||||||
|
let idService: IdService;
|
||||||
|
let userProfilesRepository: UserProfilesRepository;
|
||||||
|
|
||||||
|
let root: MiUser;
|
||||||
|
let alice: MiUser;
|
||||||
|
let alyce: MiUser;
|
||||||
|
let alycia: MiUser;
|
||||||
|
let alysha: MiUser;
|
||||||
|
let alyson: MiUser;
|
||||||
|
let alyssa: MiUser;
|
||||||
|
let bob: MiUser;
|
||||||
|
let bobbi: MiUser;
|
||||||
|
let bobbie: MiUser;
|
||||||
|
let bobby: MiUser;
|
||||||
|
|
||||||
|
async function createUser(data: Partial<MiUser> = {}) {
|
||||||
|
const user = await usersRepository
|
||||||
|
.insert({
|
||||||
|
id: idService.gen(),
|
||||||
|
...data,
|
||||||
|
})
|
||||||
|
.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
|
||||||
|
|
||||||
|
await userProfilesRepository.insert({
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createFollowings(follower: MiUser, followees: MiUser[]) {
|
||||||
|
for (const followee of followees) {
|
||||||
|
await followingsRepository.insert({
|
||||||
|
id: idService.gen(),
|
||||||
|
followerId: follower.id,
|
||||||
|
followeeId: followee.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setActive(users: MiUser[]) {
|
||||||
|
for (const user of users) {
|
||||||
|
await usersRepository.update(user.id, {
|
||||||
|
updatedAt: new Date(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setInactive(users: MiUser[]) {
|
||||||
|
for (const user of users) {
|
||||||
|
await usersRepository.update(user.id, {
|
||||||
|
updatedAt: new Date(0),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setSuspended(users: MiUser[]) {
|
||||||
|
for (const user of users) {
|
||||||
|
await usersRepository.update(user.id, {
|
||||||
|
isSuspended: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
app = await Test
|
||||||
|
.createTestingModule({
|
||||||
|
imports: [
|
||||||
|
GlobalModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
UserSearchService,
|
||||||
|
{
|
||||||
|
provide: UserEntityService, useFactory: jest.fn(() => ({
|
||||||
|
// とりあえずIDが返れば確認が出来るので
|
||||||
|
packMany: (value: any) => value,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
IdService,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.compile();
|
||||||
|
|
||||||
|
await app.init();
|
||||||
|
|
||||||
|
usersRepository = app.get(DI.usersRepository);
|
||||||
|
userProfilesRepository = app.get(DI.userProfilesRepository);
|
||||||
|
followingsRepository = app.get(DI.followingsRepository);
|
||||||
|
|
||||||
|
service = app.get(UserSearchService);
|
||||||
|
idService = app.get(IdService);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
root = await createUser({ username: 'root', usernameLower: 'root', isRoot: true });
|
||||||
|
alice = await createUser({ username: 'Alice', usernameLower: 'alice' });
|
||||||
|
alyce = await createUser({ username: 'Alyce', usernameLower: 'alyce' });
|
||||||
|
alycia = await createUser({ username: 'Alycia', usernameLower: 'alycia' });
|
||||||
|
alysha = await createUser({ username: 'Alysha', usernameLower: 'alysha' });
|
||||||
|
alyson = await createUser({ username: 'Alyson', usernameLower: 'alyson', host: 'example.com' });
|
||||||
|
alyssa = await createUser({ username: 'Alyssa', usernameLower: 'alyssa', host: 'example.com' });
|
||||||
|
bob = await createUser({ username: 'Bob', usernameLower: 'bob' });
|
||||||
|
bobbi = await createUser({ username: 'Bobbi', usernameLower: 'bobbi' });
|
||||||
|
bobbie = await createUser({ username: 'Bobbie', usernameLower: 'bobbie', host: 'example.com' });
|
||||||
|
bobby = await createUser({ username: 'Bobby', usernameLower: 'bobby', host: 'example.com' });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await usersRepository.delete({});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await app.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('search', () => {
|
||||||
|
test('フォロー中のアクティブユーザのうち、"al"から始まる人が全員ヒットする', async () => {
|
||||||
|
await createFollowings(root, [alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]);
|
||||||
|
await setActive([alice, alyce, alyssa, bob, bobbi, bobbie, bobby]);
|
||||||
|
await setInactive([alycia, alysha, alyson]);
|
||||||
|
|
||||||
|
const result = await service.search(
|
||||||
|
{ username: 'al' },
|
||||||
|
{ limit: 100 },
|
||||||
|
root,
|
||||||
|
);
|
||||||
|
|
||||||
|
// alycia, alysha, alysonは非アクティブなので後ろに行く
|
||||||
|
expect(result).toEqual([alice, alyce, alyssa, alycia, alysha, alyson].map(x => x.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('フォロー中の非アクティブユーザのうち、"al"から始まる人が全員ヒットする', async () => {
|
||||||
|
await createFollowings(root, [alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]);
|
||||||
|
await setInactive([alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]);
|
||||||
|
|
||||||
|
const result = await service.search(
|
||||||
|
{ username: 'al' },
|
||||||
|
{ limit: 100 },
|
||||||
|
root,
|
||||||
|
);
|
||||||
|
|
||||||
|
// alice, alyceはフォローしていないので後ろに行く
|
||||||
|
expect(result).toEqual([alycia, alysha, alyson, alyssa, alice, alyce].map(x => x.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('フォローしていないアクティブユーザのうち、"al"から始まる人が全員ヒットする', async () => {
|
||||||
|
await setActive([alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]);
|
||||||
|
await setInactive([alice, alyce, alycia]);
|
||||||
|
|
||||||
|
const result = await service.search(
|
||||||
|
{ username: 'al' },
|
||||||
|
{ limit: 100 },
|
||||||
|
root,
|
||||||
|
);
|
||||||
|
|
||||||
|
// alice, alyce, alyciaは非アクティブなので後ろに行く
|
||||||
|
expect(result).toEqual([alysha, alyson, alyssa, alice, alyce, alycia].map(x => x.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('フォローしていない非アクティブユーザのうち、"al"から始まる人が全員ヒットする', async () => {
|
||||||
|
await setInactive([alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]);
|
||||||
|
|
||||||
|
const result = await service.search(
|
||||||
|
{ username: 'al' },
|
||||||
|
{ limit: 100 },
|
||||||
|
root,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual([alice, alyce, alycia, alysha, alyson, alyssa].map(x => x.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('フォロー(アクティブ)、フォロー(非アクティブ)、非フォロー(アクティブ)、非フォロー(非アクティブ)混在時の優先順位度確認', async () => {
|
||||||
|
await createFollowings(root, [alyson, alyssa, bob, bobbi, bobbie]);
|
||||||
|
await setActive([root, alyssa, bob, bobbi, alyce, alycia]);
|
||||||
|
await setInactive([alyson, alice, alysha, bobbie, bobby]);
|
||||||
|
|
||||||
|
const result = await service.search(
|
||||||
|
{ },
|
||||||
|
{ limit: 100 },
|
||||||
|
root,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 見る用
|
||||||
|
// const users = await usersRepository.findBy({ id: In(result) }).then(it => new Map(it.map(x => [x.id, x])));
|
||||||
|
// console.log(result.map(x => users.get(x as any)).map(it => it?.username));
|
||||||
|
|
||||||
|
// フォローしててアクティブなので先頭: alyssa, bob, bobbi
|
||||||
|
// フォローしてて非アクティブなので次: alyson, bobbie
|
||||||
|
// フォローしてないけどアクティブなので次: alyce, alycia, root(アルファベット順的にここになる)
|
||||||
|
// フォローしてないし非アクティブなので最後: alice, alysha, bobby
|
||||||
|
expect(result).toEqual([alyssa, bob, bobbi, alyson, bobbie, alyce, alycia, root, alice, alysha, bobby].map(x => x.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[非ログイン] アクティブユーザのうち、"al"から始まる人が全員ヒットする', async () => {
|
||||||
|
await setActive([alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]);
|
||||||
|
await setInactive([alice, alyce, alycia]);
|
||||||
|
|
||||||
|
const result = await service.search(
|
||||||
|
{ username: 'al' },
|
||||||
|
{ limit: 100 },
|
||||||
|
);
|
||||||
|
|
||||||
|
// alice, alyce, alyciaは非アクティブなので後ろに行く
|
||||||
|
expect(result).toEqual([alysha, alyson, alyssa, alice, alyce, alycia].map(x => x.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[非ログイン] 非アクティブユーザのうち、"al"から始まる人が全員ヒットする', async () => {
|
||||||
|
await setInactive([alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]);
|
||||||
|
|
||||||
|
const result = await service.search(
|
||||||
|
{ username: 'al' },
|
||||||
|
{ limit: 100 },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual([alice, alyce, alycia, alysha, alyson, alyssa].map(x => x.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('フォロー中のアクティブユーザのうち、"al"から始まり"example.com"にいる人が全員ヒットする', async () => {
|
||||||
|
await createFollowings(root, [alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]);
|
||||||
|
await setActive([alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]);
|
||||||
|
|
||||||
|
const result = await service.search(
|
||||||
|
{ username: 'al', host: 'exam' },
|
||||||
|
{ limit: 100 },
|
||||||
|
root,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual([alyson, alyssa].map(x => x.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('サスペンド済みユーザは出ない', async () => {
|
||||||
|
await setActive([alice, alyce, alycia, alysha, alyson, alyssa, bob, bobbi, bobbie, bobby]);
|
||||||
|
await setSuspended([alice, alyce, alycia]);
|
||||||
|
|
||||||
|
const result = await service.search(
|
||||||
|
{ username: 'al' },
|
||||||
|
{ limit: 100 },
|
||||||
|
root,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual([alysha, alyson, alyssa].map(x => x.id));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -18,6 +18,7 @@ import { entities } from '../src/postgres.js';
|
||||||
import { loadConfig } from '../src/config.js';
|
import { loadConfig } from '../src/config.js';
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
import { type Response } from 'node-fetch';
|
import { type Response } from 'node-fetch';
|
||||||
|
import { ApiError } from "@/server/api/error.js";
|
||||||
|
|
||||||
export { server as startServer, jobQueue as startJobQueue } from '@/boot/common.js';
|
export { server as startServer, jobQueue as startJobQueue } from '@/boot/common.js';
|
||||||
|
|
||||||
|
@ -48,27 +49,28 @@ export const successfulApiCall = async <E extends keyof misskey.Endpoints, P ext
|
||||||
const res = await api(endpoint, parameters, user);
|
const res = await api(endpoint, parameters, user);
|
||||||
const status = assertion.status ?? (res.body == null ? 204 : 200);
|
const status = assertion.status ?? (res.body == null ? 204 : 200);
|
||||||
assert.strictEqual(res.status, status, inspect(res.body, { depth: 5, colors: true }));
|
assert.strictEqual(res.status, status, inspect(res.body, { depth: 5, colors: true }));
|
||||||
return res.body;
|
|
||||||
|
return res.body as misskey.api.SwitchCaseResponseType<E, P>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const failedApiCall = async <T, E extends keyof misskey.Endpoints, P extends misskey.Endpoints[E]['req']>(request: ApiRequest<E, P>, assertion: {
|
export const failedApiCall = async <E extends keyof misskey.Endpoints, P extends misskey.Endpoints[E]['req']>(request: ApiRequest<E, P>, assertion: {
|
||||||
status: number,
|
status: number,
|
||||||
code: string,
|
code: string,
|
||||||
id: string
|
id: string
|
||||||
}): Promise<T> => {
|
}): Promise<void> => {
|
||||||
const { endpoint, parameters, user } = request;
|
const { endpoint, parameters, user } = request;
|
||||||
const { status, code, id } = assertion;
|
const { status, code, id } = assertion;
|
||||||
const res = await api(endpoint, parameters, user);
|
const res = await api(endpoint, parameters, user);
|
||||||
assert.strictEqual(res.status, status, inspect(res.body));
|
assert.strictEqual(res.status, status, inspect(res.body));
|
||||||
assert.strictEqual(res.body.error.code, code, inspect(res.body));
|
assert.ok(res.body);
|
||||||
assert.strictEqual(res.body.error.id, id, inspect(res.body));
|
assert.strictEqual(castAsError(res.body as any).error.code, code, inspect(res.body));
|
||||||
return res.body;
|
assert.strictEqual(castAsError(res.body as any).error.id, id, inspect(res.body));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const api = async <E extends keyof misskey.Endpoints>(path: E, params: misskey.Endpoints[E]['req'], me?: UserToken): Promise<{
|
export const api = async <E extends keyof misskey.Endpoints, P extends misskey.Endpoints[E]['req']>(path: E, params: P, me?: UserToken): Promise<{
|
||||||
status: number,
|
status: number,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
body: any
|
body: misskey.api.SwitchCaseResponseType<E, P>
|
||||||
}> => {
|
}> => {
|
||||||
const bodyAuth: Record<string, string> = {};
|
const bodyAuth: Record<string, string> = {};
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
|
@ -89,13 +91,14 @@ export const api = async <E extends keyof misskey.Endpoints>(path: E, params: mi
|
||||||
});
|
});
|
||||||
|
|
||||||
const body = res.headers.get('content-type') === 'application/json; charset=utf-8'
|
const body = res.headers.get('content-type') === 'application/json; charset=utf-8'
|
||||||
? await res.json()
|
? await res.json() as misskey.api.SwitchCaseResponseType<E, P>
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: res.status,
|
status: res.status,
|
||||||
headers: res.headers,
|
headers: res.headers,
|
||||||
body,
|
// FIXME: removing this non-null assertion: requires better typing around empty response.
|
||||||
|
body: body!,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -141,7 +144,8 @@ export const post = async (user: UserToken, params: misskey.Endpoints['notes/cre
|
||||||
|
|
||||||
const res = await api('notes/create', q, user);
|
const res = await api('notes/create', q, user);
|
||||||
|
|
||||||
return res.body ? res.body.createdNote : null;
|
// FIXME: the return type should reflect this fact.
|
||||||
|
return (res.body ? res.body.createdNote : null)!;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createAppToken = async (user: UserToken, permissions: (typeof misskey.permissions)[number][]) => {
|
export const createAppToken = async (user: UserToken, permissions: (typeof misskey.permissions)[number][]) => {
|
||||||
|
@ -635,3 +639,9 @@ export async function sendEnvResetRequest() {
|
||||||
throw new Error('server env update failed.');
|
throw new Error('server env update failed.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 与えられた値を強制的にエラーとみなす。この関数は型安全性を破壊するため、異常系のアサーション以外で用いられるべきではない。
|
||||||
|
// FIXME(misskey-js): misskey-jsがエラー情報を公開するようになったらこの関数を廃止する
|
||||||
|
export function castAsError(obj: Record<string, unknown>): { error: ApiError } {
|
||||||
|
return obj as { error: ApiError };
|
||||||
|
}
|
||||||
|
|
|
@ -53,7 +53,6 @@ await fs.readFile(
|
||||||
'../../assets/**',
|
'../../assets/**',
|
||||||
'../../fluent-emojis/**',
|
'../../fluent-emojis/**',
|
||||||
'../../locales/ja-JP.yml',
|
'../../locales/ja-JP.yml',
|
||||||
'../../misskey-assets/**',
|
|
||||||
'assets/**',
|
'assets/**',
|
||||||
'public/**',
|
'public/**',
|
||||||
'../../pnpm-lock.yaml',
|
'../../pnpm-lock.yaml',
|
||||||
|
|
|
@ -24,12 +24,12 @@
|
||||||
"@rollup/plugin-json": "6.1.0",
|
"@rollup/plugin-json": "6.1.0",
|
||||||
"@rollup/plugin-replace": "5.0.7",
|
"@rollup/plugin-replace": "5.0.7",
|
||||||
"@rollup/pluginutils": "5.1.0",
|
"@rollup/pluginutils": "5.1.0",
|
||||||
"@syuilo/aiscript": "0.18.0",
|
"@syuilo/aiscript": "0.19.0",
|
||||||
"@tabler/icons-webfont": "3.3.0",
|
"@tabler/icons-webfont": "3.3.0",
|
||||||
"@twemoji/parser": "15.1.1",
|
"@twemoji/parser": "15.1.1",
|
||||||
"@vitejs/plugin-vue": "5.0.5",
|
"@vitejs/plugin-vue": "5.1.0",
|
||||||
"@vue/compiler-sfc": "3.4.31",
|
"@vue/compiler-sfc": "3.4.34",
|
||||||
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.9",
|
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.11",
|
||||||
"astring": "1.8.6",
|
"astring": "1.8.6",
|
||||||
"broadcast-channel": "7.0.0",
|
"broadcast-channel": "7.0.0",
|
||||||
"buraha": "0.0.1",
|
"buraha": "0.0.1",
|
||||||
|
@ -39,9 +39,9 @@
|
||||||
"chartjs-chart-matrix": "2.0.1",
|
"chartjs-chart-matrix": "2.0.1",
|
||||||
"chartjs-plugin-gradient": "0.6.1",
|
"chartjs-plugin-gradient": "0.6.1",
|
||||||
"chartjs-plugin-zoom": "2.0.1",
|
"chartjs-plugin-zoom": "2.0.1",
|
||||||
"chromatic": "11.5.4",
|
"chromatic": "11.5.6",
|
||||||
"compare-versions": "6.1.0",
|
"compare-versions": "6.1.1",
|
||||||
"cropperjs": "2.0.0-beta.5",
|
"cropperjs": "2.0.0-rc.1",
|
||||||
"date-fns": "2.30.0",
|
"date-fns": "2.30.0",
|
||||||
"escape-regexp": "0.0.1",
|
"escape-regexp": "0.0.1",
|
||||||
"estree-walker": "3.0.3",
|
"estree-walker": "3.0.3",
|
||||||
|
@ -58,85 +58,85 @@
|
||||||
"misskey-reversi": "workspace:*",
|
"misskey-reversi": "workspace:*",
|
||||||
"photoswipe": "5.4.4",
|
"photoswipe": "5.4.4",
|
||||||
"punycode": "2.3.1",
|
"punycode": "2.3.1",
|
||||||
"rollup": "4.18.0",
|
"rollup": "4.19.1",
|
||||||
"sanitize-html": "2.13.0",
|
"sanitize-html": "2.13.0",
|
||||||
"sass": "1.77.6",
|
"sass": "1.77.8",
|
||||||
"shiki": "1.10.0",
|
"shiki": "1.12.0",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
"three": "0.165.0",
|
"three": "0.167.0",
|
||||||
"throttle-debounce": "5.0.2",
|
"throttle-debounce": "5.0.2",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tsc-alias": "1.8.10",
|
"tsc-alias": "1.8.10",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typescript": "5.5.3",
|
"typescript": "5.5.4",
|
||||||
"uuid": "10.0.0",
|
"uuid": "10.0.0",
|
||||||
"v-code-diff": "1.12.0",
|
"v-code-diff": "1.12.0",
|
||||||
"vite": "5.3.2",
|
"vite": "5.3.5",
|
||||||
"vue": "3.4.31",
|
"vue": "3.4.34",
|
||||||
"vuedraggable": "next"
|
"vuedraggable": "next"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/summaly": "5.1.0",
|
"@misskey-dev/summaly": "5.1.0",
|
||||||
"@storybook/addon-actions": "8.1.11",
|
"@storybook/addon-actions": "8.2.6",
|
||||||
"@storybook/addon-essentials": "8.1.11",
|
"@storybook/addon-essentials": "8.2.6",
|
||||||
"@storybook/addon-interactions": "8.1.11",
|
"@storybook/addon-interactions": "8.2.6",
|
||||||
"@storybook/addon-links": "8.1.11",
|
"@storybook/addon-links": "8.2.6",
|
||||||
"@storybook/addon-mdx-gfm": "8.1.11",
|
"@storybook/addon-mdx-gfm": "8.2.6",
|
||||||
"@storybook/addon-storysource": "8.1.11",
|
"@storybook/addon-storysource": "8.2.6",
|
||||||
"@storybook/blocks": "8.1.11",
|
"@storybook/blocks": "8.2.6",
|
||||||
"@storybook/components": "8.1.11",
|
"@storybook/components": "8.2.6",
|
||||||
"@storybook/core-events": "8.1.11",
|
"@storybook/core-events": "8.2.6",
|
||||||
"@storybook/manager-api": "8.1.11",
|
"@storybook/manager-api": "8.2.6",
|
||||||
"@storybook/preview-api": "8.1.11",
|
"@storybook/preview-api": "8.2.6",
|
||||||
"@storybook/react": "8.1.11",
|
"@storybook/react": "8.2.6",
|
||||||
"@storybook/react-vite": "8.1.11",
|
"@storybook/react-vite": "8.2.6",
|
||||||
"@storybook/test": "8.1.11",
|
"@storybook/test": "8.2.6",
|
||||||
"@storybook/theming": "8.1.11",
|
"@storybook/theming": "8.2.6",
|
||||||
"@storybook/types": "8.1.11",
|
"@storybook/types": "8.2.6",
|
||||||
"@storybook/vue3": "8.1.11",
|
"@storybook/vue3": "8.2.6",
|
||||||
"@storybook/vue3-vite": "8.1.11",
|
"@storybook/vue3-vite": "8.1.11",
|
||||||
"@testing-library/vue": "8.1.0",
|
"@testing-library/vue": "8.1.0",
|
||||||
"@types/escape-regexp": "0.0.3",
|
"@types/escape-regexp": "0.0.3",
|
||||||
"@types/estree": "1.0.5",
|
"@types/estree": "1.0.5",
|
||||||
"@types/matter-js": "0.19.6",
|
"@types/matter-js": "0.19.7",
|
||||||
"@types/micromatch": "4.0.9",
|
"@types/micromatch": "4.0.9",
|
||||||
"@types/node": "20.14.9",
|
"@types/node": "20.14.12",
|
||||||
"@types/punycode": "2.1.4",
|
"@types/punycode": "2.1.4",
|
||||||
"@types/sanitize-html": "2.11.0",
|
"@types/sanitize-html": "2.11.0",
|
||||||
"@types/seedrandom": "3.0.8",
|
"@types/seedrandom": "3.0.8",
|
||||||
"@types/throttle-debounce": "5.0.2",
|
"@types/throttle-debounce": "5.0.2",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"@types/uuid": "10.0.0",
|
"@types/uuid": "10.0.0",
|
||||||
"@types/ws": "8.5.10",
|
"@types/ws": "8.5.11",
|
||||||
"@typescript-eslint/eslint-plugin": "7.15.0",
|
"@typescript-eslint/eslint-plugin": "7.17.0",
|
||||||
"@typescript-eslint/parser": "7.15.0",
|
"@typescript-eslint/parser": "7.17.0",
|
||||||
"@vitest/coverage-v8": "1.6.0",
|
"@vitest/coverage-v8": "1.6.0",
|
||||||
"@vue/runtime-core": "3.4.31",
|
"@vue/runtime-core": "3.4.34",
|
||||||
"acorn": "8.12.0",
|
"acorn": "8.12.1",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "13.13.0",
|
"cypress": "13.13.1",
|
||||||
"eslint-plugin-import": "2.29.1",
|
"eslint-plugin-import": "2.29.1",
|
||||||
"eslint-plugin-vue": "9.26.0",
|
"eslint-plugin-vue": "9.27.0",
|
||||||
"fast-glob": "3.3.2",
|
"fast-glob": "3.3.2",
|
||||||
"happy-dom": "10.0.3",
|
"happy-dom": "10.0.3",
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"micromatch": "4.0.7",
|
"micromatch": "4.0.7",
|
||||||
"msw": "2.3.1",
|
"msw": "2.3.4",
|
||||||
"msw-storybook-addon": "2.0.2",
|
"msw-storybook-addon": "2.0.3",
|
||||||
"nodemon": "3.1.4",
|
"nodemon": "3.1.4",
|
||||||
"prettier": "3.3.2",
|
"prettier": "3.3.3",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"seedrandom": "3.0.5",
|
"seedrandom": "3.0.5",
|
||||||
"start-server-and-test": "2.0.4",
|
"start-server-and-test": "2.0.4",
|
||||||
"storybook": "8.1.11",
|
"storybook": "8.2.6",
|
||||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
||||||
"vite-plugin-turbosnap": "1.0.3",
|
"vite-plugin-turbosnap": "1.0.3",
|
||||||
"vitest": "1.6.0",
|
"vitest": "1.6.0",
|
||||||
"vitest-fetch-mock": "0.2.2",
|
"vitest-fetch-mock": "0.2.2",
|
||||||
"vue-component-type-helpers": "2.0.24",
|
"vue-component-type-helpers": "2.0.29",
|
||||||
"vue-eslint-parser": "9.4.3",
|
"vue-eslint-parser": "9.4.3",
|
||||||
"vue-tsc": "2.0.24"
|
"vue-tsc": "2.0.29"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue