Merge branch 'develop' into enhance-thread-mute

This commit is contained in:
Sayamame-beans 2025-02-24 20:14:09 +09:00 committed by GitHub
commit 850251afb2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
431 changed files with 8321 additions and 7672 deletions

View File

@ -220,5 +220,10 @@ allowedPrivateNetworks: [
'127.0.0.1/32'
]
# Disable automatic redirect for ActivityPub object lookup. (default: false)
# This is a strong defense against potential impersonation attacks if the viewer instance has inadequate validation.
# However it will make it impossible for other instances to lookup third-party user and notes through your URL.
#disallowExternalApRedirect: true
# Upload or download file size limits (bytes)
#maxFileSize: 262144000

View File

@ -235,6 +235,11 @@ signToActivityPubGet: true
# '127.0.0.1/32'
#]
# Disable automatic redirect for ActivityPub object lookup. (default: false)
# This is a strong defense against potential impersonation attacks if the viewer instance has inadequate validation.
# However it will make it impossible for other instances to lookup third-party user and notes through your URL.
#disallowExternalApRedirect: true
# Upload or download file size limits (bytes)
#maxFileSize: 262144000

View File

@ -334,6 +334,11 @@ signToActivityPubGet: true
# '127.0.0.1/32'
#]
# Disable automatic redirect for ActivityPub object lookup. (default: false)
# This is a strong defense against potential impersonation attacks if the viewer instance has inadequate validation.
# However it will make it impossible for other instances to lookup third-party user and notes through your URL.
#disallowExternalApRedirect: true
# Upload or download file size limits (bytes)
#maxFileSize: 262144000

View File

@ -7,7 +7,9 @@
"ghcr.io/devcontainers/features/node:1": {
"version": "22.11.0"
},
"ghcr.io/devcontainers-contrib/features/corepack:1": {}
"ghcr.io/devcontainers-extra/features/corepack:1": {
"version": "0.31.0"
}
},
"forwardPorts": [3000],
"postCreateCommand": "/bin/bash .devcontainer/init.sh",

View File

@ -9,7 +9,7 @@ updates:
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 100
open-pull-requests-limit: 0
# Add only the root, not each workspace item
# https://github.com/dependabot/dependabot-core/issues/4993#issuecomment-1289133027
@ -17,7 +17,7 @@ updates:
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
open-pull-requests-limit: 0
# List dependencies required to be updated together, sharing the same version numbers.
# Those who simply have the common owner (e.g. @fastify) don't need to be listed.
groups:

View File

@ -20,12 +20,12 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4.2.2
- run: corepack enable
- name: Setup Node.js
uses: actions/setup-node@v4.1.0
uses: actions/setup-node@v4.2.0
with:
node-version-file: '.node-version'
cache: 'pnpm'

View File

@ -12,9 +12,9 @@ jobs:
steps:
- name: Checkout head
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4.2.2
- name: Setup Node.js
uses: actions/setup-node@v4.1.0
uses: actions/setup-node@v4.2.0
with:
node-version-file: '.node-version'

View File

@ -18,7 +18,7 @@ jobs:
if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }}
steps:
- name: checkout
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4.2.2
with:
submodules: true
persist-credentials: false
@ -29,7 +29,7 @@ jobs:
- name: setup node
id: setup-node
uses: actions/setup-node@v4.1.0
uses: actions/setup-node@v4.2.0
with:
node-version-file: '.node-version'
cache: pnpm
@ -66,7 +66,7 @@ jobs:
if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }}
steps:
- name: checkout
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4.2.2
with:
submodules: true
persist-credentials: false

View File

@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4.2.2
- name: Check version
run: |
if [ "$(jq -r '.version' package.json)" != "$(jq -r '.version' packages/misskey-js/package.json)" ]; then

View File

@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4.2.2
- name: Check
run: |
counter=0

View File

@ -10,7 +10,7 @@ jobs:
check_copyright_year:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.2.2
- run: |
if [ "$(grep Copyright COPYING | sed -e 's/.*2014-\([0-9]*\) .*/\1/g')" -ne "$(date +%Y)" ]; then
echo "Please change copyright year!"

View File

@ -28,7 +28,7 @@ jobs:
wait_time: ${{ steps.get-wait-time.outputs.wait_time }}
steps:
- name: Checkout
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4.2.2
- name: Check allowed users
id: check-allowed-users

View File

@ -27,7 +27,7 @@ jobs:
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Check out the repo
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4.2.2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub

View File

@ -32,7 +32,7 @@ jobs:
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Check out the repo
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4.2.2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta

View File

@ -15,7 +15,7 @@ jobs:
DOCKER_CONTENT_TRUST: 1
DOCKLE_VERSION: 0.4.14
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.2.2
- name: Download and install dockle v${{ env.DOCKLE_VERSION }}
run: |
curl -L -o dockle.deb "https://github.com/goodwithtech/dockle/releases/download/v${DOCKLE_VERSION}/dockle_${DOCKLE_VERSION}_Linux-64bit.deb"

View File

@ -30,14 +30,14 @@ jobs:
ref: refs/pull/${{ github.event.number }}/merge
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.2.2
with:
ref: ${{ matrix.ref }}
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0
uses: actions/setup-node@v4.2.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'

View File

@ -36,12 +36,12 @@ jobs:
pnpm_install:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.2.2
with:
fetch-depth: 0
submodules: true
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4.1.0
- uses: actions/setup-node@v4.2.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
@ -67,19 +67,19 @@ jobs:
eslint-cache-version: v1
eslint-cache-path: ${{ github.workspace }}/node_modules/.cache/eslint-${{ matrix.workspace }}
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.2.2
with:
fetch-depth: 0
submodules: true
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4.1.0
- uses: actions/setup-node@v4.2.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Restore eslint cache
uses: actions/cache@v4.2.0
uses: actions/cache@v4.2.1
with:
path: ${{ env.eslint-cache-path }}
key: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }}
@ -97,12 +97,12 @@ jobs:
- sw
- misskey-js
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.2.2
with:
fetch-depth: 0
submodules: true
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4.1.0
- uses: actions/setup-node@v4.2.0
with:
node-version-file: '.node-version'
cache: 'pnpm'

View File

@ -18,12 +18,12 @@ jobs:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.2.2
with:
fetch-depth: 0
submodules: true
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4.1.0
- uses: actions/setup-node@v4.2.0
with:
node-version-file: '.node-version'
cache: 'pnpm'

View File

@ -1,36 +0,0 @@
# If someone with write access comments "/ok-to-test" on a pull request, emit a repository_dispatch event
name: Ok To Test
on:
issue_comment:
types: [created]
jobs:
ok-to-test:
runs-on: ubuntu-latest
# Only run for PRs, not issue comments
if: ${{ github.event.issue.pull_request }}
steps:
# Generate a GitHub App installation access token from an App ID and private key
# To create a new GitHub App:
# https://developer.github.com/apps/building-github-apps/creating-a-github-app/
# See app.yml for an example app manifest
- name: Generate token
id: generate_token
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.DEPLOYBOT_APP_ID }}
private_key: ${{ secrets.DEPLOYBOT_PRIVATE_KEY }}
- name: Slash Command Dispatch
uses: peter-evans/slash-command-dispatch@v4
env:
TOKEN: ${{ steps.generate_token.outputs.token }}
with:
token: ${{ env.TOKEN }} # GitHub App installation access token
# token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} # PAT or OAuth token will also work
reaction-token: ${{ secrets.GITHUB_TOKEN }}
issue-type: pull-request
commands: deploy
named-args: true
permission: write

View File

@ -23,13 +23,13 @@ jobs:
node-version: [22.11.0]
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.2.2
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0
uses: actions/setup-node@v4.2.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'

View File

