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' '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) # Upload or download file size limits (bytes)
#maxFileSize: 262144000 #maxFileSize: 262144000

View File

@ -235,6 +235,11 @@ signToActivityPubGet: true
# '127.0.0.1/32' # '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) # Upload or download file size limits (bytes)
#maxFileSize: 262144000 #maxFileSize: 262144000

View File

@ -334,6 +334,11 @@ signToActivityPubGet: true
# '127.0.0.1/32' # '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) # Upload or download file size limits (bytes)
#maxFileSize: 262144000 #maxFileSize: 262144000

View File

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

View File

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

View File

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

View File

@ -12,9 +12,9 @@ jobs:
steps: steps:
- name: Checkout head - name: Checkout head
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.2.2
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.2.0
with: with:
node-version-file: '.node-version' 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 }} if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }}
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.2.2
with: with:
submodules: true submodules: true
persist-credentials: false persist-credentials: false
@ -29,7 +29,7 @@ jobs:
- name: setup node - name: setup node
id: setup-node id: setup-node
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.2.0
with: with:
node-version-file: '.node-version' node-version-file: '.node-version'
cache: pnpm cache: pnpm
@ -66,7 +66,7 @@ jobs:
if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }} if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }}
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.2.2
with: with:
submodules: true submodules: true
persist-credentials: false persist-credentials: false

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@ jobs:
DOCKER_CONTENT_TRUST: 1 DOCKER_CONTENT_TRUST: 1
DOCKLE_VERSION: 0.4.14 DOCKLE_VERSION: 0.4.14
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.2.2
- name: Download and install dockle v${{ env.DOCKLE_VERSION }} - name: Download and install dockle v${{ env.DOCKLE_VERSION }}
run: | run: |
curl -L -o dockle.deb "https://github.com/goodwithtech/dockle/releases/download/v${DOCKLE_VERSION}/dockle_${DOCKLE_VERSION}_Linux-64bit.deb" 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 ref: refs/pull/${{ github.event.number }}/merge
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.2.2
with: with:
ref: ${{ matrix.ref }} ref: ${{ matrix.ref }}
submodules: true submodules: true
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.2.0
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'pnpm' cache: 'pnpm'

View File

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

View File

@ -18,12 +18,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
continue-on-error: true continue-on-error: true
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.2.2
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: true submodules: true
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4.1.0 - uses: actions/setup-node@v4.2.0
with: with:
node-version-file: '.node-version' node-version-file: '.node-version'
cache: 'pnpm' 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] node-version: [22.11.0]
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.2.2
with: with:
submodules: true submodules: true
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.2.0
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'pnpm' 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" NODE_OPTIONS: "--max_old_space_size=7168"
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.2.2
if: github.event_name != 'pull_request_target' if: github.event_name != 'pull_request_target'
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: true submodules: true
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.2.2
if: github.event_name == 'pull_request_target' if: github.event_name == 'pull_request_target'
with: with:
fetch-depth: 0 fetch-depth: 0
@ -46,7 +46,7 @@ jobs:
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
- name: Use Node.js 20.x - name: Use Node.js 20.x
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.2.0
with: with:
node-version-file: '.node-version' node-version-file: '.node-version'
cache: 'pnpm' cache: 'pnpm'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -25,13 +25,13 @@ jobs:
node-version: [22.11.0] node-version: [22.11.0]
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.2.2
with: with:
submodules: true submodules: true
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.2.0
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'pnpm' 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 ### General
- Fix: Docker のビルドに失敗する問題を修正 - Fix: Docker のビルドに失敗する問題を修正
@ -13,12 +42,13 @@
- Fix: データセーバー有効時にもユーザーページの「ファイル」タブで画像が読み込まれてしまう問題を修正 - Fix: データセーバー有効時にもユーザーページの「ファイル」タブで画像が読み込まれてしまう問題を修正
- Fix: MFMの `sparkle` エフェクトが正しく表示されない問題を修正 - Fix: MFMの `sparkle` エフェクトが正しく表示されない問題を修正
- Fix: ページのURLにスラッシュが含まれている場合にページが正しく表示されない問題を修正 - Fix: ページのURLにスラッシュが含まれている場合にページが正しく表示されない問題を修正
- Fix: デッキのプロファイルが新規作成できない問題を修正
- Fix: セキュリティに関する修正
- ローカライゼーションの更新 - ローカライゼーションの更新
- Playが実装されたため、ページ機能の「ソースを見る」は削除されました
### Server ### Server
- Enhance: スレッドミュートにおいて、リノート、引用、リアクションの通知もミュートするように - Enhance: ページのURLに使用可能な文字を限定するように
- なお、以下のケースでは引き続き通知がミュートされません。(ミュートを行っているユーザーをAとします)
- ミュート対象ートを、当該スレッドの外にあるAへの返信/メンション付きノートにおいて引用する
- Fix: 個別お知らせページのmetaタグ出力の条件が間違っていたのを修正 - Fix: 個別お知らせページのmetaタグ出力の条件が間違っていたのを修正
## 2025.1.0 ## 2025.1.0

