mirror of
https://github.com/misskey-dev/misskey.git
synced 2024-12-26 17:00:19 +01:00
Merge branch 'develop' into feature/default-hashtag-timeline
This commit is contained in:
commit
1b66da505e
401 changed files with 11621 additions and 7382 deletions
|
@ -1,5 +1,11 @@
|
|||
# misskey settings
|
||||
# MISSKEY_URL=https://example.tld/
|
||||
|
||||
# db settings
|
||||
POSTGRES_PASSWORD=example-misskey-pass
|
||||
# DATABASE_PASSWORD=${POSTGRES_PASSWORD}
|
||||
POSTGRES_USER=example-misskey-user
|
||||
# DATABASE_USER=${POSTGRES_USER}
|
||||
POSTGRES_DB=misskey
|
||||
# DATABASE_DB=${POSTGRES_DB}
|
||||
DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}"
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#───┘ URL └─────────────────────────────────────────────────────
|
||||
|
||||
# Final accessible URL seen by a user.
|
||||
# You can set url from an environment variable instead.
|
||||
url: https://example.tld/
|
||||
|
||||
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
|
||||
|
@ -38,9 +39,11 @@ db:
|
|||
port: 5432
|
||||
|
||||
# Database name
|
||||
# You can set db from an environment variable instead.
|
||||
db: misskey
|
||||
|
||||
# Auth
|
||||
# You can set user and pass from environment variables instead.
|
||||
user: example-misskey-user
|
||||
pass: example-misskey-pass
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Misskey",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"dockerComposeFile": "compose.yml",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspace",
|
||||
"features": {
|
||||
|
|
|
@ -7,12 +7,11 @@ Dockerfile
|
|||
build/
|
||||
built/
|
||||
db/
|
||||
docker-compose.yml
|
||||
.devcontainer/compose.yml
|
||||
node_modules/
|
||||
packages/*/node_modules
|
||||
redis/
|
||||
files/
|
||||
misskey-assets/
|
||||
fluent-emojis/
|
||||
.pnp.*
|
||||
|
||||
|
@ -28,4 +27,4 @@ fluent-emojis/
|
|||
|
||||
.idea/
|
||||
packages/*/.vscode/
|
||||
packages/backend/test/docker-compose.yml
|
||||
packages/backend/test/compose.yml
|
||||
|
|
8
.github/ISSUE_TEMPLATE/01_bug-report.yml
vendored
8
.github/ISSUE_TEMPLATE/01_bug-report.yml
vendored
|
@ -53,8 +53,8 @@ body:
|
|||
Examples:
|
||||
* Model and OS of the device(s): MacBook Pro (14inch, 2021), macOS Ventura 13.4
|
||||
* Browser: Chrome 113.0.5672.126
|
||||
* Server URL: misskey.io
|
||||
* Misskey: 13.x.x
|
||||
* Server URL: misskey.example.com
|
||||
* Misskey: 2024.x.x
|
||||
value: |
|
||||
* Model and OS of the device(s):
|
||||
* Browser:
|
||||
|
@ -74,11 +74,11 @@ body:
|
|||
|
||||
Examples:
|
||||
* Installation Method or Hosting Service: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment
|
||||
* Misskey: 13.x.x
|
||||
* Misskey: 2024.x.x
|
||||
* Node: 20.x.x
|
||||
* PostgreSQL: 15.x.x
|
||||
* Redis: 7.x.x
|
||||
* OS and Architecture: Ubuntu 22.04.2 LTS aarch64
|
||||
* OS and Architecture: Ubuntu 24.04.2 LTS aarch64
|
||||
value: |
|
||||
* Installation Method or Hosting Service:
|
||||
* Misskey:
|
||||
|
|
5
.github/workflows/api-misskey-js.yml
vendored
5
.github/workflows/api-misskey-js.yml
vendored
|
@ -4,10 +4,11 @@ on:
|
|||
push:
|
||||
paths:
|
||||
- packages/misskey-js/**
|
||||
- .github/workflows/api-misskey-js.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/misskey-js/**
|
||||
|
||||
- .github/workflows/api-misskey-js.yml
|
||||
jobs:
|
||||
report:
|
||||
|
||||
|
@ -20,7 +21,7 @@ jobs:
|
|||
- run: corepack enable
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4.0.2
|
||||
uses: actions/setup-node@v4.0.3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
|
2
.github/workflows/changelog-check.yml
vendored
2
.github/workflows/changelog-check.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
|||
- name: Checkout head
|
||||
uses: actions/checkout@v4.1.1
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4.0.2
|
||||
uses: actions/setup-node@v4.0.3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
|
||||
- name: setup node
|
||||
id: setup-node
|
||||
uses: actions/setup-node@v4.0.2
|
||||
uses: actions/setup-node@v4.0.3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: pnpm
|
||||
|
|
|
@ -6,12 +6,13 @@ on:
|
|||
paths:
|
||||
- packages/misskey-js/package.json
|
||||
- package.json
|
||||
- .github/workflows/check-misskey-js-version.yml
|
||||
pull_request:
|
||||
branches: [ develop ]
|
||||
paths:
|
||||
- packages/misskey-js/package.json
|
||||
- package.json
|
||||
|
||||
- .github/workflows/check-misskey-js-version.yml
|
||||
jobs:
|
||||
check-version:
|
||||
# ルートの package.json と packages/misskey-js/package.json のバージョンが一致しているかを確認する
|
||||
|
|
2
.github/workflows/docker-develop.yml
vendored
2
.github/workflows/docker-develop.yml
vendored
|
@ -37,7 +37,7 @@ jobs:
|
|||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
|
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
|
@ -48,7 +48,7 @@ jobs:
|
|||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Build and Push to Docker Hub
|
||||
id: build
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
|
2
.github/workflows/dockle.yml
vendored
2
.github/workflows/dockle.yml
vendored
|
@ -22,7 +22,7 @@ jobs:
|
|||
sudo dpkg -i dockle.deb
|
||||
- run: |
|
||||
cp .config/docker_example.env .config/docker.env
|
||||
cp ./docker-compose_example.yml ./docker-compose.yml
|
||||
cp ./compose_example.yml ./compose.yml
|
||||
- run: |
|
||||
docker compose up -d web
|
||||
docker tag "$(docker compose images web | awk 'OFS=":" {print $4}' | tail -n +2)" misskey-web:latest
|
||||
|
|
4
.github/workflows/get-api-diff.yml
vendored
4
.github/workflows/get-api-diff.yml
vendored
|
@ -9,7 +9,7 @@ on:
|
|||
paths:
|
||||
- packages/backend/**
|
||||
- .github/workflows/get-api-diff.yml
|
||||
|
||||
- .github/workflows/get-api-diff.yml
|
||||
jobs:
|
||||
get-from-misskey:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -34,7 +34,7 @@ jobs:
|
|||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.0.2
|
||||
uses: actions/setup-node@v4.0.3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
|
24
.github/workflows/lint.yml
vendored
24
.github/workflows/lint.yml
vendored
|
@ -10,15 +10,16 @@ on:
|
|||
- packages/frontend/**
|
||||
- packages/sw/**
|
||||
- packages/misskey-js/**
|
||||
- packages/shared/.eslintrc.js
|
||||
- packages/shared/eslint.config.js
|
||||
- .github/workflows/lint.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/backend/**
|
||||
- packages/frontend/**
|
||||
- packages/sw/**
|
||||
- packages/misskey-js/**
|
||||
- packages/shared/.eslintrc.js
|
||||
|
||||
- packages/shared/eslint.config.js
|
||||
- .github/workflows/lint.yml
|
||||
jobs:
|
||||
pnpm_install:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -28,7 +29,7 @@ jobs:
|
|||
fetch-depth: 0
|
||||
submodules: true
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4.0.2
|
||||
- uses: actions/setup-node@v4.0.3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
@ -39,6 +40,8 @@ jobs:
|
|||
needs: [pnpm_install]
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
env:
|
||||
eslint-cache-version: v1
|
||||
strategy:
|
||||
matrix:
|
||||
workspace:
|
||||
|
@ -52,13 +55,20 @@ jobs:
|
|||
fetch-depth: 0
|
||||
submodules: true
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4.0.2
|
||||
- uses: actions/setup-node@v4.0.3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
- run: corepack enable
|
||||
- run: pnpm i --frozen-lockfile
|
||||
- run: pnpm --filter ${{ matrix.workspace }} run eslint
|
||||
- name: Restore eslint cache
|
||||
uses: actions/cache@v4.0.2
|
||||
with:
|
||||
path: node_modules/.cache/eslint
|
||||
key: eslint-${{ env.eslint-cache-version }}-${{ hashFiles('/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
eslint-${{ env.eslint-cache-version }}-${{ hashFiles('/pnpm-lock.yaml') }}-
|
||||
- run: pnpm --filter ${{ matrix.workspace }} run eslint --cache --cache-location node_modules/.cache/eslint --cache-strategy content
|
||||
|
||||
typecheck:
|
||||
needs: [pnpm_install]
|
||||
|
@ -75,7 +85,7 @@ jobs:
|
|||
fetch-depth: 0
|
||||
submodules: true
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4.0.2
|
||||
- uses: actions/setup-node@v4.0.3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
|
5
.github/workflows/locale.yml
vendored
5
.github/workflows/locale.yml
vendored
|
@ -4,10 +4,11 @@ on:
|
|||
push:
|
||||
paths:
|
||||
- locales/**
|
||||
- .github/workflows/locale.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- locales/**
|
||||
|
||||
- .github/workflows/locale.yml
|
||||
jobs:
|
||||
locale_verify:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -18,7 +19,7 @@ jobs:
|
|||
fetch-depth: 0
|
||||
submodules: true
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4.0.2
|
||||
- uses: actions/setup-node@v4.0.3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
|
2
.github/workflows/on-release-created.yml
vendored
2
.github/workflows/on-release-created.yml
vendored
|
@ -26,7 +26,7 @@ jobs:
|
|||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.0.2
|
||||
uses: actions/setup-node@v4.0.3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
|
19
.github/workflows/release-edit-with-push.yml
vendored
19
.github/workflows/release-edit-with-push.yml
vendored
|
@ -3,10 +3,10 @@ name: "Release Manager: sync changelog with PR"
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- release/**
|
||||
- develop
|
||||
paths:
|
||||
- 'CHANGELOG.md'
|
||||
|
||||
# - .github/workflows/release-edit-with-push.yml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
@ -20,24 +20,29 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# headがrelease/かつopenのPRを1つ取得
|
||||
# headが$GITHUB_REF_NAME, baseが$STABLE_BRANCHかつopenのPRを1つ取得
|
||||
- name: Get PR
|
||||
run: |
|
||||
echo "pr_number=$(gh pr list --limit 1 --head "$GITHUB_REF_NAME" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT
|
||||
echo "pr_number=$(gh pr list --limit 1 --search "head:$GITHUB_REF_NAME base:$STABLE_BRANCH is:open" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT
|
||||
id: get_pr
|
||||
env:
|
||||
STABLE_BRANCH: ${{ vars.STABLE_BRANCH }}
|
||||
- name: Get target version
|
||||
uses: misskey-dev/release-manager-actions/.github/actions/get-target-version@v1
|
||||
if: steps.get_pr.outputs.pr_number != ''
|
||||
uses: misskey-dev/release-manager-actions/.github/actions/get-target-version@v2
|
||||
id: v
|
||||
# CHANGELOG.mdの内容を取得
|
||||
- name: Get changelog
|
||||
uses: misskey-dev/release-manager-actions/.github/actions/get-changelog@v1
|
||||
if: steps.get_pr.outputs.pr_number != ''
|
||||
uses: misskey-dev/release-manager-actions/.github/actions/get-changelog@v2
|
||||
with:
|
||||
version: ${{ steps.v.outputs.target_version }}
|
||||
id: changelog
|
||||
# PRのnotesを更新
|
||||
- name: Update PR
|
||||
if: steps.get_pr.outputs.pr_number != ''
|
||||
run: |
|
||||
gh pr edit "$PR_NUMBER" --body "$CHANGELOG"
|
||||
env:
|
||||
CHANGELOG: ${{ steps.changelog.outputs.changelog }}
|
||||
PR_NUMBER: ${{ steps.get_pr.outputs.pr_number }}
|
||||
CHANGELOG: ${{ steps.changelog.outputs.changelog }}
|
||||
|
|
20
.github/workflows/release-with-dispatch.yml
vendored
20
.github/workflows/release-with-dispatch.yml
vendored
|
@ -33,18 +33,21 @@ jobs:
|
|||
pr_number: ${{ steps.get_pr.outputs.pr_number }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# headがrelease/かつopenのPRを1つ取得
|
||||
# headが$GITHUB_REF_NAME, baseが$STABLE_BRANCHかつopenのPRを1つ取得
|
||||
- name: Get PRs
|
||||
run: |
|
||||
echo "pr_number=$(gh pr list --limit 1 --search "head:release/ is:open" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT
|
||||
echo "pr_number=$(gh pr list --limit 1 --search "head:$GITHUB_REF_NAME base:$STABLE_BRANCH is:open" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT
|
||||
id: get_pr
|
||||
env:
|
||||
STABLE_BRANCH: ${{ vars.STABLE_BRANCH }}
|
||||
|
||||
merge:
|
||||
uses: misskey-dev/release-manager-actions/.github/workflows/merge.yml@v1
|
||||
uses: misskey-dev/release-manager-actions/.github/workflows/merge.yml@v2
|
||||
needs: get-pr
|
||||
if: ${{ needs.get-pr.outputs.pr_number != '' && inputs.merge == true }}
|
||||
with:
|
||||
pr_number: ${{ needs.get-pr.outputs.pr_number }}
|
||||
user: 'github-actions[bot]'
|
||||
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
|
||||
# Text to prepend to the changelog
|
||||
# The first line must be `## Unreleased`
|
||||
|
@ -65,15 +68,14 @@ jobs:
|
|||
secrets:
|
||||
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
|
||||
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
||||
RULESET_EDIT_APP_ID: ${{ secrets.RULESET_EDIT_APP_ID }}
|
||||
RULESET_EDIT_APP_PRIVATE_KEY: ${{ secrets.RULESET_EDIT_APP_PRIVATE_KEY }}
|
||||
|
||||
create-prerelease:
|
||||
uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v1
|
||||
uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v2
|
||||
needs: get-pr
|
||||
if: ${{ needs.get-pr.outputs.pr_number != '' && inputs.merge != true }}
|
||||
with:
|
||||
pr_number: ${{ needs.get-pr.outputs.pr_number }}
|
||||
user: 'github-actions[bot]'
|
||||
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
|
||||
use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }}
|
||||
indent: ${{ vars.INDENT }}
|
||||
|
@ -82,10 +84,11 @@ jobs:
|
|||
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
||||
|
||||
create-target:
|
||||
uses: misskey-dev/release-manager-actions/.github/workflows/create-target.yml@v1
|
||||
uses: misskey-dev/release-manager-actions/.github/workflows/create-target.yml@v2
|
||||
needs: get-pr
|
||||
if: ${{ needs.get-pr.outputs.pr_number == '' }}
|
||||
with:
|
||||
user: 'github-actions[bot]'
|
||||
# The script for version increment.
|
||||
# process.env.CURRENT_VERSION: The current version.
|
||||
#
|
||||
|
@ -118,8 +121,7 @@ jobs:
|
|||
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
|
||||
use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }}
|
||||
indent: ${{ vars.INDENT }}
|
||||
stable_branch: ${{ vars.STABLE_BRANCH }}
|
||||
secrets:
|
||||
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
|
||||
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
||||
RULESET_EDIT_APP_ID: ${{ secrets.RULESET_EDIT_APP_ID }}
|
||||
RULESET_EDIT_APP_PRIVATE_KEY: ${{ secrets.RULESET_EDIT_APP_PRIVATE_KEY }}
|
||||
|
|
13
.github/workflows/release-with-ready.yml
vendored
13
.github/workflows/release-with-ready.yml
vendored
|
@ -16,23 +16,26 @@ jobs:
|
|||
check:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
ref: ${{ steps.get_pr.outputs.ref }}
|
||||
head: ${{ steps.get_pr.outputs.head }}
|
||||
base: ${{ steps.get_pr.outputs.base }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# PR情報を取得
|
||||
- name: Get PR
|
||||
run: |
|
||||
pr_json=$(gh pr view "$PR_NUMBER" --json isDraft,headRefName)
|
||||
echo "ref=$(echo $pr_json | jq -r '.headRefName')" >> $GITHUB_OUTPUT
|
||||
pr_json=$(gh pr view "$PR_NUMBER" --json isDraft,headRefName,baseRefName)
|
||||
echo "head=$(echo $pr_json | jq -r '.headRefName')" >> $GITHUB_OUTPUT
|
||||
echo "base=$(echo $pr_json | jq -r '.baseRefName')" >> $GITHUB_OUTPUT
|
||||
id: get_pr
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
release:
|
||||
uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v1
|
||||
uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v2
|
||||
needs: check
|
||||
if: startsWith(needs.check.outputs.ref, 'release/')
|
||||
if: needs.check.outputs.head == github.event.repository.default_branch && needs.check.outputs.base == vars.STABLE_BRANCH
|
||||
with:
|
||||
pr_number: ${{ github.event.pull_request.number }}
|
||||
user: 'github-actions[bot]'
|
||||
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
|
||||
use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }}
|
||||
indent: ${{ vars.INDENT }}
|
||||
|
|
4
.github/workflows/storybook.yml
vendored
4
.github/workflows/storybook.yml
vendored
|
@ -36,7 +36,7 @@ jobs:
|
|||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
- name: Use Node.js 20.x
|
||||
uses: actions/setup-node@v4.0.2
|
||||
uses: actions/setup-node@v4.0.3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
@ -88,7 +88,7 @@ jobs:
|
|||
if [ "$BRANCH" = "misskey-dev:$HEAD_REF" ]; then
|
||||
BRANCH="$HEAD_REF"
|
||||
fi
|
||||
pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static --branch-name $BRANCH $(echo "$CHROMATIC_PARAMETER")
|
||||
pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static --branch-name "$BRANCH" $(echo "$CHROMATIC_PARAMETER")
|
||||
env:
|
||||
HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
||||
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
|
|
7
.github/workflows/test-backend.yml
vendored
7
.github/workflows/test-backend.yml
vendored
|
@ -9,12 +9,13 @@ on:
|
|||
- packages/backend/**
|
||||
# for permissions
|
||||
- packages/misskey-js/**
|
||||
- .github/workflows/test-backend.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/backend/**
|
||||
# for permissions
|
||||
- packages/misskey-js/**
|
||||
|
||||
- .github/workflows/test-backend.yml
|
||||
jobs:
|
||||
unit:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -45,7 +46,7 @@ jobs:
|
|||
- name: Install FFmpeg
|
||||
uses: FedericoCarboni/setup-ffmpeg@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.0.2
|
||||
uses: actions/setup-node@v4.0.3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
@ -92,7 +93,7 @@ jobs:
|
|||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.0.2
|
||||
uses: actions/setup-node@v4.0.3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
|
8
.github/workflows/test-frontend.yml
vendored
8
.github/workflows/test-frontend.yml
vendored
|
@ -11,7 +11,7 @@ on:
|
|||
- packages/misskey-js/**
|
||||
# for e2e
|
||||
- packages/backend/**
|
||||
|
||||
- .github/workflows/test-frontend.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/frontend/**
|
||||
|
@ -19,7 +19,7 @@ on:
|
|||
- packages/misskey-js/**
|
||||
# for e2e
|
||||
- packages/backend/**
|
||||
|
||||
- .github/workflows/test-frontend.yml
|
||||
jobs:
|
||||
vitest:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -35,7 +35,7 @@ jobs:
|
|||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.0.2
|
||||
uses: actions/setup-node@v4.0.3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
@ -90,7 +90,7 @@ jobs:
|
|||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.0.2
|
||||
uses: actions/setup-node@v4.0.3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
|
5
.github/workflows/test-misskey-js.yml
vendored
5
.github/workflows/test-misskey-js.yml
vendored
|
@ -8,11 +8,12 @@ on:
|
|||
branches: [ develop ]
|
||||
paths:
|
||||
- packages/misskey-js/**
|
||||
- .github/workflows/test-misskey-js.yml
|
||||
pull_request:
|
||||
branches: [ develop ]
|
||||
paths:
|
||||
- packages/misskey-js/**
|
||||
|
||||
- .github/workflows/test-misskey-js.yml
|
||||
jobs:
|
||||
test:
|
||||
|
||||
|
@ -30,7 +31,7 @@ jobs:
|
|||
- run: corepack enable
|
||||
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.0.2
|
||||
uses: actions/setup-node@v4.0.3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
|
2
.github/workflows/test-production.yml
vendored
2
.github/workflows/test-production.yml
vendored
|
@ -25,7 +25,7 @@ jobs:
|
|||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.0.2
|
||||
uses: actions/setup-node@v4.0.3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
|
5
.github/workflows/validate-api-json.yml
vendored
5
.github/workflows/validate-api-json.yml
vendored
|
@ -7,10 +7,11 @@ on:
|
|||
- develop
|
||||
paths:
|
||||
- packages/backend/**
|
||||
- .github/workflows/validate-api-json.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/backend/**
|
||||
|
||||
- .github/workflows/validate-api-json.yml
|
||||
jobs:
|
||||
validate-api-json:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -26,7 +27,7 @@ jobs:
|
|||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.0.2
|
||||
uses: actions/setup-node@v4.0.3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -35,8 +35,8 @@ coverage
|
|||
!/.config/example.yml
|
||||
!/.config/docker_example.yml
|
||||
!/.config/docker_example.env
|
||||
docker-compose.yml
|
||||
!/.devcontainer/docker-compose.yml
|
||||
.devcontainer/compose.yml
|
||||
!/.devcontainer/compose.yml
|
||||
|
||||
# misskey
|
||||
/build
|
||||
|
@ -59,6 +59,7 @@ ormconfig.json
|
|||
temp
|
||||
/packages/frontend/src/**/*.stories.ts
|
||||
tsdoc-metadata.json
|
||||
misskey-assets
|
||||
|
||||
# blender backups
|
||||
*.blend1
|
||||
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,6 +1,3 @@
|
|||
[submodule "misskey-assets"]
|
||||
path = misskey-assets
|
||||
url = https://github.com/misskey-dev/assets.git
|
||||
[submodule "fluent-emojis"]
|
||||
path = fluent-emojis
|
||||
url = https://github.com/misskey-dev/emojis.git
|
||||
|
|
75
CHANGELOG.md
75
CHANGELOG.md
|
@ -1,16 +1,87 @@
|
|||
## Unreleased
|
||||
## 2024.7.0
|
||||
|
||||
### Note
|
||||
- デッキUIの新着ノートをサウンドで通知する機能の追加(v2024.5.0)に伴い、以前から動作しなくなっていたクライアント設定内の「アンテナ受信」「チャンネル通知」サウンドを削除しました。
|
||||
- Streaming APIにて入力が不正な場合にはそのメッセージを無視するようになりました。 #14251
|
||||
|
||||
### General
|
||||
- Feat: 通報を受けた際、または解決した際に、予め登録した宛先に通知を飛ばせるように(mail or webhook) #13705
|
||||
- Feat: ユーザーのアイコン/バナーの変更可否をロールで設定可能に
|
||||
- 変更不可となっていても、設定済みのものを解除してデフォルト画像に戻すことは出来ます
|
||||
- Fix: 配信停止したインスタンス一覧が見れなくなる問題を修正
|
||||
- Fix: Dockerコンテナの立ち上げ時に`pnpm`のインストールで固まることがある問題
|
||||
- Fix: デフォルトテーマに無効なテーマコードを入力するとUIが使用できなくなる問題を修正
|
||||
|
||||
### Client
|
||||
- Enhance: 内蔵APIドキュメントのデザイン・パフォーマンスを改善
|
||||
- Enhance: 非ログイン時に他サーバーに遷移するアクションを追加
|
||||
- Enhance: 非ログイン時のハイライトTLのデザインを改善
|
||||
- Enhance: フロントエンドのアクセシビリティ改善
|
||||
(Based on https://github.com/taiyme/misskey/pull/226)
|
||||
- Enhance: サーバー情報ページ・お問い合わせページを改善
|
||||
(Cherry-picked from https://github.com/taiyme/misskey/pull/238)
|
||||
- Enhance: AiScriptを0.19.0にアップデート
|
||||
- Enhance: Allow negative delay for MFM animation elements (`tada`, `jelly`, `twitch`, `shake`, `spin`, `jump`, `bounce`, `rainbow`)
|
||||
- Enhance: センシティブなメディアを開く際に確認ダイアログを出せるように
|
||||
- Fix: `/about#federation` ページなどで各インスタンスのチャートが表示されなくなっていた問題を修正
|
||||
- Fix: ユーザーページの追加情報のラベルを投稿者のサーバーの絵文字で表示する (#13968)
|
||||
- Fix: リバーシの対局を正しく共有できないことがある問題を修正
|
||||
- Fix: コントロールパネルでベースロールのポリシーを編集してもUI上では変更が反映されない問題を修正
|
||||
- Fix: アンテナの編集画面のボタンに隙間を追加
|
||||
- Fix: テーマプレビューが見れない問題を修正
|
||||
- Fix: ショートカットキーが連打できる問題を修正
|
||||
(Cherry-picked from https://github.com/taiyme/misskey/pull/234)
|
||||
- Fix: MkSignin.vueのcredentialRequestからReactivityを削除(ProxyがPasskey認証処理に渡ることを避けるため)
|
||||
- Fix: 「アニメーション画像を再生しない」がオンのときでもサーバーのバナー画像・背景画像がアニメーションしてしまう問題を修正
|
||||
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/574)
|
||||
- Fix: Twitchの埋め込みが開けない問題を修正
|
||||
- Fix: 子メニューの高さがウィンドウからはみ出ることがある問題を修正
|
||||
- Fix: 個人宛てのダイアログ形式のお知らせが即時表示されない問題を修正
|
||||
- Fix: 一部の画像がセンシティブ指定されているときに画面に何も表示されないことがあるのを修正
|
||||
- Fix: リアクションしたユーザー一覧のユーザー名がはみ出る問題を修正
|
||||
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/672)
|
||||
- Fix: `/share`ページにおいて絵文字ピッカーを開くことができない問題を修正
|
||||
|
||||
### Server
|
||||
- チャート生成時にinstance.suspentionStateに置き換えられたinstance.isSuspendedが参照されてしまう問題を修正
|
||||
- Feat: レートリミット制限に引っかかったときに`Retry-After`ヘッダーを返すように (#13949)
|
||||
- Enhance: エンドポイント`clips/update`の必須項目を`clipId`のみに
|
||||
- Enhance: エンドポイント`admin/roles/update`の必須項目を`roleId`のみに
|
||||
- Enhance: エンドポイント`pages/update`の必須項目を`pageId`のみに
|
||||
- Enhance: エンドポイント`gallery/posts/update`の必須項目を`postId`のみに
|
||||
- Enhance: エンドポイント`i/webhook/update`の必須項目を`webhookId`のみに
|
||||
- Enhance: エンドポイント`admin/ad/update`の必須項目を`id`のみに
|
||||
- Enhance: `default.yml`内の`url`, `db.db`, `db.user`, `db.pass`を環境変数から読み込めるように
|
||||
- Fix: チャート生成時にinstance.suspensionStateに置き換えられたinstance.isSuspendedが参照されてしまう問題を修正
|
||||
- Fix: ユーザーのフィードページのMFMをHTMLに展開するように (#14006)
|
||||
- Fix: アンテナ・クリップ・リスト・ウェブフックがロールポリシーの上限より一つ多く作れてしまうのを修正 (#14036)
|
||||
- Fix: notRespondingSinceが実装される前に不通になったインスタンスが自動的に配信停止にならない (#14059)
|
||||
- Fix: FTT有効時、タイムライン用エンドポイントで`sinceId`にキャッシュ内最古のものより古いものを指定した場合に正しく結果が返ってこない問題を修正
|
||||
- Fix: 自分以外のクリップ内のノート個数が見えることがあるのを修正
|
||||
- Fix: 空文字列のリアクションはフォールバックされるように
|
||||
- Fix: リノートにリアクションできないように
|
||||
- Fix: ユーザー名の前後に空白文字列がある場合は省略するように
|
||||
- Fix: プロフィール編集時に名前を空白文字列のみにできる問題を修正
|
||||
- Fix: ユーザ名のサジェスト時に表示される内容と順番を調整(以下の順番になります) #14149
|
||||
1. フォロー中かつアクティブなユーザ
|
||||
2. フォロー中かつ非アクティブなユーザ
|
||||
3. フォローしていないアクティブなユーザ
|
||||
4. フォローしていない非アクティブなユーザ
|
||||
|
||||
また、自分自身のアカウントもサジェストされるようになりました。
|
||||
- Fix: 一般ユーザーから見たユーザーのバッジの一覧に公開されていないものが含まれることがある問題を修正
|
||||
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/652)
|
||||
- Fix: ユーザーのリアクション一覧でミュート/ブロックが機能していなかった問題を修正
|
||||
- Fix: FTT有効時にリモートユーザーのノートがHTLにキャッシュされる問題を修正
|
||||
- Fix: 一部の通知がローカル上のリモートユーザーに対して行われていた問題を修正
|
||||
- Fix: エラーメッセージの誤字を修正 (#14213)
|
||||
- Fix: ソーシャルタイムラインにローカルタイムラインに表示される自分へのリプライが表示されない問題を修正
|
||||
- Fix: リノートのミュートが適用されるまでに時間がかかることがある問題を修正
|
||||
(Cherry-picked from https://github.com/Type4ny-Project/Type4ny/commit/e9601029b52e0ad43d9131b555b614e56c84ebc1)
|
||||
- Fix: Steaming APIが不正なデータを受けた場合の動作が不安定である問題 #14251
|
||||
|
||||
### Misskey.js
|
||||
- Feat: `/drive/files/create` のリクエストに対応(`multipart/form-data`に対応)
|
||||
- Feat: `/admin/role/create` のロールポリシーの型を修正
|
||||
|
||||
## 2024.5.0
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Contribution guide
|
||||
We're glad you're interested in contributing Misskey! In this document you will find the information you need to contribute to the project.
|
||||
|
||||
> **Note**
|
||||
> [!NOTE]
|
||||
> This project uses Japanese as its major language, **but you do not need to translate and write the Issues/PRs in Japanese.**
|
||||
> Also, you might receive comments on your Issue/PR in Japanese, but you do not need to reply to them in Japanese as well.\
|
||||
> The accuracy of machine translation into Japanese is not high, so it will be easier for us to understand if you write it in the original language.
|
||||
|
@ -17,16 +17,31 @@ Before creating an issue, please check the following:
|
|||
- Issues should only be used to feature requests, suggestions, and bug tracking.
|
||||
- Please ask questions or troubleshooting in [GitHub Discussions](https://github.com/misskey-dev/misskey/discussions) or [Discord](https://discord.gg/Wp8gVStHW3).
|
||||
|
||||
> **Warning**
|
||||
> [!WARNING]
|
||||
> Do not close issues that are about to be resolved. It should remain open until a commit that actually resolves it is merged.
|
||||
|
||||
## Before implementation
|
||||
### Recommended discussing before implementation
|
||||
We welcome your proposal.
|
||||
|
||||
When you want to add a feature or fix a bug, **first have the design and policy reviewed in an Issue** (if it is not there, please make one). Without this step, there is a high possibility that the PR will not be merged even if it is implemented.
|
||||
|
||||
At this point, you also need to clarify the goals of the PR you will create, and make sure that the other members of the team are aware of them.
|
||||
PRs that do not have a clear set of do's and don'ts tend to be bloated and difficult to review.
|
||||
|
||||
Also, when you start implementation, assign yourself to the Issue (if you cannot do it yourself, ask another member to assign you). By expressing your intention to work the Issue, you can prevent conflicts in the work.
|
||||
Also, when you start implementation, assign yourself to the Issue (if you cannot do it yourself, ask Committer to assign you).
|
||||
By expressing your intention to work on the Issue, you can prevent conflicts in the work.
|
||||
|
||||
To the Committers: you should not assign someone on it before the Final Decision.
|
||||
|
||||
### How issues are triaged
|
||||
|
||||
The Committers may:
|
||||
* close an issue that is not reproducible on latest stable release,
|
||||
* merge an issue into another issue,
|
||||
* split an issue into multiple issues,
|
||||
* or re-open that has been closed for some reason which is not applicable anymore.
|
||||
|
||||
@syuilo reserves the Final Decision rights including whether the project will implement feature and how to implement, these rights are not always exercised.
|
||||
|
||||
## Well-known branches
|
||||
- **`master`** branch is tracking the latest release and used for production purposes.
|
||||
|
@ -77,7 +92,7 @@ An actual domain will be assigned so you can test the federation.
|
|||
|
||||
## Release
|
||||
### Release Instructions
|
||||
1. Commit version changes in the `develop` branch ([package.json](https://github.com/misskey-dev/misskey/blob/develop/package.json))
|
||||
1. Commit version changes in the `develop` branch ([package.json](package.json))
|
||||
2. Create a release PR.
|
||||
- Into `master` from `develop` branch.
|
||||
- The title must be in the format `Release: x.y.z`.
|
||||
|
@ -88,7 +103,7 @@ An actual domain will be assigned so you can test the federation.
|
|||
- The target branch must be `master`
|
||||
- The tag name must be the version
|
||||
|
||||
> **Note**
|
||||
> [!NOTE]
|
||||
> Why this instruction is necessary:
|
||||
> - To perform final QA checks
|
||||
> - To distribute responsibility
|
||||
|
@ -106,12 +121,42 @@ If your language is not listed in Crowdin, please open an issue.
|
|||
![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg)
|
||||
|
||||
## Development
|
||||
During development, it is useful to use the
|
||||
### Setup
|
||||
Before developing, you have to set up environment. Misskey requires Redis, PostgreSQL, and FFmpeg.
|
||||
|
||||
You would want to install Meilisearch to experiment related features. Technically, meilisearch is not strict requirement, but some features and tests require it.
|
||||
|
||||
There are a few ways to proceed.
|
||||
|
||||
#### Use system-wide software
|
||||
You could install them in system-wide (such as from package manager).
|
||||
|
||||
#### Use `docker compose`
|
||||
You could obtain middleware container by typing `docker compose -f $PROJECT_ROOT/compose.local-db.yml up -d`.
|
||||
|
||||
#### Use Devcontainer
|
||||
Devcontainer also has necessary setting. This method can be done by connecting from VSCode.
|
||||
|
||||
Instead of running `pnpm` locally, you can use Dev Container to set up your development environment.
|
||||
To use Dev Container, open the project directory on VSCode with Dev Containers installed.
|
||||
**Note:** If you are using Windows, please clone the repository with WSL. Using Git for Windows will result in broken files due to the difference in how newlines are handled.
|
||||
|
||||
It will run the following command automatically inside the container.
|
||||
``` bash
|
||||
git submodule update --init
|
||||
pnpm install --frozen-lockfile
|
||||
cp .devcontainer/devcontainer.yml .config/default.yml
|
||||
pnpm build
|
||||
pnpm migrate
|
||||
```
|
||||
|
||||
After finishing the migration, you can proceed.
|
||||
|
||||
### Start developing
|
||||
During development, it is useful to use the
|
||||
```
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
command.
|
||||
|
||||
- Server-side source files and automatically builds them if they are modified. Automatically start the server process(es).
|
||||
|
@ -135,26 +180,6 @@ MK_DEV_PREFER=backend pnpm dev
|
|||
- To change the port of Vite, specify with `VITE_PORT` environment variable.
|
||||
- HMR may not work in some environments such as Windows.
|
||||
|
||||
### Dev Container
|
||||
Instead of running `pnpm` locally, you can use Dev Container to set up your development environment.
|
||||
To use Dev Container, open the project directory on VSCode with Dev Containers installed.
|
||||
**Note:** If you are using Windows, please clone the repository with WSL. Using Git for Windows will result in broken files due to the difference in how newlines are handled.
|
||||
|
||||
It will run the following command automatically inside the container.
|
||||
``` bash
|
||||
git submodule update --init
|
||||
pnpm install --frozen-lockfile
|
||||
cp .devcontainer/devcontainer.yml .config/default.yml
|
||||
pnpm build
|
||||
pnpm migrate
|
||||
```
|
||||
|
||||
After finishing the migration, run the `pnpm dev` command to start the development server.
|
||||
|
||||
``` bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Testing
|
||||
- Test codes are located in [`/packages/backend/test`](/packages/backend/test).
|
||||
|
||||
|
@ -165,7 +190,7 @@ cp .github/misskey/test.yml .config/
|
|||
```
|
||||
Prepare DB/Redis for testing.
|
||||
```
|
||||
docker compose -f packages/backend/test/docker-compose.yml up
|
||||
docker compose -f packages/backend/test/compose.yml up
|
||||
```
|
||||
Alternatively, prepare an empty (data can be erased) DB and edit `.config/test.yml`.
|
||||
|
||||
|
@ -204,7 +229,7 @@ niraxは、Misskeyで使用しているオリジナルのフロントエンド
|
|||
### ルート定義
|
||||
ルート定義は、以下の形式のオブジェクトの配列です。
|
||||
|
||||
``` ts
|
||||
```ts
|
||||
{
|
||||
name?: string;
|
||||
path: string;
|
||||
|
@ -217,7 +242,7 @@ niraxは、Misskeyで使用しているオリジナルのフロントエンド
|
|||
}
|
||||
```
|
||||
|
||||
> **Warning**
|
||||
> [!WARNING]
|
||||
> 現状、ルートは定義された順に評価されます。
|
||||
> たとえば、`/foo/:id`ルート定義の次に`/foo/bar`ルート定義がされていた場合、後者がマッチすることはありません。
|
||||
|
||||
|
@ -279,7 +304,7 @@ export const Default = {
|
|||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies StoryObj<typeof MkAvatar>;
|
||||
} satisfies StoryObj<typeof MyComponent>;
|
||||
```
|
||||
|
||||
If you want to opt-out from the automatic generation, create a `MyComponent.stories.impl.ts` file and add the following line to the file.
|
||||
|
@ -517,7 +542,7 @@ https://github.com/misskey-dev/misskey/pull/10082
|
|||
|
||||
テキストhogeおよびfugaについて、片方を必須としつつ両方の指定もありうる場合:
|
||||
|
||||
```
|
||||
```ts
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
|
|
@ -82,6 +82,10 @@ RUN apt-get update \
|
|||
USER misskey
|
||||
WORKDIR /misskey
|
||||
|
||||
# add package.json to add pnpm
|
||||
COPY --chown=misskey:misskey ./package.json ./package.json
|
||||
RUN corepack install
|
||||
|
||||
COPY --chown=misskey:misskey --from=target-builder /misskey/node_modules ./node_modules
|
||||
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/backend/node_modules ./packages/backend/node_modules
|
||||
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-js/node_modules ./packages/misskey-js/node_modules
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
version: "3"
|
||||
|
||||
# このconfigは、 dockerでMisskey本体を起動せず、 redisとpostgresql などだけを起動します
|
||||
|
||||
services:
|
|
@ -1,5 +1,3 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
|
@ -19,6 +17,8 @@ services:
|
|||
networks:
|
||||
- internal_network
|
||||
- external_network
|
||||
# env_file:
|
||||
# - .config/docker.env
|
||||
volumes:
|
||||
- ./files:/misskey/files
|
||||
- ./.config:/misskey/.config:ro
|
54
locales/index.d.ts
vendored
54
locales/index.d.ts
vendored
|
@ -736,6 +736,22 @@ export interface Locale extends ILocale {
|
|||
* リモートで表示
|
||||
*/
|
||||
"showOnRemote": string;
|
||||
/**
|
||||
* リモートで続行
|
||||
*/
|
||||
"continueOnRemote": string;
|
||||
/**
|
||||
* Misskey Hubからサーバーを選択
|
||||
*/
|
||||
"chooseServerOnMisskeyHub": string;
|
||||
/**
|
||||
* サーバーのドメインを直接指定
|
||||
*/
|
||||
"specifyServerHost": string;
|
||||
/**
|
||||
* ドメインを入力してください
|
||||
*/
|
||||
"inputHostName": string;
|
||||
/**
|
||||
* 全般
|
||||
*/
|
||||
|
@ -1921,9 +1937,13 @@ export interface Locale extends ILocale {
|
|||
*/
|
||||
"onlyOneFileCanBeAttached": string;
|
||||
/**
|
||||
* 続行する前に、サインアップまたはサインインが必要です
|
||||
* 続行する前に、登録またはログインが必要です
|
||||
*/
|
||||
"signinRequired": string;
|
||||
/**
|
||||
* 続行するには、お使いのサーバーに移動するか、このサーバーに登録・ログインする必要があります
|
||||
*/
|
||||
"signinOrContinueOnRemote": string;
|
||||
/**
|
||||
* 招待
|
||||
*/
|
||||
|
@ -4984,6 +5004,18 @@ export interface Locale extends ILocale {
|
|||
* お問い合わせ
|
||||
*/
|
||||
"inquiry": string;
|
||||
/**
|
||||
* もう一度お試しください。
|
||||
*/
|
||||
"tryAgain": string;
|
||||
/**
|
||||
* センシティブなメディアを表示するとき確認する
|
||||
*/
|
||||
"confirmWhenRevealingSensitiveMedia": string;
|
||||
/**
|
||||
* センシティブなメディアです。表示しますか?
|
||||
*/
|
||||
"sensitiveMediaRevealConfirm": string;
|
||||
"_delivery": {
|
||||
/**
|
||||
* 配信状態
|
||||
|
@ -6594,6 +6626,10 @@ export interface Locale extends ILocale {
|
|||
* ファイルにNSFWを常に付与
|
||||
*/
|
||||
"alwaysMarkNsfw": string;
|
||||
/**
|
||||
* アイコンとバナーの更新を許可
|
||||
*/
|
||||
"canUpdateBioMedia": string;
|
||||
/**
|
||||
* ノートのピン留めの最大数
|
||||
*/
|
||||
|
@ -7515,14 +7551,6 @@ export interface Locale extends ILocale {
|
|||
* 通知
|
||||
*/
|
||||
"notification": string;
|
||||
/**
|
||||
* アンテナ受信
|
||||
*/
|
||||
"antenna": string;
|
||||
/**
|
||||
* チャンネル通知
|
||||
*/
|
||||
"channel": string;
|
||||
/**
|
||||
* リアクション選択時
|
||||
*/
|
||||
|
@ -9761,7 +9789,7 @@ export interface Locale extends ILocale {
|
|||
"_dataSaver": {
|
||||
"_media": {
|
||||
/**
|
||||
* メディアの読み込み
|
||||
* メディアの読み込みを無効化
|
||||
*/
|
||||
"title": string;
|
||||
/**
|
||||
|
@ -9771,7 +9799,7 @@ export interface Locale extends ILocale {
|
|||
};
|
||||
"_avatar": {
|
||||
/**
|
||||
* アイコン画像
|
||||
* アイコン画像のアニメーションを無効化
|
||||
*/
|
||||
"title": string;
|
||||
/**
|
||||
|
@ -9781,7 +9809,7 @@ export interface Locale extends ILocale {
|
|||
};
|
||||
"_urlPreview": {
|
||||
/**
|
||||
* URLプレビューのサムネイル
|
||||
* URLプレビューのサムネイルを非表示
|
||||
*/
|
||||
"title": string;
|
||||
/**
|
||||
|
@ -9791,7 +9819,7 @@ export interface Locale extends ILocale {
|
|||
};
|
||||
"_code": {
|
||||
/**
|
||||
* コードハイライト
|
||||
* コードハイライトを非表示
|
||||
*/
|
||||
"title": string;
|
||||
/**
|
||||
|
|
|
@ -52,7 +52,11 @@ const primaries = {
|
|||
const clean = (text) => text.replace(new RegExp(String.fromCodePoint(0x08), 'g'), '');
|
||||
|
||||
export function build() {
|
||||
const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(new URL(`${c}.yml`, import.meta.url), 'utf-8'))) || {}, a), {});
|
||||
// vitestの挙動を調整するため、一度ローカル変数化する必要がある
|
||||
// https://github.com/vitest-dev/vitest/issues/3988#issuecomment-1686599577
|
||||
// https://github.com/misskey-dev/misskey/pull/14057#issuecomment-2192833785
|
||||
const metaUrl = import.meta.url;
|
||||
const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(new URL(`${c}.yml`, metaUrl), 'utf-8'))) || {}, a), {});
|
||||
|
||||
// 空文字列が入ることがあり、フォールバックが動作しなくなるのでプロパティごと消す
|
||||
const removeEmpty = (obj) => {
|
||||
|
|
|
@ -180,6 +180,10 @@ addAccount: "アカウントを追加"
|
|||
reloadAccountsList: "アカウントリストの情報を更新"
|
||||
loginFailed: "ログインに失敗しました"
|
||||
showOnRemote: "リモートで表示"
|
||||
continueOnRemote: "リモートで続行"
|
||||
chooseServerOnMisskeyHub: "Misskey Hubからサーバーを選択"
|
||||
specifyServerHost: "サーバーのドメインを直接指定"
|
||||
inputHostName: "ドメインを入力してください"
|
||||
general: "全般"
|
||||
wallpaper: "壁紙"
|
||||
setWallpaper: "壁紙を設定"
|
||||
|
@ -476,7 +480,8 @@ attachAsFileQuestion: "クリップボードのテキストが長いです。テ
|
|||
noMessagesYet: "まだチャットはありません"
|
||||
newMessageExists: "新しいメッセージがあります"
|
||||
onlyOneFileCanBeAttached: "メッセージに添付できるファイルはひとつです"
|
||||
signinRequired: "続行する前に、サインアップまたはサインインが必要です"
|
||||
signinRequired: "続行する前に、登録またはログインが必要です"
|
||||
signinOrContinueOnRemote: "続行するには、お使いのサーバーに移動するか、このサーバーに登録・ログインする必要があります"
|
||||
invitations: "招待"
|
||||
invitationCode: "招待コード"
|
||||
checking: "確認しています"
|
||||
|
@ -1242,6 +1247,9 @@ keepOriginalFilenameDescription: "この設定をオフにすると、アップ
|
|||
noDescription: "説明文はありません"
|
||||
alwaysConfirmFollow: "フォローの際常に確認する"
|
||||
inquiry: "お問い合わせ"
|
||||
tryAgain: "もう一度お試しください。"
|
||||
confirmWhenRevealingSensitiveMedia: "センシティブなメディアを表示するとき確認する"
|
||||
sensitiveMediaRevealConfirm: "センシティブなメディアです。表示しますか?"
|
||||
|
||||
_delivery:
|
||||
status: "配信状態"
|
||||
|
@ -1705,6 +1713,7 @@ _role:
|
|||
canManageAvatarDecorations: "アバターデコレーションの管理"
|
||||
driveCapacity: "ドライブ容量"
|
||||
alwaysMarkNsfw: "ファイルにNSFWを常に付与"
|
||||
canUpdateBioMedia: "アイコンとバナーの更新を許可"
|
||||
pinMax: "ノートのピン留めの最大数"
|
||||
antennaMax: "アンテナの作成可能数"
|
||||
wordMuteMax: "ワードミュートの最大文字数"
|
||||
|
@ -1971,8 +1980,6 @@ _sfx:
|
|||
note: "ノート"
|
||||
noteMy: "ノート(自分)"
|
||||
notification: "通知"
|
||||
antenna: "アンテナ受信"
|
||||
channel: "チャンネル通知"
|
||||
reaction: "リアクション選択時"
|
||||
|
||||
_soundSettings:
|
||||
|
@ -2599,16 +2606,16 @@ _externalResourceInstaller:
|
|||
|
||||
_dataSaver:
|
||||
_media:
|
||||
title: "メディアの読み込み"
|
||||
title: "メディアの読み込みを無効化"
|
||||
description: "画像・動画が自動で読み込まれるのを防止します。隠れている画像・動画はタップすると読み込まれます。"
|
||||
_avatar:
|
||||
title: "アイコン画像"
|
||||
title: "アイコン画像のアニメーションを無効化"
|
||||
description: "アイコン画像のアニメーションが停止します。アニメーション画像は通常の画像よりファイルサイズが大きいことがあるので、データ通信量をさらに削減できます。"
|
||||
_urlPreview:
|
||||
title: "URLプレビューのサムネイル"
|
||||
title: "URLプレビューのサムネイルを非表示"
|
||||
description: "URLプレビューのサムネイル画像が読み込まれなくなります。"
|
||||
_code:
|
||||
title: "コードハイライト"
|
||||
title: "コードハイライトを非表示"
|
||||
description: "MFMなどでコードハイライト記法が使われている場合、タップするまで読み込まれなくなります。コードハイライトではハイライトする言語ごとにその定義ファイルを読み込む必要がありますが、それらが自動で読み込まれなくなるため、通信量の削減が見込めます。"
|
||||
|
||||
_hemisphere:
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 0179793ec891856d6f37a3be16ba4c22f67a81b5
|
26
package.json
26
package.json
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "misskey",
|
||||
"version": "2024.5.0",
|
||||
"version": "2024.7.0-beta.2",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/misskey-dev/misskey.git"
|
||||
},
|
||||
"packageManager": "pnpm@9.0.6",
|
||||
"packageManager": "pnpm@9.5.0",
|
||||
"workspaces": [
|
||||
"packages/frontend",
|
||||
"packages/backend",
|
||||
|
@ -21,7 +21,7 @@
|
|||
"build-assets": "node ./scripts/build-assets.mjs",
|
||||
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
|
||||
"build-storybook": "pnpm --filter frontend build-storybook",
|
||||
"build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
|
||||
"build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
|
||||
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
|
||||
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
||||
"init": "pnpm migrate",
|
||||
|
@ -55,20 +55,22 @@
|
|||
"js-yaml": "4.1.0",
|
||||
"postcss": "8.4.38",
|
||||
"tar": "6.2.1",
|
||||
"terser": "5.30.3",
|
||||
"typescript": "5.4.5",
|
||||
"esbuild": "0.20.2",
|
||||
"terser": "5.31.1",
|
||||
"typescript": "5.5.3",
|
||||
"esbuild": "0.22.0",
|
||||
"glob": "10.3.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.12.7",
|
||||
"@typescript-eslint/eslint-plugin": "7.7.1",
|
||||
"@typescript-eslint/parser": "7.7.1",
|
||||
"@misskey-dev/eslint-plugin": "2.0.2",
|
||||
"@types/node": "20.14.9",
|
||||
"@typescript-eslint/eslint-plugin": "7.15.0",
|
||||
"@typescript-eslint/parser": "7.15.0",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "13.7.3",
|
||||
"eslint": "8.57.0",
|
||||
"cypress": "13.13.0",
|
||||
"eslint": "9.6.0",
|
||||
"globals": "15.7.0",
|
||||
"ncp": "2.0.0",
|
||||
"start-server-and-test": "2.0.3"
|
||||
"start-server-and-test": "2.0.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tensorflow/tfjs-core": "4.4.0"
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
node_modules
|
||||
/built
|
||||
/.eslintrc.js
|
||||
/@types/**/*
|
|
@ -1,32 +0,0 @@
|
|||
module.exports = {
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json', './test/tsconfig.json'],
|
||||
},
|
||||
extends: [
|
||||
'../shared/.eslintrc.js',
|
||||
],
|
||||
rules: {
|
||||
'import/order': ['warn', {
|
||||
'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
|
||||
'pathGroups': [
|
||||
{
|
||||
'pattern': '@/**',
|
||||
'group': 'external',
|
||||
'position': 'after'
|
||||
}
|
||||
],
|
||||
}],
|
||||
'no-restricted-globals': [
|
||||
'error',
|
||||
{
|
||||
'name': '__dirname',
|
||||
'message': 'Not in ESModule. Use `import.meta.url` instead.'
|
||||
},
|
||||
{
|
||||
'name': '__filename',
|
||||
'message': 'Not in ESModule. Use `import.meta.url` instead.'
|
||||
}
|
||||
]
|
||||
},
|
||||
};
|
20
packages/backend/assets/api-doc.html
Normal file
20
packages/backend/assets/api-doc.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Misskey API</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script
|
||||
id="api-reference"
|
||||
data-url="/api.json"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,24 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Misskey API</title>
|
||||
<!-- needed for adaptive design -->
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||
|
||||
<!--
|
||||
ReDoc doesn't change outer page styles
|
||||
-->
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<redoc spec-url="/api.json" expand-responses="200" expand-single-schema-field="true"></redoc>
|
||||
<script src="https://cdn.redoc.ly/redoc/v2.1.3/bundles/redoc.standalone.js" integrity="sha256-u4DgqzYXoArvNF/Ymw3puKexfOC6lYfw0sfmeliBJ1I=" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
46
packages/backend/eslint.config.js
Normal file
46
packages/backend/eslint.config.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
import tsParser from '@typescript-eslint/parser';
|
||||
import sharedConfig from '../shared/eslint.config.js';
|
||||
|
||||
export default [
|
||||
...sharedConfig,
|
||||
{
|
||||
ignores: ['**/node_modules', 'built', '@types/**/*', 'migration'],
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: tsParser,
|
||||
project: ['./tsconfig.json', './test/tsconfig.json'],
|
||||
sourceType: 'module',
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'import/order': ['warn', {
|
||||
groups: [
|
||||
'builtin',
|
||||
'external',
|
||||
'internal',
|
||||
'parent',
|
||||
'sibling',
|
||||
'index',
|
||||
'object',
|
||||
'type',
|
||||
],
|
||||
pathGroups: [{
|
||||
pattern: '@/**',
|
||||
group: 'external',
|
||||
position: 'after',
|
||||
}],
|
||||
}],
|
||||
'no-restricted-globals': ['error', {
|
||||
name: '__dirname',
|
||||
message: 'Not in ESModule. Use `import.meta.url` instead.',
|
||||
}, {
|
||||
name: '__filename',
|
||||
message: 'Not in ESModule. Use `import.meta.url` instead.',
|
||||
}],
|
||||
},
|
||||
},
|
||||
];
|
|
@ -31,7 +31,7 @@
|
|||
"test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e",
|
||||
"test-and-coverage": "pnpm jest-and-coverage",
|
||||
"test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e",
|
||||
"generate-api-json": "pnpm build && node ./scripts/generate_api_json.js"
|
||||
"generate-api-json": "node ./scripts/generate_api_json.js"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@swc/core-android-arm64": "1.3.11",
|
||||
|
@ -65,43 +65,43 @@
|
|||
"utf-8-validate": "6.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.412.0",
|
||||
"@aws-sdk/lib-storage": "3.412.0",
|
||||
"@bull-board/api": "5.17.0",
|
||||
"@bull-board/fastify": "5.17.0",
|
||||
"@bull-board/ui": "5.17.0",
|
||||
"@aws-sdk/client-s3": "3.600.0",
|
||||
"@aws-sdk/lib-storage": "3.600.0",
|
||||
"@bull-board/api": "5.20.5",
|
||||
"@bull-board/fastify": "5.20.5",
|
||||
"@bull-board/ui": "5.20.5",
|
||||
"@discordapp/twemoji": "15.0.3",
|
||||
"@fastify/accepts": "4.3.0",
|
||||
"@fastify/cookie": "9.3.1",
|
||||
"@fastify/cors": "9.0.1",
|
||||
"@fastify/express": "3.0.0",
|
||||
"@fastify/http-proxy": "9.5.0",
|
||||
"@fastify/multipart": "8.2.0",
|
||||
"@fastify/static": "7.0.3",
|
||||
"@fastify/multipart": "8.3.0",
|
||||
"@fastify/static": "7.0.4",
|
||||
"@fastify/view": "9.1.0",
|
||||
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
||||
"@misskey-dev/summaly": "5.1.0",
|
||||
"@napi-rs/canvas": "^0.1.52",
|
||||
"@nestjs/common": "10.3.8",
|
||||
"@nestjs/core": "10.3.8",
|
||||
"@nestjs/testing": "10.3.8",
|
||||
"@napi-rs/canvas": "^0.1.53",
|
||||
"@nestjs/common": "10.3.10",
|
||||
"@nestjs/core": "10.3.10",
|
||||
"@nestjs/testing": "10.3.10",
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@sentry/node": "^8.5.0",
|
||||
"@sentry/profiling-node": "^8.5.0",
|
||||
"@sentry/node": "8.13.0",
|
||||
"@sentry/profiling-node": "8.13.0",
|
||||
"@simplewebauthn/server": "10.0.0",
|
||||
"@sinonjs/fake-timers": "11.2.2",
|
||||
"@smithy/node-http-handler": "2.5.0",
|
||||
"@swc/cli": "0.3.12",
|
||||
"@swc/core": "1.4.17",
|
||||
"@swc/core": "1.6.6",
|
||||
"@twemoji/parser": "15.1.1",
|
||||
"accepts": "1.3.8",
|
||||
"ajv": "8.13.0",
|
||||
"ajv": "8.16.0",
|
||||
"archiver": "7.0.1",
|
||||
"async-mutex": "0.5.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"blurhash": "2.0.5",
|
||||
"body-parser": "1.20.2",
|
||||
"bullmq": "5.7.8",
|
||||
"bullmq": "5.8.3",
|
||||
"cacheable-lookup": "7.0.0",
|
||||
"cbor": "9.0.2",
|
||||
"chalk": "5.3.0",
|
||||
|
@ -112,27 +112,27 @@
|
|||
"content-disposition": "0.5.4",
|
||||
"date-fns": "2.30.0",
|
||||
"deep-email-validator": "0.1.21",
|
||||
"fastify": "4.26.2",
|
||||
"fastify": "4.28.1",
|
||||
"fastify-raw-body": "4.3.0",
|
||||
"feed": "4.2.2",
|
||||
"file-type": "19.0.0",
|
||||
"fluent-ffmpeg": "2.1.2",
|
||||
"fluent-ffmpeg": "2.1.3",
|
||||
"form-data": "4.0.0",
|
||||
"got": "14.2.1",
|
||||
"got": "14.4.1",
|
||||
"happy-dom": "10.0.3",
|
||||
"hpagent": "1.2.0",
|
||||
"htmlescape": "1.1.1",
|
||||
"http-link-header": "1.1.3",
|
||||
"ioredis": "5.4.1",
|
||||
"ip-cidr": "3.1.0",
|
||||
"ip-cidr": "4.0.1",
|
||||
"ipaddr.js": "2.2.0",
|
||||
"is-svg": "5.0.0",
|
||||
"is-svg": "5.0.1",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsdom": "24.0.0",
|
||||
"jsdom": "24.1.0",
|
||||
"json5": "2.2.3",
|
||||
"jsonld": "8.3.2",
|
||||
"jsrsasign": "11.1.0",
|
||||
"meilisearch": "0.38.0",
|
||||
"meilisearch": "0.41.0",
|
||||
"mfm-js": "0.24.0",
|
||||
"microformats-parser": "2.0.2",
|
||||
"mime-types": "2.1.35",
|
||||
|
@ -142,24 +142,24 @@
|
|||
"nanoid": "5.0.7",
|
||||
"nested-property": "4.0.0",
|
||||
"node-fetch": "3.3.2",
|
||||
"nodemailer": "6.9.13",
|
||||
"nodemailer": "6.9.14",
|
||||
"nsfwjs": "2.4.2",
|
||||
"oauth": "0.10.0",
|
||||
"oauth2orize": "1.12.0",
|
||||
"oauth2orize-pkce": "0.1.2",
|
||||
"os-utils": "0.0.14",
|
||||
"otpauth": "9.2.3",
|
||||
"otpauth": "9.3.1",
|
||||
"parse5": "7.1.2",
|
||||
"pg": "8.11.5",
|
||||
"pg": "8.12.0",
|
||||
"pkce-challenge": "4.1.0",
|
||||
"probe-image-size": "7.2.3",
|
||||
"promise-limit": "2.7.0",
|
||||
"pug": "3.0.2",
|
||||
"pug": "3.0.3",
|
||||
"punycode": "2.3.1",
|
||||
"qrcode": "1.5.3",
|
||||
"random-seed": "0.3.0",
|
||||
"ratelimiter": "3.4.1",
|
||||
"re2": "1.21.2",
|
||||
"re2": "1.21.3",
|
||||
"redis-lock": "0.1.4",
|
||||
"reflect-metadata": "0.2.2",
|
||||
"rename": "1.0.4",
|
||||
|
@ -167,27 +167,26 @@
|
|||
"rxjs": "7.8.1",
|
||||
"sanitize-html": "2.13.0",
|
||||
"secure-json-parse": "2.7.0",
|
||||
"sharp": "0.33.3",
|
||||
"sharp": "0.33.4",
|
||||
"slacc": "0.0.10",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"systeminformation": "5.22.7",
|
||||
"systeminformation": "5.22.11",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tmp": "0.2.3",
|
||||
"tsc-alias": "1.8.8",
|
||||
"tsc-alias": "1.8.10",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typeorm": "0.3.20",
|
||||
"typescript": "5.4.5",
|
||||
"typescript": "5.5.3",
|
||||
"ulid": "2.3.0",
|
||||
"vary": "1.1.2",
|
||||
"web-push": "3.6.7",
|
||||
"ws": "8.17.0",
|
||||
"ws": "8.17.1",
|
||||
"xev": "3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.7.0",
|
||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||
"@nestjs/platform-express": "10.3.8",
|
||||
"@nestjs/platform-express": "10.3.10",
|
||||
"@simplewebauthn/types": "10.0.0",
|
||||
"@swc/jest": "0.2.36",
|
||||
"@types/accepts": "1.3.7",
|
||||
|
@ -197,22 +196,21 @@
|
|||
"@types/color-convert": "2.0.3",
|
||||
"@types/content-disposition": "0.5.8",
|
||||
"@types/fluent-ffmpeg": "2.1.24",
|
||||
"@types/htmlescape": "^1.1.3",
|
||||
"@types/http-link-header": "1.0.5",
|
||||
"@types/htmlescape": "1.1.3",
|
||||
"@types/http-link-header": "1.0.7",
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/jsdom": "21.1.6",
|
||||
"@types/jsonld": "1.5.13",
|
||||
"@types/jsdom": "21.1.7",
|
||||
"@types/jsonld": "1.5.14",
|
||||
"@types/jsrsasign": "10.5.14",
|
||||
"@types/mime-types": "2.1.4",
|
||||
"@types/ms": "0.7.34",
|
||||
"@types/node": "20.12.7",
|
||||
"@types/node-fetch": "3.0.3",
|
||||
"@types/node": "20.14.9",
|
||||
"@types/nodemailer": "6.4.15",
|
||||
"@types/oauth": "0.9.4",
|
||||
"@types/oauth": "0.9.5",
|
||||
"@types/oauth2orize": "1.11.5",
|
||||
"@types/oauth2orize-pkce": "0.1.2",
|
||||
"@types/pg": "8.11.5",
|
||||
"@types/pg": "8.11.6",
|
||||
"@types/pug": "2.0.10",
|
||||
"@types/punycode": "2.1.4",
|
||||
"@types/qrcode": "1.5.5",
|
||||
|
@ -228,18 +226,17 @@
|
|||
"@types/vary": "1.1.3",
|
||||
"@types/web-push": "3.6.3",
|
||||
"@types/ws": "8.5.10",
|
||||
"@typescript-eslint/eslint-plugin": "7.7.1",
|
||||
"@typescript-eslint/parser": "7.7.1",
|
||||
"aws-sdk-client-mock": "3.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "7.15.0",
|
||||
"@typescript-eslint/parser": "7.15.0",
|
||||
"aws-sdk-client-mock": "4.0.1",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"execa": "8.0.1",
|
||||
"fkill": "^9.0.0",
|
||||
"execa": "9.2.0",
|
||||
"fkill": "9.0.0",
|
||||
"jest": "29.7.0",
|
||||
"jest-mock": "29.7.0",
|
||||
"nodemon": "3.1.0",
|
||||
"nodemon": "3.1.4",
|
||||
"pid-port": "1.0.0",
|
||||
"simple-oauth2": "5.0.0"
|
||||
"simple-oauth2": "5.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ function execStart() {
|
|||
|
||||
async function killProc() {
|
||||
if (backendProcess) {
|
||||
backendProcess.catch(() => {}); // backendProcess.kill()によって発生する例外を無視するためにcatch()を呼び出す
|
||||
backendProcess.kill();
|
||||
await new Promise(resolve => backendProcess.on('exit', resolve));
|
||||
backendProcess = undefined;
|
||||
|
@ -46,6 +47,7 @@ async function killProc() {
|
|||
],
|
||||
{
|
||||
stdio: [process.stdin, process.stdout, process.stderr, 'ipc'],
|
||||
serialization: "json",
|
||||
})
|
||||
.on('message', async (message) => {
|
||||
if (message.type === 'exit') {
|
||||
|
|
|
@ -3,11 +3,34 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { loadConfig } from '../built/config.js'
|
||||
import { genOpenapiSpec } from '../built/server/api/openapi/gen-spec.js'
|
||||
import { writeFileSync } from "node:fs";
|
||||
import { execa } from 'execa';
|
||||
import { writeFileSync, existsSync } from "node:fs";
|
||||
|
||||
const config = loadConfig();
|
||||
const spec = genOpenapiSpec(config, true);
|
||||
async function main() {
|
||||
if (!process.argv.includes('--no-build')) {
|
||||
await execa('pnpm', ['run', 'build'], {
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
}
|
||||
|
||||
writeFileSync('./built/api.json', JSON.stringify(spec), 'utf-8');
|
||||
if (!existsSync('./built')) {
|
||||
throw new Error('`built` directory does not exist.');
|
||||
}
|
||||
|
||||
/** @type {import('../src/config.js')} */
|
||||
const { loadConfig } = await import('../built/config.js');
|
||||
|
||||
/** @type {import('../src/server/api/openapi/gen-spec.js')} */
|
||||
const { genOpenapiSpec } = await import('../built/server/api/openapi/gen-spec.js');
|
||||
|
||||
const config = loadConfig();
|
||||
const spec = genOpenapiSpec(config, true);
|
||||
|
||||
writeFileSync('./built/api.json', JSON.stringify(spec), 'utf-8');
|
||||
}
|
||||
|
||||
main().catch(e => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
|
@ -23,7 +23,7 @@ type RedisOptionsSource = Partial<RedisOptions> & {
|
|||
* 設定ファイルの型
|
||||
*/
|
||||
type Source = {
|
||||
url: string;
|
||||
url?: string;
|
||||
port?: number;
|
||||
socket?: string;
|
||||
chmodSocket?: string;
|
||||
|
@ -31,9 +31,9 @@ type Source = {
|
|||
db: {
|
||||
host: string;
|
||||
port: number;
|
||||
db: string;
|
||||
user: string;
|
||||
pass: string;
|
||||
db?: string;
|
||||
user?: string;
|
||||
pass?: string;
|
||||
disableCache?: boolean;
|
||||
extra?: { [x: string]: string };
|
||||
};
|
||||
|
@ -210,13 +210,17 @@ export function loadConfig(): Config {
|
|||
: { 'src/_boot_.ts': { file: 'src/_boot_.ts' } };
|
||||
const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source;
|
||||
|
||||
const url = tryCreateUrl(config.url);
|
||||
const url = tryCreateUrl(config.url ?? process.env.MISSKEY_URL ?? '');
|
||||
const version = meta.version;
|
||||
const host = url.host;
|
||||
const hostname = url.hostname;
|
||||
const scheme = url.protocol.replace(/:$/, '');
|
||||
const wsScheme = scheme.replace('http', 'ws');
|
||||
|
||||
const dbDb = config.db.db ?? process.env.DATABASE_DB ?? '';
|
||||
const dbUser = config.db.user ?? process.env.DATABASE_USER ?? '';
|
||||
const dbPass = config.db.pass ?? process.env.DATABASE_PASSWORD ?? '';
|
||||
|
||||
const externalMediaProxy = config.mediaProxy ?
|
||||
config.mediaProxy.endsWith('/') ? config.mediaProxy.substring(0, config.mediaProxy.length - 1) : config.mediaProxy
|
||||
: null;
|
||||
|
@ -239,7 +243,7 @@ export function loadConfig(): Config {
|
|||
apiUrl: `${scheme}://${host}/api`,
|
||||
authUrl: `${scheme}://${host}/auth`,
|
||||
driveUrl: `${scheme}://${host}/files`,
|
||||
db: config.db,
|
||||
db: { ...config.db, db: dbDb, user: dbUser, pass: dbPass },
|
||||
dbReplications: config.dbReplications,
|
||||
dbSlaves: config.dbSlaves,
|
||||
meilisearch: config.meilisearch,
|
||||
|
@ -267,7 +271,7 @@ export function loadConfig(): Config {
|
|||
deliverJobMaxAttempts: config.deliverJobMaxAttempts,
|
||||
inboxJobMaxAttempts: config.inboxJobMaxAttempts,
|
||||
proxyRemoteFiles: config.proxyRemoteFiles,
|
||||
signToActivityPubGet: config.signToActivityPubGet,
|
||||
signToActivityPubGet: config.signToActivityPubGet ?? true,
|
||||
mediaProxy: externalMediaProxy ?? internalMediaProxy,
|
||||
externalMediaProxyEnabled: externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy,
|
||||
videoThumbnailGenerator: config.videoThumbnailGenerator ?
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
// dummy
|
||||
export const MAX_NOTE_TEXT_LENGTH = 3000;
|
||||
|
||||
export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min
|
||||
|
|
|
@ -10,7 +10,6 @@ import sanitizeHtml from 'sanitize-html';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { GlobalEvents, GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import type {
|
||||
AbuseReportNotificationRecipientRepository,
|
||||
MiAbuseReportNotificationRecipient,
|
||||
|
@ -91,7 +90,7 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
|
|||
const recipientEMailAddresses = await this.fetchEMailRecipients().then(it => it
|
||||
.filter(it => it.isActive && it.userProfile?.emailVerified)
|
||||
.map(it => it.userProfile?.email)
|
||||
.filter(isNotNull),
|
||||
.filter(x => x != null),
|
||||
);
|
||||
|
||||
// 送信先の鮮度を保つため、毎回取得する
|
||||
|
@ -138,7 +137,7 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
|
|||
.then(it => it
|
||||
.filter(it => it.isActive && it.systemWebhookId && it.method === 'webhook')
|
||||
.map(it => it.systemWebhookId)
|
||||
.filter(isNotNull));
|
||||
.filter(x => x != null));
|
||||
for (const webhookId of recipientWebhookIds) {
|
||||
await Promise.all(
|
||||
abuseReports.map(it => {
|
||||
|
@ -340,7 +339,7 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
|
|||
@bindThis
|
||||
private async removeUnauthorizedRecipientUsers(recipients: MiAbuseReportNotificationRecipient[]): Promise<MiAbuseReportNotificationRecipient[]> {
|
||||
const userRecipients = recipients.filter(it => it.userId !== null);
|
||||
const recipientUserIds = new Set(userRecipients.map(it => it.userId).filter(isNotNull));
|
||||
const recipientUserIds = new Set(userRecipients.map(it => it.userId).filter(x => x != null));
|
||||
if (recipientUserIds.size <= 0) {
|
||||
// ユーザが通知先として設定されていない場合、この関数での処理を行うべきレコードが無い
|
||||
return recipients;
|
||||
|
|
|
@ -41,7 +41,7 @@ export class ClipService {
|
|||
const currentCount = await this.clipsRepository.countBy({
|
||||
userId: me.id,
|
||||
});
|
||||
if (currentCount > (await this.roleService.getUserPolicies(me.id)).clipLimit) {
|
||||
if (currentCount >= (await this.roleService.getUserPolicies(me.id)).clipLimit) {
|
||||
throw new ClipService.TooManyClipsError();
|
||||
}
|
||||
|
||||
|
@ -102,7 +102,7 @@ export class ClipService {
|
|||
const currentCount = await this.clipNotesRepository.countBy({
|
||||
clipId: clip.id,
|
||||
});
|
||||
if (currentCount > (await this.roleService.getUserPolicies(me.id)).noteEachClipsLimit) {
|
||||
if (currentCount >= (await this.roleService.getUserPolicies(me.id)).noteEachClipsLimit) {
|
||||
throw new ClipService.TooManyClipNotesError();
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
} from '@/core/entities/AbuseReportNotificationRecipientEntityService.js';
|
||||
import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
|
||||
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
||||
import { UserSearchService } from '@/core/UserSearchService.js';
|
||||
import { AccountMoveService } from './AccountMoveService.js';
|
||||
import { AccountUpdateService } from './AccountUpdateService.js';
|
||||
import { AiService } from './AiService.js';
|
||||
|
@ -60,6 +61,7 @@ import { UserFollowingService } from './UserFollowingService.js';
|
|||
import { UserKeypairService } from './UserKeypairService.js';
|
||||
import { UserListService } from './UserListService.js';
|
||||
import { UserMutingService } from './UserMutingService.js';
|
||||
import { UserRenoteMutingService } from './UserRenoteMutingService.js';
|
||||
import { UserSuspendService } from './UserSuspendService.js';
|
||||
import { UserAuthService } from './UserAuthService.js';
|
||||
import { VideoProcessingService } from './VideoProcessingService.js';
|
||||
|
@ -202,6 +204,8 @@ const $UserFollowingService: Provider = { provide: 'UserFollowingService', useEx
|
|||
const $UserKeypairService: Provider = { provide: 'UserKeypairService', useExisting: UserKeypairService };
|
||||
const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService };
|
||||
const $UserMutingService: Provider = { provide: 'UserMutingService', useExisting: UserMutingService };
|
||||
const $UserRenoteMutingService: Provider = { provide: 'UserRenoteMutingService', useExisting: UserRenoteMutingService };
|
||||
const $UserSearchService: Provider = { provide: 'UserSearchService', useExisting: UserSearchService };
|
||||
const $UserSuspendService: Provider = { provide: 'UserSuspendService', useExisting: UserSuspendService };
|
||||
const $UserAuthService: Provider = { provide: 'UserAuthService', useExisting: UserAuthService };
|
||||
const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', useExisting: VideoProcessingService };
|
||||
|
@ -348,6 +352,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
UserKeypairService,
|
||||
UserListService,
|
||||
UserMutingService,
|
||||
UserRenoteMutingService,
|
||||
UserSearchService,
|
||||
UserSuspendService,
|
||||
UserAuthService,
|
||||
VideoProcessingService,
|
||||
|
@ -490,6 +496,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$UserKeypairService,
|
||||
$UserListService,
|
||||
$UserMutingService,
|
||||
$UserRenoteMutingService,
|
||||
$UserSearchService,
|
||||
$UserSuspendService,
|
||||
$UserAuthService,
|
||||
$VideoProcessingService,
|
||||
|
@ -633,6 +641,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
UserKeypairService,
|
||||
UserListService,
|
||||
UserMutingService,
|
||||
UserRenoteMutingService,
|
||||
UserSearchService,
|
||||
UserSuspendService,
|
||||
UserAuthService,
|
||||
VideoProcessingService,
|
||||
|
@ -774,6 +784,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$UserKeypairService,
|
||||
$UserListService,
|
||||
$UserMutingService,
|
||||
$UserRenoteMutingService,
|
||||
$UserSearchService,
|
||||
$UserSuspendService,
|
||||
$UserAuthService,
|
||||
$VideoProcessingService,
|
||||
|
|
|
@ -55,9 +55,6 @@ export class FanoutTimelineEndpointService {
|
|||
|
||||
@bindThis
|
||||
private async getMiNotes(ps: TimelineOptions): Promise<MiNote[]> {
|
||||
let noteIds: string[];
|
||||
let shouldFallbackToDb = false;
|
||||
|
||||
// 呼び出し元と以下の処理をシンプルにするためにdbFallbackを置き換える
|
||||
if (!ps.useDbFallback) ps.dbFallback = () => Promise.resolve([]);
|
||||
|
||||
|
@ -67,12 +64,11 @@ export class FanoutTimelineEndpointService {
|
|||
const redisResult = await this.fanoutTimelineService.getMulti(ps.redisTimelines, ps.untilId, ps.sinceId);
|
||||
|
||||
// TODO: いい感じにgetMulti内でソート済だからuniqするときにredisResultが全てソート済なのを利用して再ソートを避けたい
|
||||
const redisResultIds = Array.from(new Set(redisResult.flat(1)));
|
||||
const redisResultIds = Array.from(new Set(redisResult.flat(1))).sort(idCompare);
|
||||
|
||||
redisResultIds.sort(idCompare);
|
||||
noteIds = redisResultIds.slice(0, ps.limit);
|
||||
|
||||
shouldFallbackToDb = shouldFallbackToDb || (noteIds.length === 0);
|
||||
let noteIds = redisResultIds.slice(0, ps.limit);
|
||||
const oldestNoteId = ascending ? redisResultIds[0] : redisResultIds[redisResultIds.length - 1];
|
||||
const shouldFallbackToDb = noteIds.length === 0 || ps.sinceId != null && ps.sinceId < oldestNoteId;
|
||||
|
||||
if (!shouldFallbackToDb) {
|
||||
let filter = ps.noteFilter ?? (_note => true);
|
||||
|
|
|
@ -40,6 +40,7 @@ export class FederatedInstanceService implements OnApplicationShutdown {
|
|||
firstRetrievedAt: new Date(parsed.firstRetrievedAt),
|
||||
latestRequestReceivedAt: parsed.latestRequestReceivedAt ? new Date(parsed.latestRequestReceivedAt) : null,
|
||||
infoUpdatedAt: parsed.infoUpdatedAt ? new Date(parsed.infoUpdatedAt) : null,
|
||||
notRespondingSince: parsed.notRespondingSince ? new Date(parsed.notRespondingSince) : null,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -209,6 +209,10 @@ type SerializedAll<T> = {
|
|||
[K in keyof T]: Serialized<T[K]>;
|
||||
};
|
||||
|
||||
type UndefinedAsNullAll<T> = {
|
||||
[K in keyof T]: T[K] extends undefined ? null : T[K];
|
||||
}
|
||||
|
||||
export interface InternalEventTypes {
|
||||
userChangeSuspendedState: { id: MiUser['id']; isSuspended: MiUser['isSuspended']; };
|
||||
userChangeDeletedState: { id: MiUser['id']; isDeleted: MiUser['isDeleted']; };
|
||||
|
@ -247,43 +251,45 @@ export interface InternalEventTypes {
|
|||
userListMemberRemoved: { userListId: MiUserList['id']; memberId: MiUser['id']; };
|
||||
}
|
||||
|
||||
type EventTypesToEventPayload<T> = EventUnionFromDictionary<UndefinedAsNullAll<SerializedAll<T>>>;
|
||||
|
||||
// name/messages(spec) pairs dictionary
|
||||
export type GlobalEvents = {
|
||||
internal: {
|
||||
name: 'internal';
|
||||
payload: EventUnionFromDictionary<SerializedAll<InternalEventTypes>>;
|
||||
payload: EventTypesToEventPayload<InternalEventTypes>;
|
||||
};
|
||||
broadcast: {
|
||||
name: 'broadcast';
|
||||
payload: EventUnionFromDictionary<SerializedAll<BroadcastTypes>>;
|
||||
payload: EventTypesToEventPayload<BroadcastTypes>;
|
||||
};
|
||||
main: {
|
||||
name: `mainStream:${MiUser['id']}`;
|
||||
payload: EventUnionFromDictionary<SerializedAll<MainEventTypes>>;
|
||||
payload: EventTypesToEventPayload<MainEventTypes>;
|
||||
};
|
||||
drive: {
|
||||
name: `driveStream:${MiUser['id']}`;
|
||||
payload: EventUnionFromDictionary<SerializedAll<DriveEventTypes>>;
|
||||
payload: EventTypesToEventPayload<DriveEventTypes>;
|
||||
};
|
||||
note: {
|
||||
name: `noteStream:${MiNote['id']}`;
|
||||
payload: EventUnionFromDictionary<SerializedAll<NoteStreamEventTypes>>;
|
||||
payload: EventTypesToEventPayload<NoteStreamEventTypes>;
|
||||
};
|
||||
userList: {
|
||||
name: `userListStream:${MiUserList['id']}`;
|
||||
payload: EventUnionFromDictionary<SerializedAll<UserListEventTypes>>;
|
||||
payload: EventTypesToEventPayload<UserListEventTypes>;
|
||||
};
|
||||
roleTimeline: {
|
||||
name: `roleTimelineStream:${MiRole['id']}`;
|
||||
payload: EventUnionFromDictionary<SerializedAll<RoleTimelineEventTypes>>;
|
||||
payload: EventTypesToEventPayload<RoleTimelineEventTypes>;
|
||||
};
|
||||
antenna: {
|
||||
name: `antennaStream:${MiAntenna['id']}`;
|
||||
payload: EventUnionFromDictionary<SerializedAll<AntennaEventTypes>>;
|
||||
payload: EventTypesToEventPayload<AntennaEventTypes>;
|
||||
};
|
||||
admin: {
|
||||
name: `adminStream:${MiUser['id']}`;
|
||||
payload: EventUnionFromDictionary<SerializedAll<AdminEventTypes>>;
|
||||
payload: EventTypesToEventPayload<AdminEventTypes>;
|
||||
};
|
||||
notes: {
|
||||
name: 'notesStream';
|
||||
|
@ -291,11 +297,11 @@ export type GlobalEvents = {
|
|||
};
|
||||
reversi: {
|
||||
name: `reversiStream:${MiUser['id']}`;
|
||||
payload: EventUnionFromDictionary<SerializedAll<ReversiEventTypes>>;
|
||||
payload: EventTypesToEventPayload<ReversiEventTypes>;
|
||||
};
|
||||
reversiGame: {
|
||||
name: `reversiGameStream:${MiReversiGame['id']}`;
|
||||
payload: EventUnionFromDictionary<SerializedAll<ReversiGameEventTypes>>;
|
||||
payload: EventTypesToEventPayload<ReversiGameEventTypes>;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -13,10 +13,12 @@ import { intersperse } from '@/misc/prelude/array.js';
|
|||
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||
import type { IMentionedRemoteUsers } from '@/models/Note.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import * as TreeAdapter from '../../node_modules/parse5/dist/tree-adapters/default.js';
|
||||
import type { DefaultTreeAdapterMap } from 'parse5';
|
||||
import type * as mfm from 'mfm-js';
|
||||
|
||||
const treeAdapter = TreeAdapter.defaultTreeAdapter;
|
||||
const treeAdapter = parse5.defaultTreeAdapter;
|
||||
type Node = DefaultTreeAdapterMap['node'];
|
||||
type ChildNode = DefaultTreeAdapterMap['childNode'];
|
||||
|
||||
const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/;
|
||||
const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/;
|
||||
|
@ -46,7 +48,7 @@ export class MfmService {
|
|||
|
||||
return text.trim();
|
||||
|
||||
function getText(node: TreeAdapter.Node): string {
|
||||
function getText(node: Node): string {
|
||||
if (treeAdapter.isTextNode(node)) return node.value;
|
||||
if (!treeAdapter.isElementNode(node)) return '';
|
||||
if (node.nodeName === 'br') return '\n';
|
||||
|
@ -58,7 +60,7 @@ export class MfmService {
|
|||
return '';
|
||||
}
|
||||
|
||||
function appendChildren(childNodes: TreeAdapter.ChildNode[]): void {
|
||||
function appendChildren(childNodes: ChildNode[]): void {
|
||||
if (childNodes) {
|
||||
for (const n of childNodes) {
|
||||
analyze(n);
|
||||
|
@ -66,14 +68,16 @@ export class MfmService {
|
|||
}
|
||||
}
|
||||
|
||||
function analyze(node: TreeAdapter.Node) {
|
||||
function analyze(node: Node) {
|
||||
if (treeAdapter.isTextNode(node)) {
|
||||
text += node.value;
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip comment or document type node
|
||||
if (!treeAdapter.isElementNode(node)) return;
|
||||
if (!treeAdapter.isElementNode(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (node.nodeName) {
|
||||
case 'br': {
|
||||
|
@ -81,8 +85,7 @@ export class MfmService {
|
|||
break;
|
||||
}
|
||||
|
||||
case 'a':
|
||||
{
|
||||
case 'a': {
|
||||
const txt = getText(node);
|
||||
const rel = node.attrs.find(x => x.name === 'rel');
|
||||
const href = node.attrs.find(x => x.name === 'href');
|
||||
|
@ -130,8 +133,7 @@ export class MfmService {
|
|||
break;
|
||||
}
|
||||
|
||||
case 'h1':
|
||||
{
|
||||
case 'h1': {
|
||||
text += '【';
|
||||
appendChildren(node.childNodes);
|
||||
text += '】\n';
|
||||
|
@ -139,16 +141,14 @@ export class MfmService {
|
|||
}
|
||||
|
||||
case 'b':
|
||||
case 'strong':
|
||||
{
|
||||
case 'strong': {
|
||||
text += '**';
|
||||
appendChildren(node.childNodes);
|
||||
text += '**';
|
||||
break;
|
||||
}
|
||||
|
||||
case 'small':
|
||||
{
|
||||
case 'small': {
|
||||
text += '<small>';
|
||||
appendChildren(node.childNodes);
|
||||
text += '</small>';
|
||||
|
@ -156,8 +156,7 @@ export class MfmService {
|
|||
}
|
||||
|
||||
case 's':
|
||||
case 'del':
|
||||
{
|
||||
case 'del': {
|
||||
text += '~~';
|
||||
appendChildren(node.childNodes);
|
||||
text += '~~';
|
||||
|
@ -165,8 +164,7 @@ export class MfmService {
|
|||
}
|
||||
|
||||
case 'i':
|
||||
case 'em':
|
||||
{
|
||||
case 'em': {
|
||||
text += '<i>';
|
||||
appendChildren(node.childNodes);
|
||||
text += '</i>';
|
||||
|
@ -207,8 +205,7 @@ export class MfmService {
|
|||
case 'h3':
|
||||
case 'h4':
|
||||
case 'h5':
|
||||
case 'h6':
|
||||
{
|
||||
case 'h6': {
|
||||
text += '\n\n';
|
||||
appendChildren(node.childNodes);
|
||||
break;
|
||||
|
@ -221,8 +218,7 @@ export class MfmService {
|
|||
case 'article':
|
||||
case 'li':
|
||||
case 'dt':
|
||||
case 'dd':
|
||||
{
|
||||
case 'dd': {
|
||||
text += '\n';
|
||||
appendChildren(node.childNodes);
|
||||
break;
|
||||
|
|
|
@ -59,7 +59,6 @@ import { UtilityService } from '@/core/UtilityService.js';
|
|||
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||
import { isReply } from '@/misc/is-reply.js';
|
||||
import { trackPromise } from '@/misc/promise-tracker.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
|
||||
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
||||
|
@ -849,7 +848,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
const mentions = extractMentions(tokens);
|
||||
let mentionedUsers = (await Promise.all(mentions.map(m =>
|
||||
this.remoteUserResolveService.resolveUser(m.username, m.host ?? user.host).catch(() => null),
|
||||
))).filter(isNotNull);
|
||||
))).filter(x => x != null);
|
||||
|
||||
// Drop duplicate users
|
||||
mentionedUsers = mentionedUsers.filter((u, i, self) =>
|
||||
|
@ -944,12 +943,15 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
}
|
||||
}
|
||||
|
||||
if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) { // 自分自身のHTL
|
||||
// 自分自身のHTL
|
||||
if (note.userHost == null) {
|
||||
if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) {
|
||||
this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r);
|
||||
if (note.fileIds.length > 0) {
|
||||
this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// デフォルトハッシュタグを含む投稿は、リモートであってもローカルタイムラインに含める
|
||||
if (this.config.defaultTag?.tag != null) {
|
||||
|
|
|
@ -29,6 +29,7 @@ import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
|||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { FeaturedService } from '@/core/FeaturedService.js';
|
||||
import { trackPromise } from '@/misc/promise-tracker.js';
|
||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||
|
||||
const FALLBACK = '\u2764';
|
||||
const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
|
||||
|
@ -117,11 +118,16 @@ export class ReactionService {
|
|||
throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.');
|
||||
}
|
||||
|
||||
// Check if note is Renote
|
||||
if (isRenote(note) && !isQuote(note)) {
|
||||
throw new IdentifiableError('12c35529-3c79-4327-b1cc-e2cf63a71925', 'You cannot react to Renote.');
|
||||
}
|
||||
|
||||
let reaction = _reaction ?? FALLBACK;
|
||||
|
||||
if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && (user.host != null))) {
|
||||
reaction = '\u2764';
|
||||
} else if (_reaction) {
|
||||
} else if (_reaction != null) {
|
||||
const custom = reaction.match(isCustomEmojiRegexp);
|
||||
if (custom) {
|
||||
const reacterHost = this.utilityService.toPunyNullable(user.host);
|
||||
|
|
|
@ -47,6 +47,7 @@ export type RolePolicies = {
|
|||
canHideAds: boolean;
|
||||
driveCapacityMb: number;
|
||||
alwaysMarkNsfw: boolean;
|
||||
canUpdateBioMedia: boolean;
|
||||
pinLimit: number;
|
||||
antennaLimit: number;
|
||||
wordMuteLimit: number;
|
||||
|
@ -75,6 +76,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
|||
canHideAds: false,
|
||||
driveCapacityMb: 100,
|
||||
alwaysMarkNsfw: false,
|
||||
canUpdateBioMedia: true,
|
||||
pinLimit: 5,
|
||||
antennaLimit: 5,
|
||||
wordMuteLimit: 200,
|
||||
|
@ -376,6 +378,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
|
||||
driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)),
|
||||
alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)),
|
||||
canUpdateBioMedia: calc('canUpdateBioMedia', vs => vs.some(v => v === true)),
|
||||
pinLimit: calc('pinLimit', vs => Math.max(...vs)),
|
||||
antennaLimit: calc('antennaLimit', vs => Math.max(...vs)),
|
||||
wordMuteLimit: calc('wordMuteLimit', vs => Math.max(...vs)),
|
||||
|
@ -502,14 +505,15 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||
|
||||
this.globalEventService.publishInternalEvent('userRoleAssigned', created);
|
||||
|
||||
if (role.isPublic) {
|
||||
const user = await this.usersRepository.findOneByOrFail({ id: userId });
|
||||
|
||||
if (role.isPublic && user.host === null) {
|
||||
this.notificationService.createNotification(userId, 'roleAssigned', {
|
||||
roleId: roleId,
|
||||
});
|
||||
}
|
||||
|
||||
if (moderator) {
|
||||
const user = await this.usersRepository.findOneByOrFail({ id: userId });
|
||||
this.moderationLogService.log(moderator, 'assignRole', {
|
||||
roleId: roleId,
|
||||
roleName: role.name,
|
||||
|
|
|
@ -279,9 +279,11 @@ export class UserFollowingService implements OnModuleInit {
|
|||
});
|
||||
|
||||
// 通知を作成
|
||||
if (follower.host === null) {
|
||||
this.notificationService.createNotification(follower.id, 'followRequestAccepted', {
|
||||
}, followee.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (alreadyFollowed) return;
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ export class UserListService implements OnApplicationShutdown, OnModuleInit {
|
|||
const currentCount = await this.userListMembershipsRepository.countBy({
|
||||
userListId: list.id,
|
||||
});
|
||||
if (currentCount > (await this.roleService.getUserPolicies(me.id)).userEachUserListsLimit) {
|
||||
if (currentCount >= (await this.roleService.getUserPolicies(me.id)).userEachUserListsLimit) {
|
||||
throw new UserListService.TooManyUsersError();
|
||||
}
|
||||
|
||||
|
|
52
packages/backend/src/core/UserRenoteMutingService.ts
Normal file
52
packages/backend/src/core/UserRenoteMutingService.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project , Type4ny-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { In } from 'typeorm';
|
||||
import type { RenoteMutingsRepository } from '@/models/_.js';
|
||||
import type { MiRenoteMuting } from '@/models/RenoteMuting.js';
|
||||
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
|
||||
@Injectable()
|
||||
export class UserRenoteMutingService {
|
||||
constructor(
|
||||
@Inject(DI.renoteMutingsRepository)
|
||||
private renoteMutingsRepository: RenoteMutingsRepository,
|
||||
|
||||
private idService: IdService,
|
||||
private cacheService: CacheService,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise<void> {
|
||||
await this.renoteMutingsRepository.insert({
|
||||
id: this.idService.gen(),
|
||||
muterId: user.id,
|
||||
muteeId: target.id,
|
||||
});
|
||||
|
||||
await this.cacheService.renoteMutingsCache.refresh(user.id);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async unmute(mutings: MiRenoteMuting[]): Promise<void> {
|
||||
if (mutings.length === 0) return;
|
||||
|
||||
await this.renoteMutingsRepository.delete({
|
||||
id: In(mutings.map(m => m.id)),
|
||||
});
|
||||
|
||||
const muterIds = [...new Set(mutings.map(m => m.muterId))];
|
||||
for (const muterId of muterIds) {
|
||||
await this.cacheService.renoteMutingsCache.refresh(muterId);
|
||||
}
|
||||
}
|
||||
}
|
205
packages/backend/src/core/UserSearchService.ts
Normal file
205
packages/backend/src/core/UserSearchService.ts
Normal file
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Brackets, SelectQueryBuilder } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { type FollowingsRepository, MiUser, type UsersRepository } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { Packed } from '@/misc/json-schema.js';
|
||||
|
||||
function defaultActiveThreshold() {
|
||||
return new Date(Date.now() - 1000 * 60 * 60 * 24 * 30);
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class UserSearchService {
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
@Inject(DI.followingsRepository)
|
||||
private followingsRepository: FollowingsRepository,
|
||||
private userEntityService: UserEntityService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* ユーザ名とホスト名によるユーザ検索を行う.
|
||||
*
|
||||
* - 検索結果には優先順位がつけられており、以下の順序で検索が行われる.
|
||||
* 1. フォローしているユーザのうち、一定期間以内(※)に更新されたユーザ
|
||||
* 2. フォローしているユーザのうち、一定期間以内に更新されていないユーザ
|
||||
* 3. フォローしていないユーザのうち、一定期間以内に更新されたユーザ
|
||||
* 4. フォローしていないユーザのうち、一定期間以内に更新されていないユーザ
|
||||
* - ログインしていない場合は、以下の順序で検索が行われる.
|
||||
* 1. 一定期間以内に更新されたユーザ
|
||||
* 2. 一定期間以内に更新されていないユーザ
|
||||
* - それぞれの検索結果はユーザ名の昇順でソートされる.
|
||||
* - 動作的には先に登場した検索結果の登場位置が優先される(条件的にユーザIDが重複することはないが).
|
||||
* (1で既にヒットしていた場合、2, 3, 4でヒットしても無視される)
|
||||
* - ユーザ名とホスト名の検索条件はそれぞれ前方一致で検索される.
|
||||
* - ユーザ名の検索は大文字小文字を区別しない.
|
||||
* - ホスト名の検索は大文字小文字を区別しない.
|
||||
* - 検索結果は最大で {@link opts.limit} 件までとなる.
|
||||
*
|
||||
* ※一定期間とは {@link params.activeThreshold} で指定された日時から現在までの期間を指す.
|
||||
*
|
||||
* @param params 検索条件.
|
||||
* @param opts 関数の動作を制御するオプション.
|
||||
* @param me 検索を実行するユーザの情報. 未ログインの場合は指定しない.
|
||||
* @see {@link UserSearchService#buildSearchUserQueries}
|
||||
* @see {@link UserSearchService#buildSearchUserNoLoginQueries}
|
||||
*/
|
||||
@bindThis
|
||||
public async search(
|
||||
params: {
|
||||
username?: string | null,
|
||||
host?: string | null,
|
||||
activeThreshold?: Date,
|
||||
},
|
||||
opts?: {
|
||||
limit?: number,
|
||||
detail?: boolean,
|
||||
},
|
||||
me?: MiUser | null,
|
||||
): Promise<Packed<'User'>[]> {
|
||||
const queries = me ? this.buildSearchUserQueries(me, params) : this.buildSearchUserNoLoginQueries(params);
|
||||
|
||||
let resultSet = new Set<MiUser['id']>();
|
||||
const limit = opts?.limit ?? 10;
|
||||
for (const query of queries) {
|
||||
const ids = await query
|
||||
.select('user.id')
|
||||
.limit(limit - resultSet.size)
|
||||
.orderBy('user.usernameLower', 'ASC')
|
||||
.getRawMany<{ user_id: MiUser['id'] }>()
|
||||
.then(res => res.map(x => x.user_id));
|
||||
|
||||
resultSet = new Set([...resultSet, ...ids]);
|
||||
if (resultSet.size >= limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return this.userEntityService.packMany<'UserLite' | 'UserDetailed'>(
|
||||
[...resultSet].slice(0, limit),
|
||||
me,
|
||||
{ schema: opts?.detail ? 'UserDetailed' : 'UserLite' },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* ログイン済みユーザによる検索実行時のクエリ一覧を構築する.
|
||||
* @param me
|
||||
* @param params
|
||||
* @private
|
||||
*/
|
||||
@bindThis
|
||||
private buildSearchUserQueries(
|
||||
me: MiUser,
|
||||
params: {
|
||||
username?: string | null,
|
||||
host?: string | null,
|
||||
activeThreshold?: Date,
|
||||
},
|
||||
) {
|
||||
// デフォルト30日以内に更新されたユーザーをアクティブユーザーとする
|
||||
const activeThreshold = params.activeThreshold ?? defaultActiveThreshold();
|
||||
|
||||
const followingUserQuery = this.followingsRepository.createQueryBuilder('following')
|
||||
.select('following.followeeId')
|
||||
.where('following.followerId = :followerId', { followerId: me.id });
|
||||
|
||||
const activeFollowingUsersQuery = this.generateUserQueryBuilder(params)
|
||||
.andWhere(`user.id IN (${followingUserQuery.getQuery()})`)
|
||||
.andWhere('user.updatedAt > :activeThreshold', { activeThreshold });
|
||||
activeFollowingUsersQuery.setParameters(followingUserQuery.getParameters());
|
||||
|
||||
const inactiveFollowingUsersQuery = this.generateUserQueryBuilder(params)
|
||||
.andWhere(`user.id IN (${followingUserQuery.getQuery()})`)
|
||||
.andWhere(new Brackets(qb => {
|
||||
qb
|
||||
.where('user.updatedAt IS NULL')
|
||||
.orWhere('user.updatedAt <= :activeThreshold', { activeThreshold });
|
||||
}));
|
||||
inactiveFollowingUsersQuery.setParameters(followingUserQuery.getParameters());
|
||||
|
||||
// 自分自身がヒットするとしたらここ
|
||||
const activeUserQuery = this.generateUserQueryBuilder(params)
|
||||
.andWhere(`user.id NOT IN (${followingUserQuery.getQuery()})`)
|
||||
.andWhere('user.updatedAt > :activeThreshold', { activeThreshold });
|
||||
activeUserQuery.setParameters(followingUserQuery.getParameters());
|
||||
|
||||
const inactiveUserQuery = this.generateUserQueryBuilder(params)
|
||||
.andWhere(`user.id NOT IN (${followingUserQuery.getQuery()})`)
|
||||
.andWhere('user.updatedAt <= :activeThreshold', { activeThreshold });
|
||||
inactiveUserQuery.setParameters(followingUserQuery.getParameters());
|
||||
|
||||
return [activeFollowingUsersQuery, inactiveFollowingUsersQuery, activeUserQuery, inactiveUserQuery];
|
||||
}
|
||||
|
||||
/**
|
||||
* ログインしていないユーザによる検索実行時のクエリ一覧を構築する.
|
||||
* @param params
|
||||
* @private
|
||||
*/
|
||||
@bindThis
|
||||
private buildSearchUserNoLoginQueries(params: {
|
||||
username?: string | null,
|
||||
host?: string | null,
|
||||
activeThreshold?: Date,
|
||||
}) {
|
||||
// デフォルト30日以内に更新されたユーザーをアクティブユーザーとする
|
||||
const activeThreshold = params.activeThreshold ?? defaultActiveThreshold();
|
||||
|
||||
const activeUserQuery = this.generateUserQueryBuilder(params)
|
||||
.andWhere(new Brackets(qb => {
|
||||
qb
|
||||
.where('user.updatedAt IS NULL')
|
||||
.orWhere('user.updatedAt > :activeThreshold', { activeThreshold });
|
||||
}));
|
||||
|
||||
const inactiveUserQuery = this.generateUserQueryBuilder(params)
|
||||
.andWhere('user.updatedAt <= :activeThreshold', { activeThreshold });
|
||||
|
||||
return [activeUserQuery, inactiveUserQuery];
|
||||
}
|
||||
|
||||
/**
|
||||
* ユーザ検索クエリで共通する抽出条件をあらかじめ設定したクエリビルダを生成する.
|
||||
* @param params
|
||||
* @private
|
||||
*/
|
||||
@bindThis
|
||||
private generateUserQueryBuilder(params: {
|
||||
username?: string | null,
|
||||
host?: string | null,
|
||||
}): SelectQueryBuilder<MiUser> {
|
||||
const userQuery = this.usersRepository.createQueryBuilder('user');
|
||||
|
||||
if (params.username) {
|
||||
userQuery.andWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(params.username.toLowerCase()) + '%' });
|
||||
}
|
||||
|
||||
if (params.host) {
|
||||
if (params.host === this.config.hostname || params.host === '.') {
|
||||
userQuery.andWhere('user.host IS NULL');
|
||||
} else {
|
||||
userQuery.andWhere('user.host LIKE :host', {
|
||||
host: sqlLikeEscape(params.host.toLowerCase()) + '%',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
userQuery.andWhere('user.isSuspended = FALSE');
|
||||
|
||||
return userQuery;
|
||||
}
|
||||
}
|
|
@ -8,7 +8,6 @@ import promiseLimit from 'promise-limit';
|
|||
import type { MiRemoteUser, MiUser } from '@/models/User.js';
|
||||
import { concat, unique } from '@/misc/prelude/array.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { getApIds } from './type.js';
|
||||
import { ApPersonService } from './models/ApPersonService.js';
|
||||
import type { ApObject } from './type.js';
|
||||
|
@ -41,7 +40,7 @@ export class ApAudienceService {
|
|||
const limit = promiseLimit<MiUser | null>(2);
|
||||
const mentionedUsers = (await Promise.all(
|
||||
others.map(id => limit(() => this.apPersonService.resolvePerson(id, resolver).catch(() => null))),
|
||||
)).filter(isNotNull);
|
||||
)).filter(x => x != null);
|
||||
|
||||
if (toGroups.public.length > 0) {
|
||||
return {
|
||||
|
|
|
@ -27,7 +27,6 @@ import { QueueService } from '@/core/QueueService.js';
|
|||
import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { MiRemoteUser } from '@/models/User.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { AbuseReportService } from '@/core/AbuseReportService.js';
|
||||
import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
|
||||
|
@ -538,7 +537,7 @@ export class ApInboxService {
|
|||
const userIds = uris
|
||||
.filter(uri => uri.startsWith(this.config.url + '/users/'))
|
||||
.map(uri => uri.split('/').at(-1))
|
||||
.filter(isNotNull);
|
||||
.filter(x => x != null);
|
||||
const users = await this.usersRepository.findBy({
|
||||
id: In(userIds),
|
||||
});
|
||||
|
|
|
@ -25,7 +25,7 @@ export class ApMfmService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public getNoteHtml(note: MiNote, apAppend?: string) {
|
||||
public getNoteHtml(note: Pick<MiNote, 'text' | 'mentionedRemoteUsers'>, apAppend?: string) {
|
||||
let noMisskeyContent = false;
|
||||
const srcMfm = (note.text ?? '') + (apAppend ?? '');
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@ import type { MiUserKeypair } from '@/models/UserKeypair.js';
|
|||
import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { JsonLdService } from './JsonLdService.js';
|
||||
import { ApMfmService } from './ApMfmService.js';
|
||||
|
@ -317,7 +316,7 @@ export class ApRendererService {
|
|||
const getPromisedFiles = async (ids: string[]): Promise<MiDriveFile[]> => {
|
||||
if (ids.length === 0) return [];
|
||||
const items = await this.driveFilesRepository.findBy({ id: In(ids) });
|
||||
return ids.map(id => items.find(item => item.id === id)).filter(isNotNull);
|
||||
return ids.map(id => items.find(item => item.id === id)).filter(x => x != null);
|
||||
};
|
||||
|
||||
let inReplyTo;
|
||||
|
@ -686,7 +685,7 @@ export class ApRendererService {
|
|||
if (names.length === 0) return [];
|
||||
|
||||
const allEmojis = await this.customEmojiService.localEmojisCache.fetch();
|
||||
const emojis = names.map(name => allEmojis.get(name)).filter(isNotNull);
|
||||
const emojis = names.map(name => allEmojis.get(name)).filter(x => x != null);
|
||||
|
||||
return emojis;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import promiseLimit from 'promise-limit';
|
|||
import type { MiUser } from '@/models/_.js';
|
||||
import { toArray, unique } from '@/misc/prelude/array.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { isMention } from '../type.js';
|
||||
import { Resolver } from '../ApResolverService.js';
|
||||
import { ApPersonService } from './ApPersonService.js';
|
||||
|
@ -28,7 +27,7 @@ export class ApMentionService {
|
|||
const limit = promiseLimit<MiUser | null>(2);
|
||||
const mentionedUsers = (await Promise.all(
|
||||
hrefs.map(x => limit(() => this.apPersonService.resolvePerson(x, resolver).catch(() => null))),
|
||||
)).filter(isNotNull);
|
||||
)).filter(x => x != null);
|
||||
|
||||
return mentionedUsers;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ import { UtilityService } from '@/core/UtilityService.js';
|
|||
import { bindThis } from '@/decorators.js';
|
||||
import { checkHttps } from '@/misc/check-https.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js';
|
||||
import { ApLoggerService } from '../ApLoggerService.js';
|
||||
import { ApMfmService } from '../ApMfmService.js';
|
||||
|
@ -253,7 +252,7 @@ export class ApNoteService {
|
|||
}
|
||||
};
|
||||
|
||||
const uris = unique([note._misskey_quote, note.quoteUrl].filter(isNotNull));
|
||||
const uris = unique([note._misskey_quote, note.quoteUrl].filter(x => x != null));
|
||||
const results = await Promise.all(uris.map(tryResolveNote));
|
||||
|
||||
quote = results.filter((x): x is { status: 'ok', res: MiNote } => x.status === 'ok').map(x => x.res).at(0);
|
||||
|
|
|
@ -34,11 +34,11 @@ import { StatusError } from '@/misc/status-error.js';
|
|||
import type { UtilityService } from '@/core/UtilityService.js';
|
||||
import type { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||
import type { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||
import { checkHttps } from '@/misc/check-https.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { getApId, getApType, getOneApHrefNullable, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js';
|
||||
import { extractApHashtags } from './tag.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
|
@ -101,6 +101,8 @@ export class ApPersonService implements OnModuleInit {
|
|||
|
||||
@Inject(DI.followingsRepository)
|
||||
private followingsRepository: FollowingsRepository,
|
||||
|
||||
private roleService: RoleService,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -239,6 +241,11 @@ export class ApPersonService implements OnModuleInit {
|
|||
return this.apImageService.resolveImage(user, img).catch(() => null);
|
||||
}));
|
||||
|
||||
if (((avatar != null && avatar.id != null) || (banner != null && banner.id != null))
|
||||
&& !(await this.roleService.getUserPolicies(user.id)).canUpdateBioMedia) {
|
||||
return {};
|
||||
}
|
||||
|
||||
/*
|
||||
we don't want to return nulls on errors! if the database fields
|
||||
are already null, nothing changes; if the database has old
|
||||
|
@ -637,7 +644,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
|
||||
// とりあえずidを別の時間で生成して順番を維持
|
||||
let td = 0;
|
||||
for (const note of featuredNotes.filter(isNotNull)) {
|
||||
for (const note of featuredNotes.filter(x => x != null)) {
|
||||
td -= 1000;
|
||||
transactionalEntityManager.insert(MiUserNotePining, {
|
||||
id: this.idService.gen(Date.now() + td),
|
||||
|
|
|
@ -10,7 +10,6 @@ import type { Config } from '@/config.js';
|
|||
import type { IPoll } from '@/models/Poll.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { isQuestion } from '../type.js';
|
||||
import { ApLoggerService } from '../ApLoggerService.js';
|
||||
import { ApResolverService } from '../ApResolverService.js';
|
||||
|
@ -52,7 +51,7 @@ export class ApQuestionService {
|
|||
|
||||
const choices = question[multiple ? 'anyOf' : 'oneOf']
|
||||
?.map((x) => x.name)
|
||||
.filter(isNotNull)
|
||||
.filter(x => x != null)
|
||||
?? [];
|
||||
|
||||
const votes = question[multiple ? 'anyOf' : 'oneOf']?.map((x) => x.replies?.totalItems ?? x._misskey_votes ?? 0);
|
||||
|
@ -75,10 +74,10 @@ export class ApQuestionService {
|
|||
|
||||
//#region このサーバーに既に登録されているか
|
||||
const note = await this.notesRepository.findOneBy({ uri });
|
||||
if (note == null) throw new Error('Question is not registed');
|
||||
if (note == null) throw new Error('Question is not registered');
|
||||
|
||||
const poll = await this.pollsRepository.findOneBy({ noteId: note.id });
|
||||
if (poll == null) throw new Error('Question is not registed');
|
||||
if (poll == null) throw new Error('Question is not registered');
|
||||
//#endregion
|
||||
|
||||
// resolve new Question object
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
*/
|
||||
|
||||
import { toArray } from '@/misc/prelude/array.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { isHashtag } from '../type.js';
|
||||
import type { IObject, IApHashtag } from '../type.js';
|
||||
|
||||
|
@ -16,7 +15,7 @@ export function extractApHashtags(tags: IObject | IObject[] | null | undefined):
|
|||
return hashtags.map(tag => {
|
||||
const m = tag.name.match(/^#(.+)/);
|
||||
return m ? m[1] : null;
|
||||
}).filter(isNotNull);
|
||||
}).filter(x => x != null);
|
||||
}
|
||||
|
||||
export function extractApHashtagObjects(tags: IObject | IObject[] | null | undefined): IApHashtag[] {
|
||||
|
|
|
@ -11,7 +11,6 @@ import { bindThis } from '@/decorators.js';
|
|||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { Packed } from '@/misc/json-schema.js';
|
||||
import { SystemWebhookEntityService } from '@/core/entities/SystemWebhookEntityService.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
|
||||
@Injectable()
|
||||
export class AbuseReportNotificationRecipientEntityService {
|
||||
|
@ -66,13 +65,13 @@ export class AbuseReportNotificationRecipientEntityService {
|
|||
);
|
||||
}
|
||||
|
||||
const userIds = objs.map(it => it.userId).filter(isNotNull);
|
||||
const userIds = objs.map(it => it.userId).filter(x => x != null);
|
||||
const users: Map<string, Packed<'UserLite'>> = (userIds.length > 0)
|
||||
? await this.userEntityService.packMany(userIds)
|
||||
.then(it => new Map(it.map(it => [it.id, it])))
|
||||
: new Map();
|
||||
|
||||
const systemWebhookIds = objs.map(it => it.systemWebhookId).filter(isNotNull);
|
||||
const systemWebhookIds = objs.map(it => it.systemWebhookId).filter(x => x != null);
|
||||
const systemWebhooks: Map<string, Packed<'SystemWebhook'>> = (systemWebhookIds.length > 0)
|
||||
? await this.systemWebhookEntityService.packMany(systemWebhookIds)
|
||||
.then(it => new Map(it.map(it => [it.id, it])))
|
||||
|
|
|
@ -10,7 +10,6 @@ import { awaitAll } from '@/misc/prelude/await-all.js';
|
|||
import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
|
||||
|
@ -63,7 +62,7 @@ export class AbuseUserReportEntityService {
|
|||
) {
|
||||
const _reporters = reports.map(({ reporter, reporterId }) => reporter ?? reporterId);
|
||||
const _targetUsers = reports.map(({ targetUser, targetUserId }) => targetUser ?? targetUserId);
|
||||
const _assignees = reports.map(({ assignee, assigneeId }) => assignee ?? assigneeId).filter(isNotNull);
|
||||
const _assignees = reports.map(({ assignee, assigneeId }) => assignee ?? assigneeId).filter(x => x != null);
|
||||
const _userMap = await this.userEntityService.packMany(
|
||||
[..._reporters, ..._targetUsers, ..._assignees],
|
||||
null,
|
||||
|
|
|
@ -53,7 +53,7 @@ export class ClipEntityService {
|
|||
isPublic: clip.isPublic,
|
||||
favoritedCount: await this.clipFavoritesRepository.countBy({ clipId: clip.id }),
|
||||
isFavorited: meId ? await this.clipFavoritesRepository.exists({ where: { clipId: clip.id, userId: meId } }) : undefined,
|
||||
notesCount: meId ? await this.clipNotesRepository.countBy({ clipId: clip.id }) : undefined,
|
||||
notesCount: (meId === clip.userId) ? await this.clipNotesRepository.countBy({ clipId: clip.id }) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ import { appendQuery, query } from '@/misc/prelude/url.js';
|
|||
import { deepClone } from '@/misc/clone.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isMimeImage } from '@/misc/is-mime-image.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { UtilityService } from '../UtilityService.js';
|
||||
import { VideoProcessingService } from '../VideoProcessingService.js';
|
||||
|
@ -261,11 +260,11 @@ export class DriveFileEntityService {
|
|||
files: MiDriveFile[],
|
||||
options?: PackOptions,
|
||||
): Promise<Packed<'DriveFile'>[]> {
|
||||
const _user = files.map(({ user, userId }) => user ?? userId).filter(isNotNull);
|
||||
const _user = files.map(({ user, userId }) => user ?? userId).filter(x => x != null);
|
||||
const _userMap = await this.userEntityService.packMany(_user)
|
||||
.then(users => new Map(users.map(user => [user.id, user])));
|
||||
const items = await Promise.all(files.map(f => this.packNullable(f, options, f.userId ? { packedUser: _userMap.get(f.userId) } : {})));
|
||||
return items.filter(isNotNull);
|
||||
return items.filter(x => x != null);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
@ -290,6 +289,6 @@ export class DriveFileEntityService {
|
|||
): Promise<Packed<'DriveFile'>[]> {
|
||||
if (fileIds.length === 0) return [];
|
||||
const filesMap = await this.packManyByIdsMap(fileIds, options);
|
||||
return fileIds.map(id => filesMap.get(id)).filter(isNotNull);
|
||||
return fileIds.map(id => filesMap.get(id)).filter(x => x != null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ import type { MiUser } from '@/models/User.js';
|
|||
import type { MiRegistrationTicket } from '@/models/RegistrationTicket.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
|
||||
@Injectable()
|
||||
|
@ -59,8 +58,8 @@ export class InviteCodeEntityService {
|
|||
tickets: MiRegistrationTicket[],
|
||||
me: { id: MiUser['id'] },
|
||||
) {
|
||||
const _createdBys = tickets.map(({ createdBy, createdById }) => createdBy ?? createdById).filter(isNotNull);
|
||||
const _usedBys = tickets.map(({ usedBy, usedById }) => usedBy ?? usedById).filter(isNotNull);
|
||||
const _createdBys = tickets.map(({ createdBy, createdById }) => createdBy ?? createdById).filter(x => x != null);
|
||||
const _usedBys = tickets.map(({ usedBy, usedById }) => usedBy ?? usedById).filter(x => x != null);
|
||||
const _userMap = await this.userEntityService.packMany([..._createdBys, ..._usedBys], me)
|
||||
.then(users => new Map(users.map(u => [u.id, u])));
|
||||
return Promise.all(
|
||||
|
|
|
@ -50,6 +50,22 @@ export class MetaEntityService {
|
|||
}))
|
||||
.getMany();
|
||||
|
||||
// クライアントの手間を減らすためあらかじめJSONに変換しておく
|
||||
let defaultLightTheme = null;
|
||||
let defaultDarkTheme = null;
|
||||
if (instance.defaultLightTheme) {
|
||||
try {
|
||||
defaultLightTheme = JSON.stringify(JSON5.parse(instance.defaultLightTheme));
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
if (instance.defaultDarkTheme) {
|
||||
try {
|
||||
defaultDarkTheme = JSON.stringify(JSON5.parse(instance.defaultDarkTheme));
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
const packed: Packed<'MetaLite'> = {
|
||||
maintainerName: instance.maintainerName,
|
||||
maintainerEmail: instance.maintainerEmail,
|
||||
|
@ -90,9 +106,8 @@ export class MetaEntityService {
|
|||
backgroundImageUrl: instance.backgroundImageUrl,
|
||||
logoImageUrl: instance.logoImageUrl,
|
||||
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
|
||||
// クライアントの手間を減らすためあらかじめJSONに変換しておく
|
||||
defaultLightTheme: instance.defaultLightTheme ? JSON.stringify(JSON5.parse(instance.defaultLightTheme)) : null,
|
||||
defaultDarkTheme: instance.defaultDarkTheme ? JSON.stringify(JSON5.parse(instance.defaultDarkTheme)) : null,
|
||||
defaultLightTheme,
|
||||
defaultDarkTheme,
|
||||
ads: ads.map(ad => ({
|
||||
id: ad.id,
|
||||
url: ad.url,
|
||||
|
|
|
@ -14,7 +14,6 @@ import type { MiNote } from '@/models/Note.js';
|
|||
import type { MiNoteReaction } from '@/models/NoteReaction.js';
|
||||
import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { DebounceLoader } from '@/misc/loader.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
|
@ -276,7 +275,7 @@ export class NoteEntityService implements OnModuleInit {
|
|||
packedFiles.set(k, v);
|
||||
}
|
||||
}
|
||||
return fileIds.map(id => packedFiles.get(id)).filter(isNotNull);
|
||||
return fileIds.map(id => packedFiles.get(id)).filter(x => x != null);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
@ -449,12 +448,12 @@ export class NoteEntityService implements OnModuleInit {
|
|||
|
||||
await this.customEmojiService.prefetchEmojis(this.aggregateNoteEmojis(notes));
|
||||
// TODO: 本当は renote とか reply がないのに renoteId とか replyId があったらここで解決しておく
|
||||
const fileIds = notes.map(n => [n.fileIds, n.renote?.fileIds, n.reply?.fileIds]).flat(2).filter(isNotNull);
|
||||
const fileIds = notes.map(n => [n.fileIds, n.renote?.fileIds, n.reply?.fileIds]).flat(2).filter(x => x != null);
|
||||
const packedFiles = fileIds.length > 0 ? await this.driveFileEntityService.packManyByIdsMap(fileIds) : new Map();
|
||||
const users = [
|
||||
...notes.map(({ user, userId }) => user ?? userId),
|
||||
...notes.map(({ replyUserId }) => replyUserId).filter(isNotNull),
|
||||
...notes.map(({ renoteUserId }) => renoteUserId).filter(isNotNull),
|
||||
...notes.map(({ replyUserId }) => replyUserId).filter(x => x != null),
|
||||
...notes.map(({ renoteUserId }) => renoteUserId).filter(x => x != null),
|
||||
];
|
||||
const packedUsers = await this.userEntityService.packMany(users, me)
|
||||
.then(users => new Map(users.map(u => [u.id, u])));
|
||||
|
|
|
@ -13,7 +13,6 @@ import type { MiGroupedNotification, MiNotification } from '@/models/Notificatio
|
|||
import type { MiNote } from '@/models/Note.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { FilterUnionByProperty, groupedNotificationTypes } from '@/types.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { RoleEntityService } from './RoleEntityService.js';
|
||||
|
@ -103,7 +102,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
user,
|
||||
reaction: reaction.reaction,
|
||||
};
|
||||
}))).filter(r => isNotNull(r.user));
|
||||
}))).filter(r => r.user != null);
|
||||
// if all users have been deleted, don't show this notification
|
||||
if (reactions.length === 0) {
|
||||
return null;
|
||||
|
@ -124,7 +123,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
}
|
||||
|
||||
return this.userEntityService.pack(userId, { id: meId });
|
||||
}))).filter(isNotNull);
|
||||
}))).filter(x => x != null);
|
||||
// if all users have been deleted, don't show this notification
|
||||
if (users.length === 0) {
|
||||
return null;
|
||||
|
@ -181,7 +180,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
|
||||
validNotifications = await this.#filterValidNotifier(validNotifications, meId);
|
||||
|
||||
const noteIds = validNotifications.map(x => 'noteId' in x ? x.noteId : null).filter(isNotNull);
|
||||
const noteIds = validNotifications.map(x => 'noteId' in x ? x.noteId : null).filter(x => x != null);
|
||||
const notes = noteIds.length > 0 ? await this.notesRepository.find({
|
||||
where: { id: In(noteIds) },
|
||||
relations: ['user', 'reply', 'reply.user', 'renote', 'renote.user'],
|
||||
|
@ -223,7 +222,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
);
|
||||
});
|
||||
|
||||
return (await Promise.all(packPromises)).filter(isNotNull);
|
||||
return (await Promise.all(packPromises)).filter(x => x != null);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
@ -305,7 +304,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
this.cacheService.userProfileCache.fetch(meId).then(p => new Set(p.mutedInstances)),
|
||||
]);
|
||||
|
||||
const notifierIds = notifications.map(notification => 'notifierId' in notification ? notification.notifierId : null).filter(isNotNull);
|
||||
const notifierIds = notifications.map(notification => 'notifierId' in notification ? notification.notifierId : null).filter(x => x != null);
|
||||
const notifiers = notifierIds.length > 0 ? await this.usersRepository.find({
|
||||
where: { id: In(notifierIds) },
|
||||
}) : [];
|
||||
|
@ -313,7 +312,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
const filteredNotifications = ((await Promise.all(notifications.map(async (notification) => {
|
||||
const isValid = this.#validateNotifier(notification, userIdsWhoMeMuting, userMutedInstances, notifiers);
|
||||
return isValid ? notification : null;
|
||||
}))) as [T | null] ).filter(isNotNull);
|
||||
}))) as [T | null] ).filter(x => x != null);
|
||||
|
||||
return filteredNotifications;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ import type { MiPage } from '@/models/Page.js';
|
|||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
import { DriveFileEntityService } from './DriveFileEntityService.js';
|
||||
|
||||
|
@ -106,7 +105,7 @@ export class PageEntityService {
|
|||
script: page.script,
|
||||
eyeCatchingImageId: page.eyeCatchingImageId,
|
||||
eyeCatchingImage: page.eyeCatchingImageId ? await this.driveFileEntityService.pack(page.eyeCatchingImageId) : null,
|
||||
attachedFiles: this.driveFileEntityService.packMany((await Promise.all(attachedFiles)).filter(isNotNull)),
|
||||
attachedFiles: this.driveFileEntityService.packMany((await Promise.all(attachedFiles)).filter(x => x != null)),
|
||||
likedCount: page.likedCount,
|
||||
isLiked: meId ? await this.pageLikesRepository.exists({ where: { pageId: page.id, userId: meId } }) : undefined,
|
||||
});
|
||||
|
|
|
@ -47,7 +47,6 @@ import { IdService } from '@/core/IdService.js';
|
|||
import type { AnnouncementService } from '@/core/AnnouncementService.js';
|
||||
import type { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
import type { NoteEntityService } from './NoteEntityService.js';
|
||||
import type { DriveFileEntityService } from './DriveFileEntityService.js';
|
||||
|
@ -502,11 +501,15 @@ export class UserEntityService implements OnModuleInit {
|
|||
emojis: this.customEmojiService.populateEmojis(user.emojis, user.host),
|
||||
onlineStatus: this.getOnlineStatus(user),
|
||||
// パフォーマンス上の理由でローカルユーザーのみ
|
||||
badgeRoles: user.host == null ? this.roleService.getUserBadgeRoles(user.id).then(rs => rs.sort((a, b) => b.displayOrder - a.displayOrder).map(r => ({
|
||||
badgeRoles: user.host == null ? this.roleService.getUserBadgeRoles(user.id).then((rs) => rs
|
||||
.filter((r) => r.isPublic || iAmModerator)
|
||||
.sort((a, b) => b.displayOrder - a.displayOrder)
|
||||
.map((r) => ({
|
||||
name: r.name,
|
||||
iconUrl: r.iconUrl,
|
||||
displayOrder: r.displayOrder,
|
||||
}))) : undefined,
|
||||
}))
|
||||
) : undefined,
|
||||
|
||||
...(isDetailed ? {
|
||||
url: profile!.url,
|
||||
|
@ -514,7 +517,7 @@ export class UserEntityService implements OnModuleInit {
|
|||
movedTo: user.movedToUri ? this.apPersonService.resolvePerson(user.movedToUri).then(user => user.id).catch(() => null) : null,
|
||||
alsoKnownAs: user.alsoKnownAs
|
||||
? Promise.all(user.alsoKnownAs.map(uri => this.apPersonService.fetchPerson(uri).then(user => user?.id).catch(() => null)))
|
||||
.then(xs => xs.length === 0 ? null : xs.filter(isNotNull))
|
||||
.then(xs => xs.length === 0 ? null : xs.filter(x => x != null))
|
||||
: null,
|
||||
createdAt: this.idService.parse(user.id).date.toISOString(),
|
||||
updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null,
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export function isNotNull<T extends NonNullable<unknown>>(input: T | undefined | null): input is T {
|
||||
return input != null;
|
||||
}
|
|
@ -4,6 +4,10 @@
|
|||
*/
|
||||
|
||||
export function isUserRelated(note: any, userIds: Set<string>, ignoreAuthor = false): boolean {
|
||||
if (!note) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (userIds.has(note.userId) && !ignoreAuthor) {
|
||||
return true;
|
||||
}
|
||||
|
|
8
packages/backend/src/misc/json-value.ts
Normal file
8
packages/backend/src/misc/json-value.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export type JsonValue = JsonArray | JsonObject | string | number | boolean | null;
|
||||
export type JsonObject = {[K in string]?: JsonValue};
|
||||
export type JsonArray = JsonValue[];
|
|
@ -65,44 +65,6 @@ export function maximum(xs: number[]): number {
|
|||
return Math.max(...xs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits an array based on the equivalence relation.
|
||||
* The concatenation of the result is equal to the argument.
|
||||
*/
|
||||
export function groupBy<T>(f: EndoRelation<T>, xs: T[]): T[][] {
|
||||
const groups = [] as T[][];
|
||||
for (const x of xs) {
|
||||
const lastGroup = groups.at(-1);
|
||||
if (lastGroup !== undefined && f(lastGroup[0], x)) {
|
||||
lastGroup.push(x);
|
||||
} else {
|
||||
groups.push([x]);
|
||||
}
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits an array based on the equivalence relation induced by the function.
|
||||
* The concatenation of the result is equal to the argument.
|
||||
*/
|
||||
export function groupOn<T, S>(f: (x: T) => S, xs: T[]): T[][] {
|
||||
return groupBy((a, b) => f(a) === f(b), xs);
|
||||
}
|
||||
|
||||
export function groupByX<T>(collections: T[], keySelector: (x: T) => string) {
|
||||
return collections.reduce((obj: Record<string, T[]>, item: T) => {
|
||||
const key = keySelector(item);
|
||||
if (!Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
obj[key] = [];
|
||||
}
|
||||
|
||||
obj[key].push(item);
|
||||
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two arrays by lexicographical order
|
||||
*/
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export function gcd(a: number, b: number): number {
|
||||
return b === 0 ? a : gcd(b, a % b);
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export interface IMaybe<T> {
|
||||
isJust(): this is IJust<T>;
|
||||
}
|
||||
|
||||
export interface IJust<T> extends IMaybe<T> {
|
||||
get(): T;
|
||||
}
|
||||
|
||||
export function just<T>(value: T): IJust<T> {
|
||||
return {
|
||||
isJust: () => true,
|
||||
get: () => value,
|
||||
};
|
||||
}
|
||||
|
||||
export function nothing<T>(): IMaybe<T> {
|
||||
return {
|
||||
isJust: () => false,
|
||||
};
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export function concat(xs: string[]): string {
|
||||
return xs.join('');
|
||||
}
|
||||
|
||||
export function capitalize(s: string): string {
|
||||
return toUpperCase(s.charAt(0)) + toLowerCase(s.slice(1));
|
||||
}
|
||||
|
||||
export function toUpperCase(s: string): string {
|
||||
return s.toUpperCase();
|
||||
}
|
||||
|
||||
export function toLowerCase(s: string): string {
|
||||
return s.toLowerCase();
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export const fallback = Symbol('fallback');
|
|
@ -20,7 +20,7 @@ export const packedDriveFileSchema = {
|
|||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
example: 'lenna.jpg',
|
||||
example: '192.jpg',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
|
|
|
@ -228,6 +228,10 @@ export const packedRolePoliciesSchema = {
|
|||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
canUpdateBioMedia: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
pinLimit: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
|
|
|
@ -109,6 +109,12 @@ export class DeliverProcessorService {
|
|||
suspensionState: 'autoSuspendedForNotResponding',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// isNotRespondingがtrueでnotRespondingSinceがnullの場合はnotRespondingSinceをセット
|
||||
// notRespondingSinceは新たな機能なので、それ以前のデータにはnotRespondingSinceがない場合がある
|
||||
this.federatedInstanceService.update(i.id, {
|
||||
notRespondingSince: new Date(),
|
||||
});
|
||||
}
|
||||
|
||||
this.apRequestChart.deliverFail();
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import type { PollVotesRepository, NotesRepository } from '@/models/_.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
|
@ -24,6 +25,7 @@ export class EndedPollNotificationProcessorService {
|
|||
@Inject(DI.pollVotesRepository)
|
||||
private pollVotesRepository: PollVotesRepository,
|
||||
|
||||
private cacheService: CacheService,
|
||||
private notificationService: NotificationService,
|
||||
private queueLoggerService: QueueLoggerService,
|
||||
) {
|
||||
|
@ -47,9 +49,12 @@ export class EndedPollNotificationProcessorService {
|
|||
const userIds = [...new Set([note.userId, ...votes.map(v => v.userId)])];
|
||||
|
||||
for (const userId of userIds) {
|
||||
const profile = await this.cacheService.userProfileCache.fetch(userId);
|
||||
if (profile.userHost === null) {
|
||||
this.notificationService.createNotification(userId, 'pollEnded', {
|
||||
noteId: note.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ export const paramDef = {
|
|||
startsAt: { type: 'integer' },
|
||||
dayOfWeek: { type: 'integer' },
|
||||
},
|
||||
required: ['id', 'memo', 'url', 'imageUrl', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'dayOfWeek'],
|
||||
required: ['id'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
|
@ -63,8 +63,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
ratio: ps.ratio,
|
||||
memo: ps.memo,
|
||||
imageUrl: ps.imageUrl,
|
||||
expiresAt: new Date(ps.expiresAt),
|
||||
startsAt: new Date(ps.startsAt),
|
||||
expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : undefined,
|
||||
startsAt: ps.startsAt ? new Date(ps.startsAt) : undefined,
|
||||
dayOfWeek: ps.dayOfWeek,
|
||||
});
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ export const meta = {
|
|||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
example: 'lenna.jpg',
|
||||
example: '192.jpg',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { RolesRepository } from '@/models/_.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
|
@ -50,19 +49,6 @@ export const paramDef = {
|
|||
},
|
||||
required: [
|
||||
'roleId',
|
||||
'name',
|
||||
'description',
|
||||
'color',
|
||||
'iconUrl',
|
||||
'target',
|
||||
'condFormula',
|
||||
'isPublic',
|
||||
'isModerator',
|
||||
'isAdministrator',
|
||||
'asBadge',
|
||||
'canEditMembersByModerator',
|
||||
'displayOrder',
|
||||
'policies',
|
||||
],
|
||||
} as const;
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
const currentAntennasCount = await this.antennasRepository.countBy({
|
||||
userId: me.id,
|
||||
});
|
||||
if (currentAntennasCount > (await this.roleService.getUserPolicies(me.id)).antennaLimit) {
|
||||
if (currentAntennasCount >= (await this.roleService.getUserPolicies(me.id)).antennaLimit) {
|
||||
throw new ApiError(meta.errors.tooManyAntennas);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
|
||||
import { ClipService } from '@/core/ClipService.js';
|
||||
|
@ -41,7 +41,7 @@ export const paramDef = {
|
|||
isPublic: { type: 'boolean' },
|
||||
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
|
||||
},
|
||||
required: ['clipId', 'name'],
|
||||
required: ['clipId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
|
|
|
@ -95,15 +95,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
// Check if the circular reference will occur
|
||||
const checkCircle = async (folderId: string): Promise<boolean> => {
|
||||
// Fetch folder
|
||||
const folder2 = await this.driveFoldersRepository.findOneBy({
|
||||
const folder2 = await this.driveFoldersRepository.findOneByOrFail({
|
||||
id: folderId,
|
||||
});
|
||||
|
||||
if (folder2!.id === folder!.id) {
|
||||
if (folder2.id === folder.id) {
|
||||
return true;
|
||||
} else if (folder2!.parentId) {
|
||||
return await checkCircle(folder2!.parentId);
|
||||
} else if (folder2.parentId) {
|
||||
return await checkCircle(folder2.parentId);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ import type { MiDriveFile } from '@/models/DriveFile.js';
|
|||
import { IdService } from '@/core/IdService.js';
|
||||
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['gallery'],
|
||||
|
@ -70,7 +69,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
id: fileId,
|
||||
userId: me.id,
|
||||
}),
|
||||
))).filter(isNotNull);
|
||||
))).filter(x => x != null);
|
||||
|
||||
if (files.length === 0) {
|
||||
throw new Error();
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue