mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-07-02 20:24:52 +02:00
Compare commits
15 Commits
2026.6.1-a
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1eedf04d9a | ||
|
|
2d67380f2a | ||
|
|
174fb434cc | ||
|
|
7e29f04287 | ||
|
|
bf88122140 | ||
|
|
4daa1ffe05 | ||
|
|
62f8589c05 | ||
|
|
6193c35f9f | ||
|
|
1220f05903 | ||
|
|
7544ebf7a3 | ||
|
|
ffe65caf10 | ||
|
|
4f993cef1b | ||
|
|
ba3fb4aa14 | ||
|
|
0137b1c406 | ||
|
|
797dec7d0e |
4
.github/workflows/api-misskey-js.yml
vendored
4
.github/workflows/api-misskey-js.yml
vendored
@@ -16,10 +16,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6.0.2
|
||||
uses: actions/checkout@v6.0.3
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
uses: pnpm/action-setup@v6.0.9
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6.4.0
|
||||
|
||||
2
.github/workflows/changelog-check.yml
vendored
2
.github/workflows/changelog-check.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout head
|
||||
uses: actions/checkout@v6.0.2
|
||||
uses: actions/checkout@v6.0.3
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v6.0.2
|
||||
uses: actions/checkout@v6.0.3
|
||||
with:
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v6.0.2
|
||||
uses: actions/checkout@v6.0.3
|
||||
with:
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6.0.2
|
||||
uses: actions/checkout@v6.0.3
|
||||
- name: Check version
|
||||
run: |
|
||||
if [ "$(jq -r '.version' package.json)" != "$(jq -r '.version' packages/misskey-js/package.json)" ]; then
|
||||
|
||||
2
.github/workflows/check-spdx-license-id.yml
vendored
2
.github/workflows/check-spdx-license-id.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6.0.2
|
||||
uses: actions/checkout@v6.0.3
|
||||
- name: Check
|
||||
run: |
|
||||
counter=0
|
||||
|
||||
2
.github/workflows/check_copyright_year.yml
vendored
2
.github/workflows/check_copyright_year.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
check_copyright_year:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- uses: actions/checkout@v6.0.3
|
||||
- run: |
|
||||
if [ "$(grep Copyright COPYING | sed -e 's/.*2014-\([0-9]*\) .*/\1/g')" -ne "$(date +%Y)" ]; then
|
||||
echo "Please change copyright year!"
|
||||
|
||||
2
.github/workflows/docker-develop.yml
vendored
2
.github/workflows/docker-develop.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v6.0.2
|
||||
uses: actions/checkout@v6.0.3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
- name: Log in to Docker Hub
|
||||
|
||||
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v6.0.2
|
||||
uses: actions/checkout@v6.0.3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
- name: Docker meta
|
||||
|
||||
2
.github/workflows/dockle.yml
vendored
2
.github/workflows/dockle.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
DOCKLE_VERSION: 0.4.15
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- uses: actions/checkout@v6.0.3
|
||||
|
||||
- name: Download and install dockle v${{ env.DOCKLE_VERSION }}
|
||||
run: |
|
||||
|
||||
6
.github/workflows/frontend-bundle-report.yml
vendored
6
.github/workflows/frontend-bundle-report.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
FRONTEND_JS_SIZE_LOCALE: ja-JP
|
||||
steps:
|
||||
- name: Checkout base
|
||||
uses: actions/checkout@v6.0.2
|
||||
uses: actions/checkout@v6.0.3
|
||||
with:
|
||||
repository: ${{ github.event.pull_request.base.repo.full_name }}
|
||||
ref: ${{ github.event.pull_request.base.sha }}
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
submodules: true
|
||||
|
||||
- name: Checkout pull request
|
||||
uses: actions/checkout@v6.0.2
|
||||
uses: actions/checkout@v6.0.3
|
||||
with:
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
|
||||
- name: Setup pnpm
|
||||
if: steps.check-base-visualizer.outputs.supported == 'true'
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
uses: pnpm/action-setup@v6.0.9
|
||||
with:
|
||||
package_json_file: after/package.json
|
||||
|
||||
|
||||
4
.github/workflows/get-api-diff.yml
vendored
4
.github/workflows/get-api-diff.yml
vendored
@@ -25,12 +25,12 @@ jobs:
|
||||
ref: refs/pull/${{ github.event.number }}/merge
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- uses: actions/checkout@v6.0.3
|
||||
with:
|
||||
ref: ${{ matrix.ref }}
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
uses: pnpm/action-setup@v6.0.9
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
|
||||
6
.github/workflows/get-backend-memory.yml
vendored
6
.github/workflows/get-backend-memory.yml
vendored
@@ -39,19 +39,19 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout base
|
||||
uses: actions/checkout@v6.0.2
|
||||
uses: actions/checkout@v6.0.3
|
||||
with:
|
||||
ref: ${{ github.base_ref }}
|
||||
path: base
|
||||
submodules: true
|
||||
- name: Checkout head
|
||||
uses: actions/checkout@v6.0.2
|
||||
uses: actions/checkout@v6.0.3
|
||||
with:
|
||||
ref: refs/pull/${{ github.event.number }}/merge
|
||||
path: head
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
uses: pnpm/action-setup@v6.0.9
|
||||
with:
|
||||
package_json_file: head/package.json
|
||||
- name: Use Node.js
|
||||
|
||||
12
.github/workflows/lint.yml
vendored
12
.github/workflows/lint.yml
vendored
@@ -36,12 +36,12 @@ jobs:
|
||||
pnpm_install:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- uses: actions/checkout@v6.0.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
uses: pnpm/action-setup@v6.0.9
|
||||
- uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
@@ -69,12 +69,12 @@ jobs:
|
||||
eslint-cache-version: v1
|
||||
eslint-cache-path: ${{ github.workspace }}/node_modules/.cache/eslint-${{ matrix.workspace }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- uses: actions/checkout@v6.0.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
uses: pnpm/action-setup@v6.0.9
|
||||
- uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
@@ -100,12 +100,12 @@ jobs:
|
||||
- sw
|
||||
- misskey-js
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- uses: actions/checkout@v6.0.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
uses: pnpm/action-setup@v6.0.9
|
||||
- uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
|
||||
4
.github/workflows/locale.yml
vendored
4
.github/workflows/locale.yml
vendored
@@ -16,12 +16,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- uses: actions/checkout@v6.0.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
uses: pnpm/action-setup@v6.0.9
|
||||
- uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: ".node-version"
|
||||
|
||||
4
.github/workflows/on-release-created.yml
vendored
4
.github/workflows/on-release-created.yml
vendored
@@ -16,11 +16,11 @@ jobs:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- uses: actions/checkout@v6.0.3
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
uses: pnpm/action-setup@v6.0.9
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
|
||||
2
.github/workflows/report-backend-memory.yml
vendored
2
.github/workflows/report-backend-memory.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6.0.2
|
||||
uses: actions/checkout@v6.0.3
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
|
||||
6
.github/workflows/storybook.yml
vendored
6
.github/workflows/storybook.yml
vendored
@@ -22,12 +22,12 @@ jobs:
|
||||
NODE_OPTIONS: "--max_old_space_size=7168"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- uses: actions/checkout@v6.0.3
|
||||
if: github.event_name != 'pull_request_target'
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- uses: actions/checkout@v6.0.3
|
||||
if: github.event_name == 'pull_request_target'
|
||||
with:
|
||||
fetch-depth: 0
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
if: github.event_name == 'pull_request_target'
|
||||
run: git checkout "$(git rev-list --parents -n1 HEAD | cut -d" " -f3)"
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
uses: pnpm/action-setup@v6.0.9
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
|
||||
14
.github/workflows/test-backend.yml
vendored
14
.github/workflows/test-backend.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
ports:
|
||||
- 56312:6379
|
||||
meilisearch:
|
||||
image: getmeili/meilisearch:v1.42.1
|
||||
image: getmeili/meilisearch:v1.48.1
|
||||
ports:
|
||||
- 57712:7700
|
||||
env:
|
||||
@@ -51,11 +51,11 @@ jobs:
|
||||
MEILI_ENV: development
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- uses: actions/checkout@v6.0.3
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
uses: pnpm/action-setup@v6.0.9
|
||||
- name: Install FFmpeg
|
||||
run: |
|
||||
sudo apt install -y ffmpeg
|
||||
@@ -103,11 +103,11 @@ jobs:
|
||||
- 56312:6379
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- uses: actions/checkout@v6.0.3
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
uses: pnpm/action-setup@v6.0.9
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
@@ -147,11 +147,11 @@ jobs:
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- uses: actions/checkout@v6.0.3
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
uses: pnpm/action-setup@v6.0.9
|
||||
- name: Get current date
|
||||
id: current-date
|
||||
run: echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
|
||||
2
.github/workflows/test-federation.yml
vendored
2
.github/workflows/test-federation.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
uses: pnpm/action-setup@v6.0.9
|
||||
- name: Install FFmpeg
|
||||
run: |
|
||||
sudo apt install -y ffmpeg
|
||||
|
||||
10
.github/workflows/test-frontend.yml
vendored
10
.github/workflows/test-frontend.yml
vendored
@@ -28,11 +28,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- uses: actions/checkout@v6.0.3
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
uses: pnpm/action-setup@v6.0.9
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
- 56312:6379
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- uses: actions/checkout@v6.0.3
|
||||
with:
|
||||
submodules: true
|
||||
# https://github.com/cypress-io/cypress-docker-images/issues/150
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
#- uses: browser-actions/setup-firefox@latest
|
||||
# if: ${{ matrix.browser == 'firefox' }}
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
uses: pnpm/action-setup@v6.0.9
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
- name: Cypress install
|
||||
run: pnpm exec cypress install
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@v7.1.9
|
||||
uses: cypress-io/github-action@v7.4.0
|
||||
timeout-minutes: 15
|
||||
with:
|
||||
install: false
|
||||
|
||||
4
.github/workflows/test-misskey-js.yml
vendored
4
.github/workflows/test-misskey-js.yml
vendored
@@ -22,10 +22,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6.0.2
|
||||
uses: actions/checkout@v6.0.3
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
uses: pnpm/action-setup@v6.0.9
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6.4.0
|
||||
|
||||
4
.github/workflows/test-production.yml
vendored
4
.github/workflows/test-production.yml
vendored
@@ -16,11 +16,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- uses: actions/checkout@v6.0.3
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
uses: pnpm/action-setup@v6.0.9
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
|
||||
4
.github/workflows/validate-api-json.yml
vendored
4
.github/workflows/validate-api-json.yml
vendored
@@ -17,11 +17,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- uses: actions/checkout@v6.0.3
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
uses: pnpm/action-setup@v6.0.9
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
|
||||
@@ -1 +1 @@
|
||||
22.23.1
|
||||
26.4.0
|
||||
|
||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,25 +1,33 @@
|
||||
## 2026.6.1
|
||||
|
||||
### Note
|
||||
|
||||
**今回のリリースではMisskeyの各種動作要件が変更されます。必ずアップグレード前にお使いの環境をご確認ください。**
|
||||
|
||||
### Note
|
||||
- センシティブメディアの判定 (NSFW検出) が、本体に内蔵された nsfwjs による推論から、外部サービス [sensitive-detector](https://github.com/misskey-dev/sensitive-detector) への HTTP 呼び出し方式に変更されました。
|
||||
- これに伴い、本体から `nsfwjs` / `@tensorflow/tfjs` / `@tensorflow/tfjs-node` および同梱の NSFW 判定モデルが削除され、インストール要件 (ネイティブ ML スタック) が緩和されました。
|
||||
- **センシティブ判定機能を利用しているサーバーは対応が必要です。** 別途 [sensitive-detector](https://github.com/misskey-dev/sensitive-detector) サービスを立ち上げ、コントロールパネルの「モデレーション > センシティブなメディアの検出」で接続先 URL を設定してください。接続先が未設定の場合、センシティブ判定は行われません (すべて非センシティブ扱い)。
|
||||
- 画像の正規化・動画フレームの抽出・しきい値判定・集約は引き続き本体側で行われ、外部サービスには正規化済み画像の推論のみを委譲します。
|
||||
- バックエンドで画像処理に用いているライブラリ sharp のシステム要件の変更により、**SSE4.2 命令セットをサポートしていない CPU では Misskey が正しく動作しなくなります**。仮想マシンに Misskey をデプロイしている場合や、古いハードウェアをお使いの場合は、アップデート前にお使いの環境をご確認ください。
|
||||
- Node.jsの最低サポートバージョンを 22.22.2 に引き上げました。これより古いバージョンの Node.js では Misskey が起動しません。
|
||||
- Node.js v24, v26 をサポートしました。**Node.js v22 でも動作しますが、今後のリリースで v22 のサポートを終了する予定**ですので、Node.js のアップデートをご検討ください。
|
||||
- Node.js のセキュリティアップデートに伴い、最低動作バージョンを 22.22.2 / 24.17.0 / 26.4.0 に引き上げました。
|
||||
- Docker Image は Node.js 26.4.0-trixie に更新されています。
|
||||
- バックエンドで画像処理に用いているライブラリ sharp のシステム要件の変更により、**SSE4.2 命令セットをサポートしていない x86_64 CPU では Misskey が正しく動作しなくなります**。仮想マシンに Misskey をデプロイしている場合や、古いハードウェアをお使いの場合は、アップデート前にお使いの環境をご確認ください。なお、ARM64 など x86_64 ではない環境においてはこの変更による影響はありません。
|
||||
|
||||
### General
|
||||
- Feat: コントロールパネルから二要素認証を解除できるように
|
||||
|
||||
### Client
|
||||
- Fix: チャットでIMEの変換を確定するEnterでメッセージが送信されてしまうことがある問題を修正
|
||||
- 2025.4.0 以前の設定情報の移行処理が削除されました
|
||||
- 2025.4.0 から直接 2026.6.0 以上にアップデートする場合は設定が移行されませんので注意してください。移行したい場合は一度 2026.5.1 を経由してください。
|
||||
- Fix: デバイスタイプをスマートフォンに固定している状態で画面幅が広いとき、画面左上のアイコンが表示されない問題を修正
|
||||
|
||||
### Server
|
||||
- Enhance: センシティブメディアの判定を外部サービス ([sensitive-detector](https://github.com/misskey-dev/sensitive-detector)) に分離し、`nsfwjs` / `@tensorflow/tfjs(-node)` の同梱と NSFW 判定モデルを廃止 (#16804)
|
||||
|
||||
- Enhance: Node.js 22.23.0以降、24.17.0以降、26.4.0以降をサポートするように
|
||||
- Enhance: Docker Image の Node.js を 26.4.0 に、Debian を trixie (v13) に更新
|
||||
- Fix: `/stats` API のレスポンス型が正しくない問題を修正
|
||||
- Fix: ハッシュタグに関連するデータを更新する際のエラーハンドリングを修正
|
||||
|
||||
## 2026.6.0
|
||||
|
||||
|
||||
@@ -600,6 +600,90 @@ TypeScriptでjsonをimportすると、tscでコンパイルするときにその
|
||||
コンポーネント自身がmarginを設定するのは問題の元となることはよく知られている
|
||||
marginはそのコンポーネントを使う側が設定する
|
||||
|
||||
### 命名規則
|
||||
|
||||
本来それが略称であっても、通常それでひとつのワードとして用いられるものは、略称として扱わない。
|
||||
|
||||
#### 例: IP address
|
||||
|
||||
Good: `ipAddress` / `IpAddress`
|
||||
|
||||
Bad: `IPAddress`
|
||||
|
||||
#### 例: User ID
|
||||
|
||||
Good: `userId` / `UserId`
|
||||
|
||||
Bad: `userID` / `UserID`
|
||||
|
||||
#### 例: XMLなHTTPのRequest
|
||||
|
||||
Good: `xmlHttpRequest` / `XmlHttpRequest`
|
||||
|
||||
Bad: `XMLHttpRequest` / `XMLHTTPRequest`
|
||||
|
||||
### 関数化の基準
|
||||
|
||||
汎用性が低く(例えばそれを関数化したとしてもその呼び出しが元の場所一か所しか存在しない)、内容も短い処理(例えば10行以下)は、かえって読みにくくなるため、関数化しない。
|
||||
|
||||
また、関数化する場合でも、呼び出しがある特定のスコープに限られる場合は、そのスコープ内に閉じ込めた方が分かりやすく簡潔になる場合がある(ただし本来その処理に不要であっても、構造上親のスコープにある関係のない変数や引数にもアクセスできるようになるため、必ずしもそうすれば設計上綺麗になるというわけでもない。状況に応じて判断すべし)。
|
||||
|
||||
Bad:
|
||||
|
||||
``` ts
|
||||
function withBrankets(x) {
|
||||
return `(${x})`;
|
||||
}
|
||||
|
||||
function formatPercent(x) {
|
||||
return `${x}%`;
|
||||
}
|
||||
|
||||
function formatValue(x) {
|
||||
return withBrankets(formatPercent(x));
|
||||
}
|
||||
|
||||
function showData(a, b) {
|
||||
console.log(formatValue(a));
|
||||
console.log(formatValue(b));
|
||||
}
|
||||
```
|
||||
|
||||
Good:
|
||||
|
||||
``` ts
|
||||
function formatValue(x) {
|
||||
return `(${x}%)`;
|
||||
}
|
||||
|
||||
function showData(a, b) {
|
||||
console.log(formatValue(a));
|
||||
console.log(formatValue(b));
|
||||
}
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
``` ts
|
||||
function showData(a, b) {
|
||||
function formatValue(x) {
|
||||
return `(${x}%)`;
|
||||
}
|
||||
|
||||
console.log(formatValue(a));
|
||||
console.log(formatValue(b));
|
||||
}
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
``` ts
|
||||
function showData(a, b) {
|
||||
console.log(`(${a}%)`);
|
||||
console.log(`(${b}%)`);
|
||||
}
|
||||
```
|
||||
|
||||
## その他
|
||||
### HTMLのクラス名で follow という単語は使わない
|
||||
広告ブロッカーで誤ってブロックされる
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.23
|
||||
|
||||
ARG NODE_VERSION=22.22.2-bookworm
|
||||
ARG NODE_VERSION=26.4.0-trixie
|
||||
|
||||
# build assets & compile TypeScript
|
||||
|
||||
|
||||
@@ -53,20 +53,17 @@
|
||||
"cleanall": "pnpm clean-all"
|
||||
},
|
||||
"dependencies": {
|
||||
"cssnano": "8.0.2",
|
||||
"esbuild": "0.28.1",
|
||||
"execa": "9.6.1",
|
||||
"ignore-walk": "9.0.0",
|
||||
"js-yaml": "4.2.0",
|
||||
"postcss": "8.5.15",
|
||||
"tar": "7.5.16",
|
||||
"terser": "5.48.0"
|
||||
"tar": "7.5.16"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "9.39.4",
|
||||
"@misskey-dev/eslint-plugin": "2.1.0",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/node": "24.13.2",
|
||||
"@types/node": "26.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.61.1",
|
||||
"@typescript-eslint/parser": "8.61.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260426.1",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^22.22.2 || ^24.10.0"
|
||||
"node": "^22.22.2 || ^24.17.0 || ^26.4.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "pnpm compile-config && node ./built/entry.js",
|
||||
@@ -128,7 +128,6 @@
|
||||
"reflect-metadata": "0.2.2",
|
||||
"rename": "1.0.4",
|
||||
"rss-parser": "3.13.0",
|
||||
"rxjs": "7.8.2",
|
||||
"sanitize-html": "2.17.5",
|
||||
"secure-json-parse": "4.1.0",
|
||||
"semver": "7.8.5",
|
||||
@@ -160,7 +159,7 @@
|
||||
"@types/jsonld": "1.5.15",
|
||||
"@types/mime-types": "3.0.1",
|
||||
"@types/ms": "2.1.0",
|
||||
"@types/node": "24.13.2",
|
||||
"@types/node": "26.0.0",
|
||||
"@types/nodemailer": "8.0.1",
|
||||
"@types/pg": "8.20.0",
|
||||
"@types/qrcode": "1.5.6",
|
||||
@@ -181,7 +180,7 @@
|
||||
"@typescript-eslint/parser": "8.61.1",
|
||||
"@vitest/coverage-v8": "4.1.9",
|
||||
"aws-sdk-client-mock": "4.1.0",
|
||||
"cbor": "10.0.12",
|
||||
"cbor2": "2.3.0",
|
||||
"cross-env": "10.1.0",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"execa": "9.6.1",
|
||||
|
||||
@@ -137,7 +137,8 @@ export class AiService {
|
||||
try {
|
||||
const form = new FormData();
|
||||
for (let i = 0; i < chunk.length; i++) {
|
||||
form.append(`image${i}`, new Blob([chunk[i]], { type: 'image/png' }), `${i}.png`);
|
||||
const image = Uint8Array.from(chunk[i]);
|
||||
form.append(`image${i}`, new Blob([image], { type: 'image/png' }), `${i}.png`);
|
||||
}
|
||||
|
||||
// Content-Type は FormData から boundary 付きで自動設定させるため、手動設定はしない。
|
||||
|
||||
@@ -5,20 +5,28 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { MiHashtag } from '@/models/Hashtag.js';
|
||||
import { MiHashtag } from '@/models/Hashtag.js';
|
||||
import type { HashtagsRepository, MiMeta } from '@/models/_.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { FeaturedService } from '@/core/FeaturedService.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
|
||||
import Logger from '../logger.js';
|
||||
|
||||
const logger = new Logger('hashtag/create');
|
||||
|
||||
@Injectable()
|
||||
export class HashtagService {
|
||||
constructor(
|
||||
@Inject(DI.db)
|
||||
private db: DataSource,
|
||||
|
||||
@Inject(DI.meta)
|
||||
private meta: MiMeta,
|
||||
|
||||
@@ -60,19 +68,74 @@ export class HashtagService {
|
||||
// TODO: サンプリング
|
||||
this.updateHashtagsRanking(tag, user.id);
|
||||
|
||||
const index = await this.hashtagsRepository.findOneBy({ name: tag });
|
||||
{
|
||||
const index = await this.hashtagsRepository.findOneBy({ name: tag });
|
||||
|
||||
if (index == null && !inc) return;
|
||||
if (index == null && inc) {
|
||||
try {
|
||||
if (isUserAttached) {
|
||||
await this.hashtagsRepository.insert({
|
||||
id: this.idService.gen(),
|
||||
name: tag,
|
||||
mentionedUserIds: [],
|
||||
mentionedUsersCount: 0,
|
||||
mentionedLocalUserIds: [],
|
||||
mentionedLocalUsersCount: 0,
|
||||
mentionedRemoteUserIds: [],
|
||||
mentionedRemoteUsersCount: 0,
|
||||
attachedUserIds: [user.id],
|
||||
attachedUsersCount: 1,
|
||||
attachedLocalUserIds: this.userEntityService.isLocalUser(user) ? [user.id] : [],
|
||||
attachedLocalUsersCount: this.userEntityService.isLocalUser(user) ? 1 : 0,
|
||||
attachedRemoteUserIds: this.userEntityService.isRemoteUser(user) ? [user.id] : [],
|
||||
attachedRemoteUsersCount: this.userEntityService.isRemoteUser(user) ? 1 : 0,
|
||||
} as MiHashtag);
|
||||
} else {
|
||||
await this.hashtagsRepository.insert({
|
||||
id: this.idService.gen(),
|
||||
name: tag,
|
||||
mentionedUserIds: [user.id],
|
||||
mentionedUsersCount: 1,
|
||||
mentionedLocalUserIds: this.userEntityService.isLocalUser(user) ? [user.id] : [],
|
||||
mentionedLocalUsersCount: this.userEntityService.isLocalUser(user) ? 1 : 0,
|
||||
mentionedRemoteUserIds: this.userEntityService.isRemoteUser(user) ? [user.id] : [],
|
||||
mentionedRemoteUsersCount: this.userEntityService.isRemoteUser(user) ? 1 : 0,
|
||||
attachedUserIds: [],
|
||||
attachedUsersCount: 0,
|
||||
attachedLocalUserIds: [],
|
||||
attachedLocalUsersCount: 0,
|
||||
attachedRemoteUserIds: [],
|
||||
attachedRemoteUsersCount: 0,
|
||||
} as MiHashtag);
|
||||
}
|
||||
return;
|
||||
} catch (err) {
|
||||
if (isDuplicateKeyValueError(err)) {
|
||||
logger.info(`Duplicate insertion detected. Falling back to update. #${tag}`);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (index != null) {
|
||||
const q = this.hashtagsRepository.createQueryBuilder('tag').update()
|
||||
.where('name = :name', { name: tag });
|
||||
await this.db.transaction(async transactionalEntityManager => {
|
||||
const transactionalHashtagRepository = transactionalEntityManager
|
||||
.getRepository(MiHashtag);
|
||||
|
||||
const index = await transactionalHashtagRepository
|
||||
.createQueryBuilder()
|
||||
.setLock('pessimistic_write')
|
||||
.where('name = :name', { name: tag })
|
||||
.getOne();
|
||||
|
||||
if (index == null) return;
|
||||
|
||||
const set = {} as any;
|
||||
|
||||
if (isUserAttached) {
|
||||
if (inc) {
|
||||
// 自分が初めてこのタグを使ったなら
|
||||
// 自分が初めてこのタグを使ったなら
|
||||
if (!index.attachedUserIds.some(id => id === user.id)) {
|
||||
set.attachedUserIds = () => `array_append("attachedUserIds", '${user.id}')`;
|
||||
set.attachedUsersCount = () => '"attachedUsersCount" + 1';
|
||||
@@ -117,46 +180,14 @@ export class HashtagService {
|
||||
}
|
||||
|
||||
if (Object.keys(set).length > 0) {
|
||||
q.set(set);
|
||||
q.execute();
|
||||
await transactionalHashtagRepository
|
||||
.createQueryBuilder()
|
||||
.update()
|
||||
.where('id = :id', { id: index.id })
|
||||
.set(set)
|
||||
.execute();
|
||||
}
|
||||
} else {
|
||||
if (isUserAttached) {
|
||||
this.hashtagsRepository.insert({
|
||||
id: this.idService.gen(),
|
||||
name: tag,
|
||||
mentionedUserIds: [],
|
||||
mentionedUsersCount: 0,
|
||||
mentionedLocalUserIds: [],
|
||||
mentionedLocalUsersCount: 0,
|
||||
mentionedRemoteUserIds: [],
|
||||
mentionedRemoteUsersCount: 0,
|
||||
attachedUserIds: [user.id],
|
||||
attachedUsersCount: 1,
|
||||
attachedLocalUserIds: this.userEntityService.isLocalUser(user) ? [user.id] : [],
|
||||
attachedLocalUsersCount: this.userEntityService.isLocalUser(user) ? 1 : 0,
|
||||
attachedRemoteUserIds: this.userEntityService.isRemoteUser(user) ? [user.id] : [],
|
||||
attachedRemoteUsersCount: this.userEntityService.isRemoteUser(user) ? 1 : 0,
|
||||
} as MiHashtag);
|
||||
} else {
|
||||
this.hashtagsRepository.insert({
|
||||
id: this.idService.gen(),
|
||||
name: tag,
|
||||
mentionedUserIds: [user.id],
|
||||
mentionedUsersCount: 1,
|
||||
mentionedLocalUserIds: this.userEntityService.isLocalUser(user) ? [user.id] : [],
|
||||
mentionedLocalUsersCount: this.userEntityService.isLocalUser(user) ? 1 : 0,
|
||||
mentionedRemoteUserIds: this.userEntityService.isRemoteUser(user) ? [user.id] : [],
|
||||
mentionedRemoteUsersCount: this.userEntityService.isRemoteUser(user) ? 1 : 0,
|
||||
attachedUserIds: [],
|
||||
attachedUsersCount: 0,
|
||||
attachedLocalUserIds: [],
|
||||
attachedLocalUsersCount: 0,
|
||||
attachedRemoteUserIds: [],
|
||||
attachedRemoteUsersCount: 0,
|
||||
} as MiHashtag);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
||||
@@ -35,6 +35,14 @@ export const meta = {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
reactionsCount: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
//originalReactionsCount: {
|
||||
// type: 'number',
|
||||
// optional: false, nullable: false,
|
||||
//},
|
||||
instances: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
|
||||
@@ -7,7 +7,7 @@ process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as crypto from 'node:crypto';
|
||||
import cbor from 'cbor';
|
||||
import { encode as encodeToCbor } from 'cbor2';
|
||||
import * as OTPAuth from 'otpauth';
|
||||
import { loadConfig } from '@/config.js';
|
||||
import { api, signup, sendEnvUpdateRequest } from '../utils.js';
|
||||
@@ -61,7 +61,7 @@ describe('2要素認証', () => {
|
||||
const keyDoneParam = (param: {
|
||||
token: string,
|
||||
keyName: string,
|
||||
credentialId: Buffer,
|
||||
credentialId: Uint8Array,
|
||||
creationOptions: PublicKeyCredentialCreationOptionsJSON,
|
||||
}): {
|
||||
token: string,
|
||||
@@ -70,10 +70,10 @@ describe('2要素認証', () => {
|
||||
credential: RegistrationResponseJSON,
|
||||
} => {
|
||||
// A COSE encoded public key
|
||||
const credentialPublicKey = cbor.encode(new Map<number, unknown>([
|
||||
const credentialPublicKey = encodeToCbor(new Map<number, unknown>([
|
||||
[-1, coseEc2CrvP256],
|
||||
[-2, Buffer.from(coseEc2X, 'hex')],
|
||||
[-3, Buffer.from(coseEc2Y, 'hex')],
|
||||
[-2, Uint8Array.from(Buffer.from(coseEc2X, 'hex'))],
|
||||
[-3, Uint8Array.from(Buffer.from(coseEc2Y, 'hex'))],
|
||||
[1, coseKtyEc2],
|
||||
[2, coseKid],
|
||||
[3, coseAlgEs256],
|
||||
@@ -85,21 +85,23 @@ describe('2要素認証', () => {
|
||||
credentialIdLength.writeUInt16BE(param.credentialId.length, 0);
|
||||
const authData = Buffer.concat([
|
||||
rpIdHash(), // rpIdHash(32)
|
||||
Buffer.from([0x45]), // flags(1)
|
||||
Buffer.from([0x00, 0x00, 0x00, 0x00]), // signCount(4)
|
||||
Buffer.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), // AAGUID(16)
|
||||
new Uint8Array([0x45]), // flags(1)
|
||||
new Uint8Array(4), // signCount(4)
|
||||
new Uint8Array(16), // AAGUID(16)
|
||||
credentialIdLength,
|
||||
param.credentialId,
|
||||
credentialPublicKey,
|
||||
]);
|
||||
|
||||
const credentialIdBase64url = Buffer.from(param.credentialId).toString('base64url');
|
||||
|
||||
return {
|
||||
password,
|
||||
token: param.token,
|
||||
name: param.keyName,
|
||||
credential: <RegistrationResponseJSON>{
|
||||
id: param.credentialId.toString('base64url'),
|
||||
rawId: param.credentialId.toString('base64url'),
|
||||
id: credentialIdBase64url,
|
||||
rawId: credentialIdBase64url,
|
||||
response: <AuthenticatorAttestationResponseJSON>{
|
||||
clientDataJSON: Buffer.from(JSON.stringify({
|
||||
type: 'webauthn.create',
|
||||
@@ -107,11 +109,11 @@ describe('2要素認証', () => {
|
||||
origin: config.scheme + '://' + config.host,
|
||||
androidPackageName: 'org.mozilla.firefox',
|
||||
}), 'utf-8').toString('base64url'),
|
||||
attestationObject: cbor.encode({
|
||||
attestationObject: Buffer.from(encodeToCbor({
|
||||
fmt: 'none',
|
||||
attStmt: {},
|
||||
authData,
|
||||
}).toString('base64url'),
|
||||
authData: new Uint8Array(authData),
|
||||
})).toString('base64url'),
|
||||
},
|
||||
clientExtensionResults: {},
|
||||
type: 'public-key',
|
||||
|
||||
@@ -8,9 +8,9 @@ process.env.NODE_ENV = 'test';
|
||||
import * as assert from 'assert';
|
||||
import { describe, beforeAll, test } from 'vitest';
|
||||
import { WebSocket } from 'ws';
|
||||
import { MiFollowing } from '@/models/Following.js';
|
||||
import { api, createAppToken, initTestDb, port, post, signup, waitFire } from '../utils.js';
|
||||
import { api, connectStream, createAppToken, initTestDb, port, post, signup, waitFire } from '../utils.js';
|
||||
import type * as misskey from 'misskey-js';
|
||||
import { MiFollowing } from '@/models/Following.js';
|
||||
|
||||
describe('Streaming', () => {
|
||||
let Followings: any;
|
||||
@@ -744,164 +744,83 @@ describe('Streaming', () => {
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
// XXX: QueryFailedError: duplicate key value violates unique constraint "IDX_347fec870eafea7b26c8a73bac"
|
||||
/*
|
||||
describe('Hashtag Timeline', () => {
|
||||
test('指定したハッシュタグの投稿が流れる', () => new Promise<void>(async done => {
|
||||
const ws = await connectStream(chitose, 'hashtag', ({ type, body }) => {
|
||||
if (type === 'note') {
|
||||
assert.deepStrictEqual(body.text, '#foo');
|
||||
ws.close();
|
||||
done();
|
||||
}
|
||||
}, {
|
||||
q: [
|
||||
['foo'],
|
||||
],
|
||||
});
|
||||
test('指定したハッシュタグの投稿が流れる', async () => {
|
||||
const fired = await waitFire(
|
||||
chitose, 'hashtag',
|
||||
() => api('notes/create', { text: '#foo' }, chitose),
|
||||
msg => msg.type === 'note' && msg.body.text === '#foo',
|
||||
{ q: [['foo']] },
|
||||
);
|
||||
|
||||
post(chitose, {
|
||||
text: '#foo',
|
||||
});
|
||||
}));
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
test('指定したハッシュタグの投稿が流れる (AND)', () => new Promise<void>(async done => {
|
||||
let fooCount = 0;
|
||||
let barCount = 0;
|
||||
let fooBarCount = 0;
|
||||
test('指定したハッシュタグの投稿が流れる (AND)', async () => {
|
||||
const received: string[] = [];
|
||||
const ws = await connectStream(chitose, 'hashtag', (msg) => {
|
||||
if (msg.type === 'note') received.push(msg.body.text);
|
||||
}, { q: [['foo', 'bar']] });
|
||||
|
||||
const ws = await connectStream(chitose, 'hashtag', ({ type, body }) => {
|
||||
if (type === 'note') {
|
||||
if (body.text === '#foo') fooCount++;
|
||||
if (body.text === '#bar') barCount++;
|
||||
if (body.text === '#foo #bar') fooBarCount++;
|
||||
}
|
||||
}, {
|
||||
q: [
|
||||
['foo', 'bar'],
|
||||
],
|
||||
});
|
||||
await Promise.all([
|
||||
await api('notes/create', { text: '#foo' }, chitose),
|
||||
await api('notes/create', { text: '#bar' }, chitose),
|
||||
await api('notes/create', { text: '#foo #bar' }, chitose),
|
||||
]);
|
||||
|
||||
post(chitose, {
|
||||
text: '#foo',
|
||||
});
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
ws.close();
|
||||
|
||||
post(chitose, {
|
||||
text: '#bar',
|
||||
});
|
||||
assert.strictEqual(received.includes('#foo'), false);
|
||||
assert.strictEqual(received.includes('#bar'), false);
|
||||
assert.strictEqual(received.includes('#foo #bar'), true);
|
||||
});
|
||||
|
||||
post(chitose, {
|
||||
text: '#foo #bar',
|
||||
});
|
||||
test('指定したハッシュタグの投稿が流れる (OR)', async () => {
|
||||
const received: string[] = [];
|
||||
const ws = await connectStream(chitose, 'hashtag', (msg) => {
|
||||
if (msg.type === 'note') received.push(msg.body.text);
|
||||
}, { q: [['foo'], ['bar']] });
|
||||
|
||||
setTimeout(() => {
|
||||
assert.strictEqual(fooCount, 0);
|
||||
assert.strictEqual(barCount, 0);
|
||||
assert.strictEqual(fooBarCount, 1);
|
||||
ws.close();
|
||||
done();
|
||||
}, 3000);
|
||||
}));
|
||||
await Promise.all([
|
||||
await api('notes/create', { text: '#foo' }, chitose),
|
||||
await api('notes/create', { text: '#bar' }, chitose),
|
||||
await api('notes/create', { text: '#foo #bar' }, chitose),
|
||||
await api('notes/create', { text: '#piyo' }, chitose),
|
||||
]);
|
||||
|
||||
test('指定したハッシュタグの投稿が流れる (OR)', () => new Promise<void>(async done => {
|
||||
let fooCount = 0;
|
||||
let barCount = 0;
|
||||
let fooBarCount = 0;
|
||||
let piyoCount = 0;
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
ws.close();
|
||||
|
||||
const ws = await connectStream(chitose, 'hashtag', ({ type, body }) => {
|
||||
if (type === 'note') {
|
||||
if (body.text === '#foo') fooCount++;
|
||||
if (body.text === '#bar') barCount++;
|
||||
if (body.text === '#foo #bar') fooBarCount++;
|
||||
if (body.text === '#piyo') piyoCount++;
|
||||
}
|
||||
}, {
|
||||
q: [
|
||||
['foo'],
|
||||
['bar'],
|
||||
],
|
||||
});
|
||||
assert.strictEqual(received.includes('#foo'), true);
|
||||
assert.strictEqual(received.includes('#bar'), true);
|
||||
assert.strictEqual(received.includes('#foo #bar'), true);
|
||||
assert.strictEqual(received.includes('#piyo'), false);
|
||||
});
|
||||
|
||||
post(chitose, {
|
||||
text: '#foo',
|
||||
});
|
||||
test('指定したハッシュタグの投稿が流れる (AND + OR)', async () => {
|
||||
const received: string[] = [];
|
||||
const ws = await connectStream(chitose, 'hashtag', (msg) => {
|
||||
if (msg.type === 'note') received.push(msg.body.text);
|
||||
}, { q: [['foo', 'bar'], ['piyo']] });
|
||||
|
||||
post(chitose, {
|
||||
text: '#bar',
|
||||
});
|
||||
await Promise.all([
|
||||
api('notes/create', { text: '#foo' }, chitose),
|
||||
api('notes/create', { text: '#bar' }, chitose),
|
||||
api('notes/create', { text: '#foo #bar' }, chitose),
|
||||
api('notes/create', { text: '#piyo' }, chitose),
|
||||
api('notes/create', { text: '#waaa' }, chitose),
|
||||
]);
|
||||
|
||||
post(chitose, {
|
||||
text: '#foo #bar',
|
||||
});
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
ws.close();
|
||||
|
||||
post(chitose, {
|
||||
text: '#piyo',
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
assert.strictEqual(fooCount, 1);
|
||||
assert.strictEqual(barCount, 1);
|
||||
assert.strictEqual(fooBarCount, 1);
|
||||
assert.strictEqual(piyoCount, 0);
|
||||
ws.close();
|
||||
done();
|
||||
}, 3000);
|
||||
}));
|
||||
|
||||
test('指定したハッシュタグの投稿が流れる (AND + OR)', () => new Promise<void>(async done => {
|
||||
let fooCount = 0;
|
||||
let barCount = 0;
|
||||
let fooBarCount = 0;
|
||||
let piyoCount = 0;
|
||||
let waaaCount = 0;
|
||||
|
||||
const ws = await connectStream(chitose, 'hashtag', ({ type, body }) => {
|
||||
if (type === 'note') {
|
||||
if (body.text === '#foo') fooCount++;
|
||||
if (body.text === '#bar') barCount++;
|
||||
if (body.text === '#foo #bar') fooBarCount++;
|
||||
if (body.text === '#piyo') piyoCount++;
|
||||
if (body.text === '#waaa') waaaCount++;
|
||||
}
|
||||
}, {
|
||||
q: [
|
||||
['foo', 'bar'],
|
||||
['piyo'],
|
||||
],
|
||||
});
|
||||
|
||||
post(chitose, {
|
||||
text: '#foo',
|
||||
});
|
||||
|
||||
post(chitose, {
|
||||
text: '#bar',
|
||||
});
|
||||
|
||||
post(chitose, {
|
||||
text: '#foo #bar',
|
||||
});
|
||||
|
||||
post(chitose, {
|
||||
text: '#piyo',
|
||||
});
|
||||
|
||||
post(chitose, {
|
||||
text: '#waaa',
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
assert.strictEqual(fooCount, 0);
|
||||
assert.strictEqual(barCount, 0);
|
||||
assert.strictEqual(fooBarCount, 1);
|
||||
assert.strictEqual(piyoCount, 1);
|
||||
assert.strictEqual(waaaCount, 0);
|
||||
ws.close();
|
||||
done();
|
||||
}, 3000);
|
||||
}));
|
||||
assert.strictEqual(received.includes('#foo'), false);
|
||||
assert.strictEqual(received.includes('#bar'), false);
|
||||
assert.strictEqual(received.includes('#foo #bar'), true);
|
||||
assert.strictEqual(received.includes('#piyo'), true);
|
||||
assert.strictEqual(received.includes('#waaa'), false);
|
||||
});
|
||||
});
|
||||
*/
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,10 +9,10 @@ import { basename, isAbsolute } from 'node:path';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { inspect } from 'node:util';
|
||||
import WebSocket, { ClientOptions } from 'ws';
|
||||
import fetch, { RequestInit, type Headers } from 'node-fetch';
|
||||
import fetch, { Blob, FormData } from 'node-fetch';
|
||||
import type { RequestInit, Headers, Response } from 'node-fetch';
|
||||
import * as htmlParser from 'node-html-parser';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { type Response } from 'node-fetch';
|
||||
import Fastify from 'fastify';
|
||||
import { entities } from '@/postgres.js';
|
||||
import { loadConfig } from '@/config.js';
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/estree": "1.0.9",
|
||||
"@types/node": "24.13.2",
|
||||
"@types/node": "26.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.61.1",
|
||||
"@typescript-eslint/parser": "8.61.1",
|
||||
"rollup": "4.62.2"
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/estree": "1.0.9",
|
||||
"@types/micromatch": "4.0.10",
|
||||
"@types/node": "24.13.2",
|
||||
"@types/node": "26.0.0",
|
||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@types/ws": "8.18.1",
|
||||
|
||||
@@ -149,7 +149,7 @@ const isRenote = Misskey.note.isPureRenote(note.value);
|
||||
|
||||
const rootEl = shallowRef<HTMLElement>();
|
||||
const renoteTime = shallowRef<HTMLElement>();
|
||||
const appearNote = computed(() => getAppearNote(note.value));
|
||||
const appearNote = computed(() => getAppearNote(note.value) ?? note.value);
|
||||
const showContent = ref(false);
|
||||
const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
|
||||
const isLong = shouldCollapsed(appearNote.value, []);
|
||||
|
||||
@@ -244,7 +244,7 @@ const fetchMore = async (): Promise<void> => {
|
||||
if (i === 10) item._shouldInsertAd_ = true;
|
||||
}
|
||||
|
||||
const reverseConcat = _res => {
|
||||
const reverseConcat = (_res: MisskeyEntity[]) => {
|
||||
const oldHeight = scrollableElement.value ? scrollableElement.value.scrollHeight : getBodyScrollHeight();
|
||||
const oldScroll = scrollableElement.value ? scrollableElement.value.scrollTop : window.scrollY;
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ const initialReactions = new Set(Object.keys(props.note.reactions));
|
||||
const reactions = ref<[string, number][]>([]);
|
||||
const hasMoreReactions = ref(false);
|
||||
|
||||
if (props.note.myReaction && !Object.keys(reactions.value).includes(props.note.myReaction)) {
|
||||
reactions.value[props.note.myReaction] = props.note.reactions[props.note.myReaction];
|
||||
if (props.note.myReaction != null && !(props.note.myReaction in props.note.reactions)) {
|
||||
reactions.value.push([props.note.myReaction, props.note.reactions[props.note.myReaction]]);
|
||||
}
|
||||
|
||||
function onMockToggleReaction(emoji: string, count: number) {
|
||||
|
||||
@@ -46,6 +46,9 @@ const parsed = computed(() => {
|
||||
});
|
||||
|
||||
const render = () => {
|
||||
return h(props.tag, parsed.value.map(x => typeof x === 'string' ? (props.textTag ? h(props.textTag, x) : x) : slots[x.arg]()));
|
||||
// slotsの型はTの条件型で決まり文字列インデックスアクセスができないため、
|
||||
// frontend側の同名コンポーネント (packages/frontend/src/components/global/I18n.vue) と同じくanyでキャストする
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return h(props.tag, parsed.value.map(x => typeof x === 'string' ? (props.textTag ? h(props.textTag, x) : x) : (slots as any)[x.arg]()));
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -45,11 +45,11 @@ export async function fetchCustomEmojis(force = false) {
|
||||
set('lastEmojisFetchedAt', now);
|
||||
}
|
||||
|
||||
let cachedTags;
|
||||
let cachedTags: string[] | null = null;
|
||||
export function getCustomEmojiTags() {
|
||||
if (cachedTags) return cachedTags;
|
||||
|
||||
const tags = new Set();
|
||||
const tags = new Set<string>();
|
||||
for (const emoji of customEmojis.value) {
|
||||
for (const tag of emoji.aliases) {
|
||||
tags.add(tag);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.13.2",
|
||||
"@types/node": "26.0.0",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@typescript-eslint/eslint-plugin": "8.61.1",
|
||||
"@typescript-eslint/parser": "8.61.1",
|
||||
|
||||
@@ -85,8 +85,6 @@
|
||||
"@storybook/core-events": "8.6.18",
|
||||
"@storybook/manager-api": "8.6.18",
|
||||
"@storybook/preview-api": "8.6.18",
|
||||
"@storybook/react": "10.4.6",
|
||||
"@storybook/react-vite": "10.4.6",
|
||||
"@storybook/test": "8.6.18",
|
||||
"@storybook/theming": "8.6.18",
|
||||
"@storybook/types": "8.6.18",
|
||||
@@ -99,7 +97,7 @@
|
||||
"@types/insert-text-at-cursor": "0.3.2",
|
||||
"@types/matter-js": "0.20.2",
|
||||
"@types/micromatch": "4.0.10",
|
||||
"@types/node": "24.13.2",
|
||||
"@types/node": "26.0.0",
|
||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||
"@types/sanitize-html": "2.16.1",
|
||||
"@types/seedrandom": "3.0.8",
|
||||
@@ -110,10 +108,7 @@
|
||||
"@typescript-eslint/parser": "8.61.1",
|
||||
"@vitest/coverage-v8": "4.1.9",
|
||||
"@vue/compiler-core": "3.5.38",
|
||||
"acorn": "8.17.0",
|
||||
"astring": "1.9.0",
|
||||
"cross-env": "10.1.0",
|
||||
"cypress": "15.17.0",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-vue": "10.9.2",
|
||||
"happy-dom": "20.10.6",
|
||||
@@ -132,7 +127,6 @@
|
||||
"rollup-plugin-visualizer": "7.0.1",
|
||||
"sass-embedded": "1.100.0",
|
||||
"seedrandom": "3.0.5",
|
||||
"start-server-and-test": "3.0.11",
|
||||
"storybook": "10.4.6",
|
||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
||||
"tsx": "4.22.4",
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<div v-if="show" ref="el" :class="[$style.root]">
|
||||
<div :class="[$style.upper, { [$style.slim]: narrow, [$style.thin]: thin_ }]">
|
||||
<div v-if="!thin_ && narrow && props.displayMyAvatar && $i" class="_button" @click="openAccountMenu">
|
||||
<div v-if="!thin_ && (narrow || deviceKind === 'smartphone') && props.displayMyAvatar && $i" class="_button" @click="openAccountMenu">
|
||||
<MkAvatar :class="$style.avatar" :user="$i"/>
|
||||
</div>
|
||||
<div v-else-if="!thin_ && narrow && !hideTitle" :class="$style.buttons"></div>
|
||||
@@ -62,6 +62,7 @@ import { onMounted, onUnmounted, ref, inject, useTemplateRef, computed } from 'v
|
||||
import { scrollToTop } from '@@/js/scroll.js';
|
||||
import XTabs from './MkPageHeader.tabs.vue';
|
||||
import { getAccountMenu } from '@/accounts.js';
|
||||
import { deviceKind } from '@/utility/device-kind.js';
|
||||
import { $i } from '@/i.js';
|
||||
import { DI } from '@/di.js';
|
||||
import * as os from '@/os.js';
|
||||
@@ -160,10 +161,12 @@ onUnmounted(() => {
|
||||
align-items: center;
|
||||
height: var(--height);
|
||||
|
||||
.tabs:first-child {
|
||||
.tabs:first-child,
|
||||
&:not(.slim) > :not(.titleContainer) ~ .tabs {
|
||||
margin-left: auto;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
@@ -154,6 +154,7 @@ function onDrop(ev: DragEvent): void {
|
||||
}
|
||||
|
||||
function onKeydown(ev: KeyboardEvent) {
|
||||
if (ev.isComposing || ev.key === 'Process' || ev.keyCode === 229) return;
|
||||
if (ev.key === 'Enter') {
|
||||
if (prefer.s['chat.sendOnEnter']) {
|
||||
if (!(ev.ctrlKey || ev.metaKey || ev.shiftKey)) {
|
||||
|
||||
@@ -7,18 +7,46 @@ import { vi } from 'vitest';
|
||||
import createFetchMock from 'vitest-fetch-mock';
|
||||
import type { Ref } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
// Set i18n
|
||||
import locales from 'i18n';
|
||||
import { updateI18n } from '@/i18n.js';
|
||||
|
||||
const fetchMocker = createFetchMock(vi);
|
||||
fetchMocker.enableMocks();
|
||||
|
||||
updateI18n(locales['en-US']);
|
||||
|
||||
// XXX: misskey-js panics if WebSocket is not defined
|
||||
vi.stubGlobal('WebSocket', class WebSocket extends EventTarget { static CLOSING = 2; });
|
||||
|
||||
// XXX: localStorageがない場合がある
|
||||
const localStorageMock = (() => {
|
||||
const store = new Map<string, string>();
|
||||
return {
|
||||
getItem(key: string) {
|
||||
return store.get(key) ?? null;
|
||||
},
|
||||
setItem(key: string, value: string) {
|
||||
store.set(key, value);
|
||||
},
|
||||
removeItem(key: string) {
|
||||
store.delete(key);
|
||||
},
|
||||
clear() {
|
||||
store.clear();
|
||||
},
|
||||
};
|
||||
})();
|
||||
vi.stubGlobal('localStorage', localStorageMock);
|
||||
|
||||
// 中でlocalStorageを使うので上と順番を変えてはいけない
|
||||
const { default: locales } = await import('i18n');
|
||||
|
||||
fetchMocker.mockIf(/^\/assets\/locales\/.*\.json$/, async () => {
|
||||
return {
|
||||
status: 200,
|
||||
body: JSON.stringify(locales['en-US']),
|
||||
};
|
||||
});
|
||||
|
||||
const { updateI18n } = await import('@/i18n.js');
|
||||
updateI18n(locales['en-US']);
|
||||
|
||||
export const preferState: Record<string, unknown> = {
|
||||
|
||||
// なんかtestがうまいこと動かないのでここに書く
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/// <reference types="vitest/config" />
|
||||
|
||||
import path from 'path';
|
||||
import pluginVue from '@vitejs/plugin-vue';
|
||||
import pluginGlsl from 'vite-plugin-glsl';
|
||||
@@ -265,6 +264,7 @@ export function getConfig(): UserConfig {
|
||||
|
||||
test: {
|
||||
environment: 'happy-dom',
|
||||
setupFiles: ['./test/init.ts'],
|
||||
deps: {
|
||||
optimizer: {
|
||||
web: {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/node": "24.13.2",
|
||||
"@types/node": "26.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.61.1",
|
||||
"@typescript-eslint/parser": "8.61.1",
|
||||
"chokidar": "5.0.0",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.13.2",
|
||||
"@types/node": "26.0.0",
|
||||
"@types/wawoff2": "1.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "8.61.1",
|
||||
"@typescript-eslint/parser": "8.61.1"
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/matter-js": "0.20.2",
|
||||
"@types/node": "24.13.2",
|
||||
"@types/node": "26.0.0",
|
||||
"@types/seedrandom": "3.0.8",
|
||||
"@typescript-eslint/eslint-plugin": "8.61.1",
|
||||
"@typescript-eslint/parser": "8.61.1",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@readme/openapi-parser": "6.1.3",
|
||||
"@types/node": "24.13.2",
|
||||
"@types/node": "26.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.61.1",
|
||||
"@typescript-eslint/parser": "8.61.1",
|
||||
"openapi-types": "12.1.3",
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/api-extractor": "7.58.9",
|
||||
"@types/node": "24.13.2",
|
||||
"@types/node": "26.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.61.1",
|
||||
"@typescript-eslint/parser": "8.61.1",
|
||||
"@vitest/coverage-v8": "4.1.9",
|
||||
|
||||
@@ -34287,6 +34287,7 @@ export interface operations {
|
||||
originalNotesCount: number;
|
||||
usersCount: number;
|
||||
originalUsersCount: number;
|
||||
reactionsCount: number;
|
||||
instances: number;
|
||||
driveUsageLocal: number;
|
||||
driveUsageRemote: number;
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.13.2",
|
||||
"@types/node": "26.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.61.1",
|
||||
"@typescript-eslint/parser": "8.61.1",
|
||||
"esbuild": "0.28.1",
|
||||
|
||||
1513
pnpm-lock.yaml
generated
1513
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@ allowBuilds:
|
||||
'@nestjs/core': true
|
||||
'@parcel/watcher': true
|
||||
'@sentry/profiling-node': true
|
||||
'@sentry-internal/node-cpu-profiler': true
|
||||
'@sentry/node-cpu-profiler': true
|
||||
bufferutil: true
|
||||
canvas: true
|
||||
|
||||
16
scripts/changelog-checker/package-lock.json
generated
16
scripts/changelog-checker/package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@types/mdast": "4.0.4",
|
||||
"@types/node": "24.13.2",
|
||||
"@types/node": "26.0.0",
|
||||
"@vitest/coverage-v8": "4.1.9",
|
||||
"mdast-util-to-string": "4.0.0",
|
||||
"remark": "15.0.1",
|
||||
@@ -505,13 +505,13 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz",
|
||||
"integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==",
|
||||
"version": "26.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz",
|
||||
"integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.18.0"
|
||||
"undici-types": "~8.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
@@ -2074,9 +2074,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
|
||||
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mdast": "4.0.4",
|
||||
"@types/node": "24.13.2",
|
||||
"@types/node": "26.0.0",
|
||||
"@vitest/coverage-v8": "4.1.9",
|
||||
"mdast-util-to-string": "4.0.0",
|
||||
"remark": "15.0.1",
|
||||
|
||||
Reference in New Issue
Block a user