View File

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

View File

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

View File

@ -2365,9 +2365,6 @@ _pages:
newPage: "pa" newPage: "pa"
editPage: "Editar la pàgina" editPage: "Editar la pàgina"
readPage: "Veure el codi font d'aquesta 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" pageSetting: "Configuració de la pàgina"
nameAlreadyExists: "L'adreça URL de la pàgina ja existeix" nameAlreadyExists: "L'adreça URL de la pàgina ja existeix"
invalidNameTitle: "L'adreça URL de la pàgina no és vàlida" 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" newPage: "Vytvořit novou stránku"
editPage: "Upravit stránku" editPage: "Upravit stránku"
readPage: "Prohlížení zdroje této stránky" 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" pageSetting: "Nastavení stránky"
nameAlreadyExists: "Zadaná adresa URL stránky již existuje" nameAlreadyExists: "Zadaná adresa URL stránky již existuje"
invalidNameTitle: "Zadaná adresa URL stránky je neplatná" invalidNameTitle: "Zadaná adresa URL stránky je neplatná"

View File

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

View File

@ -2365,9 +2365,6 @@ _pages:
newPage: "Create a new Page" newPage: "Create a new Page"
editPage: "Edit this Page" editPage: "Edit this Page"
readPage: "Viewing this Page's source" readPage: "Viewing this Page's source"
created: "Page successfully created"
updated: "Page successfully edited"
deleted: "Page successfully deleted"
pageSetting: "Page settings" pageSetting: "Page settings"
nameAlreadyExists: "The specified Page URL already exists" nameAlreadyExists: "The specified Page URL already exists"
invalidNameTitle: "The specified Page URL is invalid" invalidNameTitle: "The specified Page URL is invalid"
@ -2775,6 +2772,7 @@ _customEmojisManager:
confirmUpdateEmojisDescription: "Update {count} Emoji(s). Are you sure to continue?" confirmUpdateEmojisDescription: "Update {count} Emoji(s). Are you sure to continue?"
confirmDeleteEmojisDescription: "Delete checked {count} Emoji(s). Are you sure to continue?" confirmDeleteEmojisDescription: "Delete checked {count} Emoji(s). Are you sure to continue?"
confirmResetDescription: "" 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" dialogSelectRoleTitle: "Search by roll set in Emojis"
_register: _register:
uploadSettingTitle: "Upload settings" uploadSettingTitle: "Upload settings"

View File

@ -2294,9 +2294,6 @@ _pages:
newPage: "Crear página" newPage: "Crear página"
editPage: "Editar página" editPage: "Editar página"
readPage: "Viendo la fuente" 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" pageSetting: "Configurar página"
nameAlreadyExists: "La URL de la página especificada ya existe" nameAlreadyExists: "La URL de la página especificada ya existe"
invalidNameTitle: "URL inválida" invalidNameTitle: "URL inválida"

View File

@ -2118,9 +2118,6 @@ _pages:
newPage: "Créer une page" newPage: "Créer une page"
editPage: "Modifier une page" editPage: "Modifier une page"
readPage: "Affichage de la source en cours" 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" pageSetting: "Paramètres de la Page"
nameAlreadyExists: "L'URL de page spécifiée existe déjà" nameAlreadyExists: "L'URL de page spécifiée existe déjà"
invalidNameTitle: "L'URL de page spécifiée nest pas valide" invalidNameTitle: "L'URL de page spécifiée nest pas valide"

View File