@ -1,92 +0,0 @@
# Run secret-dependent integration tests only after /deploy approval
on:
repository_dispatch:
types: [deploy-command]
name: Deploy preview environment
jobs:
# Repo owner has commented /deploy on a (fork-based) pull request
deploy-preview-environment:
runs-on: ubuntu-latest
if:
github.event.client_payload.slash_command.sha != '' &&
contains(github.event.client_payload.pull_request.head.sha, github.event.client_payload.slash_command.sha)
steps:
- uses: actions/github-script@v7.0.1
id: check-id
env:
number: ${{ github.event.client_payload.pull_request.number }}
job: ${{ github.job }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
result-encoding: string
script: |
const { data: pull } = await github.rest.pulls.get({
...context.repo,
pull_number: process.env.number
});
const ref = pull.head.sha;
const { data: checks } = await github.rest.checks.listForRef({
...context.repo,
ref
});
const check = checks.check_runs.filter(c => c.name === process.env.job);
return check[0].id;
- uses: actions/github-script@v7.0.1
env:
check_id: ${{ steps.check-id.outputs.result }}
details_url: ${{ github.server_url }}/${{ github.repository }}/runs/${{ github.run_id }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
await github.rest.checks.update({
...context.repo,
check_run_id: process.env.check_id,
status: 'in_progress',
details_url: process.env.details_url
});
# Check out merge commit
- name: Fork based /deploy checkout
uses: actions/checkout@v4.1.1
with:
ref: 'refs/pull/${{ github.event.client_payload.pull_request.number }}/merge'
# <insert integration tests needing secrets>
- name: Context
uses: okteto/context@latest
with:
token: ${{ secrets.OKTETO_TOKEN }}
- name: Deploy preview environment
uses: ikuradon/deploy-preview@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
name: pr-${{ github.event.client_payload.pull_request.number }}-syuilo
timeout: 15m
# Update check run called "integration-fork"
- uses: actions/github-script@v7.0.1
id: update-check-run
if: ${{ always() }}
env:
# Conveniently, job.status maps to https://developer.github.com/v3/checks/runs/#update-a-check-run
conclusion: ${{ job.status }}
check_id: ${{ steps.check-id.outputs.result }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { data: result } = await github.rest.checks.update({
...context.repo,
check_run_id: process.env.check_id,
status: 'completed',
conclusion: process.env.conclusion
});
return result;

View File

@ -1,54 +0,0 @@
# file: .github/workflows/preview-closed.yaml
on:
pull_request:
types:
- closed
name: Destroy preview environment
jobs:
destroy-preview-environment:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7.0.1
id: check-conclusion
env:
number: ${{ github.event.number }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
result-encoding: string
script: |
const { data: pull } = await github.rest.pulls.get({
...context.repo,
pull_number: process.env.number
});
const ref = pull.head.sha;
const { data: checks } = await github.rest.checks.listForRef({
...context.repo,
ref
});
const check = checks.check_runs.filter(c => c.name === 'deploy-preview-environment');
if (check.length === 0) {
return;
}
const { data: result } = await github.rest.checks.get({
...context.repo,
check_run_id: check[0].id,
});
return result.conclusion;
- name: Context
if: steps.check-conclusion.outputs.result == 'success'
uses: okteto/context@latest
with:
token: ${{ secrets.OKTETO_TOKEN }}
- name: Destroy preview environment
if: steps.check-conclusion.outputs.result == 'success'
uses: okteto/destroy-preview@latest
with:
name: pr-${{ github.event.number }}-syuilo

View File

@ -26,12 +26,12 @@ jobs:
NODE_OPTIONS: "--max_old_space_size=7168"
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.2.2
if: github.event_name != 'pull_request_target'
with:
fetch-depth: 0
submodules: true
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.2.2
if: github.event_name == 'pull_request_target'
with:
fetch-depth: 0
@ -46,7 +46,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Use Node.js 20.x
uses: actions/setup-node@v4.1.0
uses: actions/setup-node@v4.2.0
with:
node-version-file: '.node-version'
cache: 'pnpm'

View File

@ -45,7 +45,7 @@ jobs:
- 56312:6379
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.2.2
with:
submodules: true
- name: Install pnpm
@ -66,7 +66,7 @@ jobs:
fi
done
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0
uses: actions/setup-node@v4.2.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
@ -108,13 +108,13 @@ jobs:
- 56312:6379
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.2.2
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0
uses: actions/setup-node@v4.2.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'

View File

@ -47,7 +47,7 @@ jobs:
fi
done
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0
uses: actions/setup-node@v4.2.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'

View File

@ -36,13 +36,13 @@ jobs:
node-version: [22.11.0]
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.2.2
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0
uses: actions/setup-node@v4.2.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
@ -86,7 +86,7 @@ jobs:
- 56312:6379
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.2.2
with:
submodules: true
# https://github.com/cypress-io/cypress-docker-images/issues/150
@ -98,7 +98,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0
uses: actions/setup-node@v4.2.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'

View File

@ -31,12 +31,12 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4.1.1
uses: actions/checkout@v4.2.2
- run: corepack enable
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0
uses: actions/setup-node@v4.2.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'

View File

@ -21,13 +21,13 @@ jobs:
node-version: [22.11.0]
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.2.2
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0
uses: actions/setup-node@v4.2.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'

View File

@ -25,13 +25,13 @@ jobs:
node-version: [22.11.0]
steps:
- uses: actions/checkout@v4.1.1
- uses: actions/checkout@v4.2.2
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0
uses: actions/setup-node@v4.2.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'

View File

@ -1,7 +1,36 @@
## 2025.2.0
## 2025.2.1
### Note
- ページの「ソースを見る」機能は削除されました
### General
- Feat: アクセストークン発行時に通知するように
- 依存関係の更新
### Client
- Feat: 投稿フォームで画像をプレビュー可能に
- Enhance: 投稿フォームの「迷惑になる可能性があります」のダイアログを表示する条件においてCWを考慮するように
- Enhance: アンテナ、リスト等の名前をカラム名のデフォルト値にするように `#13992`
- Enhance: クライアントエラー画面の多言語対応
- Enhance: 開発者モードでメニューからファイルIDをコピー出来るように `#15441'
- Enhance: ノートに埋め込まれたメディアのコンテキストメニューから管理者用のファイル管理画面を開けるように ( #15440 )
- Enhance: リアクションする際に確認ダイアログを表示できるように
- Enhance: CWの注釈で入力済みの文字数を表示
- Fix: コンディショナルロールを手動で割り当てできる導線を削除 `#13529`
- Fix: 埋め込みプレイヤーから外部ページに移動できない問題を修正
- Fix: Play の再読込時に UI が以前の状態を引き継いでしまう問題を修正 `#14378`
- Fix: カスタム絵文字管理画面(beta)にてisSensitive/localOnlyの絞り込みが上手くいかない問題の修正 ( #15445 )
- Fix: CWの注釈が100文字を超えている場合、ート投稿ボタンを非アクティブに
### Server
- Enhance: 成り済まし対策として、ActivityPub照会された時にリモートのリダイレクトを拒否できるように (config.disallowExternalApRedirect)
- Enhance: スレッドミュートにおいて、リノート、引用、リアクションの通知もミュートするように
- なお、以下のケースでは引き続き通知がミュートされません。(ミュートを行っているユーザーをAとします)
- ミュート対象ートを、当該スレッドの外にあるAへの返信/メンション付きノートにおいて引用する
- Fix: `following/invalidate`でフォロワーを解除しようとしているユーザーの情報を返すように
- Fix: オブジェクトストレージの設定でPrefixを設定していなかった場合nullまたは空文字になる問題を修正
- Fix: pgroongaでの検索時にはじめのキーワードのみが検索に使用される問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/886)
- Fix: メールアドレスの形式が正しくなければ以降の処理を行わないように
## 2025.2.0
### General
- Fix: Docker のビルドに失敗する問題を修正
@ -13,12 +42,13 @@
- Fix: データセーバー有効時にもユーザーページの「ファイル」タブで画像が読み込まれてしまう問題を修正
- Fix: MFMの `sparkle` エフェクトが正しく表示されない問題を修正
- Fix: ページのURLにスラッシュが含まれている場合にページが正しく表示されない問題を修正
- Fix: デッキのプロファイルが新規作成できない問題を修正
- Fix: セキュリティに関する修正
- ローカライゼーションの更新
- Playが実装されたため、ページ機能の「ソースを見る」は削除されました
### Server
- Enhance: スレッドミュートにおいて、リノート、引用、リアクションの通知もミュートするように
- なお、以下のケースでは引き続き通知がミュートされません。(ミュートを行っているユーザーをAとします)
- ミュート対象ートを、当該スレッドの外にあるAへの返信/メンション付きノートにおいて引用する
- Enhance: ページのURLに使用可能な文字を限定するように
- Fix: 個別お知らせページのmetaタグ出力の条件が間違っていたのを修正
## 2025.1.0

View File

@ -1460,9 +1460,6 @@ _pages:
newPage: "أنشئ صفحة جديدة"
editPage: "عدّل الصفحة"
readPage: "نُشّط عرض المصدر"
created: "نجح إنشاء الصفحة"
updated: "نجح تعديل الصفحة"
deleted: "نجح حذف الصفحة"
pageSetting: "إعدادات الصفحة"
nameAlreadyExists: "رابط الصفحة موجود مسبقًا"
invalidNameTitle: "رابط الصفحة ليس صالحًا"

View File

@ -1237,9 +1237,6 @@ _pages:
newPage: "নতুন পৃষ্ঠা বানান"
editPage: "পৃষ্ঠাটি সম্পাদনা করুন"
readPage: "উৎস দেখছেন"
created: "পৃষ্ঠা তৈরি করা হয়েছে"
updated: "পৃষ্ঠা সম্পাদনা করা হয়েছে"
deleted: "পৃষ্ঠা মুছে ফেলা হয়েছে"
pageSetting: "পৃষ্ঠার সেটিংস"
nameAlreadyExists: "পৃষ্ঠার URLটি ইতিমধ্যেই ব্যাবহার করা হয়েছে"
invalidNameTitle: "পৃষ্ঠার URL অবৈধ"

View File

@ -2365,9 +2365,6 @@ _pages:
newPage: "pa"
editPage: "Editar la pàgina"
readPage: "Veure el codi font d'aquesta pàgina"
created: "La pàgina ha sigut creada correctament"
updated: "La pàgina s'ha editat correctament"
deleted: "La pàgina s'ha esborrat sense problemes"
pageSetting: "Configuració de la pàgina"
nameAlreadyExists: "L'adreça URL de la pàgina ja existeix"
invalidNameTitle: "L'adreça URL de la pàgina no és vàlida"

View File

@ -1883,9 +1883,6 @@ _pages:
newPage: "Vytvořit novou stránku"
editPage: "Upravit stránku"
readPage: "Prohlížení zdroje této stránky"
created: "Stránka byla úspěšně vytvořena"
updated: "Stránka byla úspěšně aktualizována"
deleted: "Stránka byla úspěšně smazána"
pageSetting: "Nastavení stránky"
nameAlreadyExists: "Zadaná adresa URL stránky již existuje"
invalidNameTitle: "Zadaná adresa URL stránky je neplatná"

View File

@ -2277,9 +2277,6 @@ _pages:
newPage: "Seite erstellen"
editPage: "Seite bearbeiten"
readPage: "Quelltextansicht"
created: "Seite erfolgreich erstellt"
updated: "Seite erfolgreich aktualisiert"
deleted: "Seite erfolgreich gelöscht"
pageSetting: "Seiteneinstellungen"
nameAlreadyExists: "Die angegebene Seiten-URL existiert bereits"
invalidNameTitle: "Die angegebene Seiten-URL ist ungültig"

View File

@ -2365,9 +2365,6 @@ _pages:
newPage: "Create a new Page"
editPage: "Edit this Page"
readPage: "Viewing this Page's source"
created: "Page successfully created"
updated: "Page successfully edited"
deleted: "Page successfully deleted"
pageSetting: "Page settings"
nameAlreadyExists: "The specified Page URL already exists"
invalidNameTitle: "The specified Page URL is invalid"
@ -2775,6 +2772,7 @@ _customEmojisManager:
confirmUpdateEmojisDescription: "Update {count} Emoji(s). Are you sure to continue?"
confirmDeleteEmojisDescription: "Delete checked {count} Emoji(s). Are you sure to continue?"
confirmResetDescription: ""
confirmMovePageDesciption: "Changes have been made to the Emojis on this page.\nIf you leave the page without saving, all changes made on this page will be discarded."
dialogSelectRoleTitle: "Search by roll set in Emojis"
_register:
uploadSettingTitle: "Upload settings"

View File

@ -2294,9 +2294,6 @@ _pages:
newPage: "Crear página"
editPage: "Editar página"
readPage: "Viendo la fuente"
created: "La página fue creada"
updated: "La página fue actualizada"
deleted: "La página borrada"
pageSetting: "Configurar página"
nameAlreadyExists: "La URL de la página especificada ya existe"
invalidNameTitle: "URL inválida"

View File

@ -2118,9 +2118,6 @@ _pages:
newPage: "Créer une page"
editPage: "Modifier une page"
readPage: "Affichage de la source en cours"
created: "La page a été créée !"
updated: "La page a été mise à jour !"
deleted: "La page a été supprimée"
pageSetting: "Paramètres de la Page"
nameAlreadyExists: "L'URL de page spécifiée existe déjà"
invalidNameTitle: "L'URL de page spécifiée nest pas valide"

View File

@ -2285,9 +2285,6 @@ _pages:
newPage: "Buat halaman baru"
editPage: "Sunting halaman"
readPage: "Lihat sumber kode aktif"
created: "Halaman berhasil dibuat"
updated: "Halaman berhasil diperbaharui!"
deleted: "Halaman telah dihapus"
pageSetting: "Pengaturan Halaman"
nameAlreadyExists: "URL Halaman yang ditentukan sudah ada"
invalidNameTitle: "URL Halaman yang ditentukan tidak valid"

84
locales/index.d.ts vendored
View File

@ -4195,7 +4195,7 @@ export interface Locale extends ILocale {
*/
"invalidParamError": string;
/**
*
*
*/
"invalidParamErrorDescription": string;
/**
@ -5254,6 +5254,14 @@ export interface Locale extends ILocale {
*
*/
"federationDisabled": string;
/**
*
*/
"confirmOnReact": string;
/**
* " {emoji} "
*/
"reactAreYouSure": ParameterizedString<"emoji">;
"_accountSettings": {
/**
*
@ -9180,18 +9188,6 @@ export interface Locale extends ILocale {
*
*/
"readPage": string;
/**
*
*/
"created": string;
/**
*
*/
"updated": string;
/**
*
*/
"deleted": string;
/**
*
*/
@ -9484,6 +9480,14 @@ export interface Locale extends ILocale {
*
*/
"login": string;
/**
*
*/
"createToken": string;
/**
* {text}
*/
"createTokenDescription": ParameterizedString<"text">;
"_types": {
/**
*
@ -10892,13 +10896,7 @@ export interface Locale extends ILocale {
*/
"title": string;
/**
*
*/
"description": string;
};
"_responseInvalidIdHostNotMatch": {
/**
* URIのドメインと最終的に得られたURIのドメインとが異なりますURIを使用して照会し直してください
* URIを使用して照会し直してください
*/
"description": string;
};
@ -10956,6 +10954,52 @@ export interface Locale extends ILocale {
};
};
};
"_bootErrors": {
/**
*
*/
"title": string;
/**
* Error IDを添えてサーバー管理者に連絡してください
*/
"serverError": string;
/**
*
*/
"solution": string;
/**
* OSを最新バージョンに更新する
*/
"solution1": string;
/**
*
*/
"solution2": string;
/**
*
*/
"solution3": string;
/**
* (Tor Browser) dom.webaudio.enabledをtrueに設定する
*/
"solution4": string;
/**
*
*/
"otherOption": string;
/**
*
*/
"otherOption1": string;
/**
*
*/
"otherOption2": string;
/**
*
*/
"otherOption3": string;
};
}
declare const locales: {
[lang: string]: Locale;

View File

@ -2365,9 +2365,6 @@ _pages:
newPage: "Crea pagina"
editPage: "Modifica pagina"
readPage: "Visualizzando fonte "
created: "Pagina creata!"
updated: "Pagina aggiornata con successo!"
deleted: "Pagina eliminata"
pageSetting: "Impostazioni pagina"
nameAlreadyExists: "Esiste già una pagina con lo stesso URL."
invalidNameTitle: "L'URL di pagina definito non è valido"

View File

@ -1044,7 +1044,7 @@ youCannotCreateAnymore: "これ以上作成することはできません。"
cannotPerformTemporary: "一時的に利用できません"
cannotPerformTemporaryDescription: "操作回数が制限を超過するため一時的に利用できません。しばらく時間を置いてから再度お試しください。"
invalidParamError: "パラメータエラー"
invalidParamErrorDescription: "リクエストパラメータに問題があります。通常これはバグですが、入力した文字数が多すぎる等の可能性もあります。"
invalidParamErrorDescription: "リクエストパラメータに問題があります。通常これはバグですが、入力した文字数が多すぎる・許可されていない文字を入力している等の可能性もあります。"
permissionDeniedError: "操作が拒否されました"
permissionDeniedErrorDescription: "このアカウントにはこの操作を行うための権限がありません。"
preset: "プリセット"
@ -1309,6 +1309,8 @@ availableRoles: "利用可能なロール"
acknowledgeNotesAndEnable: "注意事項を理解した上でオンにします。"
federationSpecified: "このサーバーはホワイトリスト連合で運用されています。管理者が指定したサーバー以外とやり取りすることはできません。"
federationDisabled: "このサーバーは連合が無効化されています。他のサーバーのユーザーとやり取りすることはできません。"
confirmOnReact: "リアクションする際に確認する"
reactAreYouSure: "\" {emoji} \" をリアクションしますか?"
_accountSettings:
requireSigninToViewContents: "コンテンツの表示にログインを必須にする"
@ -2422,9 +2424,6 @@ _pages:
newPage: "ページの作成"
editPage: "ページの編集"
readPage: "ソースを表示中"
created: "ページを作成しました"
updated: "ページを更新しました"
deleted: "ページを削除しました"
pageSetting: "ページ設定"
nameAlreadyExists: "指定されたページURLは既に存在しています"
invalidNameTitle: "不正なページURLです"
@ -2503,6 +2502,8 @@ _notification:
flushNotification: "通知の履歴をリセットする"
exportOfXCompleted: "{x}のエクスポートが完了しました"
login: "ログインがありました"
createToken: "アクセストークンが作成されました"
createTokenDescription: "心当たりがない場合は「{text}」を通じてアクセストークンを削除してください。"
_types:
all: "すべて"
@ -2910,9 +2911,7 @@ _remoteLookupErrors:
description: "このサーバーとの通信に失敗しました。相手サーバーがダウンしている可能性があります。また、不正なURIや存在しないURIを入力していないか確認してください。"
_responseInvalid:
title: "レスポンスが不正です"
description: "このサーバーと通信することはできましたが、得られたデータが不正なものでした。"
_responseInvalidIdHostNotMatch:
description: "入力されたURIのドメインと最終的に得られたURIのドメインとが異なります。第三者のサーバーを介してリモートのコンテンツを照会している場合は、発信元のサーバーで取得できるURIを使用して照会し直してください。"
description: "このサーバーと通信することはできましたが、得られたデータが不正なものでした。第三者のサーバーを介してリモートのコンテンツを照会している場合は、発信元のサーバーで取得できるURIを使用して照会し直してください。"
_noSuchObject:
title: "見つかりません"
description: "要求されたリソースは見つかりませんでした。URIをもう一度お確かめください。"
@ -2930,3 +2929,16 @@ _captcha:
_unknown:
title: "CAPTCHAエラー"
text: "想定外のエラーが発生しました。"
_bootErrors:
title: "読み込みに失敗しました"
serverError: "少し待ってからリロードしてもまだ問題が解決されない場合、以下のError IDを添えてサーバー管理者に連絡してください。"
solution: "以下を行うと解決する可能性があります。"
solution1: "ブラウザおよびOSを最新バージョンに更新する"
solution2: "アドブロッカーを無効にする"
solution3: "ブラウザのキャッシュをクリアする"
solution4: "(Tor Browser) dom.webaudio.enabledをtrueに設定する"
otherOption: "その他のオプション"
otherOption1: "クライアント設定とキャッシュを削除"
otherOption2: "簡易クライアントを起動"
otherOption3: "修復ツールを起動"

View File

@ -2357,9 +2357,6 @@ _pages:
newPage: "ページを作る"
editPage: "ページの編集"
readPage: "ソースを表示中"
created: "ページを作成したで"
updated: "ページを更新したで"
deleted: "ページを削除したで"
pageSetting: "ページ設定"
nameAlreadyExists: "指定されたページURLはもうあるみたいや"
invalidNameTitle: "正しくないページURLみたいやで"

View File

@ -2365,9 +2365,6 @@ _pages:
newPage: "페이지 만들기"
editPage: "페이지 수정"
readPage: "소스 표시 중"
created: "페이지를 만들었습니다"
updated: "페이지를 수정했습니다"
deleted: "페이지가 삭제되었습니다"
pageSetting: "페이지 설정"
nameAlreadyExists: "지정한 페이지 URL이 이미 존재합니다"
invalidNameTitle: "유효하지 않은 페이지 URL입니다"

View File

@ -1459,9 +1459,6 @@ _pages:
newPage: "Utwórz stronę"
editPage: "Edytuj tę stronę"
readPage: "Aktywowano widok źródła"
created: "Pomyślnie utworzono stronę!"
updated: "Pomyślnie zaktualizowano stronę!"
deleted: "Strona została usunięta"
pageSetting: "Ustawienia strony"
nameAlreadyExists: "Określony adres URL strony już istnieje"
invalidNameTitle: "Podany adres URL strony jest nieprawidłowy"

View File

@ -2357,9 +2357,6 @@ _pages:
newPage: "Criar uma Página"
editPage: "Editar essa Página"
readPage: "Ver a fonte dessa Página"
created: "Página criada com sucesso"
updated: "Página atualizada com sucesso"
deleted: "Página excluída com sucesso"
pageSetting: "Configurações da página"
nameAlreadyExists: "O URL de Página especificado já existe"
invalidNameTitle: "O URL de Página especificado é inválido"

View File

@ -1976,9 +1976,6 @@ _pages:
newPage: "Создать страницу"
editPage: "Править страницу"
readPage: "Читать страницу"
created: "Страница успешно создана."
updated: "Страница успешно обновлена."
deleted: "Страница успешно удалена."
pageSetting: "Настройки страницы"
nameAlreadyExists: "Указанный адрес страницы уже существует."
invalidNameTitle: "Указанный адрес страницы недопустим."

View File

@ -1332,9 +1332,6 @@ _pages:
newPage: "Vytvoriť novú stránku"
editPage: "Upraviť túto stránku"
readPage: "Zobrazenie zdroja aktívne"
created: "Stránka úspešne vytvorená"
updated: "Stránka úspešne upravená"
deleted: "Stránka úspešne odstránená"
pageSetting: "Nastavenia stránky"
nameAlreadyExists: "Zadaná URL stránku už existuje"
invalidNameTitle: "Zadaná URL stránku je nesprávna"

View File

@ -2331,9 +2331,6 @@ _pages:
newPage: "สร้างหน้าเพจใหม่"
editPage: "แก้ไขหน้าเพจ"
readPage: "กำลังดูแหล่งที่มาของเพจนี้"
created: "สร้างหน้าเพจสำเร็จเรียบร้อยแล้ว"
updated: "แก้ไขหน้าเพจสำเร็จเรียบร้อยแล้ว"
deleted: "ลบหน้าเพจสำเร็จเรียบร้อยแล้ว"
pageSetting: "การตั้งค่าหน้าเพจ"
nameAlreadyExists: "URL ของหน้าที่ระบุนั้นมีอยู่แล้ว"
invalidNameTitle: "URL ของหน้าที่ระบุนั้นไม่ถูกต้อง"

View File

@ -1513,9 +1513,6 @@ _pages:
newPage: "Створити сторінку"
editPage: "Редагувати сторінку"
readPage: "Перегляд вихідного коду"
created: "Сторінка успішно створена."
updated: "Сторінка успішно оновлена."
deleted: "Сторінку видалено"
pageSetting: "Налаштування сторінки"
nameAlreadyExists: "Вказана адреса сторінки вже існує."
invalidNameTitle: "Вказана адреса сторінки неприпустима."

View File

@ -1004,9 +1004,6 @@ _play:
_pages:
newPage: "Yangi Sahifa yaratish"
editPage: "Ushbu Sahifani tahrirlash"
created: "Sahifa muvaffaqiyatli yaratildi"
updated: "Sahifa muvaffaqiyatli tahrirlandi"
deleted: "Sahifa muvaffaqiyatli o'chirildi"
pageSetting: "Sahifa sozlamalari"
nameAlreadyExists: "Ko'rsatilgan Sahifa URL'i allaqachon mavjud"
invalidNameTitle: "Ko'rsatilgan Sahifa URL'i yaroqsiz"

View File

@ -1802,9 +1802,6 @@ _pages:
newPage: "Tạo Trang mới"
editPage: "Sửa Trang này"
readPage: "Xem mã nguồn Trang này"
created: "Trang đã được tạo thành công"
updated: "Trang đã được cập nhật thành công"
deleted: "Trang đã được xóa thành công"
pageSetting: "Cài đặt trang"
nameAlreadyExists: "URL Trang đã tồn tại"
invalidNameTitle: "URL Trang không hợp lệ"

View File

@ -2365,9 +2365,6 @@ _pages:
newPage: "创建页面"
editPage: "编辑页面"
readPage: "查看页面"
created: "页面已创建"
updated: "页面已更新"
deleted: "该页面已被删除"
pageSetting: "页面设置"
nameAlreadyExists: "该页面 URL 已存在"
invalidNameTitle: "无效的页面 URL"

View File

@ -2365,9 +2365,6 @@ _pages:
newPage: "建立頁面"
editPage: "編輯頁面"
readPage: "正在檢視原始碼"
created: "頁面已建立"
updated: "頁面已更新"
deleted: "頁面已被刪除"
pageSetting: "頁面設定"
nameAlreadyExists: "該頁面 URL 已存在"
invalidNameTitle: "無效的頁面 URL"

View File

@ -1,12 +1,12 @@
{
"name": "misskey",
"version": "2025.2.0-beta.0",
"version": "2025.2.1-beta.0",
"codename": "nasubi",
"repository": {
"type": "git",
"url": "https://github.com/misskey-dev/misskey.git"
},
"packageManager": "pnpm@9.6.0",
"packageManager": "pnpm@9.15.4",
"workspaces": [
"packages/frontend-shared",
"packages/frontend",
@ -47,35 +47,35 @@
"cleanall": "pnpm clean-all"
},
"resolutions": {
"chokidar": "3.5.3",
"chokidar": "3.6.0",
"lodash": "4.17.21"
},
"dependencies": {
"cssnano": "6.1.2",
"cssnano": "7.0.6",
"execa": "8.0.1",
"fast-glob": "3.3.2",
"fast-glob": "3.3.3",
"ignore-walk": "6.0.5",
"js-yaml": "4.1.0",
"postcss": "8.4.49",
"postcss": "8.5.2",
"tar": "6.2.1",
"terser": "5.36.0",
"typescript": "5.6.3",
"esbuild": "0.24.0",
"glob": "11.0.0"
"terser": "5.39.0",
"typescript": "5.7.3",
"esbuild": "0.25.0",
"glob": "11.0.1"
},
"devDependencies": {
"@misskey-dev/eslint-plugin": "2.0.3",
"@types/node": "22.9.0",
"@typescript-eslint/eslint-plugin": "7.17.0",
"@typescript-eslint/parser": "7.17.0",
"@misskey-dev/eslint-plugin": "2.1.0",
"@types/node": "22.13.4",
"@typescript-eslint/eslint-plugin": "8.24.0",
"@typescript-eslint/parser": "8.24.0",
"cross-env": "7.0.3",
"cypress": "13.15.2",
"eslint": "9.14.0",
"globals": "15.12.0",
"cypress": "14.0.3",
"eslint": "9.20.1",
"globals": "15.15.0",
"ncp": "2.0.0",
"start-server-and-test": "2.0.8"
"start-server-and-test": "2.0.10"
},
"optionalDependencies": {
"@tensorflow/tfjs-core": "4.4.0"
"@tensorflow/tfjs-core": "4.22.0"
}
}

View File

@ -1,5 +1,5 @@
{
"$schema": "https://json.schemastore.org/swcrc",
"$schema": "https://swc.rs/schema.json",
"jsc": {
"parser": {
"syntax": "typescript",

View File

@ -37,20 +37,20 @@
},
"optionalDependencies": {
"@swc/core-android-arm64": "1.3.11",
"@swc/core-darwin-arm64": "1.3.56",
"@swc/core-darwin-x64": "1.3.56",
"@swc/core-darwin-arm64": "1.10.16",
"@swc/core-darwin-x64": "1.10.16",
"@swc/core-freebsd-x64": "1.3.11",
"@swc/core-linux-arm-gnueabihf": "1.3.56",
"@swc/core-linux-arm64-gnu": "1.3.56",
"@swc/core-linux-arm64-musl": "1.3.56",
"@swc/core-linux-x64-gnu": "1.3.56",
"@swc/core-linux-x64-musl": "1.3.56",
"@swc/core-win32-arm64-msvc": "1.3.56",
"@swc/core-win32-ia32-msvc": "1.3.56",
"@swc/core-win32-x64-msvc": "1.3.56",
"@tensorflow/tfjs": "4.4.0",
"@tensorflow/tfjs-node": "4.4.0",
"bufferutil": "4.0.7",
"@swc/core-linux-arm-gnueabihf": "1.10.16",
"@swc/core-linux-arm64-gnu": "1.10.16",
"@swc/core-linux-arm64-musl": "1.10.16",
"@swc/core-linux-x64-gnu": "1.10.16",
"@swc/core-linux-x64-musl": "1.10.16",
"@swc/core-win32-arm64-msvc": "1.10.16",
"@swc/core-win32-ia32-msvc": "1.10.16",
"@swc/core-win32-x64-msvc": "1.10.16",
"@tensorflow/tfjs": "4.22.0",
"@tensorflow/tfjs-node": "4.22.0",
"bufferutil": "4.0.9",
"slacc-android-arm-eabi": "0.0.10",
"slacc-android-arm64": "0.0.10",
"slacc-darwin-arm64": "0.0.10",
@ -64,37 +64,37 @@
"slacc-linux-x64-musl": "0.0.10",
"slacc-win32-arm64-msvc": "0.0.10",
"slacc-win32-x64-msvc": "0.0.10",
"utf-8-validate": "6.0.3"
"utf-8-validate": "6.0.5"
},
"dependencies": {
"@aws-sdk/client-s3": "3.620.0",
"@aws-sdk/lib-storage": "3.620.0",
"@bull-board/api": "6.5.0",
"@bull-board/fastify": "6.5.0",
"@bull-board/ui": "6.5.0",
"@aws-sdk/client-s3": "3.749.0",
"@aws-sdk/lib-storage": "3.749.0",
"@bull-board/api": "6.7.7",
"@bull-board/fastify": "6.7.7",
"@bull-board/ui": "6.7.7",
"@discordapp/twemoji": "15.1.0",
"@fastify/accepts": "5.0.1",
"@fastify/cookie": "11.0.1",
"@fastify/cors": "10.0.1",
"@fastify/express": "4.0.1",
"@fastify/http-proxy": "10.0.1",
"@fastify/multipart": "9.0.1",
"@fastify/static": "8.0.2",
"@fastify/view": "10.0.1",
"@fastify/accepts": "5.0.2",
"@fastify/cookie": "11.0.2",
"@fastify/cors": "10.0.2",
"@fastify/express": "4.0.2",
"@fastify/http-proxy": "10.0.2",
"@fastify/multipart": "9.0.3",
"@fastify/static": "8.1.0",
"@fastify/view": "10.0.2",
"@misskey-dev/sharp-read-bmp": "1.2.0",
"@misskey-dev/summaly": "5.1.0",
"@napi-rs/canvas": "0.1.56",
"@nestjs/common": "10.4.7",
"@nestjs/core": "10.4.7",
"@nestjs/testing": "10.4.7",
"@misskey-dev/summaly": "5.2.0",
"@napi-rs/canvas": "0.1.67",
"@nestjs/common": "11.0.9",
"@nestjs/core": "11.0.9",
"@nestjs/testing": "11.0.9",
"@peertube/http-signature": "1.7.0",
"@sentry/node": "8.38.0",
"@sentry/profiling-node": "8.38.0",
"@simplewebauthn/server": "10.0.1",
"@sinonjs/fake-timers": "11.2.2",
"@sentry/node": "8.55.0",
"@sentry/profiling-node": "8.55.0",
"@simplewebauthn/server": "12.0.0",
"@sinonjs/fake-timers": "11.3.1",
"@smithy/node-http-handler": "2.5.0",
"@swc/cli": "0.3.12",
"@swc/core": "1.9.2",
"@swc/cli": "0.6.0",
"@swc/core": "1.10.16",
"@twemoji/parser": "15.1.1",
"accepts": "1.3.8",
"ajv": "8.17.1",
@ -103,10 +103,10 @@
"bcryptjs": "2.4.3",
"blurhash": "2.0.5",
"body-parser": "1.20.3",
"bullmq": "5.26.1",
"bullmq": "5.41.1",
"cacheable-lookup": "7.0.0",
"cbor": "9.0.2",
"chalk": "5.3.0",
"chalk": "5.4.1",
"chalk-template": "1.1.0",
"chokidar": "3.6.0",
"cli-highlight": "2.1.11",
@ -114,46 +114,46 @@
"content-disposition": "0.5.4",
"date-fns": "2.30.0",
"deep-email-validator": "0.1.21",
"fastify": "5.0.0",
"fastify": "5.2.1",
"fastify-raw-body": "5.0.0",
"feed": "4.2.2",
"file-type": "19.6.0",
"fluent-ffmpeg": "2.1.3",
"form-data": "4.0.1",
"got": "14.4.4",
"happy-dom": "15.11.4",
"form-data": "4.0.2",
"got": "14.4.6",
"happy-dom": "16.8.1",
"hpagent": "1.2.0",
"htmlescape": "1.1.1",
"http-link-header": "1.1.3",
"ioredis": "5.4.1",
"ioredis": "5.5.0",
"ip-cidr": "4.0.2",
"ipaddr.js": "2.2.0",
"is-svg": "5.1.0",
"js-yaml": "4.1.0",
"jsdom": "24.1.1",
"jsdom": "26.0.0",
"json5": "2.2.3",
"jsonld": "8.3.2",
"jsonld": "8.3.3",
"jsrsasign": "11.1.0",
"juice": "11.0.0",
"meilisearch": "0.45.0",
"meilisearch": "0.48.2",
"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.8",
"nanoid": "5.1.0",
"nested-property": "4.0.0",
"node-fetch": "3.3.2",
"nodemailer": "6.9.16",
"nodemailer": "6.10.0",
"nsfwjs": "4.2.0",
"oauth": "0.10.0",
"oauth2orize": "1.12.0",
"oauth2orize-pkce": "0.1.2",
"os-utils": "0.0.14",
"otpauth": "9.3.4",
"otpauth": "9.3.6",
"parse5": "7.2.1",
"pg": "8.13.1",
"pg": "8.13.3",
"pkce-challenge": "4.1.0",
"probe-image-size": "7.2.3",
"promise-limit": "2.7.0",
@ -167,19 +167,19 @@
"rename": "1.0.4",
"rss-parser": "3.13.0",
"rxjs": "7.8.1",
"sanitize-html": "2.13.1",
"secure-json-parse": "2.7.0",
"sanitize-html": "2.14.0",
"secure-json-parse": "3.0.2",
"sharp": "0.33.5",
"slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
"systeminformation": "5.23.5",
"systeminformation": "5.25.11",
"tinycolor2": "1.6.0",
"tmp": "0.2.3",
"tsc-alias": "1.8.10",
"tsconfig-paths": "4.2.0",
"typeorm": "0.3.20",
"typescript": "5.6.3",
"typescript": "5.7.3",
"ulid": "2.3.0",
"vary": "1.1.2",
"web-push": "3.6.7",
@ -188,8 +188,8 @@
},
"devDependencies": {
"@jest/globals": "29.7.0",
"@nestjs/platform-express": "10.4.7",
"@simplewebauthn/types": "10.0.0",
"@nestjs/platform-express": "10.4.15",
"@simplewebauthn/types": "12.0.0",
"@swc/jest": "0.2.37",
"@types/accepts": "1.3.7",
"@types/archiver": "6.0.3",
@ -204,15 +204,15 @@
"@types/js-yaml": "4.0.9",
"@types/jsdom": "21.1.7",
"@types/jsonld": "1.5.15",
"@types/jsrsasign": "10.5.14",
"@types/jsrsasign": "10.5.15",
"@types/mime-types": "2.1.4",
"@types/ms": "0.7.34",
"@types/node": "22.9.0",
"@types/nodemailer": "6.4.16",
"@types/node": "22.13.4",
"@types/nodemailer": "6.4.17",
"@types/oauth": "0.9.6",
"@types/oauth2orize": "1.11.5",
"@types/oauth2orize-pkce": "0.1.2",
"@types/pg": "8.11.10",
"@types/pg": "8.11.11",
"@types/pug": "2.0.10",
"@types/qrcode": "1.5.5",
"@types/random-seed": "0.3.5",
@ -226,18 +226,18 @@
"@types/tmp": "0.2.6",
"@types/vary": "1.1.3",
"@types/web-push": "3.6.4",
"@types/ws": "8.5.13",
"@typescript-eslint/eslint-plugin": "7.17.0",
"@typescript-eslint/parser": "7.17.0",
"aws-sdk-client-mock": "4.0.1",
"@types/ws": "8.5.14",
"@typescript-eslint/eslint-plugin": "8.24.0",
"@typescript-eslint/parser": "8.24.0",
"aws-sdk-client-mock": "4.1.0",
"cross-env": "7.0.3",
"eslint-plugin-import": "2.30.0",
"eslint-plugin-import": "2.31.0",
"execa": "8.0.1",
"fkill": "9.0.0",
"jest": "29.7.0",
"jest-mock": "29.7.0",
"nodemon": "3.1.7",
"pid-port": "1.0.0",
"nodemon": "3.1.9",
"pid-port": "1.0.2",
"simple-oauth2": "5.1.0"
}
}

View File

@ -73,6 +73,7 @@ type Source = {
proxyBypassHosts?: string[];
allowedPrivateNetworks?: string[];
disallowExternalApRedirect?: boolean;
maxFileSize?: number;
@ -105,8 +106,8 @@ type Source = {
logging?: {
sql?: {
disableQueryTruncation? : boolean,
enableQueryParamLogging? : boolean,
disableQueryTruncation?: boolean,
enableQueryParamLogging?: boolean,
}
}
};
@ -149,6 +150,7 @@ export type Config = {
proxySmtp: string | undefined;
proxyBypassHosts: string[] | undefined;
allowedPrivateNetworks: string[] | undefined;
disallowExternalApRedirect: boolean;
maxFileSize: number;
clusterLimit: number | undefined;
id: string;
@ -166,8 +168,8 @@ export type Config = {
signToActivityPubGet: boolean | undefined;
logging?: {
sql?: {
disableQueryTruncation? : boolean,
enableQueryParamLogging? : boolean,
disableQueryTruncation?: boolean,
enableQueryParamLogging?: boolean,
}
}
@ -287,6 +289,7 @@ export function loadConfig(): Config {
proxySmtp: config.proxySmtp,
proxyBypassHosts: config.proxyBypassHosts,
allowedPrivateNetworks: config.allowedPrivateNetworks,
disallowExternalApRedirect: config.disallowExternalApRedirect ?? false,
maxFileSize: config.maxFileSize ?? 262144000,
clusterLimit: config.clusterLimit,
outgoingAddress: config.outgoingAddress,

View File

@ -43,7 +43,7 @@ export type CaptchaSetting = {
siteKey: string | null;
secretKey: string | null;
}
}
};
export class CaptchaError extends Error {
public readonly code: CaptchaErrorCode;
@ -59,11 +59,11 @@ export class CaptchaError extends Error {
export type CaptchaSaveSuccess = {
success: true;
}
};
export type CaptchaSaveFailure = {
success: false;
error: CaptchaError;
}
};
export type CaptchaSaveResult = CaptchaSaveSuccess | CaptchaSaveFailure;
type CaptchaResponse = {

View File

@ -173,7 +173,8 @@ export class DriveService {
?? `${ this.meta.objectStorageUseSSL ? 'https' : 'http' }://${ this.meta.objectStorageEndpoint }${ this.meta.objectStoragePort ? `:${this.meta.objectStoragePort}` : '' }/${ this.meta.objectStorageBucket }`;
// for original
const key = `${this.meta.objectStoragePrefix}/${randomUUID()}${ext}`;
const prefix = this.meta.objectStoragePrefix ? `${this.meta.objectStoragePrefix}/` : '';
const key = `${prefix}${randomUUID()}${ext}`;
const url = `${ baseUrl }/${ key }`;
// for alts
@ -190,7 +191,7 @@ export class DriveService {
];
if (alts.webpublic) {
webpublicKey = `${this.meta.objectStoragePrefix}/webpublic-${randomUUID()}.${alts.webpublic.ext}`;
webpublicKey = `${prefix}webpublic-${randomUUID()}.${alts.webpublic.ext}`;
webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
this.registerLogger.info(`uploading webpublic: ${webpublicKey}`);
@ -198,7 +199,7 @@ export class DriveService {
}
if (alts.thumbnail) {
thumbnailKey = `${this.meta.objectStoragePrefix}/thumbnail-${randomUUID()}.${alts.thumbnail.ext}`;
thumbnailKey = `${prefix}thumbnail-${randomUUID()}.${alts.thumbnail.ext}`;
thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`;
this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`);

View File

@ -164,6 +164,13 @@ export class EmailService {
available: boolean;
reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned' | 'network' | 'blacklist';
}> {
if (!this.utilityService.validateEmailFormat(emailAddress)) {
return {
available: false,
reason: 'format',
};
}
const exist = await this.userProfilesRepository.countBy({
emailVerified: true,
email: emailAddress,

View File

@ -9,7 +9,7 @@ import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js';
export type FanoutTimelineName =
export type FanoutTimelineName = (
// home timeline
| `homeTimeline:${string}`
| `homeTimelineWithFiles:${string}` // only notes with files are included
@ -37,6 +37,7 @@ export type FanoutTimelineName =
// role timelines
| `roleTimeline:${string}` // any notes are included
);
@Injectable()
export class FanoutTimelineService {

View File

@ -211,7 +211,7 @@ type SerializedAll<T> = {
type UndefinedAsNullAll<T> = {
[K in keyof T]: T[K] extends undefined ? null : T[K];
}
};
export interface InternalEventTypes {
userChangeSuspendedState: { id: MiUser['id']; isSuspended: MiUser['isSuspended']; };

View File

@ -16,7 +16,7 @@ import type { Config } from '@/config.js';
import { StatusError } from '@/misc/status-error.js';
import { bindThis } from '@/decorators.js';
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
import { assertActivityMatchesUrls } from '@/core/activitypub/misc/check-against-url.js';
import { assertActivityMatchesUrls, FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
import type { IObject } from '@/core/activitypub/type.js';
import type { Response } from 'node-fetch';
import type { URL } from 'node:url';
@ -215,7 +215,7 @@ export class HttpRequestService {
}
@bindThis
public async getActivityJson(url: string, isLocalAddressAllowed = false): Promise<IObject> {
public async getActivityJson(url: string, isLocalAddressAllowed = false, allowSoftfail: FetchAllowSoftFailMask = FetchAllowSoftFailMask.Strict): Promise<IObject> {
const res = await this.send(url, {
method: 'GET',
headers: {
@ -232,7 +232,7 @@ export class HttpRequestService {
const finalUrl = res.url; // redirects may have been involved
const activity = await res.json() as IObject;
assertActivityMatchesUrls(activity, [finalUrl]);
assertActivityMatchesUrls(url, activity, [finalUrl], allowSoftfail);
return activity;
}

View File

@ -492,7 +492,8 @@ export class MfmService {
appendChildren(nodes, body);
const serialized = new XMLSerializer().serializeToString(body);
// Remove the unnecessary namespace
const serialized = new XMLSerializer().serializeToString(body).replace(/^\s*<p xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">/, '<p>');
happyDOM.close().catch(err => {});

View File

@ -220,7 +220,7 @@ export class SearchService {
.leftJoinAndSelect('renote.user', 'renoteUser');
if (this.config.fulltextSearch?.provider === 'sqlPgroonga') {
query.andWhere('note.text &@ :q', { q });
query.andWhere('note.text &@~ :q', { q });
} else {
query.andWhere('LOWER(note.text) LIKE :q', { q: `%${ sqlLikeEscape(q.toLowerCase()) }%` });
}

View File

@ -15,7 +15,7 @@ import { QueueService } from '@/core/QueueService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
export type UserWebhookPayload<T extends WebhookEventTypes> =
T extends 'note' | 'reply' | 'renote' |'mention' ? {
T extends 'note' | 'reply' | 'renote' | 'mention' ? {
note: Packed<'Note'>,
} :
T extends 'follow' | 'unfollow' ? {

View File

@ -38,6 +38,14 @@ export class UtilityService {
return this.punyHost(uri) === this.toPuny(this.config.host);
}
// メールアドレスのバリデーションを行う
// https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
@bindThis
public validateEmailFormat(email: string): boolean {
const regexp = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
return regexp.test(email);
}
@bindThis
public isBlockedHost(blockedHosts: string[], host: string | null): boolean {
if (host == null) return false;

View File

@ -127,11 +127,11 @@ export class WebAuthnService {
const { registrationInfo } = verification;
return {
credentialID: registrationInfo.credentialID,
credentialPublicKey: registrationInfo.credentialPublicKey,
credentialID: registrationInfo.credential.id,
credentialPublicKey: registrationInfo.credential.publicKey,
attestationObject: registrationInfo.attestationObject,
fmt: registrationInfo.fmt,
counter: registrationInfo.counter,
counter: registrationInfo.credential.counter,
userVerified: registrationInfo.userVerified,
credentialDeviceType: registrationInfo.credentialDeviceType,
credentialBackedUp: registrationInfo.credentialBackedUp,
@ -212,9 +212,9 @@ export class WebAuthnService {
expectedChallenge: challenge,
expectedOrigin: relyingParty.origin,
expectedRPID: relyingParty.rpId,
authenticator: {
credentialID: key.id,
credentialPublicKey: Buffer.from(key.publicKey, 'base64url'),
credential: {
id: key.id,
publicKey: Buffer.from(key.publicKey, 'base64url'),
counter: key.counter,
transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined,
},
@ -292,9 +292,9 @@ export class WebAuthnService {
expectedChallenge: challenge,
expectedOrigin: relyingParty.origin,
expectedRPID: relyingParty.rpId,
authenticator: {
credentialID: key.id,
credentialPublicKey: Buffer.from(key.publicKey, 'base64url'),
credential: {
id: key.id,
publicKey: Buffer.from(key.publicKey, 'base64url'),
counter: key.counter,
transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined,
},

View File

@ -17,7 +17,7 @@ import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js';
import type Logger from '@/logger.js';
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
import { assertActivityMatchesUrls } from '@/core/activitypub/misc/check-against-url.js';
import { assertActivityMatchesUrls, FetchAllowSoftFailMask as FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
import type { IObject } from './type.js';
type Request = {
@ -185,7 +185,7 @@ export class ApRequestService {
* @param url URL to fetch
*/
@bindThis
public async signedGet(url: string, user: { id: MiUser['id'] }, followAlternate?: boolean): Promise<unknown> {
public async signedGet(url: string, user: { id: MiUser['id'] }, allowSoftfail: FetchAllowSoftFailMask = FetchAllowSoftFailMask.Strict, followAlternate?: boolean): Promise<unknown> {
const _followAlternate = followAlternate ?? true;
const keypair = await this.userKeypairService.getUserKeypair(user.id);
@ -243,7 +243,7 @@ export class ApRequestService {
if (alternate) {
const href = alternate.getAttribute('href');
if (href && this.utilityService.punyHost(url) === this.utilityService.punyHost(href)) {
return await this.signedGet(href, user, false);
return await this.signedGet(href, user, allowSoftfail, false);
}
}
} catch (e) {
@ -258,7 +258,7 @@ export class ApRequestService {
const finalUrl = res.url; // redirects may have been involved
const activity = await res.json() as IObject;
assertActivityMatchesUrls(activity, [finalUrl]);
assertActivityMatchesUrls(url, activity, [finalUrl], allowSoftfail);
return activity;
}

View File

@ -21,6 +21,7 @@ import { ApRendererService } from './ApRendererService.js';
import { ApRequestService } from './ApRequestService.js';
import type { IObject, ICollection, IOrderedCollection } from './type.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { FetchAllowSoftFailMask } from './misc/check-against-url.js';
export class Resolver {
private history: Set<string>;
@ -72,7 +73,7 @@ export class Resolver {
}
@bindThis
public async resolve(value: string | IObject): Promise<IObject> {
public async resolve(value: string | IObject, allowSoftfail: FetchAllowSoftFailMask = FetchAllowSoftFailMask.Strict): Promise<IObject> {
if (typeof value !== 'string') {
return value;
}
@ -108,8 +109,8 @@ export class Resolver {
}
const object = (this.user
? await this.apRequestService.signedGet(value, this.user) as IObject
: await this.httpRequestService.getActivityJson(value)) as IObject;
? await this.apRequestService.signedGet(value, this.user, allowSoftfail) as IObject
: await this.httpRequestService.getActivityJson(value, undefined, allowSoftfail)) as IObject;
if (
Array.isArray(object['@context']) ?
@ -118,19 +119,7 @@ export class Resolver {
) {
throw new IdentifiableError('72180409-793c-4973-868e-5a118eb5519b', 'invalid response');
}
// HttpRequestService / ApRequestService have already checked that
// `object.id` or `object.url` matches the URL used to fetch the
// object after redirects; here we double-check that no redirects
// bounced between hosts
if (object.id == null) {
throw new IdentifiableError('ad2dc287-75c1-44c4-839d-3d2e64576675', 'invalid AP object: missing id');
}
if (this.utilityService.punyHost(object.id) !== this.utilityService.punyHost(value)) {
throw new IdentifiableError('fd93c2fa-69a8-440f-880b-bf178e0ec877', `invalid AP object ${value}: id ${object.id} has different host`);
}
return object;
}

View File

@ -4,18 +4,124 @@
*/
import type { IObject } from '../type.js';
export function assertActivityMatchesUrls(activity: IObject, urls: string[]) {
const hosts = urls.map(it => new URL(it).host);
const idOk = activity.id !== undefined && hosts.includes(new URL(activity.id).host);
// technically `activity.url` could be an `ApObject = IObject |
// string | (IObject | string)[]`, but if it's a complicated thing
// and the `activity.id` doesn't match, I think we're fine
// rejecting the activity
const urlOk = typeof(activity.url) === 'string' && hosts.includes(new URL(activity.url).host);
if (!idOk && !urlOk) {
throw new Error(`bad Activity: neither id(${activity?.id}) nor url(${activity?.url}) match location(${urls})`);
}
export enum FetchAllowSoftFailMask {
// Allow no softfail flags
Strict = 0,
// The values in tuple (requestUrl, finalUrl, objectId) are not all identical
//
// This condition is common for user-initiated lookups but should not be allowed in federation loop
//
// Allow variations:
// good example: https://alice.example.com/@user -> https://alice.example.com/user/:userId
// problematic example: https://alice.example.com/redirect?url=https://bad.example.com/ -> https://bad.example.com/ -> https://alice.example.com/somethingElse
NonCanonicalId = 1 << 0,
// Allow the final object to be at most one subdomain deeper than the request URL, similar to SPF relaxed alignment
//
// Currently no code path allows this flag to be set, but is kept in case of future use as some niche deployments do this, and we provide a pre-reviewed mechanism to opt-in.
//
// Allow variations:
// good example: https://example.com/@user -> https://activitypub.example.com/@user { id: 'https://activitypub.example.com/@user' }
// problematic example: https://example.com/@user -> https://untrusted.example.com/@user { id: 'https://untrusted.example.com/@user' }
MisalignedOrigin = 1 << 1,
// The requested URL has a different host than the returned object ID, although the final URL is still consistent with the object ID
//
// This condition is common for user-initiated lookups using an intermediate host but should not be allowed in federation loops
//
// Allow variations:
// good example: https://alice.example.com/@user@bob.example.com -> https://bob.example.com/@user { id: 'https://bob.example.com/@user' }
// problematic example: https://alice.example.com/definitelyAlice -> https://bob.example.com/@somebodyElse { id: 'https://bob.example.com/@somebodyElse' }
CrossOrigin = 1 << 2 | MisalignedOrigin,
// Allow all softfail flags
//
// do not use this flag on released code
Any = ~0,
}
/**
* Fuzz match on whether the candidate host has authority over the request host
*
* @param requestHost The host of the requested resources
* @param candidateHost The host of final response
* @returns Whether the candidate host has authority over the request host, or if a soft fail is required for a match
*/
function hostFuzzyMatch(requestHost: string, candidateHost: string): FetchAllowSoftFailMask {
const requestFqdn = requestHost.endsWith('.') ? requestHost : `${requestHost}.`;
const candidateFqdn = candidateHost.endsWith('.') ? candidateHost : `${candidateHost}.`;
if (requestFqdn === candidateFqdn) {
return FetchAllowSoftFailMask.Strict;
}
// allow only one case where candidateHost is a first-level subdomain of requestHost
const requestDnsDepth = requestFqdn.split('.').length;
const candidateDnsDepth = candidateFqdn.split('.').length;
if ((candidateDnsDepth - requestDnsDepth) !== 1) {
return FetchAllowSoftFailMask.CrossOrigin;
}
if (`.${candidateHost}`.endsWith(`.${requestHost}`)) {
return FetchAllowSoftFailMask.MisalignedOrigin;
}
return FetchAllowSoftFailMask.CrossOrigin;
}
// normalize host names by removing www. prefix
function normalizeSynonymousSubdomain(url: URL | string): URL {
const urlParsed = url instanceof URL ? url : new URL(url);
const host = urlParsed.host;
const normalizedHost = host.replace(/^www\./, '');
return new URL(urlParsed.toString().replace(host, normalizedHost));
}
export function assertActivityMatchesUrls(requestUrl: string | URL, activity: IObject, candidateUrls: (string | URL)[], allowSoftfail: FetchAllowSoftFailMask): FetchAllowSoftFailMask {
// must have a unique identifier to verify authority
if (!activity.id) {
throw new Error('bad Activity: missing id field');
}
let softfail = 0;
// if the flag is allowed, set the flag on return otherwise throw
const requireSoftfail = (needed: FetchAllowSoftFailMask, message: string) => {
if ((allowSoftfail & needed) !== needed) {
throw new Error(message);
}
softfail |= needed;
};
const requestUrlParsed = normalizeSynonymousSubdomain(requestUrl);
const idParsed = normalizeSynonymousSubdomain(activity.id);
const candidateUrlsParsed = candidateUrls.map(it => normalizeSynonymousSubdomain(it));
const requestUrlSecure = requestUrlParsed.protocol === 'https:';
const finalUrlSecure = candidateUrlsParsed.every(it => it.protocol === 'https:');
if (requestUrlSecure && !finalUrlSecure) {
throw new Error(`bad Activity: id(${activity.id}) is not allowed to have http:// in the url`);
}
// Compare final URL to the ID
if (!candidateUrlsParsed.some(it => it.href === idParsed.href)) {
requireSoftfail(FetchAllowSoftFailMask.NonCanonicalId, `bad Activity: id(${activity.id}) does not match response url(${candidateUrlsParsed.map(it => it.toString())})`);
// at lease host need to match exactly (ActivityPub requirement)
if (!candidateUrlsParsed.some(it => idParsed.host === it.host)) {
throw new Error(`bad Activity: id(${activity.id}) does not match response host(${candidateUrlsParsed.map(it => it.host)})`);
}
}
// Compare request URL to the ID
if (!requestUrlParsed.href.includes(idParsed.href)) {
requireSoftfail(FetchAllowSoftFailMask.NonCanonicalId, `bad Activity: id(${activity.id}) does not match request url(${requestUrlParsed.toString()})`);
// if cross-origin lookup is allowed, we can accept some variation between the original request URL to the final object ID (but not between the final URL and the object ID)
const hostResult = hostFuzzyMatch(requestUrlParsed.host, idParsed.host);
requireSoftfail(hostResult, `bad Activity: id(${activity.id}) is valid but is not the same origin as request url(${requestUrlParsed.toString()})`);
}
return softfail;
}

View File

@ -57,12 +57,14 @@ const ajv = new Ajv();
function isLocalUser(user: MiUser): user is MiLocalUser;
function isLocalUser<T extends { host: MiUser['host'] }>(user: T): user is (T & { host: null; });
function isLocalUser(user: MiUser | { host: MiUser['host'] }): boolean {
return user.host == null;
}
function isRemoteUser(user: MiUser): user is MiRemoteUser;
function isRemoteUser<T extends { host: MiUser['host'] }>(user: T): user is (T & { host: string; });
function isRemoteUser(user: MiUser | { host: MiUser['host'] }): boolean {
return !isLocalUser(user);
}
@ -78,7 +80,7 @@ export type UserRelation = {
isBlocked: boolean
isMuted: boolean
isRenoteMuted: boolean
}
};
@Injectable()
export class UserEntityService implements OnModuleInit {

View File

@ -143,7 +143,7 @@ type OfSchema = {
readonly anyOf?: ReadonlyArray<Schema>;
readonly oneOf?: ReadonlyArray<Schema>;
readonly allOf?: ReadonlyArray<Schema>;
}
};
export interface Schema extends OfSchema {
readonly type?: TypeStringef;
@ -217,7 +217,7 @@ type ObjectSchemaTypeDef<p extends Schema> =
:
p['anyOf'] extends ReadonlyArray<Schema> ? never : // see CONTRIBUTING.md
p['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> :
any
any;
type ObjectSchemaType<p extends Schema> = NullOrUndefined<p, ObjectSchemaTypeDef<p>>;

View File

@ -4,7 +4,7 @@
*/
export type JsonValue = JsonArray | JsonObject | string | number | boolean | null;
export type JsonObject = {[K in string]?: JsonValue};
export type JsonObject = { [K in string]?: JsonValue };
export type JsonArray = JsonValue[];
export function isJsonObject(value: JsonValue | undefined): value is JsonObject {

View File

@ -90,6 +90,10 @@ export type MiNotification = {
type: 'login';
id: string;
createdAt: string;
} | {
type: 'createToken';
id: string;
createdAt: string;
} | {
type: 'app';
id: string;

View File

@ -118,3 +118,5 @@ export class MiPage {
}
}
}
export const pageNameSchema = { type: 'string', pattern: /^[^\s:\/?#\[\]@!$&'()*+,;=\\%\x00-\x20]{1,256}$/.source } as const;

View File

@ -288,24 +288,24 @@ export class MiUser {
export type MiLocalUser = MiUser & {
host: null;
uri: null;
}
};
export type MiPartialLocalUser = Partial<MiUser> & {
id: MiUser['id'];
host: null;
uri: null;
}
};
export type MiRemoteUser = MiUser & {
host: string;
uri: string;
}
};
export type MiPartialRemoteUser = Partial<MiUser> & {
id: MiUser['id'];
host: string;
uri: string;
}
};
export const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const;
export const passwordSchema = { type: 'string', minLength: 1 } as const;

View File

@ -332,6 +332,16 @@ export const packedNotificationSchema = {
enum: ['login'],
},
},
}, {
type: 'object',
properties: {
...baseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['createToken'],
},
},
}, {
type: 'object',
properties: {

View File

@ -92,7 +92,7 @@ const sqlLogger = dbLogger.createSubLogger('sql', 'gray');
export type LoggerProps = {
disableQueryTruncation?: boolean;
enableQueryParamLogging?: boolean;
}
};
function highlightSql(sql: string) {
return highlight.highlight(sql, {

View File

@ -29,7 +29,7 @@ export type ModeratorInactivityEvaluationResult = {
isModeratorsInactive: boolean;
inactiveModerators: MiUser[];
remainingTime: ModeratorInactivityRemainingTime;
}
};
export type ModeratorInactivityRemainingTime = {
time: number;

View File

@ -107,12 +107,12 @@ export class InboxProcessorService implements OnApplicationShutdown {
// それでもわからなければ終了
if (authUser == null) {
throw new Bull.UnrecoverableError('skip: failed to resolve user');
throw new Bull.UnrecoverableError(`skip: failed to resolve user ${getApId(activity.actor)}`);
}
// publicKey がなくても終了
if (authUser.key == null) {
throw new Bull.UnrecoverableError('skip: failed to resolve user publicKey');
throw new Bull.UnrecoverableError(`skip: failed to resolve user publicKey ${getApId(activity.actor)}`);
}
// HTTP-Signatureの検証

View File

@ -38,7 +38,7 @@ export type RelationshipJobData = {
silent?: boolean;
requestId?: string;
withReplies?: boolean;
}
};
export type DbJobData<T extends keyof DbJobMap> = DbJobMap[T];
@ -61,11 +61,11 @@ export type DbJobMap = {
importUserLists: DbUserImportJobData;
importCustomEmojis: DbUserImportJobData;
deleteAccount: DbUserDeleteJobData;
}
};
export type DbJobDataWithUser = {
user: ThinUser;
}
};
export type DbExportFollowingData = {
user: ThinUser;
@ -75,7 +75,7 @@ export type DbExportFollowingData = {
export type DBExportAntennasData = {
user: ThinUser
}
};
export type DbUserDeleteJobData = {
user: ThinUser;
@ -91,7 +91,7 @@ export type DbUserImportJobData = {
export type DBAntennaImportJobData = {
user: ThinUser,
antenna: Antenna
}
};
export type DbUserImportToDbJobData = {
user: ThinUser;

View File

@ -103,6 +103,43 @@ export class ServerService implements OnApplicationShutdown {
serve: false,
});
// if the requester looks like to be performing an ActivityPub object lookup, reject all external redirects
//
// this will break lookup that involve copying a URL from a third-party server, like trying to lookup http://charlie.example.com/@alice@alice.com
//
// this is not required by standard but protect us from peers that did not validate final URL.
if (this.config.disallowExternalApRedirect) {
const maybeApLookupRegex = /application\/activity\+json|application\/ld\+json.+activitystreams/i;
fastify.addHook('onSend', (request, reply, _, done) => {
const location = reply.getHeader('location');
if (reply.statusCode < 300 || reply.statusCode >= 400 || typeof location !== 'string') {
done();
return;
}
if (!maybeApLookupRegex.test(request.headers.accept ?? '')) {
done();
return;
}
const effectiveLocation = process.env.NODE_ENV === 'production' ? location : location.replace(/^http:\/\//, 'https://');
if (effectiveLocation.startsWith(`https://${this.config.host}/`)) {
done();
return;
}
reply.status(406);
reply.removeHeader('location');
reply.header('content-type', 'text/plain; charset=utf-8');
reply.header('link', `<${encodeURI(location)}>; rel="canonical"`);
done(null, [
"Refusing to relay remote ActivityPub object lookup.",
"",
`Please remove 'application/activity+json' and 'application/ld+json' from the Accept header or fetch using the authoritative URL at ${location}.`,
].join('\n'));
});
}
fastify.register(this.apiServerService.createServer, { prefix: '/api' });
fastify.register(this.openApiServerService.createServer);
fastify.register(this.fileServerService.createServer);

View File

@ -122,7 +122,7 @@ export type IEndpointMeta = (Omit<IEndpointMetaBase, 'requireCrential' | 'requir
}) | (Omit<IEndpointMetaBase, 'requireAdmin' | 'kind'> & {
requireAdmin: true,
kind: (typeof permissions)[number],
})
});
export interface IEndpoint {
name: string;

View File

@ -512,6 +512,7 @@ export const meta = {
},
federation: {
type: 'string',
enum: ['all', 'specified', 'none'],
optional: false, nullable: false,
},
federationHosts: {

View File

@ -20,6 +20,7 @@ import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
import { ApiError } from '../../error.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
export const meta = {
tags: ['federation'],
@ -53,11 +54,6 @@ export const meta = {
code: 'RESPONSE_INVALID',
id: '70193c39-54f3-4813-82f0-70a680f7495b',
},
responseInvalidIdHostNotMatch: {
message: 'Requested URI and response URI host does not match.',
code: 'RESPONSE_INVALID_ID_HOST_NOT_MATCH',
id: 'a2c9c61a-cb72-43ab-a964-3ca5fddb410a',
},
noSuchObject: {
message: 'No such object.',
code: 'NO_SUCH_OBJECT',
@ -153,7 +149,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
// リモートから一旦オブジェクトフェッチ
const resolver = this.apResolverService.createResolver();
const object = await resolver.resolve(uri).catch((err) => {
// allow ap/show exclusively to lookup URLs that are cross-origin or non-canonical (like https://alice.example.com/@bob@bob.example.com -> https://bob.example.com/@bob)
const object = await resolver.resolve(uri, FetchAllowSoftFailMask.CrossOrigin | FetchAllowSoftFailMask.NonCanonicalId).catch((err) => {
if (err instanceof IdentifiableError) {
switch (err.id) {
// resolve
@ -165,10 +162,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
case '09d79f9e-64f1-4316-9cfa-e75c4d091574':
throw new ApiError(meta.errors.federationNotAllowed);
case '72180409-793c-4973-868e-5a118eb5519b':
case 'ad2dc287-75c1-44c4-839d-3d2e64576675':
throw new ApiError(meta.errors.responseInvalid);
case 'fd93c2fa-69a8-440f-880b-bf178e0ec877':
throw new ApiError(meta.errors.responseInvalidIdHostNotMatch);
// resolveLocal
case '02b40cd0-fa92-4b0c-acc9-fb2ada952ab8':

View File

@ -96,7 +96,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
await this.userFollowingService.unfollow(follower, followee);
return await this.userEntityService.pack(followee.id, me);
return await this.userEntityService.pack(follower.id, me);
});
}
}

View File

@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { AccessTokensRepository } from '@/models/_.js';
import { IdService } from '@/core/IdService.js';
import { NotificationService } from '@/core/NotificationService.js';
import { secureRndstr } from '@/misc/secure-rndstr.js';
import { DI } from '@/di-symbols.js';
@ -50,6 +51,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private accessTokensRepository: AccessTokensRepository,
private idService: IdService,
private notificationService: NotificationService,
) {
super(meta, paramDef, async (ps, me) => {
// Generate access token
@ -71,6 +73,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
permission: ps.permission,
});
// アクセストークンが生成されたことを通知
this.notificationService.createNotification(me.id, 'createToken', {});
return {
token: accessToken,
};

View File

@ -7,7 +7,7 @@ import ms from 'ms';
import { Inject, Injectable } from '@nestjs/common';
import type { DriveFilesRepository, PagesRepository } from '@/models/_.js';
import { IdService } from '@/core/IdService.js';
import { MiPage } from '@/models/Page.js';
import { MiPage, pageNameSchema } from '@/models/Page.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { PageEntityService } from '@/core/entities/PageEntityService.js';
import { DI } from '@/di-symbols.js';
@ -51,7 +51,7 @@ export const paramDef = {
type: 'object',
properties: {
title: { type: 'string' },
name: { type: 'string', minLength: 1 },
name: { ...pageNameSchema, minLength: 1 },
summary: { type: 'string', nullable: true },
content: { type: 'array', items: {
type: 'object', additionalProperties: true,

View File

@ -10,6 +10,7 @@ import type { PagesRepository, DriveFilesRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
import { pageNameSchema } from '@/models/Page.js';
export const meta = {
tags: ['pages'],
@ -31,13 +32,11 @@ export const meta = {
code: 'NO_SUCH_PAGE',
id: '21149b9e-3616-4778-9592-c4ce89f5a864',
},
accessDenied: {
message: 'Access denied.',
code: 'ACCESS_DENIED',
id: '3c15cd52-3b4b-4274-967d-6456fc4f792b',
},
noSuchFile: {
message: 'No such file.',
code: 'NO_SUCH_FILE',
@ -56,7 +55,7 @@ export const paramDef = {
properties: {
pageId: { type: 'string', format: 'misskey:id' },
title: { type: 'string' },
name: { type: 'string', minLength: 1 },
name: { ...pageNameSchema, minLength: 1 },
summary: { type: 'string', nullable: true },
content: { type: 'array', items: {
type: 'object', additionalProperties: true,

View File

@ -210,9 +210,15 @@ export function genOpenapiSpec(config: Config, includeSelfRef = false) {
spec.paths['/' + endpoint.name] = {
...(endpoint.meta.allowGet ? {
get: info,
get: {
...info,
operationId: 'get___' + info.operationId,
},
} : {}),
post: info,
post: {
...info,
operationId: 'post___' + info.operationId,
},
};
}

View File

@ -82,8 +82,8 @@ export default abstract class Channel {
this.connection = connection;
}
public send(payload: { type: string, body: JsonValue }): void
public send(type: string, payload: JsonValue): void
public send(payload: { type: string, body: JsonValue }): void;
public send(type: string, payload: JsonValue): void;
@bindThis
public send(typeOrPayload: { type: string, body: JsonValue } | string, payload?: JsonValue) {
const type = payload === undefined ? (typeOrPayload as { type: string, body: JsonValue }).type : (typeOrPayload as string);
@ -108,4 +108,4 @@ export type MiChannelService<T extends boolean> = {
requireCredential: T;
kind: T extends true ? string : string | null | undefined;
create: (id: string, connection: Connection) => Channel;
}
};

View File

@ -114,13 +114,17 @@
if (document.readyState === 'loading') {
await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve));
}
const locale = JSON.parse(localStorage.getItem('locale') || '{}');
const title = locale?._bootErrors?.title || 'Failed to initialize Misskey';
const reload = locale?.reload || 'Reload';
document.body.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M12 9v4" /><path d="M12 16v.01" /></svg>
<div class="message">読み込みに失敗しました</div>
<div class="submessage">Failed to initialize Misskey</div>
<div class="message">${title}</div>
<div class="submessage">Error Code: ${code}</div>
<button onclick="location.reload(!0)">
<div>リロード</div>
<div><small>Reload</small></div>
<div>${reload}</div>
</button>`;
addStyle(`
#misskey_app,

View File

@ -151,6 +151,22 @@
await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve));
}
const locale = JSON.parse(localStorage.getItem('locale') || '{}');
const messages = Object.assign({
title: 'Failed to initialize Misskey',
solution: 'The following actions may solve the problem.',
solution1: 'Update your os and browser',
solution2: 'Disable an adblocker',
solution3: 'Clear the browser cache',
solution4: '(Tor Browser) Set dom.webaudio.enabled to true',
otherOption: 'Other options',
otherOption1: 'Clear preferences and cache',
otherOption2: 'Start the simple client',
otherOption3: 'Start the repair tool',
}, locale?._bootErrors || {});
const reload = locale?.reload || 'Reload';
let errorsElement = document.getElementById('errors');
if (!errorsElement) {
@ -160,32 +176,32 @@
<path d="M12 9v2m0 4v.01"></path>
<path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path>
</svg>
<h1>Failed to load<br>読み込みに失敗しました</h1>
<h1>${messages.title}</h1>
<button class="button-big" onclick="location.reload(true);">
<span class="button-label-big">Reload / リロード</span>
<span class="button-label-big">${reload}</span>
</button>
<p><b>The following actions may solve the problem. / 以下を行うと解決する可能性があります</b></p>
<p>Update your os and browser / ブラウザおよびOSを最新バージョンに更新する</p>
<p>Disable an adblocker / アドブロッカーを無効にする</p>
<p>Clear the browser cache / ブラウザのキャッシュをクリアする</p>
<p>&#40;Tor Browser&#41; Set dom.webaudio.enabled to true / dom.webaudio.enabledをtrueに設定する</p>
<p><b>${messages.solution}</b></p>
<p>${messages.solution1}</p>
<p>${messages.solution2}</p>
<p>${messages.solution3}</p>
<p>${messages.solution4}</p>
<details style="color: #86b300;">
<summary>Other options / その他のオプション</summary>
<summary>${messages.otherOption}</summary>
<a href="/flush">
<button class="button-small">
<span class="button-label-small">Clear preferences and cache</span>
<span class="button-label-small">${messages.otherOption1}</span>
</button>
</a>
<br>
<a href="/cli">
<button class="button-small">
<span class="button-label-small">Start the simple client</span>
<span class="button-label-small">${messages.otherOption2}</span>
</button>
</a>
<br>
<a href="/bios">
<button class="button-small">
<span class="button-label-small">Start the repair tool</span>
<span class="button-label-small">${messages.otherOption3}</span>
</button>
</a>
</details>

View File

@ -5,112 +5,107 @@
*/
* {
font-family: BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
font-family: BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
}
#misskey_app,
#splash {
display: none !important;
display: none !important;
}
body,
html {
background-color: #222;
color: #dfddcc;
justify-content: center;
margin: auto;
padding: 10px;
text-align: center;
background-color: #222;
color: #dfddcc;
justify-content: center;
margin: auto;
padding: 10px;
text-align: center;
}
button {
border-radius: 999px;
padding: 0px 12px 0px 12px;
border: none;
cursor: pointer;
margin-bottom: 12px;
border-radius: 999px;
padding: 0px 12px 0px 12px;
border: none;
cursor: pointer;
margin-bottom: 12px;
}
.button-big {
background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0));
line-height: 50px;
background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0));
line-height: 50px;
}
.button-big:hover {
background: rgb(153, 204, 0);
background: rgb(153, 204, 0);
}
.button-small {
background: #444;
line-height: 40px;
background: #444;
line-height: 40px;
}
.button-small:hover {
background: #555;
background: #555;
}
.button-label-big {
color: #222;
font-weight: bold;
font-size: 20px;
padding: 12px;
color: #222;
font-weight: bold;
font-size: 1.2em;
padding: 12px;
}
.button-label-small {
color: rgb(153, 204, 0);
font-size: 16px;
padding: 12px;
color: rgb(153, 204, 0);
font-size: 16px;
padding: 12px;
}
a {
color: rgb(134, 179, 0);
text-decoration: none;
color: rgb(134, 179, 0);
text-decoration: none;
}
p,
li {
font-size: 16px;
}
.dont-worry,
#msg {
font-size: 18px;
font-size: 16px;
}
.icon-warning {
color: #dec340;
height: 4rem;
padding-top: 2rem;
color: #dec340;
height: 4rem;
padding-top: 2rem;
}
h1 {
font-size: 32px;
font-size: 1.5em;
margin: 1em;
}
code {
display: block;
font-family: Fira, FiraCode, monospace;
background: #333;
padding: 0.5rem 1rem;
max-width: 40rem;
border-radius: 10px;
justify-content: center;
margin: auto;
white-space: pre-wrap;
word-break: break-word;
display: block;
font-family: Fira, FiraCode, monospace;
background: #333;
padding: 0.5rem 1rem;
max-width: 40rem;
border-radius: 10px;
justify-content: center;
margin: auto;
white-space: pre-wrap;
word-break: break-word;
}
summary {
cursor: pointer;
#errorInfo summary {
cursor: pointer;
}
summary > * {
display: inline;
white-space: pre-wrap;
#errorInfo summary>* {
display: inline;
}
@media screen and (max-width: 500px) {
details {
width: 50%;
}
#errorInfo {
width: 50%;
}
}

View File

@ -0,0 +1,40 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
'use strict';
(() => {
document.addEventListener('DOMContentLoaded', () => {
const locale = JSON.parse(localStorage.getItem('locale') || '{}');
const messages = Object.assign({
title: 'Failed to initialize Misskey',
serverError: 'If reloading after a period of time does not resolve the problem, contact the server administrator with the following ERROR ID.',
solution: 'The following actions may solve the problem.',
solution1: 'Update your os and browser',
solution2: 'Disable an adblocker',
solution3: 'Clear the browser cache',
solution4: '(Tor Browser) Set dom.webaudio.enabled to true',
otherOption: 'Other options',
otherOption1: 'Clear preferences and cache',
otherOption2: 'Start the simple client',
otherOption3: 'Start the repair tool',
}, locale?._bootErrors || {});
const reload = locale?.reload || 'Reload';
const reloadEls = document.querySelectorAll('[data-i18n-reload]');
for (const el of reloadEls) {
el.textContent = reload;
}
const i18nEls = document.querySelectorAll('[data-i18n]');
for (const el of i18nEls) {
const key = el.dataset.i18n;
if (key && messages[key]) {
el.textContent = messages[key];
}
}
});
})();

View File

@ -2,15 +2,15 @@ doctype html
//
-
_____ _ _
| |_|___ ___| |_ ___ _ _
_____ _ _
| |_|___ ___| |_ ___ _ _
| | | | |_ -|_ -| '_| -_| | |
|_|_|_|_|___|___|_,_|___|_ |
|___|
|___|
Thank you for using Misskey!
If you are reading this message... how about joining the development?
https://github.com/misskey-dev/misskey
html
@ -27,39 +27,45 @@ html
style
include ../error.css
script
include ../error.js
body
svg.icon-warning(xmlns="http://www.w3.org/2000/svg", viewBox="0 0 24 24", stroke-width="2", stroke="currentColor", fill="none", stroke-linecap="round", stroke-linejoin="round")
path(stroke="none", d="M0 0h24v24H0z", fill="none")
path(d="M12 9v2m0 4v.01")
path(d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75")
h1 An error has occurred!
h1(data-i18n="title") Failed to initialize Misskey
button.button-big(onclick="location.reload();")
span.button-label-big Refresh
span.button-label-big(data-i18n-reload) Reload
p.dont-worry Don't worry, it's (probably) not your fault.
p If reloading after a period of time does not resolve the problem, contact the server administrator with the following ERROR ID.
p(data-i18n="serverError") If reloading after a period of time does not resolve the problem, contact the server administrator with the following ERROR ID.
div#errors
code.
ERROR CODE: #{code}
ERROR ID: #{id}
p You may also try the following options:
p
b(data-i18n="solution") The following actions may solve the problem.
p Update your os and browser.
p Disable an adblocker.
p(data-i18n="solution1") Update your os and browser
p(data-i18n="solution2") Disable an adblocker
p(data-i18n="solution3") Clear your browser cache
p(data-i18n="solution4") (Tor Browser) Set dom.webaudio.enabled to true
a(href="/flush")
button.button-small
span.button-label-small Clear preferences and cache
br
a(href="/cli")
button.button-small
span.button-label-small Start the simple client
br
a(href="/bios")
button.button-small
span.button-label-small Start the repair tool
details(style="color: #86b300;")
summary(data-i18n="otherOption") Other options
a(href="/flush")
button.button-small
span.button-label-small(data-i18n="otherOption1") Clear preferences and cache
br
a(href="/cli")
button.button-small
span.button-label-small(data-i18n="otherOption2") Start the simple client
br
a(href="/bios")
button.button-small
span.button-label-small(data-i18n="otherOption3") Start the repair tool

View File

@ -18,6 +18,7 @@
* achievementEarned -
* exportCompleted -
* login -
* createToken -
* app -
* test -
*/
@ -36,6 +37,7 @@ export const notificationTypes = [
'achievementEarned',
'exportCompleted',
'login',
'createToken',
'app',
'test',
] as const;

View File

@ -22,7 +22,7 @@ export type LoginUser = SigninResponse & {
client: Misskey.api.APIClient;
username: string;
password: string;
}
};
/** used for avoiding overload and some endpoints */
export type Request = <

Some files were not shown because too many files have changed in this diff Show More