diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index f8d9905ecd..31b6212cb5 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -4,12 +4,10 @@
"service": "app",
"workspaceFolder": "/workspace",
"features": {
- "ghcr.io/devcontainers-contrib/features/pnpm:2": {
- "version": "8.9.2"
- },
"ghcr.io/devcontainers/features/node:1": {
- "version": "20.10.0"
- }
+ "version": "20.12.2"
+ },
+ "ghcr.io/devcontainers-contrib/features/corepack:1": {}
},
"forwardPorts": [3000],
"postCreateCommand": "sudo chmod 755 .devcontainer/init.sh && .devcontainer/init.sh",
diff --git a/.devcontainer/init.sh b/.devcontainer/init.sh
index bcad3e6d85..729e1a9d2d 100755
--- a/.devcontainer/init.sh
+++ b/.devcontainer/init.sh
@@ -4,6 +4,8 @@ set -xe
sudo chown -R node /workspace
git submodule update --init
+corepack install
+corepack enable
pnpm config set store-dir /home/node/.local/share/pnpm/store
pnpm install --frozen-lockfile
cp .devcontainer/devcontainer.yml .config/default.yml
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index c6b2a1611c..d42b58abc0 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,3 +1,4 @@
# These are supported funding model platforms
+github: [misskey-dev]
patreon: syuilo
diff --git a/.github/workflows/check-misskey-js-autogen.yml b/.github/workflows/check-misskey-js-autogen.yml
index 4aa0646b7b..9052b2e372 100644
--- a/.github/workflows/check-misskey-js-autogen.yml
+++ b/.github/workflows/check-misskey-js-autogen.yml
@@ -26,7 +26,7 @@ jobs:
- name: setup pnpm
uses: pnpm/action-setup@v3
with:
- version: 8
+ version: 9
- name: setup node
id: setup-node
diff --git a/.github/workflows/get-api-diff.yml b/.github/workflows/get-api-diff.yml
index e737b89b42..146e0686e5 100644
--- a/.github/workflows/get-api-diff.yml
+++ b/.github/workflows/get-api-diff.yml
@@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
- node-version: [20.10.0]
+ node-version: [20.12.2]
api-json-name: [api-base.json, api-head.json]
include:
- api-json-name: api-base.json
@@ -34,7 +34,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v3
with:
- version: 8
+ version: 9
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.2
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 9b3f85fe1d..9a269014ab 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -29,7 +29,7 @@ jobs:
submodules: true
- uses: pnpm/action-setup@v3
with:
- version: 8
+ version: 9
run_install: false
- uses: actions/setup-node@v4.0.2
with:
@@ -56,7 +56,7 @@ jobs:
submodules: true
- uses: pnpm/action-setup@v3
with:
- version: 7
+ version: 9
run_install: false
- uses: actions/setup-node@v4.0.2
with:
@@ -82,7 +82,7 @@ jobs:
submodules: true
- uses: pnpm/action-setup@v3
with:
- version: 7
+ version: 9
run_install: false
- uses: actions/setup-node@v4.0.2
with:
diff --git a/.github/workflows/on-release-created.yml b/.github/workflows/on-release-created.yml
index 069534bd53..52463d7542 100644
--- a/.github/workflows/on-release-created.yml
+++ b/.github/workflows/on-release-created.yml
@@ -17,7 +17,7 @@ jobs:
strategy:
matrix:
- node-version: [20.10.0]
+ node-version: [20.12.2]
steps:
- uses: actions/checkout@v4.1.1
@@ -26,7 +26,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v3
with:
- version: 8
+ version: 9
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.2
diff --git a/.github/workflows/release-with-dispatch.yml b/.github/workflows/release-with-dispatch.yml
index 1a954739d9..bc6448cb37 100644
--- a/.github/workflows/release-with-dispatch.yml
+++ b/.github/workflows/release-with-dispatch.yml
@@ -61,6 +61,7 @@ jobs:
-
use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }}
+ indent: ${{ vars.INDENT }}
secrets:
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
@@ -75,6 +76,7 @@ jobs:
pr_number: ${{ needs.get-pr.outputs.pr_number }}
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }}
+ indent: ${{ vars.INDENT }}
secrets:
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
@@ -115,6 +117,7 @@ jobs:
# }
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }}
+ indent: ${{ vars.INDENT }}
secrets:
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
diff --git a/.github/workflows/release-with-ready.yml b/.github/workflows/release-with-ready.yml
index b64ed20791..139503e563 100644
--- a/.github/workflows/release-with-ready.yml
+++ b/.github/workflows/release-with-ready.yml
@@ -33,6 +33,7 @@ jobs:
pr_number: ${{ github.event.pull_request.number }}
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }}
+ indent: ${{ vars.INDENT }}
secrets:
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml
index ca82f4bcf3..3bc354b331 100644
--- a/.github/workflows/storybook.yml
+++ b/.github/workflows/storybook.yml
@@ -36,7 +36,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v3
with:
- version: 8
+ version: 9
run_install: false
- name: Use Node.js 20.x
uses: actions/setup-node@v4.0.2
diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml
index a803db4508..525cd0916b 100644
--- a/.github/workflows/test-backend.yml
+++ b/.github/workflows/test-backend.yml
@@ -21,7 +21,7 @@ jobs:
strategy:
matrix:
- node-version: [20.10.0]
+ node-version: [20.12.2]
services:
postgres:
@@ -43,7 +43,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v3
with:
- version: 8
+ version: 9
run_install: false
- name: Install FFmpeg
uses: FedericoCarboni/setup-ffmpeg@v3
@@ -73,7 +73,7 @@ jobs:
strategy:
matrix:
- node-version: [20.10.0]
+ node-version: [20.12.2]
services:
postgres:
@@ -95,7 +95,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v3
with:
- version: 8
+ version: 9
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.2
diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml
index 1e020b7368..9df3c98393 100644
--- a/.github/workflows/test-frontend.yml
+++ b/.github/workflows/test-frontend.yml
@@ -26,7 +26,7 @@ jobs:
strategy:
matrix:
- node-version: [20.10.0]
+ node-version: [20.12.2]
steps:
- uses: actions/checkout@v4.1.1
@@ -35,7 +35,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v3
with:
- version: 8
+ version: 9
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.2
@@ -64,7 +64,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- node-version: [20.10.0]
+ node-version: [20.12.2]
browser: [chrome]
services:
@@ -93,7 +93,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v3
with:
- version: 7
+ version: 9
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.2
diff --git a/.github/workflows/test-misskey-js.yml b/.github/workflows/test-misskey-js.yml
index f73bd0b08f..2589d908b8 100644
--- a/.github/workflows/test-misskey-js.yml
+++ b/.github/workflows/test-misskey-js.yml
@@ -20,7 +20,7 @@ jobs:
strategy:
matrix:
- node-version: [20.10.0]
+ node-version: [20.12.2]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
diff --git a/.github/workflows/test-production.yml b/.github/workflows/test-production.yml
index 77af08b6fe..24a530e073 100644
--- a/.github/workflows/test-production.yml
+++ b/.github/workflows/test-production.yml
@@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
- node-version: [20.10.0]
+ node-version: [20.12.2]
steps:
- uses: actions/checkout@v4.1.1
@@ -25,7 +25,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v3
with:
- version: 8
+ version: 9
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.2
diff --git a/.github/workflows/validate-api-json.yml b/.github/workflows/validate-api-json.yml
index 36ed8d273f..229c447893 100644
--- a/.github/workflows/validate-api-json.yml
+++ b/.github/workflows/validate-api-json.yml
@@ -17,7 +17,7 @@ jobs:
strategy:
matrix:
- node-version: [20.10.0]
+ node-version: [20.12.2]
steps:
- uses: actions/checkout@v4.1.1
@@ -26,7 +26,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v3
with:
- version: 8
+ version: 9
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.2
diff --git a/.node-version b/.node-version
index d5a159609d..87834047a6 100644
--- a/.node-version
+++ b/.node-version
@@ -1 +1 @@
-20.10.0
+20.12.2
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8c8bcf0ea4..d95ea3fc38 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
### Note
- コントロールパネル内にあるサマリープロキシの設定個所がセキュリティから全般へ変更となります。
+- 悪意のある第三者がリモートユーザーになりすましたアクティビティを受け取れてしまう問題を修正しました。詳しくは[GitHub security advisory](https://github.com/misskey-dev/misskey/security/advisories/GHSA-2vxv-pv3m-3wvj)をご覧ください。
### General
- Enhance: URLプレビューの有効化・無効化を設定できるように #13569
@@ -35,6 +36,10 @@
- Enhance: リプライにて引用がある場合テキストが空でもノートできるように
- 引用したいノートのURLをコピーしリプライ投稿画面にペーストして添付することで達成できます
- Enhance: フォローするかどうかの確認ダイアログを出せるように
+- Enhance: Playを手動でリロードできるように
+- Enhance: 通報のコメント内のリンクをクリックした際、ウィンドウで開くように
+- Enhance: `Ui:C:postForm` および `Ui:C:postFormButton` に `localOnly` と `visibility` を設定できるように
+- Enhance: AiScriptを0.18.0にバージョンアップ
- Fix: 一部のページ内リンクが正しく動作しない問題を修正
- Fix: 周年の実績が閏年を考慮しない問題を修正
- Fix: ローカルURLのプレビューポップアップが左上に表示される
@@ -49,10 +54,18 @@
- Fix: ノート詳細ページにおいてCW付き引用リノートのCWボタンのラベルに「引用」が含まれていない問題を修正
- Fix: ダイアログの入力で字数制限に違反していてもEnterキーが押せてしまう問題を修正
- Fix: ダイレクト投稿の宛先が保存されない問題を修正
+- Fix: Playのページを離れたときに、Playが正常に初期化されない問題を修正
+- Fix: ページのOGP URLが間違っているのを修正
+- Fix: リバーシの対局を正しく共有できないことがある問題を修正
+- Fix: 通知をグループ化している際に、人数が正常に表示されないことがある問題を修正
+- Fix: 連合なしの状態の読み書きができない問題を修正
### Server
- Enhance: エンドポイント`antennas/update`の必須項目を`antennaId`のみに
- Enhance: misskey-dev/summaly@5.1.0の取り込み(プレビュー生成処理の効率化)
+- Enhance: ドライブのファイルがNSFWかどうか個別に連合されるように (#13756)
+ - 可能な場合、ノートの添付ファイルのセンシティブ判定がファイル単位になります
+- Fix: リモートから配送されたアクティビティにJSON-LD compactionをかける
- Fix: フォローリクエストを作成する際に既存のものは削除するように
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/440)
- Fix: エンドポイント`notes/translate`のエラーを改善
@@ -61,10 +74,12 @@
- Fix: リプライのみの引用リノートと、CWのみの引用リノートが純粋なリノートとして誤って扱われてしまう問題を修正
- Fix: 登録にメール認証が必須になっている場合、登録されているメールアドレスを削除できないように
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/606)
+- Fix: Add Cache-Control to Bull Board
- Fix: nginx経由で/files/にRangeリクエストされた場合に正しく応答できないのを修正
- Fix: 一部のタイムラインのストリーミングでインスタンスミュートが効かない問題を修正
- Fix: グローバルタイムラインで返信が表示されないことがある問題を修正
- Fix: リノートをミュートしたユーザの投稿のリノートがミュートされる問題を修正
+- Fix: AP Link等は添付ファイル扱いしないようになど (#13754)
## 2024.3.1
diff --git a/Dockerfile b/Dockerfile
index ee3a30a3c1..9fc2d611cd 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,6 +1,6 @@
# syntax = docker/dockerfile:1.4
-ARG NODE_VERSION=20.10.0-bullseye
+ARG NODE_VERSION=20.12.2-bullseye
# build assets & compile TypeScript
diff --git a/locales/index.d.ts b/locales/index.d.ts
index 5feb706ce3..50dc0f4dff 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -4952,6 +4952,10 @@ export interface Locale extends ILocale {
* フォローの際常に確認する
*/
"alwaysConfirmFollow": string;
+ /**
+ * お問い合わせ
+ */
+ "inquiry": string;
/**
* チュートリアルをスキップできないようにする
*/
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 1b1d12b06b..3995dd20fa 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1234,6 +1234,7 @@ keepOriginalFilename: "オリジナルのファイル名を保持"
keepOriginalFilenameDescription: "この設定をオフにすると、アップロード時にファイル名が自動でランダム文字列に置き換えられます。"
noDescription: "説明文はありません"
alwaysConfirmFollow: "フォローの際常に確認する"
+inquiry: "お問い合わせ"
prohibitSkippingInitialTutorial: "チュートリアルをスキップできないようにする"
prohibitSkippingInitialTutorialDescription: "新規登録したユーザーに表示されるチュートリアルをスキップできないようにします。チュートリアルを完了しなかったりチュートリアルページを回避したりした場合でも、強制的にリダイレクトされます。"
diff --git a/package.json b/package.json
index 84d6db5124..23e0ea0ee5 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
"type": "git",
"url": "https://github.com/misskey-dev/misskey.git"
},
- "packageManager": "pnpm@8.15.4",
+ "packageManager": "pnpm@9.0.6",
"workspaces": [
"packages/frontend",
"packages/backend",
@@ -48,24 +48,24 @@
"lodash": "4.17.21"
},
"dependencies": {
- "cssnano": "6.0.5",
+ "cssnano": "6.1.2",
"execa": "8.0.1",
"fast-glob": "3.3.2",
"ignore-walk": "6.0.4",
"js-yaml": "4.1.0",
- "postcss": "8.4.35",
- "tar": "6.2.0",
- "terser": "5.28.1",
- "typescript": "5.3.3",
- "esbuild": "0.19.11",
- "glob": "10.3.10"
+ "postcss": "8.4.38",
+ "tar": "6.2.1",
+ "terser": "5.30.3",
+ "typescript": "5.4.5",
+ "esbuild": "0.20.2",
+ "glob": "10.3.12"
},
"devDependencies": {
- "@types/node": "^20.11.28",
- "@typescript-eslint/eslint-plugin": "7.1.0",
- "@typescript-eslint/parser": "7.1.0",
+ "@types/node": "20.12.7",
+ "@typescript-eslint/eslint-plugin": "7.7.1",
+ "@typescript-eslint/parser": "7.7.1",
"cross-env": "7.0.3",
- "cypress": "13.6.6",
+ "cypress": "13.7.3",
"eslint": "8.57.0",
"ncp": "2.0.0",
"start-server-and-test": "2.0.3"
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 7f70ae0c97..23b3bfdb8b 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -12,9 +12,9 @@
"migrate": "pnpm typeorm migration:run -d ormconfig.js",
"revert": "pnpm typeorm migration:revert -d ormconfig.js",
"check:connect": "node ./scripts/check_connect.js",
- "build": "swc src -d built -D",
- "build:test": "swc test-server -d built-test -D --config-file test-server/.swcrc",
- "watch:swc": "swc src -d built -D -w",
+ "build": "swc src -d built -D --strip-leading-paths",
+ "build:test": "swc test-server -d built-test -D --config-file test-server/.swcrc --strip-leading-paths",
+ "watch:swc": "swc src -d built -D -w --strip-leading-paths",
"build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
"watch": "node ./scripts/watch.mjs",
"restart": "pnpm build && pnpm start",
@@ -67,38 +67,39 @@
"dependencies": {
"@aws-sdk/client-s3": "3.412.0",
"@aws-sdk/lib-storage": "3.412.0",
- "@bull-board/api": "5.14.2",
- "@bull-board/fastify": "5.14.2",
- "@bull-board/ui": "5.14.2",
- "@discordapp/twemoji": "15.0.2",
+ "@bull-board/api": "5.17.0",
+ "@bull-board/fastify": "5.17.0",
+ "@bull-board/ui": "5.17.0",
+ "@discordapp/twemoji": "15.0.3",
"@fastify/accepts": "4.3.0",
"@fastify/cookie": "9.3.1",
- "@fastify/cors": "8.5.0",
- "@fastify/express": "2.3.0",
- "@fastify/http-proxy": "9.3.0",
- "@fastify/multipart": "8.1.0",
- "@fastify/static": "6.12.0",
- "@fastify/view": "8.2.0",
+ "@fastify/cors": "9.0.1",
+ "@fastify/express": "3.0.0",
+ "@fastify/http-proxy": "9.5.0",
+ "@fastify/multipart": "8.2.0",
+ "@fastify/static": "7.0.3",
+ "@fastify/view": "9.1.0",
"@misskey-dev/sharp-read-bmp": "1.2.0",
"@misskey-dev/summaly": "5.1.0",
- "@nestjs/common": "10.3.3",
- "@nestjs/core": "10.3.3",
- "@nestjs/testing": "10.3.3",
+ "@napi-rs/canvas": "^0.1.52",
+ "@nestjs/common": "10.3.8",
+ "@nestjs/core": "10.3.8",
+ "@nestjs/testing": "10.3.8",
"@peertube/http-signature": "1.7.0",
- "@simplewebauthn/server": "9.0.3",
+ "@simplewebauthn/server": "10.0.0",
"@sinonjs/fake-timers": "11.2.2",
- "@smithy/node-http-handler": "2.1.10",
- "@swc/cli": "0.1.63",
- "@swc/core": "1.3.107",
- "@twemoji/parser": "15.0.0",
+ "@smithy/node-http-handler": "2.5.0",
+ "@swc/cli": "0.3.12",
+ "@swc/core": "1.4.17",
+ "@twemoji/parser": "15.1.1",
"accepts": "1.3.8",
- "ajv": "8.12.0",
- "archiver": "6.0.1",
- "async-mutex": "0.4.1",
+ "ajv": "8.13.0",
+ "archiver": "7.0.1",
+ "async-mutex": "0.5.0",
"bcryptjs": "2.4.3",
"blurhash": "2.0.5",
"body-parser": "1.20.2",
- "bullmq": "5.4.0",
+ "bullmq": "5.7.8",
"cacheable-lookup": "7.0.0",
"cbor": "9.0.2",
"chalk": "5.3.0",
@@ -109,85 +110,84 @@
"content-disposition": "0.5.4",
"date-fns": "2.30.0",
"deep-email-validator": "0.1.21",
- "fastify": "4.25.2",
+ "fastify": "4.26.2",
"fastify-raw-body": "4.3.0",
"feed": "4.2.2",
"file-type": "19.0.0",
"fluent-ffmpeg": "2.1.2",
"form-data": "4.0.0",
- "got": "14.2.0",
- "happy-dom": "10.0.3",
+ "got": "14.2.1",
+ "happy-dom": "14.7.1",
"hpagent": "1.2.0",
"htmlescape": "1.1.1",
- "http-link-header": "1.1.2",
- "ioredis": "5.3.2",
+ "http-link-header": "1.1.3",
+ "ioredis": "5.4.1",
"ip-cidr": "3.1.0",
- "ipaddr.js": "2.1.0",
+ "ipaddr.js": "2.2.0",
"is-svg": "5.0.0",
"js-yaml": "4.1.0",
- "jsdom": "23.2.0",
+ "jsdom": "24.0.0",
"json5": "2.2.3",
"jsonld": "8.3.2",
"jsrsasign": "11.1.0",
- "meilisearch": "0.37.0",
+ "meilisearch": "0.38.0",
"mfm-js": "0.24.0",
"microformats-parser": "2.0.2",
"mime-types": "2.1.35",
"misskey-js": "workspace:*",
"misskey-reversi": "workspace:*",
"ms": "3.0.0-canary.1",
- "nanoid": "5.0.6",
+ "nanoid": "5.0.7",
"nested-property": "4.0.0",
"node-fetch": "3.3.2",
- "nodemailer": "6.9.10",
+ "nodemailer": "6.9.13",
"nsfwjs": "2.4.2",
"oauth": "0.10.0",
"oauth2orize": "1.12.0",
"oauth2orize-pkce": "0.1.2",
"os-utils": "0.0.14",
- "otpauth": "9.2.2",
+ "otpauth": "9.2.3",
"parse5": "7.1.2",
- "pg": "8.11.3",
+ "pg": "8.11.5",
"pkce-challenge": "4.1.0",
"probe-image-size": "7.2.3",
"promise-limit": "2.7.0",
"pug": "3.0.2",
"punycode": "2.3.1",
- "pureimage": "0.3.17",
"qrcode": "1.5.3",
"random-seed": "0.3.0",
"ratelimiter": "3.4.1",
- "re2": "1.20.9",
+ "re2": "1.20.10",
"redis-lock": "0.1.4",
- "reflect-metadata": "0.2.1",
+ "reflect-metadata": "0.2.2",
"rename": "1.0.4",
"rss-parser": "3.13.0",
"rxjs": "7.8.1",
- "sanitize-html": "2.12.1",
+ "sanitize-html": "2.13.0",
"secure-json-parse": "2.7.0",
- "sharp": "0.33.2",
+ "sharp": "0.33.3",
"slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
- "systeminformation": "5.22.0",
+ "systeminformation": "5.22.7",
"tinycolor2": "1.6.0",
- "tmp": "0.2.2",
+ "tmp": "0.2.3",
"tsc-alias": "1.8.8",
"tsconfig-paths": "4.2.0",
"typeorm": "0.3.20",
- "typescript": "5.3.3",
+ "typescript": "5.4.5",
"ulid": "2.3.0",
"vary": "1.1.2",
"web-push": "3.6.7",
- "ws": "8.16.0",
+ "ws": "8.17.0",
"xev": "3.0.2"
},
"devDependencies": {
"@jest/globals": "29.7.0",
"@misskey-dev/eslint-plugin": "1.0.0",
- "@nestjs/platform-express": "10.3.3",
- "@simplewebauthn/types": "9.0.1",
- "@swc/jest": "0.2.31",
+ "@nestjs/platform-express": "10.3.8",
+ "@simplewebauthn/types": "10.0.0",
+ "@swc/jest": "0.2.36",
"@types/accepts": "1.3.7",
"@types/archiver": "6.0.2",
"@types/bcryptjs": "2.4.6",
@@ -197,20 +197,20 @@
"@types/fluent-ffmpeg": "2.1.24",
"@types/htmlescape": "^1.1.3",
"@types/http-link-header": "1.0.5",
- "@types/jest": "29.5.11",
+ "@types/jest": "29.5.12",
"@types/js-yaml": "4.0.9",
"@types/jsdom": "21.1.6",
"@types/jsonld": "1.5.13",
- "@types/jsrsasign": "10.5.12",
+ "@types/jsrsasign": "10.5.14",
"@types/mime-types": "2.1.4",
"@types/ms": "0.7.34",
- "@types/node": "20.11.22",
+ "@types/node": "20.12.7",
"@types/node-fetch": "3.0.3",
- "@types/nodemailer": "6.4.14",
+ "@types/nodemailer": "6.4.15",
"@types/oauth": "0.9.4",
- "@types/oauth2orize": "1.11.3",
+ "@types/oauth2orize": "1.11.5",
"@types/oauth2orize-pkce": "0.1.2",
- "@types/pg": "8.11.2",
+ "@types/pg": "8.11.5",
"@types/pug": "2.0.10",
"@types/punycode": "2.1.4",
"@types/qrcode": "1.5.5",
@@ -226,8 +226,8 @@
"@types/vary": "1.1.3",
"@types/web-push": "3.6.3",
"@types/ws": "8.5.10",
- "@typescript-eslint/eslint-plugin": "7.1.0",
- "@typescript-eslint/parser": "7.1.0",
+ "@typescript-eslint/eslint-plugin": "7.7.1",
+ "@typescript-eslint/parser": "7.7.1",
"aws-sdk-client-mock": "3.0.1",
"cross-env": "7.0.3",
"eslint": "8.57.0",
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index 2c27d33c06..5953155872 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -127,7 +127,7 @@ import { ApMfmService } from './activitypub/ApMfmService.js';
import { ApRendererService } from './activitypub/ApRendererService.js';
import { ApRequestService } from './activitypub/ApRequestService.js';
import { ApResolverService } from './activitypub/ApResolverService.js';
-import { LdSignatureService } from './activitypub/LdSignatureService.js';
+import { JsonLdService } from './activitypub/JsonLdService.js';
import { RemoteLoggerService } from './RemoteLoggerService.js';
import { RemoteUserResolveService } from './RemoteUserResolveService.js';
import { WebfingerService } from './WebfingerService.js';
@@ -266,7 +266,7 @@ const $ApMfmService: Provider = { provide: 'ApMfmService', useExisting: ApMfmSer
const $ApRendererService: Provider = { provide: 'ApRendererService', useExisting: ApRendererService };
const $ApRequestService: Provider = { provide: 'ApRequestService', useExisting: ApRequestService };
const $ApResolverService: Provider = { provide: 'ApResolverService', useExisting: ApResolverService };
-const $LdSignatureService: Provider = { provide: 'LdSignatureService', useExisting: LdSignatureService };
+const $JsonLdService: Provider = { provide: 'JsonLdService', useExisting: JsonLdService };
const $RemoteLoggerService: Provider = { provide: 'RemoteLoggerService', useExisting: RemoteLoggerService };
const $RemoteUserResolveService: Provider = { provide: 'RemoteUserResolveService', useExisting: RemoteUserResolveService };
const $WebfingerService: Provider = { provide: 'WebfingerService', useExisting: WebfingerService };
@@ -406,7 +406,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
ApRendererService,
ApRequestService,
ApResolverService,
- LdSignatureService,
+ JsonLdService,
RemoteLoggerService,
RemoteUserResolveService,
WebfingerService,
@@ -542,7 +542,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$ApRendererService,
$ApRequestService,
$ApResolverService,
- $LdSignatureService,
+ $JsonLdService,
$RemoteLoggerService,
$RemoteUserResolveService,
$WebfingerService,
@@ -678,7 +678,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
ApRendererService,
ApRequestService,
ApResolverService,
- LdSignatureService,
+ JsonLdService,
RemoteLoggerService,
RemoteUserResolveService,
WebfingerService,
@@ -813,7 +813,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$ApRendererService,
$ApRequestService,
$ApResolverService,
- $LdSignatureService,
+ $JsonLdService,
$RemoteLoggerService,
$RemoteUserResolveService,
$WebfingerService,
diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts
index edb9335b6e..1c75566755 100644
--- a/packages/backend/src/core/CustomEmojiService.ts
+++ b/packages/backend/src/core/CustomEmojiService.ts
@@ -20,7 +20,7 @@ import { query } from '@/misc/prelude/url.js';
import type { Serialized } from '@/types.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
-const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/;
+const parseEmojiStrRegexp = /^([-\w]+)(?:@([\w.-]+))?$/;
@Injectable()
export class CustomEmojiService implements OnApplicationShutdown {
diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts
index 2fb731201b..9786f8b8bb 100644
--- a/packages/backend/src/core/MfmService.ts
+++ b/packages/backend/src/core/MfmService.ts
@@ -6,7 +6,7 @@
import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
import * as parse5 from 'parse5';
-import { Window } from 'happy-dom';
+import { Window, XMLSerializer } from 'happy-dom';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { intersperse } from '@/misc/prelude/array.js';
@@ -247,6 +247,8 @@ export class MfmService {
const doc = window.document;
+ const body = doc.createElement('p');
+
function appendChildren(children: mfm.MfmNode[], targetElement: any): void {
if (children) {
for (const child of children.map(x => (handlers as any)[x.type](x))) targetElement.appendChild(child);
@@ -457,8 +459,8 @@ export class MfmService {
},
};
- appendChildren(nodes, doc.body);
+ appendChildren(nodes, body);
- return `
${doc.body.innerHTML}
`;
+ return new XMLSerializer().serializeToString(body);
}
}
diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts
index 42fbed2110..ec9f4484a4 100644
--- a/packages/backend/src/core/WebAuthnService.ts
+++ b/packages/backend/src/core/WebAuthnService.ts
@@ -10,7 +10,7 @@ import {
generateRegistrationOptions, verifyAuthenticationResponse,
verifyRegistrationResponse,
} from '@simplewebauthn/server';
-import { AttestationFormat, isoCBOR } from '@simplewebauthn/server/helpers';
+import { AttestationFormat, isoCBOR, isoUint8Array } from '@simplewebauthn/server/helpers';
import { DI } from '@/di-symbols.js';
import type { UserSecurityKeysRepository } from '@/models/_.js';
import type { Config } from '@/config.js';
@@ -49,7 +49,7 @@ export class WebAuthnService {
const instance = await this.metaService.fetch();
return {
origin: this.config.url,
- rpId: this.config.host,
+ rpId: this.config.hostname,
rpName: instance.name ?? this.config.host,
rpIcon: instance.iconUrl ?? undefined,
};
@@ -65,13 +65,12 @@ export class WebAuthnService {
const registrationOptions = await generateRegistrationOptions({
rpName: relyingParty.rpName,
rpID: relyingParty.rpId,
- userID: userId,
+ userID: isoUint8Array.fromUTF8String(userId),
userName: userName,
userDisplayName: userDisplayName,
attestationType: 'indirect',
- excludeCredentials: keys.map(key => ({
- id: Buffer.from(key.id, 'base64url'),
- type: 'public-key',
+ excludeCredentials: keys.map(key => (<{ id: string; transports?: AuthenticatorTransportFuture[]; }>{
+ id: key.id,
transports: key.transports ?? undefined,
})),
authenticatorSelection: {
@@ -87,7 +86,7 @@ export class WebAuthnService {
@bindThis
public async verifyRegistration(userId: MiUser['id'], response: RegistrationResponseJSON): Promise<{
- credentialID: Uint8Array;
+ credentialID: string;
credentialPublicKey: Uint8Array;
attestationObject: Uint8Array;
fmt: AttestationFormat;
@@ -144,6 +143,7 @@ export class WebAuthnService {
@bindThis
public async initiateAuthentication(userId: MiUser['id']): Promise {
+ const relyingParty = await this.getRelyingParty();
const keys = await this.userSecurityKeysRepository.findBy({
userId: userId,
});
@@ -153,9 +153,9 @@ export class WebAuthnService {
}
const authenticationOptions = await generateAuthenticationOptions({
- allowCredentials: keys.map(key => ({
- id: Buffer.from(key.id, 'base64url'),
- type: 'public-key',
+ rpID: relyingParty.rpId,
+ allowCredentials: keys.map(key => (<{ id: string; transports?: AuthenticatorTransportFuture[]; }>{
+ id: key.id,
transports: key.transports ?? undefined,
})),
userVerification: 'preferred',
@@ -219,7 +219,7 @@ export class WebAuthnService {
expectedOrigin: relyingParty.origin,
expectedRPID: relyingParty.rpId,
authenticator: {
- credentialID: Buffer.from(key.id, 'base64url'),
+ credentialID: key.id,
credentialPublicKey: Buffer.from(key.publicKey, 'base64url'),
counter: key.counter,
transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined,
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index d7fb977a99..4fc724b548 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -28,8 +28,9 @@ import { bindThis } from '@/decorators.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { isNotNull } from '@/misc/is-not-null.js';
import { IdService } from '@/core/IdService.js';
-import { LdSignatureService } from './LdSignatureService.js';
+import { JsonLdService } from './JsonLdService.js';
import { ApMfmService } from './ApMfmService.js';
+import { CONTEXT } from './misc/contexts.js';
import type { IAccept, IActivity, IAdd, IAnnounce, IApDocument, IApEmoji, IApHashtag, IApImage, IApMention, IBlock, ICreate, IDelete, IFlag, IFollow, IKey, ILike, IMove, IObject, IPost, IQuestion, IReject, IRemove, ITombstone, IUndo, IUpdate } from './type.js';
@Injectable()
@@ -56,7 +57,7 @@ export class ApRendererService {
private customEmojiService: CustomEmojiService,
private userEntityService: UserEntityService,
private driveFileEntityService: DriveFileEntityService,
- private ldSignatureService: LdSignatureService,
+ private jsonLdService: JsonLdService,
private userKeypairService: UserKeypairService,
private apMfmService: ApMfmService,
private mfmService: MfmService,
@@ -166,6 +167,7 @@ export class ApRendererService {
mediaType: file.webpublicType ?? file.type,
url: this.driveFileEntityService.getPublicUrl(file),
name: file.comment,
+ sensitive: file.isSensitive,
};
}
@@ -617,48 +619,16 @@ export class ApRendererService {
x.id = `${this.config.url}/${randomUUID()}`;
}
- return Object.assign({
- '@context': [
- 'https://www.w3.org/ns/activitystreams',
- 'https://w3id.org/security/v1',
- {
- Key: 'sec:Key',
- // as non-standards
- manuallyApprovesFollowers: 'as:manuallyApprovesFollowers',
- sensitive: 'as:sensitive',
- Hashtag: 'as:Hashtag',
- quoteUrl: 'as:quoteUrl',
- // Mastodon
- toot: 'http://joinmastodon.org/ns#',
- Emoji: 'toot:Emoji',
- featured: 'toot:featured',
- discoverable: 'toot:discoverable',
- // schema
- schema: 'http://schema.org#',
- PropertyValue: 'schema:PropertyValue',
- value: 'schema:value',
- // Misskey
- misskey: 'https://misskey-hub.net/ns#',
- '_misskey_content': 'misskey:_misskey_content',
- '_misskey_quote': 'misskey:_misskey_quote',
- '_misskey_reaction': 'misskey:_misskey_reaction',
- '_misskey_votes': 'misskey:_misskey_votes',
- '_misskey_summary': 'misskey:_misskey_summary',
- 'isCat': 'misskey:isCat',
- // vcard
- vcard: 'http://www.w3.org/2006/vcard/ns#',
- },
- ],
- }, x as T & { id: string });
+ return Object.assign({ '@context': CONTEXT }, x as T & { id: string });
}
@bindThis
public async attachLdSignature(activity: any, user: { id: MiUser['id']; host: null; }): Promise {
const keypair = await this.userKeypairService.getUserKeypair(user.id);
- const ldSignature = this.ldSignatureService.use();
- ldSignature.debug = false;
- activity = await ldSignature.signRsaSignature2017(activity, keypair.privateKey, `${this.config.url}/users/${user.id}#main-key`);
+ const jsonLd = this.jsonLdService.use();
+ jsonLd.debug = false;
+ activity = await jsonLd.signRsaSignature2017(activity, keypair.privateKey, `${this.config.url}/users/${user.id}#main-key`);
return activity;
}
diff --git a/packages/backend/src/core/activitypub/LdSignatureService.ts b/packages/backend/src/core/activitypub/JsonLdService.ts
similarity index 83%
rename from packages/backend/src/core/activitypub/LdSignatureService.ts
rename to packages/backend/src/core/activitypub/JsonLdService.ts
index 9de184336f..100d4fa19f 100644
--- a/packages/backend/src/core/activitypub/LdSignatureService.ts
+++ b/packages/backend/src/core/activitypub/JsonLdService.ts
@@ -7,14 +7,14 @@ import * as crypto from 'node:crypto';
import { Injectable } from '@nestjs/common';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { bindThis } from '@/decorators.js';
-import { CONTEXTS } from './misc/contexts.js';
+import { CONTEXT, PRELOADED_CONTEXTS } from './misc/contexts.js';
import { validateContentTypeSetAsJsonLD } from './misc/validator.js';
import type { JsonLdDocument } from 'jsonld';
-import type { JsonLd, RemoteDocument } from 'jsonld/jsonld-spec.js';
+import type { JsonLd as JsonLdObject, RemoteDocument } from 'jsonld/jsonld-spec.js';
-// RsaSignature2017 based from https://github.com/transmute-industries/RsaSignature2017
+// RsaSignature2017 implementation is based on https://github.com/transmute-industries/RsaSignature2017
-class LdSignature {
+class JsonLd {
public debug = false;
public preLoad = true;
public loderTimeout = 5000;
@@ -89,10 +89,18 @@ class LdSignature {
}
@bindThis
- public async normalize(data: JsonLdDocument): Promise {
+ public async compact(data: any, context: any = CONTEXT): Promise {
const customLoader = this.getLoader();
// XXX: Importing jsonld dynamically since Jest frequently fails to import it statically
// https://github.com/misskey-dev/misskey/pull/9894#discussion_r1103753595
+ return (await import('jsonld')).default.compact(data, context, {
+ documentLoader: customLoader,
+ });
+ }
+
+ @bindThis
+ public async normalize(data: JsonLdDocument): Promise {
+ const customLoader = this.getLoader();
return (await import('jsonld')).default.normalize(data, {
documentLoader: customLoader,
});
@@ -104,11 +112,11 @@ class LdSignature {
if (!/^https?:\/\//.test(url)) throw new Error(`Invalid URL ${url}`);
if (this.preLoad) {
- if (url in CONTEXTS) {
+ if (url in PRELOADED_CONTEXTS) {
if (this.debug) console.debug(`HIT: ${url}`);
return {
contextUrl: undefined,
- document: CONTEXTS[url],
+ document: PRELOADED_CONTEXTS[url],
documentUrl: url,
};
}
@@ -125,7 +133,7 @@ class LdSignature {
}
@bindThis
- private async fetchDocument(url: string): Promise {
+ private async fetchDocument(url: string): Promise {
const json = await this.httpRequestService.send(
url,
{
@@ -146,7 +154,7 @@ class LdSignature {
}
});
- return json as JsonLd;
+ return json as JsonLdObject;
}
@bindThis
@@ -158,14 +166,14 @@ class LdSignature {
}
@Injectable()
-export class LdSignatureService {
+export class JsonLdService {
constructor(
private httpRequestService: HttpRequestService,
) {
}
@bindThis
- public use(): LdSignature {
- return new LdSignature(this.httpRequestService);
+ public use(): JsonLd {
+ return new JsonLd(this.httpRequestService);
}
}
diff --git a/packages/backend/src/core/activitypub/misc/contexts.ts b/packages/backend/src/core/activitypub/misc/contexts.ts
index 88afdefcd3..feb8c42c56 100644
--- a/packages/backend/src/core/activitypub/misc/contexts.ts
+++ b/packages/backend/src/core/activitypub/misc/contexts.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import type { JsonLd } from 'jsonld/jsonld-spec.js';
+import type { Context, JsonLd } from 'jsonld/jsonld-spec.js';
/* eslint:disable:quotemark indent */
const id_v1 = {
@@ -526,7 +526,42 @@ const activitystreams = {
},
} satisfies JsonLd;
-export const CONTEXTS: Record = {
+const context_iris = [
+ 'https://www.w3.org/ns/activitystreams',
+ 'https://w3id.org/security/v1',
+];
+
+const extension_context_definition = {
+ Key: 'sec:Key',
+ // as non-standards
+ manuallyApprovesFollowers: 'as:manuallyApprovesFollowers',
+ sensitive: 'as:sensitive',
+ Hashtag: 'as:Hashtag',
+ quoteUrl: 'as:quoteUrl',
+ // Mastodon
+ toot: 'http://joinmastodon.org/ns#',
+ Emoji: 'toot:Emoji',
+ featured: 'toot:featured',
+ discoverable: 'toot:discoverable',
+ // schema
+ schema: 'http://schema.org#',
+ PropertyValue: 'schema:PropertyValue',
+ value: 'schema:value',
+ // Misskey
+ misskey: 'https://misskey-hub.net/ns#',
+ '_misskey_content': 'misskey:_misskey_content',
+ '_misskey_quote': 'misskey:_misskey_quote',
+ '_misskey_reaction': 'misskey:_misskey_reaction',
+ '_misskey_votes': 'misskey:_misskey_votes',
+ '_misskey_summary': 'misskey:_misskey_summary',
+ 'isCat': 'misskey:isCat',
+ // vcard
+ vcard: 'http://www.w3.org/2006/vcard/ns#',
+} satisfies Context;
+
+export const CONTEXT: (string | Context)[] = [...context_iris, extension_context_definition];
+
+export const PRELOADED_CONTEXTS: Record = {
'https://w3id.org/identity/v1': id_v1,
'https://w3id.org/security/v1': security_v1,
'https://www.w3.org/ns/activitystreams': activitystreams,
diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts
index 89b6ef23d0..3691967270 100644
--- a/packages/backend/src/core/activitypub/models/ApImageService.ts
+++ b/packages/backend/src/core/activitypub/models/ApImageService.ts
@@ -17,7 +17,7 @@ import { bindThis } from '@/decorators.js';
import { checkHttps } from '@/misc/check-https.js';
import { ApResolverService } from '../ApResolverService.js';
import { ApLoggerService } from '../ApLoggerService.js';
-import type { IObject } from '../type.js';
+import { isDocument, type IObject } from '../type.js';
@Injectable()
export class ApImageService {
@@ -39,7 +39,7 @@ export class ApImageService {
* Imageを作成します。
*/
@bindThis
- public async createImage(actor: MiRemoteUser, value: string | IObject): Promise {
+ public async createImage(actor: MiRemoteUser, value: string | IObject): Promise {
// 投稿者が凍結されていたらスキップ
if (actor.isSuspended) {
throw new Error('actor has been suspended');
@@ -47,16 +47,18 @@ export class ApImageService {
const image = await this.apResolverService.createResolver().resolve(value);
+ if (!isDocument(image)) return null;
+
if (image.url == null) {
- throw new Error('invalid image: url not provided');
+ return null;
}
if (typeof image.url !== 'string') {
- throw new Error('invalid image: unexpected type of url: ' + JSON.stringify(image.url, null, 2));
+ return null;
}
if (!checkHttps(image.url)) {
- throw new Error('invalid image: unexpected schema of url: ' + image.url);
+ return null;
}
this.logger.info(`Creating the Image: ${image.url}`);
@@ -86,12 +88,11 @@ export class ApImageService {
/**
* Imageを解決します。
*
- * Misskeyに対象のImageが登録されていればそれを返し、そうでなければ
- * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
+ * ImageをリモートサーバーからフェッチしてMisskeyに登録しそれを返します。
*/
@bindThis
- public async resolveImage(actor: MiRemoteUser, value: string | IObject): Promise {
- // TODO
+ public async resolveImage(actor: MiRemoteUser, value: string | IObject): Promise {
+ // TODO: Misskeyに対象のImageが登録されていればそれを返す
// リモートサーバーからフェッチしてきて登録
return await this.createImage(actor, value);
diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts
index 4d64b08e15..4e361b57bc 100644
--- a/packages/backend/src/core/activitypub/models/ApNoteService.ts
+++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts
@@ -4,7 +4,6 @@
*/
import { forwardRef, Inject, Injectable } from '@nestjs/common';
-import promiseLimit from 'promise-limit';
import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type { PollsRepository, EmojisRepository } from '@/models/_.js';
@@ -209,15 +208,13 @@ export class ApNoteService {
}
// 添付ファイル
- // TODO: attachmentは必ずしもImageではない
- // TODO: attachmentは必ずしも配列ではない
- const limit = promiseLimit(2);
- const files = (await Promise.all(toArray(note.attachment).map(attach => (
- limit(() => this.apImageService.resolveImage(actor, {
- ...attach,
- sensitive: note.sensitive, // Noteがsensitiveなら添付もsensitiveにする
- }))
- ))));
+ const files: MiDriveFile[] = [];
+
+ for (const attach of toArray(note.attachment)) {
+ attach.sensitive ??= note.sensitive;
+ const file = await this.apImageService.resolveImage(actor, attach);
+ if (file) files.push(file);
+ }
// リプライ
const reply: MiNote | null = note.inReplyTo
diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts
index b43dddad61..09322888d5 100644
--- a/packages/backend/src/core/activitypub/type.ts
+++ b/packages/backend/src/core/activitypub/type.ts
@@ -25,6 +25,7 @@ export interface IObject {
endTime?: Date;
icon?: any;
image?: any;
+ mediaType?: string;
url?: ApObject | string;
href?: string;
tag?: IObject | IObject[];
@@ -240,14 +241,14 @@ export interface IKey extends IObject {
}
export interface IApDocument extends IObject {
- type: 'Document';
- name: string | null;
- mediaType: string;
+ type: 'Audio' | 'Document' | 'Image' | 'Page' | 'Video';
}
-export interface IApImage extends IObject {
+export const isDocument = (object: IObject): object is IApDocument =>
+ ['Audio', 'Document', 'Image', 'Page', 'Video'].includes(getApType(object));
+
+export interface IApImage extends IApDocument {
type: 'Image';
- name: string | null;
}
export interface ICreate extends IActivity {
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index df2b27d709..b80a1ec206 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -249,20 +249,41 @@ export class UserEntityService implements OnModuleInit {
] = await Promise.all([
this.followingsRepository.findBy({ followerId: me })
.then(f => new Map(f.map(it => [it.followeeId, it]))),
- this.followingsRepository.findBy({ followeeId: me })
- .then(it => it.map(it => it.followerId)),
- this.followRequestsRepository.findBy({ followerId: me })
- .then(it => it.map(it => it.followeeId)),
- this.followRequestsRepository.findBy({ followeeId: me })
- .then(it => it.map(it => it.followerId)),
- this.blockingsRepository.findBy({ blockerId: me })
- .then(it => it.map(it => it.blockeeId)),
- this.blockingsRepository.findBy({ blockeeId: me })
- .then(it => it.map(it => it.blockerId)),
- this.mutingsRepository.findBy({ muterId: me })
- .then(it => it.map(it => it.muteeId)),
- this.renoteMutingsRepository.findBy({ muterId: me })
- .then(it => it.map(it => it.muteeId)),
+ this.followingsRepository.createQueryBuilder('f')
+ .select('f.followerId')
+ .where('f.followeeId = :me', { me })
+ .getRawMany<{ f_followerId: string }>()
+ .then(it => it.map(it => it.f_followerId)),
+ this.followRequestsRepository.createQueryBuilder('f')
+ .select('f.followeeId')
+ .where('f.followerId = :me', { me })
+ .getRawMany<{ f_followeeId: string }>()
+ .then(it => it.map(it => it.f_followeeId)),
+ this.followRequestsRepository.createQueryBuilder('f')
+ .select('f.followerId')
+ .where('f.followeeId = :me', { me })
+ .getRawMany<{ f_followerId: string }>()
+ .then(it => it.map(it => it.f_followerId)),
+ this.blockingsRepository.createQueryBuilder('b')
+ .select('b.blockeeId')
+ .where('b.blockerId = :me', { me })
+ .getRawMany<{ b_blockeeId: string }>()
+ .then(it => it.map(it => it.b_blockeeId)),
+ this.blockingsRepository.createQueryBuilder('b')
+ .select('b.blockerId')
+ .where('b.blockeeId = :me', { me })
+ .getRawMany<{ b_blockerId: string }>()
+ .then(it => it.map(it => it.b_blockerId)),
+ this.mutingsRepository.createQueryBuilder('m')
+ .select('m.muteeId')
+ .where('m.muterId = :me', { me })
+ .getRawMany<{ m_muteeId: string }>()
+ .then(it => it.map(it => it.m_muteeId)),
+ this.renoteMutingsRepository.createQueryBuilder('m')
+ .select('m.muteeId')
+ .where('m.muterId = :me', { me })
+ .getRawMany<{ m_muteeId: string }>()
+ .then(it => it.map(it => it.m_muteeId)),
]);
return new Map(
@@ -637,18 +658,17 @@ export class UserEntityService implements OnModuleInit {
}
const _userIds = _users.map(u => u.id);
- // -- 特に前提条件のない値群を取得
-
- const profilesMap = await this.userProfilesRepository.findBy({ userId: In(_userIds) })
- .then(profiles => new Map(profiles.map(p => [p.userId, p])));
-
// -- 実行者の有無や指定スキーマの種別によって要否が異なる値群を取得
+ let profilesMap: Map = new Map();
let userRelations: Map = new Map();
let userMemos: Map = new Map();
let pinNotes: Map = new Map();
if (options?.schema !== 'UserLite') {
+ profilesMap = await this.userProfilesRepository.findBy({ userId: In(_userIds) })
+ .then(profiles => new Map(profiles.map(p => [p.userId, p])));
+
const meId = me ? me.id : null;
if (meId) {
userMemos = await this.userMemosRepository.findBy({ userId: meId })
diff --git a/packages/backend/src/misc/gen-identicon.ts b/packages/backend/src/misc/gen-identicon.ts
index 62a8ab8ace..342e0f8602 100644
--- a/packages/backend/src/misc/gen-identicon.ts
+++ b/packages/backend/src/misc/gen-identicon.ts
@@ -8,9 +8,8 @@
* https://en.wikipedia.org/wiki/Identicon
*/
-import * as p from 'pureimage';
+import { createCanvas } from '@napi-rs/canvas';
import gen from 'random-seed';
-import type { WriteStream } from 'node:fs';
const size = 128; // px
const n = 5; // resolution
@@ -45,9 +44,9 @@ const sideN = Math.floor(n / 2);
/**
* Generate buffer of an identicon by seed
*/
-export function genIdenticon(seed: string, stream: WriteStream): Promise {
+export async function genIdenticon(seed: string): Promise {
const rand = gen.create(seed);
- const canvas = p.make(size, size, undefined);
+ const canvas = createCanvas(size, size);
const ctx = canvas.getContext('2d');
const bgColors = colors[rand(colors.length)];
@@ -101,5 +100,5 @@ export function genIdenticon(seed: string, stream: WriteStream): Promise {
}
}
- return p.encodePNGToStream(canvas, stream);
+ return await canvas.encode('png');
}
diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts
index 3addead058..1d05f4ade1 100644
--- a/packages/backend/src/queue/processors/InboxProcessorService.ts
+++ b/packages/backend/src/queue/processors/InboxProcessorService.ts
@@ -15,13 +15,14 @@ import InstanceChart from '@/core/chart/charts/instance.js';
import ApRequestChart from '@/core/chart/charts/ap-request.js';
import FederationChart from '@/core/chart/charts/federation.js';
import { getApId } from '@/core/activitypub/type.js';
+import type { IActivity } from '@/core/activitypub/type.js';
import type { MiRemoteUser } from '@/models/User.js';
import type { MiUserPublickey } from '@/models/UserPublickey.js';
import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
import { StatusError } from '@/misc/status-error.js';
import { UtilityService } from '@/core/UtilityService.js';
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
-import { LdSignatureService } from '@/core/activitypub/LdSignatureService.js';
+import { JsonLdService } from '@/core/activitypub/JsonLdService.js';
import { ApInboxService } from '@/core/activitypub/ApInboxService.js';
import { bindThis } from '@/decorators.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
@@ -38,7 +39,7 @@ export class InboxProcessorService {
private apInboxService: ApInboxService,
private federatedInstanceService: FederatedInstanceService,
private fetchInstanceMetadataService: FetchInstanceMetadataService,
- private ldSignatureService: LdSignatureService,
+ private jsonLdService: JsonLdService,
private apPersonService: ApPersonService,
private apDbResolverService: ApDbResolverService,
private instanceChart: InstanceChart,
@@ -52,7 +53,7 @@ export class InboxProcessorService {
@bindThis
public async process(job: Bull.Job): Promise {
const signature = job.data.signature; // HTTP-signature
- const activity = job.data.activity;
+ let activity = job.data.activity;
//#region Log
const info = Object.assign({}, activity);
@@ -110,20 +111,21 @@ export class InboxProcessorService {
// また、signatureのsignerは、activity.actorと一致する必要がある
if (!httpSignatureValidated || authUser.user.uri !== activity.actor) {
// 一致しなくても、でもLD-Signatureがありそうならそっちも見る
- if (activity.signature) {
- if (activity.signature.type !== 'RsaSignature2017') {
- throw new Bull.UnrecoverableError(`skip: unsupported LD-signature type ${activity.signature.type}`);
+ const ldSignature = activity.signature;
+ if (ldSignature) {
+ if (ldSignature.type !== 'RsaSignature2017') {
+ throw new Bull.UnrecoverableError(`skip: unsupported LD-signature type ${ldSignature.type}`);
}
- // activity.signature.creator: https://example.oom/users/user#main-key
+ // ldSignature.creator: https://example.oom/users/user#main-key
// みたいになっててUserを引っ張れば公開キーも入ることを期待する
- if (activity.signature.creator) {
- const candicate = activity.signature.creator.replace(/#.*/, '');
+ if (ldSignature.creator) {
+ const candicate = ldSignature.creator.replace(/#.*/, '');
await this.apPersonService.resolvePerson(candicate).catch(() => null);
}
// keyIdからLD-Signatureのユーザーを取得
- authUser = await this.apDbResolverService.getAuthUserFromKeyId(activity.signature.creator);
+ authUser = await this.apDbResolverService.getAuthUserFromKeyId(ldSignature.creator);
if (authUser == null) {
throw new Bull.UnrecoverableError('skip: LD-Signatureのユーザーが取得できませんでした');
}
@@ -132,13 +134,31 @@ export class InboxProcessorService {
throw new Bull.UnrecoverableError('skip: LD-SignatureのユーザーはpublicKeyを持っていませんでした');
}
+ const jsonLd = this.jsonLdService.use();
+
// LD-Signature検証
- const ldSignature = this.ldSignatureService.use();
- const verified = await ldSignature.verifyRsaSignature2017(activity, authUser.key.keyPem).catch(() => false);
+ const verified = await jsonLd.verifyRsaSignature2017(activity, authUser.key.keyPem).catch(() => false);
if (!verified) {
throw new Bull.UnrecoverableError('skip: LD-Signatureの検証に失敗しました');
}
+ // アクティビティを正規化
+ delete activity.signature;
+ try {
+ activity = await jsonLd.compact(activity) as IActivity;
+ } catch (e) {
+ throw new Bull.UnrecoverableError(`skip: failed to compact activity: ${e}`);
+ }
+ // TODO: 元のアクティビティと非互換な形に正規化される場合は転送をスキップする
+ // https://github.com/mastodon/mastodon/blob/664b0ca/app/services/activitypub/process_collection_service.rb#L24-L29
+ activity.signature = ldSignature;
+
+ //#region Log
+ const compactedInfo = Object.assign({}, activity);
+ delete compactedInfo['@context'];
+ this.logger.debug(`compacted: ${JSON.stringify(compactedInfo, null, 2)}`);
+ //#endregion
+
// もう一度actorチェック
if (authUser.user.uri !== activity.actor) {
throw new Bull.UnrecoverableError(`skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`);
diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts
index 671dd31eb1..da17a88e03 100644
--- a/packages/backend/src/server/ServerService.ts
+++ b/packages/backend/src/server/ServerService.ts
@@ -18,7 +18,6 @@ import { DI } from '@/di-symbols.js';
import type Logger from '@/logger.js';
import * as Acct from '@/misc/acct.js';
import { genIdenticon } from '@/misc/gen-identicon.js';
-import { createTemp } from '@/misc/create-temp.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js';
@@ -120,12 +119,20 @@ export class ServerService implements OnApplicationShutdown {
return;
}
- const name = path.split('@')[0].replace(/\.webp$/i, '');
- const host = path.split('@')[1]?.replace(/\.webp$/i, '');
+ const emojiPath = path.replace(/\.webp$/i, '');
+ const pathChunks = emojiPath.split('@');
+
+ if (pathChunks.length > 2) {
+ reply.code(400);
+ return;
+ }
+
+ const name = pathChunks.shift();
+ const host = pathChunks.pop();
const emoji = await this.emojisRepository.findOneBy({
// `@.` is the spec of ReactionService.decodeReaction
- host: (host == null || host === '.') ? IsNull() : host,
+ host: (host === undefined || host === '.') ? IsNull() : host,
name: name,
});
@@ -184,9 +191,7 @@ export class ServerService implements OnApplicationShutdown {
reply.header('Cache-Control', 'public, max-age=86400');
if ((await this.metaService.fetch()).enableIdenticonGeneration) {
- const [temp, cleanup] = await createTemp();
- await genIdenticon(request.params.x, fs.createWriteStream(temp));
- return fs.createReadStream(temp).on('close', () => cleanup());
+ return await genIdenticon(request.params.x);
} else {
return reply.redirect('/static-assets/avatar.png');
}
diff --git a/packages/backend/src/server/api/endpoints/fetch-rss.ts b/packages/backend/src/server/api/endpoints/fetch-rss.ts
index 2085b06365..ba48b0119e 100644
--- a/packages/backend/src/server/api/endpoints/fetch-rss.ts
+++ b/packages/backend/src/server/api/endpoints/fetch-rss.ts
@@ -20,13 +20,188 @@ export const meta = {
res: {
type: 'object',
properties: {
+ image: {
+ type: 'object',
+ optional: true,
+ properties: {
+ link: {
+ type: 'string',
+ optional: true,
+ },
+ url: {
+ type: 'string',
+ optional: false,
+ },
+ title: {
+ type: 'string',
+ optional: true,
+ },
+ },
+ },
+ paginationLinks: {
+ type: 'object',
+ optional: true,
+ properties: {
+ self: {
+ type: 'string',
+ optional: true,
+ },
+ first: {
+ type: 'string',
+ optional: true,
+ },
+ next: {
+ type: 'string',
+ optional: true,
+ },
+ last: {
+ type: 'string',
+ optional: true,
+ },
+ prev: {
+ type: 'string',
+ optional: true,
+ },
+ },
+ },
+ link: {
+ type: 'string',
+ optional: true,
+ },
+ title: {
+ type: 'string',
+ optional: true,
+ },
items: {
type: 'array',
+ optional: false,
items: {
type: 'object',
+ properties: {
+ link: {
+ type: 'string',
+ optional: true,
+ },
+ guid: {
+ type: 'string',
+ optional: true,
+ },
+ title: {
+ type: 'string',
+ optional: true,
+ },
+ pubDate: {
+ type: 'string',
+ optional: true,
+ },
+ creator: {
+ type: 'string',
+ optional: true,
+ },
+ summary: {
+ type: 'string',
+ optional: true,
+ },
+ content: {
+ type: 'string',
+ optional: true,
+ },
+ isoDate: {
+ type: 'string',
+ optional: true,
+ },
+ categories: {
+ type: 'array',
+ optional: true,
+ items: {
+ type: 'string',
+ },
+ },
+ contentSnippet: {
+ type: 'string',
+ optional: true,
+ },
+ enclosure: {
+ type: 'object',
+ optional: true,
+ properties: {
+ url: {
+ type: 'string',
+ optional: false,
+ },
+ length: {
+ type: 'number',
+ optional: true,
+ },
+ type: {
+ type: 'string',
+ optional: true,
+ },
+ },
+ },
+ },
},
- }
- }
+ },
+ feedUrl: {
+ type: 'string',
+ optional: true,
+ },
+ description: {
+ type: 'string',
+ optional: true,
+ },
+ itunes: {
+ type: 'object',
+ optional: true,
+ additionalProperties: true,
+ properties: {
+ image: {
+ type: 'string',
+ optional: true,
+ },
+ owner: {
+ type: 'object',
+ optional: true,
+ properties: {
+ name: {
+ type: 'string',
+ optional: true,
+ },
+ email: {
+ type: 'string',
+ optional: true,
+ },
+ },
+ },
+ author: {
+ type: 'string',
+ optional: true,
+ },
+ summary: {
+ type: 'string',
+ optional: true,
+ },
+ explicit: {
+ type: 'string',
+ optional: true,
+ },
+ categories: {
+ type: 'array',
+ optional: true,
+ items: {
+ type: 'string',
+ },
+ },
+ keywords: {
+ type: 'array',
+ optional: true,
+ items: {
+ type: 'string',
+ },
+ },
+ },
+ },
+ },
},
} as const;
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
index 5f738420f2..65eece5b97 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
@@ -96,10 +96,10 @@ export default class extends Endpoint {
}
const keyInfo = await this.webAuthnService.verifyRegistration(me.id, ps.credential);
+ const keyId = keyInfo.credentialID;
- const credentialId = Buffer.from(keyInfo.credentialID).toString('base64url');
await this.userSecurityKeysRepository.insert({
- id: credentialId,
+ id: keyId,
userId: me.id,
name: ps.name,
publicKey: Buffer.from(keyInfo.credentialPublicKey).toString('base64url'),
@@ -116,7 +116,7 @@ export default class extends Endpoint {
}));
return {
- id: credentialId,
+ id: keyId,
name: ps.name,
};
});
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index b1af0c3df6..1394616752 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -199,9 +199,18 @@ export class ClientServerService {
// Authenticate
fastify.addHook('onRequest', async (request, reply) => {
+ if (request.routeOptions.url == null) {
+ reply.code(404).send('Not found');
+ return;
+ }
+
// %71ueueとかでリクエストされたら困るため
const url = decodeURI(request.routeOptions.url);
if (url === bullBoardPath || url.startsWith(bullBoardPath + '/')) {
+ if (!url.startsWith(bullBoardPath + '/static/')) {
+ reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
+ }
+
const token = request.cookies.token;
if (token == null) {
reply.code(401).send('Login required');
diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug
index 123336809b..1d9146e22a 100644
--- a/packages/backend/src/server/web/views/base.pug
+++ b/packages/backend/src/server/web/views/base.pug
@@ -36,7 +36,7 @@ html
link(rel='prefetch' href=infoImageUrl)
link(rel='prefetch' href=notFoundImageUrl)
//- https://github.com/misskey-dev/misskey/issues/9842
- link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.44.0')
+ link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v3.3.0')
link(rel='modulepreload' href=`/vite/${clientEntry.file}`)
if !config.clientManifestExists
diff --git a/packages/backend/src/server/web/views/page.pug b/packages/backend/src/server/web/views/page.pug
index 08bb08ffe7..03c50eca8a 100644
--- a/packages/backend/src/server/web/views/page.pug
+++ b/packages/backend/src/server/web/views/page.pug
@@ -3,7 +3,7 @@ extends ./base
block vars
- const user = page.user;
- const title = page.title;
- - const url = `${config.url}/@${user.username}/${page.name}`;
+ - const url = `${config.url}/@${user.username}/pages/${page.name}`;
block title
= `${title} | ${instanceName}`
diff --git a/packages/backend/test/unit/MfmService.ts b/packages/backend/test/unit/MfmService.ts
index f613fe9c7c..fd4a03413b 100644
--- a/packages/backend/test/unit/MfmService.ts
+++ b/packages/backend/test/unit/MfmService.ts
@@ -39,6 +39,12 @@ describe('MfmService', () => {
const output = 'foo bar
';
assert.equal(mfmService.toHtml(mfm.parse(input)), output);
});
+
+ test('escape', () => {
+ const input = '```\nHello, world!
\n```';
+ const output = '<p>Hello, world!</p>
';
+ assert.equal(mfmService.toHtml(mfm.parse(input)), output);
+ });
});
describe('fromHtml', () => {
diff --git a/packages/backend/test/unit/activitypub.ts b/packages/backend/test/unit/activitypub.ts
index b4b06b06bd..6962608106 100644
--- a/packages/backend/test/unit/activitypub.ts
+++ b/packages/backend/test/unit/activitypub.ts
@@ -13,11 +13,13 @@ import { ApImageService } from '@/core/activitypub/models/ApImageService.js';
import { ApNoteService } from '@/core/activitypub/models/ApNoteService.js';
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
+import { JsonLdService } from '@/core/activitypub/JsonLdService.js';
+import { CONTEXT } from '@/core/activitypub/misc/contexts.js';
import { GlobalModule } from '@/GlobalModule.js';
import { CoreModule } from '@/core/CoreModule.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { LoggerService } from '@/core/LoggerService.js';
-import type { IActor, IApDocument, ICollection, IPost } from '@/core/activitypub/type.js';
+import type { IActor, IApDocument, ICollection, IObject, IPost } from '@/core/activitypub/type.js';
import { MiMeta, MiNote } from '@/models/_.js';
import { secureRndstr } from '@/misc/secure-rndstr.js';
import { DownloadService } from '@/core/DownloadService.js';
@@ -88,6 +90,7 @@ describe('ActivityPub', () => {
let noteService: ApNoteService;
let personService: ApPersonService;
let rendererService: ApRendererService;
+ let jsonLdService: JsonLdService;
let resolver: MockResolver;
const metaInitial = {
@@ -128,6 +131,7 @@ describe('ActivityPub', () => {
personService = app.get(ApPersonService);
rendererService = app.get(ApRendererService);
imageService = app.get(ApImageService);
+ jsonLdService = app.get(JsonLdService);
resolver = new MockResolver(await app.resolve(LoggerService));
// Prevent ApPersonService from fetching instance, as it causes Jest import-after-test error
@@ -295,7 +299,7 @@ describe('ActivityPub', () => {
await createRandomRemoteUser(resolver, personService),
imageObject,
);
- assert.ok(!driveFile.isLink);
+ assert.ok(driveFile && !driveFile.isLink);
const sensitiveImageObject: IApDocument = {
type: 'Document',
@@ -308,7 +312,7 @@ describe('ActivityPub', () => {
await createRandomRemoteUser(resolver, personService),
sensitiveImageObject,
);
- assert.ok(!sensitiveDriveFile.isLink);
+ assert.ok(sensitiveDriveFile && !sensitiveDriveFile.isLink);
});
test('cacheRemoteFiles=false disables caching', async () => {
@@ -324,7 +328,7 @@ describe('ActivityPub', () => {
await createRandomRemoteUser(resolver, personService),
imageObject,
);
- assert.ok(driveFile.isLink);
+ assert.ok(driveFile && driveFile.isLink);
const sensitiveImageObject: IApDocument = {
type: 'Document',
@@ -337,7 +341,7 @@ describe('ActivityPub', () => {
await createRandomRemoteUser(resolver, personService),
sensitiveImageObject,
);
- assert.ok(sensitiveDriveFile.isLink);
+ assert.ok(sensitiveDriveFile && sensitiveDriveFile.isLink);
});
test('cacheRemoteSensitiveFiles=false only affects sensitive files', async () => {
@@ -353,7 +357,7 @@ describe('ActivityPub', () => {
await createRandomRemoteUser(resolver, personService),
imageObject,
);
- assert.ok(!driveFile.isLink);
+ assert.ok(driveFile && !driveFile.isLink);
const sensitiveImageObject: IApDocument = {
type: 'Document',
@@ -366,7 +370,57 @@ describe('ActivityPub', () => {
await createRandomRemoteUser(resolver, personService),
sensitiveImageObject,
);
- assert.ok(sensitiveDriveFile.isLink);
+ assert.ok(sensitiveDriveFile && sensitiveDriveFile.isLink);
+ });
+
+ test('Link is not an attachment files', async () => {
+ const linkObject: IObject = {
+ type: 'Link',
+ href: 'https://example.com/',
+ };
+ const driveFile = await imageService.createImage(
+ await createRandomRemoteUser(resolver, personService),
+ linkObject,
+ );
+ assert.strictEqual(driveFile, null);
+ });
+ });
+
+ describe('JSON-LD', () =>{
+ test('Compaction', async () => {
+ const jsonLd = jsonLdService.use();
+
+ const object = {
+ '@context': [
+ 'https://www.w3.org/ns/activitystreams',
+ {
+ _misskey_quote: 'https://misskey-hub.net/ns#_misskey_quote',
+ unknown: 'https://example.org/ns#unknown',
+ undefined: null,
+ },
+ ],
+ id: 'https://example.com/notes/42',
+ type: 'Note',
+ attributedTo: 'https://example.com/users/1',
+ to: ['https://www.w3.org/ns/activitystreams#Public'],
+ content: 'test test foo',
+ _misskey_quote: 'https://example.com/notes/1',
+ unknown: 'test test bar',
+ undefined: 'test test baz',
+ };
+ const compacted = await jsonLd.compact(object);
+
+ assert.deepStrictEqual(compacted, {
+ '@context': CONTEXT,
+ id: 'https://example.com/notes/42',
+ type: 'Note',
+ attributedTo: 'https://example.com/users/1',
+ to: 'as:Public',
+ content: 'test test foo',
+ _misskey_quote: 'https://example.com/notes/1',
+ 'https://example.org/ns#unknown': 'test test bar',
+ // undefined: 'test test baz',
+ });
});
});
});
diff --git a/packages/frontend/.storybook/fakes.ts b/packages/frontend/.storybook/fakes.ts
index 48c9e0261d..3a24ccb248 100644
--- a/packages/frontend/.storybook/fakes.ts
+++ b/packages/frontend/.storybook/fakes.ts
@@ -27,7 +27,7 @@ export function galleryPost(isSensitive = false) {
id: 'somepostid',
createdAt: '2016-12-28T22:49:51.000Z',
updatedAt: '2016-12-28T22:49:51.000Z',
- userid: 'someuserid',
+ userId: 'someuserid',
user: userDetailed(),
title: 'Some post title',
description: 'Some post description',
@@ -75,9 +75,8 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host = 'mi
avatarUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true',
avatarBlurhash: 'eQFRshof5NWBRi},juayfPju53WB?0ofs;s*a{ofjuay^SoMEJR%ay',
avatarDecorations: [],
- emojis: [],
+ emojis: {},
bannerBlurhash: 'eQA^IW^-MH8w9tE8I=S^o{$*R4RikXtSxutRozjEnNR.RQadoyozog',
- bannerColor: '#000000',
bannerUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true',
birthday: '2014-06-20',
createdAt: '2016-12-28T22:49:51.000Z',
@@ -118,11 +117,16 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host = 'mi
publicReactions: false,
securityKeys: false,
twoFactorEnabled: false,
+ usePasswordLessLogin: false,
twoFactorBackupCodesStock: 'none',
updatedAt: null,
+ lastFetchedAt: null,
uri: null,
url: null,
+ movedTo: null,
+ alsoKnownAs: null,
notify: 'none',
+ memo: null
};
}
diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx
index c1149a0cb2..1965fee347 100644
--- a/packages/frontend/.storybook/generate.tsx
+++ b/packages/frontend/.storybook/generate.tsx
@@ -82,23 +82,16 @@ function h(
return Object.assign(props || {}, { type }) as T;
}
-declare global {
- namespace JSX {
- type Element = estree.Node;
- type ElementClass = never;
- type ElementAttributesProperty = never;
- type ElementChildrenAttribute = never;
- type IntrinsicAttributes = never;
- type IntrinsicClassAttributes = never;
- type IntrinsicElements = {
- [T in keyof typeof generator as ToKebab>>]: {
- [K in keyof Omit<
- Parameters<(typeof generator)[T]>[0],
- 'type'
- >]?: Parameters<(typeof generator)[T]>[0][K];
- };
+declare namespace h.JSX {
+ type Element = estree.Node;
+ type IntrinsicElements = {
+ [T in keyof typeof generator as ToKebab>>]: {
+ [K in keyof Omit<
+ Parameters<(typeof generator)[T]>[0],
+ 'type'
+ >]?: Parameters<(typeof generator)[T]>[0][K];
};
- }
+ };
}
function toStories(component: string): Promise {
@@ -388,6 +381,7 @@ function toStories(component: string): Promise {
'/* eslint-disable @typescript-eslint/explicit-function-return-type */\n' +
'/* eslint-disable import/no-default-export */\n' +
'/* eslint-disable import/no-duplicates */\n' +
+ '/* eslint-disable import/order */\n' +
generate(program, { generator }) +
(hasImplStories ? readFileSync(`${implStories}.ts`, 'utf-8') : ''),
{
diff --git a/packages/frontend/.storybook/main.ts b/packages/frontend/.storybook/main.ts
index 0a87488573..d3822942cd 100644
--- a/packages/frontend/.storybook/main.ts
+++ b/packages/frontend/.storybook/main.ts
@@ -34,7 +34,7 @@ const config = {
disableTelemetry: true,
},
async viteFinal(config) {
- const replacePluginForIsChromatic = config.plugins?.findIndex((plugin) => plugin && (plugin as Partial)?.name === 'replace') ?? -1;
+ const replacePluginForIsChromatic = config.plugins?.findIndex((plugin: Plugin) => plugin && plugin.name === 'replace') ?? -1;
if (~replacePluginForIsChromatic) {
config.plugins?.splice(replacePluginForIsChromatic, 1);
}
diff --git a/packages/frontend/.storybook/mocks.ts b/packages/frontend/.storybook/mocks.ts
index 817b0125e7..29cb112ccb 100644
--- a/packages/frontend/.storybook/mocks.ts
+++ b/packages/frontend/.storybook/mocks.ts
@@ -6,7 +6,8 @@
import { type SharedOptions, http, HttpResponse } from 'msw';
export const onUnhandledRequest = ((req, print) => {
- if (req.url.hostname !== 'localhost' || /^\/(?:client-assets\/|fluent-emojis?\/|iframe.html$|node_modules\/|src\/|sb-|static-assets\/|vite\/)/.test(req.url.pathname)) {
+ const url = new URL(req.url);
+ if (url.hostname !== 'localhost' || /^\/(?:client-assets\/|fluent-emojis?\/|iframe.html$|node_modules\/|src\/|sb-|static-assets\/|vite\/)/.test(url.pathname)) {
return
}
print.warning()
diff --git a/packages/frontend/.storybook/preview-head.html b/packages/frontend/.storybook/preview-head.html
index e50c488243..4722fe7f5f 100644
--- a/packages/frontend/.storybook/preview-head.html
+++ b/packages/frontend/.storybook/preview-head.html
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+