@ -2285,9 +2285,6 @@ _pages:
newPage: "Buat halaman baru" newPage: "Buat halaman baru"
editPage: "Sunting halaman" editPage: "Sunting halaman"
readPage: "Lihat sumber kode aktif" readPage: "Lihat sumber kode aktif"
created: "Halaman berhasil dibuat"
updated: "Halaman berhasil diperbaharui!"
deleted: "Halaman telah dihapus"
pageSetting: "Pengaturan Halaman" pageSetting: "Pengaturan Halaman"
nameAlreadyExists: "URL Halaman yang ditentukan sudah ada" nameAlreadyExists: "URL Halaman yang ditentukan sudah ada"
invalidNameTitle: "URL Halaman yang ditentukan tidak valid" 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; "invalidParamError": string;
/** /**
* *
*/ */
"invalidParamErrorDescription": string; "invalidParamErrorDescription": string;
/** /**
@ -5254,6 +5254,14 @@ export interface Locale extends ILocale {
* *
*/ */
"federationDisabled": string; "federationDisabled": string;
/**
*
*/
"confirmOnReact": string;
/**
* " {emoji} "
*/
"reactAreYouSure": ParameterizedString<"emoji">;
"_accountSettings": { "_accountSettings": {
/** /**
* *
@ -9180,18 +9188,6 @@ export interface Locale extends ILocale {
* *
*/ */
"readPage": string; "readPage": string;
/**
*
*/
"created": string;
/**
*
*/
"updated": string;
/**
*
*/
"deleted": string;
/** /**
* *
*/ */
@ -9484,6 +9480,14 @@ export interface Locale extends ILocale {
* *
*/ */
"login": string; "login": string;
/**
*
*/
"createToken": string;
/**
* {text}
*/
"createTokenDescription": ParameterizedString<"text">;
"_types": { "_types": {
/** /**
* *
@ -10892,13 +10896,7 @@ export interface Locale extends ILocale {
*/ */
"title": string; "title": string;
/** /**
* * URIを使用して照会し直してください
*/
"description": string;
};
"_responseInvalidIdHostNotMatch": {
/**
* URIのドメインと最終的に得られたURIのドメインとが異なりますURIを使用して照会し直してください
*/ */
"description": string; "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: { declare const locales: {
[lang: string]: Locale; [lang: string]: Locale;

View File

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

View File

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

View File

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

View File

@ -1459,9 +1459,6 @@ _pages:
newPage: "Utwórz stronę" newPage: "Utwórz stronę"
editPage: "Edytuj tę stronę" editPage: "Edytuj tę stronę"
readPage: "Aktywowano widok źródła" 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" pageSetting: "Ustawienia strony"
nameAlreadyExists: "Określony adres URL strony już istnieje" nameAlreadyExists: "Określony adres URL strony już istnieje"
invalidNameTitle: "Podany adres URL strony jest nieprawidłowy" invalidNameTitle: "Podany adres URL strony jest nieprawidłowy"

View File

@ -2357,9 +2357,6 @@ _pages:
newPage: "Criar uma Página" newPage: "Criar uma Página"
editPage: "Editar essa Página" editPage: "Editar essa Página"
readPage: "Ver a fonte dessa 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" pageSetting: "Configurações da página"
nameAlreadyExists: "O URL de Página especificado já existe" nameAlreadyExists: "O URL de Página especificado já existe"
invalidNameTitle: "O URL de Página especificado é inválido" invalidNameTitle: "O URL de Página especificado é inválido"

View File

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

View File

@ -1332,9 +1332,6 @@ _pages:
newPage: "Vytvoriť novú stránku" newPage: "Vytvoriť novú stránku"
editPage: "Upraviť túto stránku" editPage: "Upraviť túto stránku"
readPage: "Zobrazenie zdroja aktívne" 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" pageSetting: "Nastavenia stránky"
nameAlreadyExists: "Zadaná URL stránku už existuje" nameAlreadyExists: "Zadaná URL stránku už existuje"
invalidNameTitle: "Zadaná URL stránku je nesprávna" invalidNameTitle: "Zadaná URL stránku je nesprávna"

View File

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

View File

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

View File

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

View File

@ -1802,9 +1802,6 @@ _pages:
newPage: "Tạo Trang mới" newPage: "Tạo Trang mới"
editPage: "Sửa Trang này" editPage: "Sửa Trang này"
readPage: "Xem mã nguồn 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" pageSetting: "Cài đặt trang"
nameAlreadyExists: "URL Trang đã tồn tại" nameAlreadyExists: "URL Trang đã tồn tại"
invalidNameTitle: "URL Trang không hợp lệ" invalidNameTitle: "URL Trang không hợp lệ"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -43,7 +43,7 @@ export type CaptchaSetting = {
siteKey: string | null; siteKey: string | null;
secretKey: string | null; secretKey: string | null;
} }
} };
export class CaptchaError extends Error { export class CaptchaError extends Error {
public readonly code: CaptchaErrorCode; public readonly code: CaptchaErrorCode;
@ -59,11 +59,11 @@ export class CaptchaError extends Error {
export type CaptchaSaveSuccess = { export type CaptchaSaveSuccess = {
success: true; success: true;
} };
export type CaptchaSaveFailure = { export type CaptchaSaveFailure = {
success: false; success: false;
error: CaptchaError; error: CaptchaError;
} };
export type CaptchaSaveResult = CaptchaSaveSuccess | CaptchaSaveFailure; export type CaptchaSaveResult = CaptchaSaveSuccess | CaptchaSaveFailure;
type CaptchaResponse = { 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 }`; ?? `${ this.meta.objectStorageUseSSL ? 'https' : 'http' }://${ this.meta.objectStorageEndpoint }${ this.meta.objectStoragePort ? `:${this.meta.objectStoragePort}` : '' }/${ this.meta.objectStorageBucket }`;
// for original // 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 }`; const url = `${ baseUrl }/${ key }`;
// for alts // for alts
@ -190,7 +191,7 @@ export class DriveService {
]; ];
if (alts.webpublic) { if (alts.webpublic) {
webpublicKey = `${this.meta.objectStoragePrefix}/webpublic-${randomUUID()}.${alts.webpublic.ext}`; webpublicKey = `${prefix}webpublic-${randomUUID()}.${alts.webpublic.ext}`;
webpublicUrl = `${ baseUrl }/${ webpublicKey }`; webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
this.registerLogger.info(`uploading webpublic: ${webpublicKey}`); this.registerLogger.info(`uploading webpublic: ${webpublicKey}`);
@ -198,7 +199,7 @@ export class DriveService {
} }
if (alts.thumbnail) { if (alts.thumbnail) {
thumbnailKey = `${this.meta.objectStoragePrefix}/thumbnail-${randomUUID()}.${alts.thumbnail.ext}`; thumbnailKey = `${prefix}thumbnail-${randomUUID()}.${alts.thumbnail.ext}`;
thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`; thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`;
this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`); this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`);

View File

@ -164,6 +164,13 @@ export class EmailService {
available: boolean; available: boolean;
reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned' | 'network' | 'blacklist'; 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({ const exist = await this.userProfilesRepository.countBy({
emailVerified: true, emailVerified: true,
email: emailAddress, email: emailAddress,

View File

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

View File

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

View File

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

View File

@ -492,7 +492,8 @@ export class MfmService {
appendChildren(nodes, body); 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 => {}); happyDOM.close().catch(err => {});

View File

@ -220,7 +220,7 @@ export class SearchService {
.leftJoinAndSelect('renote.user', 'renoteUser'); .leftJoinAndSelect('renote.user', 'renoteUser');
if (this.config.fulltextSearch?.provider === 'sqlPgroonga') { if (this.config.fulltextSearch?.provider === 'sqlPgroonga') {
query.andWhere('note.text &@ :q', { q }); query.andWhere('note.text &@~ :q', { q });
} else { } else {
query.andWhere('LOWER(note.text) LIKE :q', { q: `%${ sqlLikeEscape(q.toLowerCase()) }%` }); 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'; import type { OnApplicationShutdown } from '@nestjs/common';
export type UserWebhookPayload<T extends WebhookEventTypes> = export type UserWebhookPayload<T extends WebhookEventTypes> =
T extends 'note' | 'reply' | 'renote' |'mention' ? { T extends 'note' | 'reply' | 'renote' | 'mention' ? {
note: Packed<'Note'>, note: Packed<'Note'>,
} : } :
T extends 'follow' | 'unfollow' ? { T extends 'follow' | 'unfollow' ? {

View File

@ -38,6 +38,14 @@ export class UtilityService {
return this.punyHost(uri) === this.toPuny(this.config.host); 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 @bindThis
public isBlockedHost(blockedHosts: string[], host: string | null): boolean { public isBlockedHost(blockedHosts: string[], host: string | null): boolean {
if (host == null) return false; if (host == null) return false;

View File

@ -127,11 +127,11 @@ export class WebAuthnService {
const { registrationInfo } = verification; const { registrationInfo } = verification;
return { return {
credentialID: registrationInfo.credentialID, credentialID: registrationInfo.credential.id,
credentialPublicKey: registrationInfo.credentialPublicKey, credentialPublicKey: registrationInfo.credential.publicKey,
attestationObject: registrationInfo.attestationObject, attestationObject: registrationInfo.attestationObject,
fmt: registrationInfo.fmt, fmt: registrationInfo.fmt,
counter: registrationInfo.counter, counter: registrationInfo.credential.counter,
userVerified: registrationInfo.userVerified, userVerified: registrationInfo.userVerified,
credentialDeviceType: registrationInfo.credentialDeviceType, credentialDeviceType: registrationInfo.credentialDeviceType,
credentialBackedUp: registrationInfo.credentialBackedUp, credentialBackedUp: registrationInfo.credentialBackedUp,
@ -212,9 +212,9 @@ export class WebAuthnService {
expectedChallenge: challenge, expectedChallenge: challenge,
expectedOrigin: relyingParty.origin, expectedOrigin: relyingParty.origin,
expectedRPID: relyingParty.rpId, expectedRPID: relyingParty.rpId,
authenticator: { credential: {
credentialID: key.id, id: key.id,
credentialPublicKey: Buffer.from(key.publicKey, 'base64url'), publicKey: Buffer.from(key.publicKey, 'base64url'),
counter: key.counter, counter: key.counter,
transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined, transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined,
}, },
@ -292,9 +292,9 @@ export class WebAuthnService {
expectedChallenge: challenge, expectedChallenge: challenge,
expectedOrigin: relyingParty.origin, expectedOrigin: relyingParty.origin,
expectedRPID: relyingParty.rpId, expectedRPID: relyingParty.rpId,
authenticator: { credential: {
credentialID: key.id, id: key.id,
credentialPublicKey: Buffer.from(key.publicKey, 'base64url'), publicKey: Buffer.from(key.publicKey, 'base64url'),
counter: key.counter, counter: key.counter,
transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined, 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 { bindThis } from '@/decorators.js';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.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'; import type { IObject } from './type.js';
type Request = { type Request = {
@ -185,7 +185,7 @@ export class ApRequestService {
* @param url URL to fetch * @param url URL to fetch
*/ */
@bindThis @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 _followAlternate = followAlternate ?? true;
const keypair = await this.userKeypairService.getUserKeypair(user.id); const keypair = await this.userKeypairService.getUserKeypair(user.id);
@ -243,7 +243,7 @@ export class ApRequestService {
if (alternate) { if (alternate) {
const href = alternate.getAttribute('href'); const href = alternate.getAttribute('href');
if (href && this.utilityService.punyHost(url) === this.utilityService.punyHost(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) { } catch (e) {
@ -258,7 +258,7 @@ export class ApRequestService {
const finalUrl = res.url; // redirects may have been involved const finalUrl = res.url; // redirects may have been involved
const activity = await res.json() as IObject; const activity = await res.json() as IObject;
assertActivityMatchesUrls(activity, [finalUrl]); assertActivityMatchesUrls(url, activity, [finalUrl], allowSoftfail);
return activity; return activity;
} }

View File

@ -21,6 +21,7 @@ import { ApRendererService } from './ApRendererService.js';
import { ApRequestService } from './ApRequestService.js'; import { ApRequestService } from './ApRequestService.js';
import type { IObject, ICollection, IOrderedCollection } from './type.js'; import type { IObject, ICollection, IOrderedCollection } from './type.js';
import { IdentifiableError } from '@/misc/identifiable-error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js';
import { FetchAllowSoftFailMask } from './misc/check-against-url.js';
export class Resolver { export class Resolver {
private history: Set<string>; private history: Set<string>;
@ -72,7 +73,7 @@ export class Resolver {
} }
@bindThis @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') { if (typeof value !== 'string') {
return value; return value;
} }
@ -108,8 +109,8 @@ export class Resolver {
} }
const object = (this.user const object = (this.user
? await this.apRequestService.signedGet(value, this.user) as IObject ? await this.apRequestService.signedGet(value, this.user, allowSoftfail) as IObject
: await this.httpRequestService.getActivityJson(value)) as IObject; : await this.httpRequestService.getActivityJson(value, undefined, allowSoftfail)) as IObject;
if ( if (
Array.isArray(object['@context']) ? Array.isArray(object['@context']) ?
@ -118,19 +119,7 @@ export class Resolver {
) { ) {
throw new IdentifiableError('72180409-793c-4973-868e-5a118eb5519b', 'invalid response'); 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; return object;
} }

View File

@ -4,18 +4,124 @@
*/ */
import type { IObject } from '../type.js'; import type { IObject } from '../type.js';
export function assertActivityMatchesUrls(activity: IObject, urls: string[]) { export enum FetchAllowSoftFailMask {
const hosts = urls.map(it => new URL(it).host); // Allow no softfail flags
Strict = 0,
const idOk = activity.id !== undefined && hosts.includes(new URL(activity.id).host); // The values in tuple (requestUrl, finalUrl, objectId) are not all identical
//
// technically `activity.url` could be an `ApObject = IObject | // This condition is common for user-initiated lookups but should not be allowed in federation loop
// string | (IObject | string)[]`, but if it's a complicated thing //
// and the `activity.id` doesn't match, I think we're fine // Allow variations:
// rejecting the activity // good example: https://alice.example.com/@user -> https://alice.example.com/user/:userId
const urlOk = typeof(activity.url) === 'string' && hosts.includes(new URL(activity.url).host); // problematic example: https://alice.example.com/redirect?url=https://bad.example.com/ -> https://bad.example.com/ -> https://alice.example.com/somethingElse
NonCanonicalId = 1 << 0,
if (!idOk && !urlOk) { // Allow the final object to be at most one subdomain deeper than the request URL, similar to SPF relaxed alignment
throw new Error(`bad Activity: neither id(${activity?.id}) nor url(${activity?.url}) match location(${urls})`); //
} // 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(user: MiUser): user is MiLocalUser;
function isLocalUser<T extends { host: MiUser['host'] }>(user: T): user is (T & { host: null; }); function isLocalUser<T extends { host: MiUser['host'] }>(user: T): user is (T & { host: null; });
function isLocalUser(user: MiUser | { host: MiUser['host'] }): boolean { function isLocalUser(user: MiUser | { host: MiUser['host'] }): boolean {
return user.host == null; return user.host == null;
} }
function isRemoteUser(user: MiUser): user is MiRemoteUser; function isRemoteUser(user: MiUser): user is MiRemoteUser;
function isRemoteUser<T extends { host: MiUser['host'] }>(user: T): user is (T & { host: string; }); function isRemoteUser<T extends { host: MiUser['host'] }>(user: T): user is (T & { host: string; });
function isRemoteUser(user: MiUser | { host: MiUser['host'] }): boolean { function isRemoteUser(user: MiUser | { host: MiUser['host'] }): boolean {
return !isLocalUser(user); return !isLocalUser(user);
} }
@ -78,7 +80,7 @@ export type UserRelation = {
isBlocked: boolean isBlocked: boolean
isMuted: boolean isMuted: boolean
isRenoteMuted: boolean isRenoteMuted: boolean
} };
@Injectable() @Injectable()
export class UserEntityService implements OnModuleInit { export class UserEntityService implements OnModuleInit {

View File

@ -143,7 +143,7 @@ type OfSchema = {
readonly anyOf?: ReadonlyArray<Schema>; readonly anyOf?: ReadonlyArray<Schema>;
readonly oneOf?: ReadonlyArray<Schema>; readonly oneOf?: ReadonlyArray<Schema>;
readonly allOf?: ReadonlyArray<Schema>; readonly allOf?: ReadonlyArray<Schema>;
} };
export interface Schema extends OfSchema { export interface Schema extends OfSchema {
readonly type?: TypeStringef; readonly type?: TypeStringef;
@ -217,7 +217,7 @@ type ObjectSchemaTypeDef<p extends Schema> =
: :
p['anyOf'] extends ReadonlyArray<Schema> ? never : // see CONTRIBUTING.md p['anyOf'] extends ReadonlyArray<Schema> ? never : // see CONTRIBUTING.md
p['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> : p['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> :
any any;
type ObjectSchemaType<p extends Schema> = NullOrUndefined<p, ObjectSchemaTypeDef<p>>; 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 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 type JsonArray = JsonValue[];
export function isJsonObject(value: JsonValue | undefined): value is JsonObject { export function isJsonObject(value: JsonValue | undefined): value is JsonObject {

View File

@ -90,6 +90,10 @@ export type MiNotification = {
type: 'login'; type: 'login';
id: string; id: string;
createdAt: string; createdAt: string;
} | {
type: 'createToken';
id: string;
createdAt: string;
} | { } | {
type: 'app'; type: 'app';
id: string; 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 & { export type MiLocalUser = MiUser & {
host: null; host: null;
uri: null; uri: null;
} };
export type MiPartialLocalUser = Partial<MiUser> & { export type MiPartialLocalUser = Partial<MiUser> & {
id: MiUser['id']; id: MiUser['id'];
host: null; host: null;
uri: null; uri: null;
} };
export type MiRemoteUser = MiUser & { export type MiRemoteUser = MiUser & {
host: string; host: string;
uri: string; uri: string;
} };
export type MiPartialRemoteUser = Partial<MiUser> & { export type MiPartialRemoteUser = Partial<MiUser> & {
id: MiUser['id']; id: MiUser['id'];
host: string; host: string;
uri: string; uri: string;
} };
export const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const; export const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const;
export const passwordSchema = { type: 'string', minLength: 1 } as const; export const passwordSchema = { type: 'string', minLength: 1 } as const;

View File

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

View File

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

View File

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

View File

@ -107,12 +107,12 @@ export class InboxProcessorService implements OnApplicationShutdown {
// それでもわからなければ終了 // それでもわからなければ終了
if (authUser == null) { 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 がなくても終了 // publicKey がなくても終了
if (authUser.key == null) { 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の検証 // HTTP-Signatureの検証

View File

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

View File

@ -103,6 +103,43 @@ export class ServerService implements OnApplicationShutdown {
serve: false, 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.apiServerService.createServer, { prefix: '/api' });
fastify.register(this.openApiServerService.createServer); fastify.register(this.openApiServerService.createServer);
fastify.register(this.fileServerService.createServer); fastify.register(this.fileServerService.createServer);

View File

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

View File

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

View File

@ -20,6 +20,7 @@ import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { ApiError } from '../../error.js'; import { ApiError } from '../../error.js';
import { IdentifiableError } from '@/misc/identifiable-error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js';
import { FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
export const meta = { export const meta = {
tags: ['federation'], tags: ['federation'],
@ -53,11 +54,6 @@ export const meta = {
code: 'RESPONSE_INVALID', code: 'RESPONSE_INVALID',
id: '70193c39-54f3-4813-82f0-70a680f7495b', 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: { noSuchObject: {
message: 'No such object.', message: 'No such object.',
code: '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 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) { if (err instanceof IdentifiableError) {
switch (err.id) { switch (err.id) {
// resolve // resolve
@ -165,10 +162,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
case '09d79f9e-64f1-4316-9cfa-e75c4d091574': case '09d79f9e-64f1-4316-9cfa-e75c4d091574':
throw new ApiError(meta.errors.federationNotAllowed); throw new ApiError(meta.errors.federationNotAllowed);
case '72180409-793c-4973-868e-5a118eb5519b': case '72180409-793c-4973-868e-5a118eb5519b':
case 'ad2dc287-75c1-44c4-839d-3d2e64576675':
throw new ApiError(meta.errors.responseInvalid); throw new ApiError(meta.errors.responseInvalid);
case 'fd93c2fa-69a8-440f-880b-bf178e0ec877':
throw new ApiError(meta.errors.responseInvalidIdHostNotMatch);
// resolveLocal // resolveLocal
case '02b40cd0-fa92-4b0c-acc9-fb2ada952ab8': 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); 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 { Endpoint } from '@/server/api/endpoint-base.js';
import type { AccessTokensRepository } from '@/models/_.js'; import type { AccessTokensRepository } from '@/models/_.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { NotificationService } from '@/core/NotificationService.js';
import { secureRndstr } from '@/misc/secure-rndstr.js'; import { secureRndstr } from '@/misc/secure-rndstr.js';
import { DI } from '@/di-symbols.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 accessTokensRepository: AccessTokensRepository,
private idService: IdService, private idService: IdService,
private notificationService: NotificationService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
// Generate access token // Generate access token
@ -71,6 +73,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
permission: ps.permission, permission: ps.permission,
}); });
// アクセストークンが生成されたことを通知
this.notificationService.createNotification(me.id, 'createToken', {});
return { return {
token: accessToken, token: accessToken,
}; };

View File

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

View File

@ -210,9 +210,15 @@ export function genOpenapiSpec(config: Config, includeSelfRef = false) {
spec.paths['/' + endpoint.name] = { spec.paths['/' + endpoint.name] = {
...(endpoint.meta.allowGet ? { ...(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; this.connection = connection;
} }
public send(payload: { type: string, body: JsonValue }): void public send(payload: { type: string, body: JsonValue }): void;
public send(type: string, payload: JsonValue): void public send(type: string, payload: JsonValue): void;
@bindThis @bindThis
public send(typeOrPayload: { type: string, body: JsonValue } | string, payload?: JsonValue) { public send(typeOrPayload: { type: string, body: JsonValue } | string, payload?: JsonValue) {
const type = payload === undefined ? (typeOrPayload as { type: string, body: JsonValue }).type : (typeOrPayload as string); 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; requireCredential: T;
kind: T extends true ? string : string | null | undefined; kind: T extends true ? string : string | null | undefined;
create: (id: string, connection: Connection) => Channel; create: (id: string, connection: Connection) => Channel;
} };

View File

@ -114,13 +114,17 @@
if (document.readyState === 'loading') { if (document.readyState === 'loading') {
await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve)); 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> 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="message">${title}</div>
<div class="submessage">Failed to initialize Misskey</div>
<div class="submessage">Error Code: ${code}</div> <div class="submessage">Error Code: ${code}</div>
<button onclick="location.reload(!0)"> <button onclick="location.reload(!0)">
<div>リロード</div> <div>${reload}</div>
<div><small>Reload</small></div>
</button>`; </button>`;
addStyle(` addStyle(`
#misskey_app, #misskey_app,

View File

@ -151,6 +151,22 @@
await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve)); 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'); let errorsElement = document.getElementById('errors');
if (!errorsElement) { if (!errorsElement) {
@ -160,32 +176,32 @@
<path d="M12 9v2m0 4v.01"></path> <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> <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> </svg>
<h1>Failed to load<br>読み込みに失敗しました</h1> <h1>${messages.title}</h1>
<button class="button-big" onclick="location.reload(true);"> <button class="button-big" onclick="location.reload(true);">
<span class="button-label-big">Reload / リロード</span> <span class="button-label-big">${reload}</span>
</button> </button>
<p><b>The following actions may solve the problem. / 以下を行うと解決する可能性があります</b></p> <p><b>${messages.solution}</b></p>
<p>Update your os and browser / ブラウザおよびOSを最新バージョンに更新する</p> <p>${messages.solution1}</p>
<p>Disable an adblocker / アドブロッカーを無効にする</p> <p>${messages.solution2}</p>
<p>Clear the browser cache / ブラウザのキャッシュをクリアする</p> <p>${messages.solution3}</p>
<p>&#40;Tor Browser&#41; Set dom.webaudio.enabled to true / dom.webaudio.enabledをtrueに設定する</p> <p>${messages.solution4}</p>
<details style="color: #86b300;"> <details style="color: #86b300;">
<summary>Other options / その他のオプション</summary> <summary>${messages.otherOption}</summary>
<a href="/flush"> <a href="/flush">
<button class="button-small"> <button class="button-small">
<span class="button-label-small">Clear preferences and cache</span> <span class="button-label-small">${messages.otherOption1}</span>
</button> </button>
</a> </a>
<br> <br>
<a href="/cli"> <a href="/cli">
<button class="button-small"> <button class="button-small">
<span class="button-label-small">Start the simple client</span> <span class="button-label-small">${messages.otherOption2}</span>
</button> </button>
</a> </a>
<br> <br>
<a href="/bios"> <a href="/bios">
<button class="button-small"> <button class="button-small">
<span class="button-label-small">Start the repair tool</span> <span class="button-label-small">${messages.otherOption3}</span>
</button> </button>
</a> </a>
</details> </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, #misskey_app,
#splash { #splash {
display: none !important; display: none !important;
} }
body, body,
html { html {
background-color: #222; background-color: #222;
color: #dfddcc; color: #dfddcc;
justify-content: center; justify-content: center;
margin: auto; margin: auto;
padding: 10px; padding: 10px;
text-align: center; text-align: center;
} }
button { button {
border-radius: 999px; border-radius: 999px;
padding: 0px 12px 0px 12px; padding: 0px 12px 0px 12px;
border: none; border: none;
cursor: pointer; cursor: pointer;
margin-bottom: 12px; margin-bottom: 12px;
} }
.button-big { .button-big {
background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0)); background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0));
line-height: 50px; line-height: 50px;
} }
.button-big:hover { .button-big:hover {
background: rgb(153, 204, 0); background: rgb(153, 204, 0);
} }
.button-small { .button-small {
background: #444; background: #444;
line-height: 40px; line-height: 40px;
} }
.button-small:hover { .button-small:hover {
background: #555; background: #555;
} }
.button-label-big { .button-label-big {
color: #222; color: #222;
font-weight: bold; font-weight: bold;
font-size: 20px; font-size: 1.2em;
padding: 12px; padding: 12px;
} }
.button-label-small { .button-label-small {
color: rgb(153, 204, 0); color: rgb(153, 204, 0);
font-size: 16px; font-size: 16px;
padding: 12px; padding: 12px;
} }
a { a {
color: rgb(134, 179, 0); color: rgb(134, 179, 0);
text-decoration: none; text-decoration: none;
} }
p, p,
li { li {
font-size: 16px; font-size: 16px;
}
.dont-worry,
#msg {
font-size: 18px;
} }
.icon-warning { .icon-warning {
color: #dec340; color: #dec340;
height: 4rem; height: 4rem;
padding-top: 2rem; padding-top: 2rem;
} }
h1 { h1 {
font-size: 32px; font-size: 1.5em;
margin: 1em;
} }
code { code {
display: block; display: block;
font-family: Fira, FiraCode, monospace; font-family: Fira, FiraCode, monospace;
background: #333; background: #333;
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
max-width: 40rem; max-width: 40rem;
border-radius: 10px; border-radius: 10px;
justify-content: center; justify-content: center;
margin: auto; margin: auto;
white-space: pre-wrap; white-space: pre-wrap;
word-break: break-word; word-break: break-word;
} }
summary { #errorInfo summary {
cursor: pointer; cursor: pointer;
} }
summary > * { #errorInfo summary>* {
display: inline; display: inline;
white-space: pre-wrap;
} }
@media screen and (max-width: 500px) { @media screen and (max-width: 500px) {
details { #errorInfo {
width: 50%; 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! Thank you for using Misskey!
If you are reading this message... how about joining the development? If you are reading this message... how about joining the development?
https://github.com/misskey-dev/misskey https://github.com/misskey-dev/misskey
html html
@ -27,39 +27,45 @@ html
style style
include ../error.css include ../error.css
script
include ../error.js
body 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") 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(stroke="none", d="M0 0h24v24H0z", fill="none")
path(d="M12 9v2m0 4v.01") 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") 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();") 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(data-i18n="serverError") If reloading after a period of time does not resolve the problem, contact the server administrator with the following ERROR ID.
p If reloading after a period of time does not resolve the problem, contact the server administrator with the following ERROR ID.
div#errors div#errors
code. code.
ERROR CODE: #{code} ERROR CODE: #{code}
ERROR ID: #{id} 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(data-i18n="solution1") Update your os and browser
p Disable an adblocker. 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") details(style="color: #86b300;")
button.button-small summary(data-i18n="otherOption") Other options
span.button-label-small Clear preferences and cache a(href="/flush")
br button.button-small
a(href="/cli") span.button-label-small(data-i18n="otherOption1") Clear preferences and cache
button.button-small br
span.button-label-small Start the simple client a(href="/cli")
br button.button-small
a(href="/bios") span.button-label-small(data-i18n="otherOption2") Start the simple client
button.button-small br
span.button-label-small Start the repair tool a(href="/bios")
button.button-small
span.button-label-small(data-i18n="otherOption3") Start the repair tool

View File

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

View File

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

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