Compare commits
23 Commits
2024.5.0-b
...
logger-ins
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a7ec9241ab | ||
![]() |
a84cbeb155 | ||
![]() |
285c8ab334 | ||
![]() |
40781f56e7 | ||
![]() |
973d18a552 | ||
![]() |
78238d427a | ||
![]() |
1433a7aecf | ||
![]() |
b42577e3ad | ||
![]() |
493829b3ee | ||
![]() |
7ce4a26970 | ||
![]() |
1f79cd1760 | ||
![]() |
8e55285e48 | ||
![]() |
198d38431a | ||
![]() |
c8fe7b337e | ||
![]() |
a7e3f6c1a9 | ||
![]() |
670d509696 | ||
![]() |
ec914d9f0c | ||
![]() |
18f13cea98 | ||
![]() |
d803ec3e3e | ||
![]() |
d84a5767bf | ||
![]() |
84a6877f57 | ||
![]() |
c9ff8d0624 | ||
![]() |
f7e8862d28 |
@@ -38,7 +38,7 @@
|
||||
# Option 3: If neither of the above applies to you.
|
||||
# (In this case, the source code should be published
|
||||
# on the Misskey interface. IT IS NOT ENOUGH TO
|
||||
# DISCLOSE THE SOURCE CODE WHEN A USER REQUESTS IT BY
|
||||
# DISCLOSE THE SOURCE CODE WEHN A USER REQUESTS IT BY
|
||||
# E-MAIL OR OTHER MEANS. If you are not satisfied
|
||||
# with this, it is recommended that you read the
|
||||
# license again carefully. Anyway, enabling this
|
||||
|
@@ -8,7 +8,7 @@
|
||||
"version": "8.9.2"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "20.12.2"
|
||||
"version": "20.10.0"
|
||||
}
|
||||
},
|
||||
"forwardPorts": [3000],
|
||||
@@ -19,6 +19,7 @@
|
||||
"editorconfig.editorconfig",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"Vue.volar",
|
||||
"Vue.vscode-typescript-vue-plugin",
|
||||
"Orta.vscode-jest",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"mrmlnc.vscode-json5"
|
||||
|
147
.github/workflows/check-misskey-js-autogen.yml
vendored
147
.github/workflows/check-misskey-js-autogen.yml
vendored
@@ -5,28 +5,29 @@ on:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
- improve-misskey-js-autogen-check
|
||||
paths:
|
||||
- packages/backend/**
|
||||
|
||||
jobs:
|
||||
# pull_request_target safety: permissions: read-all, and there are no secrets used in this job
|
||||
generate-misskey-js:
|
||||
check-misskey-js-autogen:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }}
|
||||
pull-requests: write
|
||||
|
||||
env:
|
||||
api_json_name: "api-head.json"
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
submodules: true
|
||||
ref: refs/pull/${{ github.event.pull_request.number }}/merge
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
version: 8
|
||||
|
||||
- name: setup node
|
||||
id: setup-node
|
||||
@@ -38,81 +39,79 @@ jobs:
|
||||
- name: install dependencies
|
||||
run: pnpm i --frozen-lockfile
|
||||
|
||||
# generate api.json
|
||||
- name: Copy Config
|
||||
run: cp .config/example.yml .config/default.yml
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
- name: Generate API JSON
|
||||
run: pnpm --filter backend generate-api-json
|
||||
- name: wait get-api-diff
|
||||
uses: lewagon/wait-on-check-action@v1.3.3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
check-regexp: get-from-misskey .+
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
wait-interval: 30
|
||||
|
||||
# build misskey js
|
||||
- name: Build misskey-js
|
||||
- name: Download artifact
|
||||
uses: actions/github-script@v7.0.1
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
const workflows = await github.rest.actions.listWorkflowRunsForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
head_sha: `${{ github.event.pull_request.head.sha }}`
|
||||
}).then(x => x.data.workflow_runs);
|
||||
|
||||
console.log(workflows.map(x => ({name: x.name, title: x.display_title})));
|
||||
|
||||
const run_id = workflows.find(x => x.name.includes("Get api.json from Misskey")).id;
|
||||
|
||||
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: run_id,
|
||||
});
|
||||
|
||||
let matchArtifacts = allArtifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name.startsWith("api-artifact-") || artifact.name == "api-artifact"
|
||||
});
|
||||
|
||||
await Promise.all(matchArtifacts.map(async (artifact) => {
|
||||
let download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: artifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
await fs.promises.writeFile(`${process.env.GITHUB_WORKSPACE}/${artifact.name}.zip`, Buffer.from(download.data));
|
||||
}));
|
||||
|
||||
- name: unzip artifacts
|
||||
run: |-
|
||||
cp packages/backend/built/api.json packages/misskey-js/generator/api.json
|
||||
pnpm run --filter misskey-js-type-generator generate
|
||||
find . -mindepth 1 -maxdepth 1 -type f -name '*.zip' -exec unzip {} -d . ';'
|
||||
ls -la
|
||||
|
||||
# packages/misskey-js/generator/built/autogen
|
||||
- name: Upload Generated
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: generated-misskey-js
|
||||
path: packages/misskey-js/generator/built/autogen
|
||||
- name: get head checksum
|
||||
run: |-
|
||||
checksum=$(realpath head_checksum)
|
||||
|
||||
# pull_request_target safety: permissions: read-all, and there are no secrets used in this job
|
||||
get-actual-misskey-js:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
submodules: true
|
||||
ref: refs/pull/${{ github.event.pull_request.number }}/merge
|
||||
cd packages/misskey-js/src
|
||||
find autogen -type f -exec sh -c 'echo $(sed -E "s/^\s+\*\s+generatedAt:.+$//" {} | sha256sum | cut -d" " -f 1) {}' \; > $checksum
|
||||
cd ../../..
|
||||
|
||||
- name: Upload From Merged
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: actual-misskey-js
|
||||
path: packages/misskey-js/src/autogen
|
||||
- name: build autogen
|
||||
run: |-
|
||||
checksum=$(realpath ${api_json_name}_checksum)
|
||||
mv $api_json_name packages/misskey-js/generator/api.json
|
||||
|
||||
# pull_request_target safety: nothing is cloned from repository
|
||||
comment-misskey-js-autogen:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [generate-misskey-js, get-actual-misskey-js]
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: download generated-misskey-js
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: generated-misskey-js
|
||||
path: misskey-js-generated
|
||||
cd packages/misskey-js/generator
|
||||
pnpm run generate
|
||||
cd built
|
||||
find autogen -type f -exec sh -c 'echo $(sed -E "s/^\s+\*\s+generatedAt:.+$//" {} | sha256sum | cut -d" " -f 1) {}' \; > $checksum
|
||||
cd ../../../..
|
||||
|
||||
- name: download actual-misskey-js
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: actual-misskey-js
|
||||
path: misskey-js-actual
|
||||
|
||||
- name: check misskey-js changes
|
||||
id: check-changes
|
||||
run: |
|
||||
diff -r -u --label=generated --label=on-tree ./misskey-js-generated ./misskey-js-actual > misskey-js.diff || true
|
||||
|
||||
if [ -s misskey-js.diff ]; then
|
||||
echo "changes=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "changes=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Print full diff
|
||||
run: cat ./misskey-js.diff
|
||||
- name: check update for type definitions
|
||||
run: diff head_checksum ${api_json_name}_checksum
|
||||
|
||||
- name: send message
|
||||
if: steps.check-changes.outputs.changes == 'true'
|
||||
if: failure()
|
||||
uses: thollander/actions-comment-pull-request@v2
|
||||
with:
|
||||
comment_tag: check-misskey-js-autogen
|
||||
@@ -126,7 +125,7 @@ jobs:
|
||||
```
|
||||
|
||||
- name: send message
|
||||
if: steps.check-changes.outputs.changes == 'false'
|
||||
if: success()
|
||||
uses: thollander/actions-comment-pull-request@v2
|
||||
with:
|
||||
comment_tag: check-misskey-js-autogen
|
||||
|
75
.github/workflows/check-spdx-license-id.yml
vendored
75
.github/workflows/check-spdx-license-id.yml
vendored
@@ -1,75 +0,0 @@
|
||||
name: Check SPDX-License-Identifier
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
check-spdx-license-id:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.1
|
||||
- name: Check
|
||||
run: |
|
||||
counter=0
|
||||
|
||||
search() {
|
||||
local directory="$1"
|
||||
find "$directory" -type f \
|
||||
'(' \
|
||||
-name "*.cjs" -and -not -name '*.config.cjs' -o \
|
||||
-name "*.html" -o \
|
||||
-name "*.js" -and -not -name '*.config.js' -o \
|
||||
-name "*.mjs" -and -not -name '*.config.mjs' -o \
|
||||
-name "*.scss" -o \
|
||||
-name "*.ts" -and -not -name '*.config.ts' -o \
|
||||
-name "*.vue" \
|
||||
')' -and \
|
||||
-not -name '*eslint*'
|
||||
}
|
||||
|
||||
check() {
|
||||
local file="$1"
|
||||
if ! (
|
||||
grep -q "SPDX-FileCopyrightText: syuilo and misskey-project" "$file" ||
|
||||
grep -q "SPDX-License-Identifier: AGPL-3.0-only" "$file"
|
||||
); then
|
||||
echo "Missing: $file"
|
||||
((counter++))
|
||||
fi
|
||||
}
|
||||
|
||||
directories=(
|
||||
"cypress/e2e"
|
||||
"packages/backend/migration"
|
||||
"packages/backend/src"
|
||||
"packages/backend/test"
|
||||
"packages/frontend/.storybook"
|
||||
"packages/frontend/@types"
|
||||
"packages/frontend/lib"
|
||||
"packages/frontend/public"
|
||||
"packages/frontend/src"
|
||||
"packages/frontend/test"
|
||||
"packages/misskey-bubble-game/src"
|
||||
"packages/misskey-reversi/src"
|
||||
"packages/sw/src"
|
||||
"scripts"
|
||||
)
|
||||
|
||||
for directory in "${directories[@]}"; do
|
||||
for file in $(search $directory); do
|
||||
check "$file"
|
||||
done
|
||||
done
|
||||
|
||||
if [ $counter -gt 0 ]; then
|
||||
echo "SPDX-License-Identifier is missing in $counter files."
|
||||
exit 1
|
||||
else
|
||||
echo "SPDX-License-Identifier is certainly described in all target files!"
|
||||
exit 0
|
||||
fi
|
@@ -50,9 +50,12 @@ jobs:
|
||||
|
||||
- name: Get PR ref
|
||||
id: get-ref
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
PR_REF="refs/pull/${{ github.event.issue.number }}/head"
|
||||
echo "pr-ref=$PR_REF" >> $GITHUB_OUTPUT
|
||||
PR_NUMBER=$(jq --raw-output .issue.number $GITHUB_EVENT_PATH)
|
||||
PR_REF=$(gh pr view $PR_NUMBER --json headRefName -q '.headRefName')
|
||||
echo "pr-ref=$PR_REF" > $GITHUB_OUTPUT
|
||||
|
||||
- name: Extract wait time
|
||||
id: get-wait-time
|
||||
|
4
.github/workflows/get-api-diff.yml
vendored
4
.github/workflows/get-api-diff.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.12.2]
|
||||
node-version: [20.10.0]
|
||||
api-json-name: [api-base.json, api-head.json]
|
||||
include:
|
||||
- api-json-name: api-base.json
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
version: 8
|
||||
run_install: false
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.0.2
|
||||
|
8
.github/workflows/lint.yml
vendored
8
.github/workflows/lint.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
submodules: true
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
version: 8
|
||||
run_install: false
|
||||
- uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
submodules: true
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
version: 7
|
||||
run_install: false
|
||||
- uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
submodules: true
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
version: 7
|
||||
run_install: false
|
||||
- uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
@@ -92,6 +92,6 @@ jobs:
|
||||
- run: pnpm i --frozen-lockfile
|
||||
- run: pnpm --filter misskey-js run build
|
||||
if: ${{ matrix.workspace == 'backend' }}
|
||||
- run: pnpm --filter misskey-reversi run build
|
||||
- run: pnpm --filter misskey-reversi run build:tsc
|
||||
if: ${{ matrix.workspace == 'backend' }}
|
||||
- run: pnpm --filter ${{ matrix.workspace }} run typecheck
|
||||
|
4
.github/workflows/on-release-created.yml
vendored
4
.github/workflows/on-release-created.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.12.2]
|
||||
node-version: [20.10.0]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
version: 8
|
||||
run_install: false
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.0.2
|
||||
|
9
.github/workflows/storybook.yml
vendored
9
.github/workflows/storybook.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
version: 8
|
||||
run_install: false
|
||||
- name: Use Node.js 20.x
|
||||
uses: actions/setup-node@v4.0.2
|
||||
@@ -87,13 +87,12 @@ jobs:
|
||||
if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
BRANCH="${{ github.event.pull_request.head.user.login }}:$HEAD_REF"
|
||||
if [ "$BRANCH" = "misskey-dev:$HEAD_REF" ]; then
|
||||
BRANCH="$HEAD_REF"
|
||||
BRANCH="${{ github.event.pull_request.head.user.login }}:${{ github.event.pull_request.head.ref }}"
|
||||
if [ "$BRANCH" = "misskey-dev:${{ github.event.pull_request.head.ref }}" ]; then
|
||||
BRANCH="${{ github.event.pull_request.head.ref }}"
|
||||
fi
|
||||
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 }}
|
||||
- name: Notify that Chromatic detects changes
|
||||
uses: actions/github-script@v7.0.1
|
||||
|
10
.github/workflows/test-backend.yml
vendored
10
.github/workflows/test-backend.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.12.2]
|
||||
node-version: [20.10.0]
|
||||
|
||||
services:
|
||||
postgres:
|
||||
@@ -43,10 +43,8 @@ jobs:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
version: 8
|
||||
run_install: false
|
||||
- name: Install FFmpeg
|
||||
uses: FedericoCarboni/setup-ffmpeg@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
@@ -73,7 +71,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.12.2]
|
||||
node-version: [20.10.0]
|
||||
|
||||
services:
|
||||
postgres:
|
||||
@@ -95,7 +93,7 @@ jobs:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
version: 8
|
||||
run_install: false
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.0.2
|
||||
|
8
.github/workflows/test-frontend.yml
vendored
8
.github/workflows/test-frontend.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.12.2]
|
||||
node-version: [20.10.0]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
version: 8
|
||||
run_install: false
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.0.2
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version: [20.12.2]
|
||||
node-version: [20.10.0]
|
||||
browser: [chrome]
|
||||
|
||||
services:
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
version: 7
|
||||
run_install: false
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.0.2
|
||||
|
2
.github/workflows/test-misskey-js.yml
vendored
2
.github/workflows/test-misskey-js.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.12.2]
|
||||
node-version: [20.10.0]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
|
4
.github/workflows/test-production.yml
vendored
4
.github/workflows/test-production.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.12.2]
|
||||
node-version: [20.10.0]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
version: 8
|
||||
run_install: false
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.0.2
|
||||
|
4
.github/workflows/validate-api-json.yml
vendored
4
.github/workflows/validate-api-json.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.12.2]
|
||||
node-version: [20.10.0]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
version: 8
|
||||
run_install: false
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.0.2
|
||||
|
@@ -1 +1 @@
|
||||
20.12.2
|
||||
20.10.0
|
||||
|
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@@ -3,7 +3,9 @@
|
||||
"editorconfig.editorconfig",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"Vue.volar",
|
||||
"Vue.vscode-typescript-vue-plugin",
|
||||
"Orta.vscode-jest",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"mrmlnc.vscode-json5"
|
||||
]
|
||||
}
|
||||
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -7,7 +7,7 @@
|
||||
"*.test.ts": "typescript"
|
||||
},
|
||||
"jest.jestCommandLine": "pnpm run jest",
|
||||
"jest.runMode": "on-demand",
|
||||
"jest.autoRun": "off",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": "explicit"
|
||||
},
|
||||
|
80
CHANGELOG.md
80
CHANGELOG.md
@@ -1,85 +1,13 @@
|
||||
## 2024.5.0
|
||||
|
||||
### Note
|
||||
- コントロールパネル内にあるサマリープロキシの設定個所がセキュリティから全般へ変更となります。
|
||||
- 悪意のある第三者がリモートユーザーになりすましたアクティビティを受け取れてしまう問題を修正しました。詳しくは[GitHub security advisory](https://github.com/misskey-dev/misskey/security/advisories/GHSA-2vxv-pv3m-3wvj)をご覧ください。
|
||||
## Unreleased
|
||||
|
||||
### General
|
||||
- Enhance: URLプレビューの有効化・無効化を設定できるように #13569
|
||||
- Enhance: アンテナでBotによるノートを除外できるように
|
||||
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/545)
|
||||
- Enhance: クリップのノート数を表示するように
|
||||
- Enhance: コンディショナルロールの条件として以下を新たに追加 (#13667)
|
||||
- 猫ユーザーか
|
||||
- botユーザーか
|
||||
- サスペンド済みユーザーか
|
||||
- 鍵アカウントユーザーか
|
||||
- 「アカウントを見つけやすくする」が有効なユーザーか
|
||||
- Fix: Play作成時に設定した公開範囲が機能していない問題を修正
|
||||
- Fix: 正規化されていない状態のhashtagが連合されてきたhtmlに含まれているとhashtagが正しくhashtagに復元されない問題を修正
|
||||
-
|
||||
|
||||
### Client
|
||||
- Feat: アップロードするファイルの名前をランダム文字列にできるように
|
||||
- Enhance: 自分のノートの添付ファイルから直接ファイルの詳細ページに飛べるように
|
||||
- Enhance: 広告がMisskeyと同一ドメインの場合はRouterで遷移するように
|
||||
- Enhance: リアクション・いいねの総数を表示するように
|
||||
- Enhance: リアクション受け入れが「いいねのみ」の場合はリアクション絵文字一覧を表示しないように
|
||||
- Enhance: 設定>プラグインのページからプラグインの簡易的なログやエラーを見られるように
|
||||
- 実装の都合により、プラグインは1つエラーを起こした時に即時停止するようになりました
|
||||
- Enhance: ページのデザインを変更
|
||||
- Enhance: 2要素認証(ワンタイムパスワード)の入力欄を改善
|
||||
- Enhance: 「今日誕生日のフォロー中ユーザー」ウィジェットを手動でリロードできるように
|
||||
- Enhance: 映像・音声の再生にブラウザのネイティブプレイヤーを使用できるように
|
||||
- Enhance: 映像・音声の再生メニューに「再生速度」「ループ再生」「ピクチャインピクチャ」を追加
|
||||
- Enhance: 映像・音声の再生にキーボードショートカットが使えるように
|
||||
- Enhance: ノートについているリアクションの「もっと!」から、リアクションの一覧を表示できるように
|
||||
- Enhance: リプライにて引用がある場合テキストが空でもノートできるように
|
||||
- 引用したいノートのURLをコピーしリプライ投稿画面にペーストして添付することで達成できます
|
||||
- Enhance: フォローするかどうかの確認ダイアログを出せるように
|
||||
- Enhance: Playを手動でリロードできるように
|
||||
- Enhance: 通報のコメント内のリンクをクリックした際、ウィンドウで開くように
|
||||
- Enhance: `Ui:C:postForm` および `Ui:C:postFormButton` に `localOnly` と `visibility` を設定できるように
|
||||
- Enhance: AiScriptを0.18.0にバージョンアップ
|
||||
- Fix: 一部のページ内リンクが正しく動作しない問題を修正
|
||||
- Fix: 周年の実績が閏年を考慮しない問題を修正
|
||||
- Fix: ローカルURLのプレビューポップアップが左上に表示される
|
||||
- Fix: WebGL2をサポートしないブラウザで「季節に応じた画面の演出」が有効になっているとき、Misskeyが起動できなくなる問題を修正
|
||||
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/459)
|
||||
- Fix: ページタイトルでローカルユーザーとリモートユーザーの区別がつかない問題を修正
|
||||
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/528)
|
||||
- Fix: コードブロックのシンタックスハイライトで使用される定義ファイルをCDNから取得するように #13177
|
||||
- CDNから取得せずMisskey本体にバンドルする場合は`pacakges/frontend/vite.config.ts`を修正してください。
|
||||
- Fix: タイムゾーンによっては、「今日誕生日のフォロー中ユーザー」ウィジェットが正しく動作しない問題を修正
|
||||
- Fix: CWのみの引用リノートが詳細ページで純粋なリノートとして誤って扱われてしまう問題を修正
|
||||
- Fix: ノート詳細ページにおいてCW付き引用リノートのCWボタンのラベルに「引用」が含まれていない問題を修正
|
||||
- Fix: ダイアログの入力で字数制限に違反していてもEnterキーが押せてしまう問題を修正
|
||||
- Fix: ダイレクト投稿の宛先が保存されない問題を修正
|
||||
- Fix: Playのページを離れたときに、Playが正常に初期化されない問題を修正
|
||||
- Fix: ページのOGP URLが間違っているのを修正
|
||||
- Fix: リバーシの対局を正しく共有できないことがある問題を修正
|
||||
- Fix: 通知をグループ化している際に、人数が正常に表示されないことがある問題を修正
|
||||
- Fix: 連合なしの状態の読み書きができない問題を修正
|
||||
-
|
||||
|
||||
### Server
|
||||
- Enhance: エンドポイント`antennas/update`の必須項目を`antennaId`のみに
|
||||
- Enhance: misskey-dev/summaly@5.1.0の取り込み(プレビュー生成処理の効率化)
|
||||
- Enhance: ドライブのファイルがNSFWかどうか個別に連合されるように (#13756)
|
||||
- 可能な場合、ノートの添付ファイルのセンシティブ判定がファイル単位になります
|
||||
- Fix: リモートから配送されたアクティビティにJSON-LD compactionをかける
|
||||
- Fix: フォローリクエストを作成する際に既存のものは削除するように
|
||||
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/440)
|
||||
- Fix: エンドポイント`notes/translate`のエラーを改善
|
||||
- Fix: CleanRemoteFilesProcessorService report progress from 100% (#13632)
|
||||
- Fix: 一部の音声ファイルが映像ファイルとして扱われる問題を修正
|
||||
- Fix: リプライのみの引用リノートと、CWのみの引用リノートが純粋なリノートとして誤って扱われてしまう問題を修正
|
||||
- Fix: 登録にメール認証が必須になっている場合、登録されているメールアドレスを削除できないように
|
||||
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/606)
|
||||
- Fix: Add Cache-Control to Bull Board
|
||||
- Fix: nginx経由で/files/にRangeリクエストされた場合に正しく応答できないのを修正
|
||||
- Fix: 一部のタイムラインのストリーミングでインスタンスミュートが効かない問題を修正
|
||||
- Fix: グローバルタイムラインで返信が表示されないことがある問題を修正
|
||||
- Fix: リノートをミュートしたユーザの投稿のリノートがミュートされる問題を修正
|
||||
- Fix: AP Link等は添付ファイル扱いしないようになど (#13754)
|
||||
-
|
||||
|
||||
## 2024.3.1
|
||||
|
||||
|
@@ -316,98 +316,6 @@ export const handlers = [
|
||||
|
||||
Don't forget to re-run the `.storybook/generate.js` script after adding, editing, or removing the above files.
|
||||
|
||||
## Nest
|
||||
|
||||
### Nest Service Circular dependency / Nestでサービスの循環参照でエラーが起きた場合
|
||||
|
||||
#### forwardRef
|
||||
まずは簡単に`forwardRef`を試してみる
|
||||
|
||||
```typescript
|
||||
export class FooService {
|
||||
constructor(
|
||||
@Inject(forwardRef(() => BarService))
|
||||
private barService: BarService
|
||||
) {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### OnModuleInit
|
||||
できなければ`OnModuleInit`を使う
|
||||
|
||||
```typescript
|
||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import { BarService } from '@/core/BarService';
|
||||
|
||||
@Injectable()
|
||||
export class FooService implements OnModuleInit {
|
||||
private barService: BarService // constructorから移動してくる
|
||||
|
||||
constructor(
|
||||
private moduleRef: ModuleRef,
|
||||
) {
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
this.barService = this.moduleRef.get(BarService.name);
|
||||
}
|
||||
|
||||
public async niceMethod() {
|
||||
return await this.barService.incredibleMethod({ hoge: 'fuga' });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### Service Unit Test
|
||||
テストで`onModuleInit`を呼び出す必要がある
|
||||
|
||||
```typescript
|
||||
// import ...
|
||||
|
||||
describe('test', () => {
|
||||
let app: TestingModule;
|
||||
let fooService: FooService; // for test case
|
||||
let barService: BarService; // for test case
|
||||
|
||||
beforeEach(async () => {
|
||||
app = await Test.createTestingModule({
|
||||
imports: ...,
|
||||
providers: [
|
||||
FooService,
|
||||
{ // mockする (mockは必須ではないかもしれない)
|
||||
provide: BarService,
|
||||
useFactory: () => ({
|
||||
incredibleMethod: jest.fn(),
|
||||
}),
|
||||
},
|
||||
{ // Provideにする
|
||||
provide: BarService.name,
|
||||
useExisting: BarService,
|
||||
},
|
||||
],
|
||||
})
|
||||
.useMocker(...
|
||||
.compile();
|
||||
|
||||
fooService = app.get<FooService>(FooService);
|
||||
barService = app.get<BarService>(BarService) as jest.Mocked<BarService>;
|
||||
|
||||
// onModuleInitを実行する
|
||||
await fooService.onModuleInit();
|
||||
});
|
||||
|
||||
test('nice', () => {
|
||||
await fooService.niceMethod();
|
||||
|
||||
expect(barService.incredibleMethod).toHaveBeenCalled();
|
||||
expect(barService.incredibleMethod.mock.lastCall![0])
|
||||
.toEqual({ hoge: 'fuga' });
|
||||
});
|
||||
})
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
### Misskeyのドメイン固有の概念は`Mi`をprefixする
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.4
|
||||
|
||||
ARG NODE_VERSION=20.12.2-bullseye
|
||||
ARG NODE_VERSION=20.10.0-bullseye
|
||||
|
||||
# build assets & compile TypeScript
|
||||
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
describe('Before setup instance', () => {
|
||||
beforeEach(() => {
|
||||
cy.resetState();
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
describe('Router transition', () => {
|
||||
describe('Redirect', () => {
|
||||
// サーバの初期化。ルートのテストに関しては各describeごとに1度だけ実行で十分だと思う(使いまわした方が早い)
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/* flaky
|
||||
describe('After user signed in', () => {
|
||||
beforeEach(() => {
|
@@ -30,13 +30,9 @@ Cypress.Commands.add('visitHome', () => {
|
||||
})
|
||||
|
||||
Cypress.Commands.add('resetState', () => {
|
||||
// iframe.contentWindow.indexedDB.deleteDatabase() がchromeのバグで使用できないため、indexedDBを無効化している。
|
||||
// see https://github.com/misskey-dev/misskey/issues/13605#issuecomment-2053652123
|
||||
/*
|
||||
cy.window().then(win => {
|
||||
cy.window(win => {
|
||||
win.indexedDB.deleteDatabase('keyval-store');
|
||||
});
|
||||
*/
|
||||
cy.request('POST', '/api/reset-db', {}).as('reset');
|
||||
cy.get('@reset').its('status').should('equal', 204);
|
||||
cy.reload(true);
|
@@ -1,19 +0,0 @@
|
||||
declare global {
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
login(username: string, password: string): Chainable<void>;
|
||||
|
||||
registerUser(
|
||||
username: string,
|
||||
password: string,
|
||||
isAdmin?: boolean
|
||||
): Chainable<void>;
|
||||
|
||||
resetState(): Chainable<void>;
|
||||
|
||||
visitHome(): Chainable<void>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "es5"],
|
||||
"target": "es5",
|
||||
"types": ["cypress", "node"]
|
||||
},
|
||||
"include": ["./**/*.ts"]
|
||||
}
|
166
locales/index.d.ts
vendored
166
locales/index.d.ts
vendored
@@ -1616,10 +1616,6 @@ export interface Locale extends ILocale {
|
||||
* 除外キーワード
|
||||
*/
|
||||
"antennaExcludeKeywords": string;
|
||||
/**
|
||||
* Botアカウントを除外
|
||||
*/
|
||||
"antennaExcludeBots": string;
|
||||
/**
|
||||
* スペースで区切るとAND指定になり、改行で区切るとOR指定になります
|
||||
*/
|
||||
@@ -1996,10 +1992,6 @@ export interface Locale extends ILocale {
|
||||
* ノートのアクションをホバー時のみ表示する
|
||||
*/
|
||||
"showNoteActionsOnlyHover": string;
|
||||
/**
|
||||
* ノートのリアクション数を表示する
|
||||
*/
|
||||
"showReactionsCount": string;
|
||||
/**
|
||||
* 履歴はありません
|
||||
*/
|
||||
@@ -4916,46 +4908,6 @@ export interface Locale extends ILocale {
|
||||
* リトライ
|
||||
*/
|
||||
"gameRetry": string;
|
||||
/**
|
||||
* 使用しない場合は空欄にしてください
|
||||
*/
|
||||
"notUsePleaseLeaveBlank": string;
|
||||
/**
|
||||
* ワンタイムパスワードを使う
|
||||
*/
|
||||
"useTotp": string;
|
||||
/**
|
||||
* バックアップコードを使う
|
||||
*/
|
||||
"useBackupCode": string;
|
||||
/**
|
||||
* アプリを起動
|
||||
*/
|
||||
"launchApp": string;
|
||||
/**
|
||||
* 動画・音声の再生にブラウザのUIを使用する
|
||||
*/
|
||||
"useNativeUIForVideoAudioPlayer": string;
|
||||
/**
|
||||
* オリジナルのファイル名を保持
|
||||
*/
|
||||
"keepOriginalFilename": string;
|
||||
/**
|
||||
* この設定をオフにすると、アップロード時にファイル名が自動でランダム文字列に置き換えられます。
|
||||
*/
|
||||
"keepOriginalFilenameDescription": string;
|
||||
/**
|
||||
* 説明文はありません
|
||||
*/
|
||||
"noDescription": string;
|
||||
/**
|
||||
* フォローの際常に確認する
|
||||
*/
|
||||
"alwaysConfirmFollow": string;
|
||||
/**
|
||||
* お問い合わせ
|
||||
*/
|
||||
"inquiry": string;
|
||||
"_bubbleGame": {
|
||||
/**
|
||||
* 遊び方
|
||||
@@ -6596,26 +6548,6 @@ export interface Locale extends ILocale {
|
||||
* リモートユーザー
|
||||
*/
|
||||
"isRemote": string;
|
||||
/**
|
||||
* 猫ユーザー
|
||||
*/
|
||||
"isCat": string;
|
||||
/**
|
||||
* botユーザー
|
||||
*/
|
||||
"isBot": string;
|
||||
/**
|
||||
* サスペンド済みユーザー
|
||||
*/
|
||||
"isSuspended": string;
|
||||
/**
|
||||
* 鍵アカウントユーザー
|
||||
*/
|
||||
"isLocked": string;
|
||||
/**
|
||||
* 「アカウントを見つけやすくする」が有効なユーザー
|
||||
*/
|
||||
"isExplorable": string;
|
||||
/**
|
||||
* アカウント作成から~以内
|
||||
*/
|
||||
@@ -6869,10 +6801,6 @@ export interface Locale extends ILocale {
|
||||
* ソースを表示
|
||||
*/
|
||||
"viewSource": string;
|
||||
/**
|
||||
* ログを表示
|
||||
*/
|
||||
"viewLog": string;
|
||||
};
|
||||
"_preferencesBackups": {
|
||||
/**
|
||||
@@ -7590,9 +7518,13 @@ export interface Locale extends ILocale {
|
||||
*/
|
||||
"step1": ParameterizedString<"a" | "b">;
|
||||
/**
|
||||
* 次に、表示されているQRコードをアプリでスキャンするか、ボタンをクリックして端末上でアプリを開きます。
|
||||
* 次に、表示されているQRコードをアプリでスキャンします。
|
||||
*/
|
||||
"step2": string;
|
||||
/**
|
||||
* QRコードをクリックすると、お使いの端末にインストールされている認証アプリやキーリングに登録できます。
|
||||
*/
|
||||
"step2Click": string;
|
||||
/**
|
||||
* デスクトップアプリを使用する場合は次のURIを入力します
|
||||
*/
|
||||
@@ -7685,10 +7617,6 @@ export interface Locale extends ILocale {
|
||||
* バックアップコードが全て使用されました。認証アプリを利用できない場合、これ以上アカウントにアクセスできなくなります。認証アプリを再登録してください。
|
||||
*/
|
||||
"backupCodesExhaustedWarning": string;
|
||||
/**
|
||||
* 詳細なガイドはこちら
|
||||
*/
|
||||
"moreDetailedGuideHere": string;
|
||||
};
|
||||
"_permissions": {
|
||||
/**
|
||||
@@ -8699,10 +8627,6 @@ export interface Locale extends ILocale {
|
||||
* 説明
|
||||
*/
|
||||
"summary": string;
|
||||
/**
|
||||
* 非公開に設定するとプロフィールに表示されなくなりますが、URLを知っている人は引き続きアクセスできます。
|
||||
*/
|
||||
"visibilityDescription": string;
|
||||
};
|
||||
"_pages": {
|
||||
/**
|
||||
@@ -8874,14 +8798,6 @@ export interface Locale extends ILocale {
|
||||
* ボタン
|
||||
*/
|
||||
"button": string;
|
||||
/**
|
||||
* 動的ブロック
|
||||
*/
|
||||
"dynamic": string;
|
||||
/**
|
||||
* このブロックは廃止されています。今後は{play}を利用してください。
|
||||
*/
|
||||
"dynamicDescription": ParameterizedString<"play">;
|
||||
/**
|
||||
* ノート埋め込み
|
||||
*/
|
||||
@@ -8993,10 +8909,6 @@ export interface Locale extends ILocale {
|
||||
* {n}人がリアクションしました
|
||||
*/
|
||||
"reactedBySomeUsers": ParameterizedString<"n">;
|
||||
/**
|
||||
* {n}人がいいねしました
|
||||
*/
|
||||
"likedBySomeUsers": ParameterizedString<"n">;
|
||||
/**
|
||||
* {n}人がリノートしました
|
||||
*/
|
||||
@@ -9836,74 +9748,6 @@ export interface Locale extends ILocale {
|
||||
*/
|
||||
"header": string;
|
||||
};
|
||||
"_urlPreviewSetting": {
|
||||
/**
|
||||
* URLプレビューの設定
|
||||
*/
|
||||
"title": string;
|
||||
/**
|
||||
* URLプレビューを有効にする
|
||||
*/
|
||||
"enable": string;
|
||||
/**
|
||||
* プレビュー取得時のタイムアウト(ms)
|
||||
*/
|
||||
"timeout": string;
|
||||
/**
|
||||
* プレビュー取得の所要時間がこの値を超えた場合、プレビューは生成されません。
|
||||
*/
|
||||
"timeoutDescription": string;
|
||||
/**
|
||||
* Content-Lengthの最大値(byte)
|
||||
*/
|
||||
"maximumContentLength": string;
|
||||
/**
|
||||
* Content-Lengthがこの値を超えた場合、プレビューは生成されません。
|
||||
*/
|
||||
"maximumContentLengthDescription": string;
|
||||
/**
|
||||
* Content-Lengthが取得できた場合のみプレビューを生成
|
||||
*/
|
||||
"requireContentLength": string;
|
||||
/**
|
||||
* 相手サーバがContent-Lengthを返さない場合、プレビューは生成されません。
|
||||
*/
|
||||
"requireContentLengthDescription": string;
|
||||
/**
|
||||
* User-Agent
|
||||
*/
|
||||
"userAgent": string;
|
||||
/**
|
||||
* プレビュー取得時に使用されるUser-Agentを設定します。空欄の場合、デフォルトのUser-Agentが使用されます。
|
||||
*/
|
||||
"userAgentDescription": string;
|
||||
/**
|
||||
* プレビューを生成するプロキシのエンドポイント
|
||||
*/
|
||||
"summaryProxy": string;
|
||||
/**
|
||||
* Misskey本体ではなく、サマリープロキシを使用してプレビューを生成します。
|
||||
*/
|
||||
"summaryProxyDescription": string;
|
||||
/**
|
||||
* プロキシには下記パラメータがクエリ文字列として連携されます。プロキシ側がこれらをサポートしない場合、設定値は無視されます。
|
||||
*/
|
||||
"summaryProxyDescription2": string;
|
||||
};
|
||||
"_mediaControls": {
|
||||
/**
|
||||
* ピクチャインピクチャ
|
||||
*/
|
||||
"pip": string;
|
||||
/**
|
||||
* 再生速度
|
||||
*/
|
||||
"playbackRate": string;
|
||||
/**
|
||||
* ループ再生
|
||||
*/
|
||||
"loop": string;
|
||||
};
|
||||
}
|
||||
declare const locales: {
|
||||
[lang: string]: Locale;
|
||||
|
@@ -400,7 +400,6 @@ name: "名前"
|
||||
antennaSource: "受信ソース"
|
||||
antennaKeywords: "受信キーワード"
|
||||
antennaExcludeKeywords: "除外キーワード"
|
||||
antennaExcludeBots: "Botアカウントを除外"
|
||||
antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
|
||||
notifyAntenna: "新しいノートを通知する"
|
||||
withFileAntenna: "ファイルが添付されたノートのみ"
|
||||
@@ -495,7 +494,6 @@ emojiStyle: "絵文字のスタイル"
|
||||
native: "ネイティブ"
|
||||
disableDrawer: "メニューをドロワーで表示しない"
|
||||
showNoteActionsOnlyHover: "ノートのアクションをホバー時のみ表示する"
|
||||
showReactionsCount: "ノートのリアクション数を表示する"
|
||||
noHistory: "履歴はありません"
|
||||
signinHistory: "ログイン履歴"
|
||||
enableAdvancedMfm: "高度なMFMを有効にする"
|
||||
@@ -1225,16 +1223,6 @@ enableHorizontalSwipe: "スワイプしてタブを切り替える"
|
||||
loading: "読み込み中"
|
||||
surrender: "やめる"
|
||||
gameRetry: "リトライ"
|
||||
notUsePleaseLeaveBlank: "使用しない場合は空欄にしてください"
|
||||
useTotp: "ワンタイムパスワードを使う"
|
||||
useBackupCode: "バックアップコードを使う"
|
||||
launchApp: "アプリを起動"
|
||||
useNativeUIForVideoAudioPlayer: "動画・音声の再生にブラウザのUIを使用する"
|
||||
keepOriginalFilename: "オリジナルのファイル名を保持"
|
||||
keepOriginalFilenameDescription: "この設定をオフにすると、アップロード時にファイル名が自動でランダム文字列に置き換えられます。"
|
||||
noDescription: "説明文はありません"
|
||||
alwaysConfirmFollow: "フォローの際常に確認する"
|
||||
inquiry: "お問い合わせ"
|
||||
|
||||
_bubbleGame:
|
||||
howToPlay: "遊び方"
|
||||
@@ -1704,11 +1692,6 @@ _role:
|
||||
roleAssignedTo: "マニュアルロールにアサイン済み"
|
||||
isLocal: "ローカルユーザー"
|
||||
isRemote: "リモートユーザー"
|
||||
isCat: "猫ユーザー"
|
||||
isBot: "botユーザー"
|
||||
isSuspended: "サスペンド済みユーザー"
|
||||
isLocked: "鍵アカウントユーザー"
|
||||
isExplorable: "「アカウントを見つけやすくする」が有効なユーザー"
|
||||
createdLessThan: "アカウント作成から~以内"
|
||||
createdMoreThan: "アカウント作成から~経過"
|
||||
followersLessThanOrEq: "フォロワー数が~以下"
|
||||
@@ -1788,7 +1771,6 @@ _plugin:
|
||||
installWarn: "信頼できないプラグインはインストールしないでください。"
|
||||
manage: "プラグインの管理"
|
||||
viewSource: "ソースを表示"
|
||||
viewLog: "ログを表示"
|
||||
|
||||
_preferencesBackups:
|
||||
list: "作成したバックアップ"
|
||||
@@ -1995,7 +1977,8 @@ _2fa:
|
||||
alreadyRegistered: "既に設定は完了しています。"
|
||||
registerTOTP: "認証アプリの設定を開始"
|
||||
step1: "まず、{a}や{b}などの認証アプリをお使いのデバイスにインストールします。"
|
||||
step2: "次に、表示されているQRコードをアプリでスキャンするか、ボタンをクリックして端末上でアプリを開きます。"
|
||||
step2: "次に、表示されているQRコードをアプリでスキャンします。"
|
||||
step2Click: "QRコードをクリックすると、お使いの端末にインストールされている認証アプリやキーリングに登録できます。"
|
||||
step2Uri: "デスクトップアプリを使用する場合は次のURIを入力します"
|
||||
step3Title: "確認コードを入力"
|
||||
step3: "アプリに表示されている確認コード(トークン)を入力します。"
|
||||
@@ -2019,7 +2002,6 @@ _2fa:
|
||||
backupCodesDescription: "認証アプリが使用できなくなった場合、以下のバックアップコードを使ってアカウントにアクセスできます。これらのコードは必ず安全な場所に保管してください。各コードは一回だけ使用できます。"
|
||||
backupCodeUsedWarning: "バックアップコードが使用されました。認証アプリが使えなくなっている場合、なるべく早く認証アプリを再設定してください。"
|
||||
backupCodesExhaustedWarning: "バックアップコードが全て使用されました。認証アプリを利用できない場合、これ以上アカウントにアクセスできなくなります。認証アプリを再登録してください。"
|
||||
moreDetailedGuideHere: "詳細なガイドはこちら"
|
||||
|
||||
_permissions:
|
||||
"read:account": "アカウントの情報を見る"
|
||||
@@ -2296,7 +2278,6 @@ _play:
|
||||
title: "タイトル"
|
||||
script: "スクリプト"
|
||||
summary: "説明"
|
||||
visibilityDescription: "非公開に設定するとプロフィールに表示されなくなりますが、URLを知っている人は引き続きアクセスできます。"
|
||||
|
||||
_pages:
|
||||
newPage: "ページの作成"
|
||||
@@ -2342,8 +2323,6 @@ _pages:
|
||||
section: "セクション"
|
||||
image: "画像"
|
||||
button: "ボタン"
|
||||
dynamic: "動的ブロック"
|
||||
dynamicDescription: "このブロックは廃止されています。今後は{play}を利用してください。"
|
||||
|
||||
note: "ノート埋め込み"
|
||||
_note:
|
||||
@@ -2376,7 +2355,6 @@ _notification:
|
||||
sendTestNotification: "テスト通知を送信する"
|
||||
notificationWillBeDisplayedLikeThis: "通知はこのように表示されます"
|
||||
reactedBySomeUsers: "{n}人がリアクションしました"
|
||||
likedBySomeUsers: "{n}人がいいねしました"
|
||||
renotedBySomeUsers: "{n}人がリノートしました"
|
||||
followedBySomeUsers: "{n}人にフォローされました"
|
||||
flushNotification: "通知の履歴をリセットする"
|
||||
@@ -2619,22 +2597,3 @@ _offlineScreen:
|
||||
title: "オフライン - サーバーに接続できません"
|
||||
header: "サーバーに接続できません"
|
||||
|
||||
_urlPreviewSetting:
|
||||
title: "URLプレビューの設定"
|
||||
enable: "URLプレビューを有効にする"
|
||||
timeout: "プレビュー取得時のタイムアウト(ms)"
|
||||
timeoutDescription: "プレビュー取得の所要時間がこの値を超えた場合、プレビューは生成されません。"
|
||||
maximumContentLength: "Content-Lengthの最大値(byte)"
|
||||
maximumContentLengthDescription: "Content-Lengthがこの値を超えた場合、プレビューは生成されません。"
|
||||
requireContentLength: "Content-Lengthが取得できた場合のみプレビューを生成"
|
||||
requireContentLengthDescription: "相手サーバがContent-Lengthを返さない場合、プレビューは生成されません。"
|
||||
userAgent: "User-Agent"
|
||||
userAgentDescription: "プレビュー取得時に使用されるUser-Agentを設定します。空欄の場合、デフォルトのUser-Agentが使用されます。"
|
||||
summaryProxy: "プレビューを生成するプロキシのエンドポイント"
|
||||
summaryProxyDescription: "Misskey本体ではなく、サマリープロキシを使用してプレビューを生成します。"
|
||||
summaryProxyDescription2: "プロキシには下記パラメータがクエリ文字列として連携されます。プロキシ側がこれらをサポートしない場合、設定値は無視されます。"
|
||||
|
||||
_mediaControls:
|
||||
pip: "ピクチャインピクチャ"
|
||||
playbackRate: "再生速度"
|
||||
loop: "ループ再生"
|
||||
|
145
package.json
145
package.json
@@ -1,76 +1,73 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "2024.5.0-beta.1",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/misskey-dev/misskey.git"
|
||||
},
|
||||
"packageManager": "pnpm@9.0.6",
|
||||
"workspaces": [
|
||||
"packages/frontend",
|
||||
"packages/backend",
|
||||
"packages/sw",
|
||||
"packages/misskey-js",
|
||||
"packages/misskey-reversi",
|
||||
"packages/misskey-bubble-game"
|
||||
],
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build-pre": "node ./scripts/build-pre.js",
|
||||
"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",
|
||||
"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",
|
||||
"migrate": "cd packages/backend && pnpm migrate",
|
||||
"revert": "cd packages/backend && pnpm revert",
|
||||
"check:connect": "cd packages/backend && pnpm check:connect",
|
||||
"migrateandstart": "pnpm migrate && pnpm start",
|
||||
"watch": "pnpm dev",
|
||||
"dev": "node scripts/dev.mjs",
|
||||
"lint": "pnpm -r lint",
|
||||
"cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts",
|
||||
"cy:run": "pnpm cypress run",
|
||||
"e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy:run",
|
||||
"jest": "cd packages/backend && pnpm jest",
|
||||
"jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage",
|
||||
"test": "pnpm -r test",
|
||||
"test-and-coverage": "pnpm -r test-and-coverage",
|
||||
"clean": "node ./scripts/clean.js",
|
||||
"clean-all": "node ./scripts/clean-all.js",
|
||||
"cleanall": "pnpm clean-all"
|
||||
},
|
||||
"resolutions": {
|
||||
"chokidar": "3.5.3",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"dependencies": {
|
||||
"cssnano": "6.1.2",
|
||||
"execa": "8.0.1",
|
||||
"fast-glob": "3.3.2",
|
||||
"ignore-walk": "6.0.4",
|
||||
"js-yaml": "4.1.0",
|
||||
"postcss": "8.4.38",
|
||||
"tar": "6.2.1",
|
||||
"terser": "5.30.3",
|
||||
"typescript": "5.4.5",
|
||||
"esbuild": "0.20.2",
|
||||
"glob": "10.3.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.12.7",
|
||||
"@typescript-eslint/eslint-plugin": "7.7.1",
|
||||
"@typescript-eslint/parser": "7.7.1",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "13.7.3",
|
||||
"eslint": "8.57.0",
|
||||
"ncp": "2.0.0",
|
||||
"start-server-and-test": "2.0.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tensorflow/tfjs-core": "4.4.0"
|
||||
}
|
||||
"name": "misskey",
|
||||
"version": "2024.3.1",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/misskey-dev/misskey.git"
|
||||
},
|
||||
"packageManager": "pnpm@8.15.4",
|
||||
"workspaces": [
|
||||
"packages/frontend",
|
||||
"packages/backend",
|
||||
"packages/sw",
|
||||
"packages/misskey-js",
|
||||
"packages/misskey-reversi",
|
||||
"packages/misskey-bubble-game"
|
||||
],
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build-pre": "node ./scripts/build-pre.js",
|
||||
"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",
|
||||
"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",
|
||||
"migrate": "cd packages/backend && pnpm migrate",
|
||||
"revert": "cd packages/backend && pnpm revert",
|
||||
"check:connect": "cd packages/backend && pnpm check:connect",
|
||||
"migrateandstart": "pnpm migrate && pnpm start",
|
||||
"watch": "pnpm dev",
|
||||
"dev": "node scripts/dev.mjs",
|
||||
"lint": "pnpm -r lint",
|
||||
"cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts",
|
||||
"cy:run": "pnpm cypress run",
|
||||
"e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy:run",
|
||||
"jest": "cd packages/backend && pnpm jest",
|
||||
"jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage",
|
||||
"test": "pnpm -r test",
|
||||
"test-and-coverage": "pnpm -r test-and-coverage",
|
||||
"clean": "node ./scripts/clean.js",
|
||||
"clean-all": "node ./scripts/clean-all.js",
|
||||
"cleanall": "pnpm clean-all"
|
||||
},
|
||||
"resolutions": {
|
||||
"chokidar": "3.5.3",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"dependencies": {
|
||||
"cssnano": "6.0.5",
|
||||
"execa": "8.0.1",
|
||||
"fast-glob": "3.3.2",
|
||||
"ignore-walk": "6.0.4",
|
||||
"js-yaml": "4.1.0",
|
||||
"postcss": "8.4.35",
|
||||
"tar": "6.2.0",
|
||||
"terser": "5.28.1",
|
||||
"typescript": "5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "7.1.0",
|
||||
"@typescript-eslint/parser": "7.1.0",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "13.6.6",
|
||||
"eslint": "8.57.0",
|
||||
"ncp": "2.0.0",
|
||||
"start-server-and-test": "2.0.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tensorflow/tfjs-core": "4.4.0"
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,5 @@
|
||||
},
|
||||
"target": "es2022"
|
||||
},
|
||||
"minify": false,
|
||||
"sourceMaps": "inline"
|
||||
"minify": false
|
||||
}
|
||||
|
@@ -19,6 +19,6 @@
|
||||
</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>
|
||||
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import Redis from 'ioredis';
|
||||
import { loadConfig } from '../built/config.js';
|
||||
import { loadConfig } from './built/config.js';
|
||||
|
||||
const config = loadConfig();
|
||||
const redis = new Redis(config.redis);
|
8
packages/backend/generate_api_json.js
Normal file
8
packages/backend/generate_api_json.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import { loadConfig } from './built/config.js'
|
||||
import { genOpenapiSpec } from './built/server/api/openapi/gen-spec.js'
|
||||
import { writeFileSync } from "node:fs";
|
||||
|
||||
const config = loadConfig();
|
||||
const spec = genOpenapiSpec(config, true);
|
||||
|
||||
writeFileSync('./built/api.json', JSON.stringify(spec), 'utf-8');
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class UserBlacklistAnntena1689325027964 {
|
||||
name = 'UserBlacklistAnntena1689325027964'
|
||||
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class FixRenoteMuting1690417561185 {
|
||||
name = 'FixRenoteMuting1690417561185'
|
||||
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class ChangeCacheRemoteFilesDefault1690417561186 {
|
||||
name = 'ChangeCacheRemoteFilesDefault1690417561186'
|
||||
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class Fix1690417561187 {
|
||||
name = 'Fix1690417561187'
|
||||
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class User2faBackupCodes1690569881926 {
|
||||
name = 'User2faBackupCodes1690569881926'
|
||||
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class RefineAnnouncement1691649257651 {
|
||||
name = 'RefineAnnouncement1691649257651'
|
||||
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class RefineAnnouncement21691657412740 {
|
||||
name = 'RefineAnnouncement21691657412740'
|
||||
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class VerifiedLinks1695260774117 {
|
||||
name = 'VerifiedLinks1695260774117'
|
||||
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class FollowingNotify1695288787870 {
|
||||
name = 'FollowingNotify1695288787870'
|
||||
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class ShortName1695440131671 {
|
||||
name = 'ShortName1695440131671'
|
||||
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class MutingNotificationTypes1695605508898 {
|
||||
name = 'MutingNotificationTypes1695605508898'
|
||||
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class NoteUpdatedAt1695901659683 {
|
||||
name = 'NoteUpdatedAt1695901659683'
|
||||
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class UserListMembership1696323464251 {
|
||||
name = 'UserListMembership1696323464251'
|
||||
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class Hibernation1696331570827 {
|
||||
name = 'Hibernation1696331570827'
|
||||
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class Clean1696332072038 {
|
||||
name = 'Clean1696332072038'
|
||||
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class HardMute1700383825690 {
|
||||
name = 'HardMute1700383825690'
|
||||
|
||||
|
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class UrlPreviewMeta1710512074000 {
|
||||
name = 'UrlPreviewMeta1710512074000'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`
|
||||
alter table meta
|
||||
rename column "summalyProxy" to "urlPreviewSummaryProxyUrl";
|
||||
alter table meta
|
||||
add "urlPreviewEnabled" boolean default true not null;
|
||||
alter table meta
|
||||
add "urlPreviewTimeout" integer default 10000 not null;
|
||||
alter table meta
|
||||
add "urlPreviewMaximumContentLength" bigint default 10485760 not null;
|
||||
alter table meta
|
||||
add "urlPreviewRequireContentLength" boolean default false not null;
|
||||
alter table meta
|
||||
add "urlPreviewUserAgent" varchar(1024) default null;
|
||||
`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`
|
||||
alter table meta
|
||||
rename column "urlPreviewSummaryProxyUrl" to "summalyProxy";
|
||||
alter table meta
|
||||
drop column "urlPreviewEnabled";
|
||||
alter table meta
|
||||
drop column "urlPreviewTimeout";
|
||||
alter table meta
|
||||
drop column "urlPreviewMaximumContentLength";
|
||||
alter table meta
|
||||
drop column "urlPreviewRequireContentLength";
|
||||
alter table meta
|
||||
drop column "urlPreviewUserAgent";
|
||||
`);
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class AntennaExcludeBots1710919614510 {
|
||||
name = 'AntennaExcludeBots1710919614510'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "antenna" ADD "excludeBots" boolean NOT NULL DEFAULT false`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "excludeBots"`);
|
||||
}
|
||||
}
|
@@ -11,15 +11,15 @@
|
||||
"start:test": "cross-env NODE_ENV=test node ./built/boot/entry.js",
|
||||
"migrate": "pnpm typeorm migration:run -d ormconfig.js",
|
||||
"revert": "pnpm typeorm migration:revert -d ormconfig.js",
|
||||
"check:connect": "node ./scripts/check_connect.js",
|
||||
"build": "swc src -d built -D --strip-leading-paths",
|
||||
"build:test": "swc test-server -d built-test -D --config-file test-server/.swcrc --strip-leading-paths",
|
||||
"watch:swc": "swc src -d built -D -w --strip-leading-paths",
|
||||
"check:connect": "node ./check_connect.js",
|
||||
"build": "swc src -d built -D",
|
||||
"build:test": "swc test-server -d built-test -D --config-file test-server/.swcrc",
|
||||
"watch:swc": "swc src -d built -D -w",
|
||||
"build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
|
||||
"watch": "node ./scripts/watch.mjs",
|
||||
"watch": "node watch.mjs",
|
||||
"restart": "pnpm build && pnpm start",
|
||||
"dev": "node ./scripts/dev.mjs",
|
||||
"typecheck": "tsc --noEmit && tsc -p test --noEmit",
|
||||
"dev": "nodemon -w src -e ts,js,mjs,cjs,json --exec \"cross-env NODE_ENV=development pnpm run restart\"",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"eslint": "eslint --quiet \"src/**/*.ts\"",
|
||||
"lint": "pnpm typecheck && pnpm eslint",
|
||||
"jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.unit.cjs",
|
||||
@@ -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": "pnpm build && node ./generate_api_json.js"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@swc/core-android-arm64": "1.3.11",
|
||||
@@ -67,39 +67,38 @@
|
||||
"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",
|
||||
"@discordapp/twemoji": "15.0.3",
|
||||
"@bull-board/api": "5.14.2",
|
||||
"@bull-board/fastify": "5.14.2",
|
||||
"@bull-board/ui": "5.14.2",
|
||||
"@discordapp/twemoji": "15.0.2",
|
||||
"@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/view": "9.1.0",
|
||||
"@fastify/cors": "8.5.0",
|
||||
"@fastify/express": "2.3.0",
|
||||
"@fastify/http-proxy": "9.3.0",
|
||||
"@fastify/multipart": "8.1.0",
|
||||
"@fastify/static": "6.12.0",
|
||||
"@fastify/view": "8.2.0",
|
||||
"@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",
|
||||
"@misskey-dev/summaly": "5.0.3",
|
||||
"@nestjs/common": "10.3.3",
|
||||
"@nestjs/core": "10.3.3",
|
||||
"@nestjs/testing": "10.3.3",
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@simplewebauthn/server": "10.0.0",
|
||||
"@simplewebauthn/server": "9.0.3",
|
||||
"@sinonjs/fake-timers": "11.2.2",
|
||||
"@smithy/node-http-handler": "2.5.0",
|
||||
"@swc/cli": "0.3.12",
|
||||
"@swc/core": "1.4.17",
|
||||
"@twemoji/parser": "15.1.1",
|
||||
"@smithy/node-http-handler": "2.1.10",
|
||||
"@swc/cli": "0.1.63",
|
||||
"@swc/core": "1.3.107",
|
||||
"@twemoji/parser": "15.0.0",
|
||||
"accepts": "1.3.8",
|
||||
"ajv": "8.13.0",
|
||||
"archiver": "7.0.1",
|
||||
"async-mutex": "0.5.0",
|
||||
"ajv": "8.12.0",
|
||||
"archiver": "6.0.1",
|
||||
"async-mutex": "0.4.1",
|
||||
"bcryptjs": "2.4.3",
|
||||
"blurhash": "2.0.5",
|
||||
"body-parser": "1.20.2",
|
||||
"bullmq": "5.7.8",
|
||||
"bullmq": "5.4.0",
|
||||
"cacheable-lookup": "7.0.0",
|
||||
"cbor": "9.0.2",
|
||||
"chalk": "5.3.0",
|
||||
@@ -110,84 +109,85 @@
|
||||
"content-disposition": "0.5.4",
|
||||
"date-fns": "2.30.0",
|
||||
"deep-email-validator": "0.1.21",
|
||||
"fastify": "4.26.2",
|
||||
"fastify": "4.25.2",
|
||||
"fastify-raw-body": "4.3.0",
|
||||
"feed": "4.2.2",
|
||||
"file-type": "19.0.0",
|
||||
"fluent-ffmpeg": "2.1.2",
|
||||
"form-data": "4.0.0",
|
||||
"got": "14.2.1",
|
||||
"happy-dom": "14.7.1",
|
||||
"got": "14.2.0",
|
||||
"happy-dom": "10.0.3",
|
||||
"hpagent": "1.2.0",
|
||||
"htmlescape": "1.1.1",
|
||||
"http-link-header": "1.1.3",
|
||||
"ioredis": "5.4.1",
|
||||
"http-link-header": "1.1.2",
|
||||
"ioredis": "5.3.2",
|
||||
"ip-cidr": "3.1.0",
|
||||
"ipaddr.js": "2.2.0",
|
||||
"ipaddr.js": "2.1.0",
|
||||
"is-svg": "5.0.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsdom": "24.0.0",
|
||||
"jsdom": "23.2.0",
|
||||
"json5": "2.2.3",
|
||||
"jsonld": "8.3.2",
|
||||
"jsrsasign": "11.1.0",
|
||||
"meilisearch": "0.38.0",
|
||||
"meilisearch": "0.37.0",
|
||||
"mfm-js": "0.24.0",
|
||||
"microformats-parser": "2.0.2",
|
||||
"mime-types": "2.1.35",
|
||||
"misskey-js": "workspace:*",
|
||||
"misskey-reversi": "workspace:*",
|
||||
"ms": "3.0.0-canary.1",
|
||||
"nanoid": "5.0.7",
|
||||
"nanoid": "5.0.6",
|
||||
"nested-property": "4.0.0",
|
||||
"node-fetch": "3.3.2",
|
||||
"nodemailer": "6.9.13",
|
||||
"nodemailer": "6.9.10",
|
||||
"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.2.2",
|
||||
"parse5": "7.1.2",
|
||||
"pg": "8.11.5",
|
||||
"pg": "8.11.3",
|
||||
"pkce-challenge": "4.1.0",
|
||||
"probe-image-size": "7.2.3",
|
||||
"promise-limit": "2.7.0",
|
||||
"pug": "3.0.2",
|
||||
"punycode": "2.3.1",
|
||||
"pureimage": "0.3.17",
|
||||
"qrcode": "1.5.3",
|
||||
"random-seed": "0.3.0",
|
||||
"ratelimiter": "3.4.1",
|
||||
"re2": "1.20.10",
|
||||
"re2": "1.20.9",
|
||||
"redis-lock": "0.1.4",
|
||||
"reflect-metadata": "0.2.2",
|
||||
"reflect-metadata": "0.2.1",
|
||||
"rename": "1.0.4",
|
||||
"rss-parser": "3.13.0",
|
||||
"rxjs": "7.8.1",
|
||||
"sanitize-html": "2.13.0",
|
||||
"sanitize-html": "2.12.1",
|
||||
"secure-json-parse": "2.7.0",
|
||||
"sharp": "0.33.3",
|
||||
"sharp": "0.33.2",
|
||||
"slacc": "0.0.10",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"systeminformation": "5.22.7",
|
||||
"systeminformation": "5.22.0",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tmp": "0.2.3",
|
||||
"tmp": "0.2.2",
|
||||
"tsc-alias": "1.8.8",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typeorm": "0.3.20",
|
||||
"typescript": "5.4.5",
|
||||
"typescript": "5.3.3",
|
||||
"ulid": "2.3.0",
|
||||
"vary": "1.1.2",
|
||||
"web-push": "3.6.7",
|
||||
"ws": "8.17.0",
|
||||
"ws": "8.16.0",
|
||||
"xev": "3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.7.0",
|
||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||
"@nestjs/platform-express": "10.3.8",
|
||||
"@simplewebauthn/types": "10.0.0",
|
||||
"@swc/jest": "0.2.36",
|
||||
"@nestjs/platform-express": "10.3.3",
|
||||
"@simplewebauthn/types": "9.0.1",
|
||||
"@swc/jest": "0.2.31",
|
||||
"@types/accepts": "1.3.7",
|
||||
"@types/archiver": "6.0.2",
|
||||
"@types/bcryptjs": "2.4.6",
|
||||
@@ -197,20 +197,20 @@
|
||||
"@types/fluent-ffmpeg": "2.1.24",
|
||||
"@types/htmlescape": "^1.1.3",
|
||||
"@types/http-link-header": "1.0.5",
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/jest": "29.5.11",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/jsdom": "21.1.6",
|
||||
"@types/jsonld": "1.5.13",
|
||||
"@types/jsrsasign": "10.5.14",
|
||||
"@types/jsrsasign": "10.5.12",
|
||||
"@types/mime-types": "2.1.4",
|
||||
"@types/ms": "0.7.34",
|
||||
"@types/node": "20.12.7",
|
||||
"@types/node": "20.11.22",
|
||||
"@types/node-fetch": "3.0.3",
|
||||
"@types/nodemailer": "6.4.15",
|
||||
"@types/nodemailer": "6.4.14",
|
||||
"@types/oauth": "0.9.4",
|
||||
"@types/oauth2orize": "1.11.5",
|
||||
"@types/oauth2orize": "1.11.3",
|
||||
"@types/oauth2orize-pkce": "0.1.2",
|
||||
"@types/pg": "8.11.5",
|
||||
"@types/pg": "8.11.2",
|
||||
"@types/pug": "2.0.10",
|
||||
"@types/punycode": "2.1.4",
|
||||
"@types/qrcode": "1.5.5",
|
||||
@@ -226,8 +226,8 @@
|
||||
"@types/vary": "1.1.3",
|
||||
"@types/web-push": "3.6.3",
|
||||
"@types/ws": "8.5.10",
|
||||
"@typescript-eslint/eslint-plugin": "7.7.1",
|
||||
"@typescript-eslint/parser": "7.7.1",
|
||||
"@typescript-eslint/eslint-plugin": "7.1.0",
|
||||
"@typescript-eslint/parser": "7.1.0",
|
||||
"aws-sdk-client-mock": "3.0.1",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.57.0",
|
||||
|
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { execa, execaNode } from 'execa';
|
||||
|
||||
/** @type {import('execa').ExecaChildProcess | undefined} */
|
||||
let backendProcess;
|
||||
|
||||
async function execBuildAssets() {
|
||||
await execa('pnpm', ['run', 'build-assets'], {
|
||||
cwd: '../../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
})
|
||||
}
|
||||
|
||||
function execStart() {
|
||||
// pnpm run start を呼び出したいが、windowsだとプロセスグループ単位でのkillが出来ずゾンビプロセス化するので
|
||||
// 上記と同等の動きをするコマンドで子・孫プロセスを作らないようにしたい
|
||||
backendProcess = execaNode('./built/boot/entry.js', [], {
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
env: {
|
||||
'NODE_ENV': 'development',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function killProc() {
|
||||
if (backendProcess) {
|
||||
backendProcess.kill();
|
||||
await new Promise(resolve => backendProcess.on('exit', resolve));
|
||||
backendProcess = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
execaNode(
|
||||
'./node_modules/nodemon/bin/nodemon.js',
|
||||
[
|
||||
'-w', 'src',
|
||||
'-e', 'ts,js,mjs,cjs,json',
|
||||
'--exec', 'pnpm', 'run', 'build',
|
||||
],
|
||||
{
|
||||
stdio: [process.stdin, process.stdout, process.stderr, 'ipc'],
|
||||
})
|
||||
.on('message', async (message) => {
|
||||
if (message.type === 'exit') {
|
||||
// かならずbuild->build-assetsの順番で呼び出したいので、
|
||||
// 少々トリッキーだがnodemonからのexitイベントを利用してbuild-assets->startを行う。
|
||||
// pnpm restartをbuildが終わる前にbuild-assetsが動いてしまうので、バラバラに呼び出す必要がある
|
||||
|
||||
await killProc();
|
||||
await execBuildAssets();
|
||||
execStart();
|
||||
}
|
||||
})
|
||||
})();
|
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* 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";
|
||||
|
||||
const config = loadConfig();
|
||||
const spec = genOpenapiSpec(config, true);
|
||||
|
||||
writeFileSync('./built/api.json', JSON.stringify(spec), 'utf-8');
|
@@ -28,7 +28,7 @@ const bootLogger = logger.createSubLogger('boot', 'magenta', false);
|
||||
const themeColor = chalk.hex('#86b300');
|
||||
|
||||
function greet() {
|
||||
if (!envOption.quiet) {
|
||||
if (!envOption.quiet && !envOption.logJson) {
|
||||
//#region Misskey logo
|
||||
const v = `v${meta.version}`;
|
||||
console.log(themeColor(' _____ _ _ '));
|
||||
@@ -46,7 +46,7 @@ function greet() {
|
||||
}
|
||||
|
||||
bootLogger.info('Welcome to Misskey!');
|
||||
bootLogger.info(`Misskey v${meta.version}`, null, true);
|
||||
bootLogger.info(`Misskey v${meta.version}`, { version: meta.version, hostname: os.hostname(), pid: process.pid }, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -305,7 +305,7 @@ export class AccountMoveService {
|
||||
let resultUser: MiLocalUser | MiRemoteUser | null = null;
|
||||
|
||||
if (this.userEntityService.isRemoteUser(dst)) {
|
||||
if (Date.now() - (dst.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) {
|
||||
if ((new Date()).getTime() - (dst.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) {
|
||||
await this.apPersonService.updatePerson(dst.uri);
|
||||
}
|
||||
dst = await this.apPersonService.fetchPerson(dst.uri) ?? dst;
|
||||
@@ -321,7 +321,7 @@ export class AccountMoveService {
|
||||
if (!src) continue; // oldAccountを探してもこのサーバーに存在しない場合はフォロー関係もないということなのでスルー
|
||||
|
||||
if (this.userEntityService.isRemoteUser(dst)) {
|
||||
if (Date.now() - (src.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) {
|
||||
if ((new Date()).getTime() - (src.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) {
|
||||
await this.apPersonService.updatePerson(srcUri);
|
||||
}
|
||||
|
||||
|
@@ -92,7 +92,7 @@ export class AntennaService implements OnApplicationShutdown {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async addNoteToAntennas(note: MiNote, noteUser: { id: MiUser['id']; username: string; host: string | null; isBot: boolean; }): Promise<void> {
|
||||
public async addNoteToAntennas(note: MiNote, noteUser: { id: MiUser['id']; username: string; host: string | null; }): Promise<void> {
|
||||
const antennas = await this.getAntennas();
|
||||
const antennasWithMatchResult = await Promise.all(antennas.map(antenna => this.checkHitAntenna(antenna, note, noteUser).then(hit => [antenna, hit] as const)));
|
||||
const matchedAntennas = antennasWithMatchResult.filter(([, hit]) => hit).map(([antenna]) => antenna);
|
||||
@@ -110,12 +110,10 @@ export class AntennaService implements OnApplicationShutdown {
|
||||
// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている
|
||||
|
||||
@bindThis
|
||||
public async checkHitAntenna(antenna: MiAntenna, note: (MiNote | Packed<'Note'>), noteUser: { id: MiUser['id']; username: string; host: string | null; isBot: boolean; }): Promise<boolean> {
|
||||
public async checkHitAntenna(antenna: MiAntenna, note: (MiNote | Packed<'Note'>), noteUser: { id: MiUser['id']; username: string; host: string | null; }): Promise<boolean> {
|
||||
if (note.visibility === 'specified') return false;
|
||||
if (note.visibility === 'followers') return false;
|
||||
|
||||
if (antenna.excludeBots && noteUser.isBot) return false;
|
||||
|
||||
if (antenna.localOnly && noteUser.host != null) return false;
|
||||
|
||||
if (!antenna.withReplies && note.replyId != null) return false;
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import Redis from 'ioredis';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
@@ -127,7 +127,7 @@ import { ApMfmService } from './activitypub/ApMfmService.js';
|
||||
import { ApRendererService } from './activitypub/ApRendererService.js';
|
||||
import { ApRequestService } from './activitypub/ApRequestService.js';
|
||||
import { ApResolverService } from './activitypub/ApResolverService.js';
|
||||
import { JsonLdService } from './activitypub/JsonLdService.js';
|
||||
import { LdSignatureService } from './activitypub/LdSignatureService.js';
|
||||
import { RemoteLoggerService } from './RemoteLoggerService.js';
|
||||
import { RemoteUserResolveService } from './RemoteUserResolveService.js';
|
||||
import { WebfingerService } from './WebfingerService.js';
|
||||
@@ -266,7 +266,7 @@ const $ApMfmService: Provider = { provide: 'ApMfmService', useExisting: ApMfmSer
|
||||
const $ApRendererService: Provider = { provide: 'ApRendererService', useExisting: ApRendererService };
|
||||
const $ApRequestService: Provider = { provide: 'ApRequestService', useExisting: ApRequestService };
|
||||
const $ApResolverService: Provider = { provide: 'ApResolverService', useExisting: ApResolverService };
|
||||
const $JsonLdService: Provider = { provide: 'JsonLdService', useExisting: JsonLdService };
|
||||
const $LdSignatureService: Provider = { provide: 'LdSignatureService', useExisting: LdSignatureService };
|
||||
const $RemoteLoggerService: Provider = { provide: 'RemoteLoggerService', useExisting: RemoteLoggerService };
|
||||
const $RemoteUserResolveService: Provider = { provide: 'RemoteUserResolveService', useExisting: RemoteUserResolveService };
|
||||
const $WebfingerService: Provider = { provide: 'WebfingerService', useExisting: WebfingerService };
|
||||
@@ -406,7 +406,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
ApRendererService,
|
||||
ApRequestService,
|
||||
ApResolverService,
|
||||
JsonLdService,
|
||||
LdSignatureService,
|
||||
RemoteLoggerService,
|
||||
RemoteUserResolveService,
|
||||
WebfingerService,
|
||||
@@ -542,7 +542,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
$ApRendererService,
|
||||
$ApRequestService,
|
||||
$ApResolverService,
|
||||
$JsonLdService,
|
||||
$LdSignatureService,
|
||||
$RemoteLoggerService,
|
||||
$RemoteUserResolveService,
|
||||
$WebfingerService,
|
||||
@@ -678,7 +678,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
ApRendererService,
|
||||
ApRequestService,
|
||||
ApResolverService,
|
||||
JsonLdService,
|
||||
LdSignatureService,
|
||||
RemoteLoggerService,
|
||||
RemoteUserResolveService,
|
||||
WebfingerService,
|
||||
@@ -813,7 +813,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
$ApRendererService,
|
||||
$ApRequestService,
|
||||
$ApResolverService,
|
||||
$JsonLdService,
|
||||
$LdSignatureService,
|
||||
$RemoteLoggerService,
|
||||
$RemoteUserResolveService,
|
||||
$WebfingerService,
|
||||
|
@@ -20,7 +20,7 @@ import { query } from '@/misc/prelude/url.js';
|
||||
import type { Serialized } from '@/types.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
const parseEmojiStrRegexp = /^([-\w]+)(?:@([\w.-]+))?$/;
|
||||
const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/;
|
||||
|
||||
@Injectable()
|
||||
export class CustomEmojiService implements OnApplicationShutdown {
|
||||
|
@@ -13,7 +13,7 @@ import type { NotesRepository } from '@/models/_.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { FanoutTimelineName, FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||
import { isPureRenote } from '@/misc/is-pure-renote.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { isReply } from '@/misc/is-reply.js';
|
||||
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
|
||||
@@ -95,7 +95,7 @@ export class FanoutTimelineEndpointService {
|
||||
|
||||
if (ps.excludePureRenotes) {
|
||||
const parentFilter = filter;
|
||||
filter = (note) => (!isRenote(note) || isQuote(note)) && parentFilter(note);
|
||||
filter = (note) => !isPureRenote(note) && parentFilter(note);
|
||||
}
|
||||
|
||||
if (ps.me) {
|
||||
@@ -116,7 +116,7 @@ export class FanoutTimelineEndpointService {
|
||||
filter = (note) => {
|
||||
if (isUserRelated(note, userIdsWhoBlockingMe, ps.ignoreAuthorFromBlock)) return false;
|
||||
if (isUserRelated(note, userIdsWhoMeMuting, ps.ignoreAuthorFromMute)) return false;
|
||||
if (!ps.ignoreAuthorFromMute && isRenote(note) && !isQuote(note) && userIdsWhoMeMutingRenotes.has(note.userId)) return false;
|
||||
if (isPureRenote(note) && isUserRelated(note, userIdsWhoMeMutingRenotes, ps.ignoreAuthorFromMute)) return false;
|
||||
if (isInstanceMuted(note, userMutedInstances)) return false;
|
||||
|
||||
return parentFilter(note);
|
||||
|
@@ -14,12 +14,11 @@ import FFmpeg from 'fluent-ffmpeg';
|
||||
import isSvg from 'is-svg';
|
||||
import probeImageSize from 'probe-image-size';
|
||||
import { type predictionType } from 'nsfwjs';
|
||||
import sharp from 'sharp';
|
||||
import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
|
||||
import { encode } from 'blurhash';
|
||||
import { createTempDir } from '@/misc/create-temp.js';
|
||||
import { AiService } from '@/core/AiService.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
export type FileInfo = {
|
||||
@@ -50,13 +49,9 @@ const TYPE_SVG = {
|
||||
|
||||
@Injectable()
|
||||
export class FileInfoService {
|
||||
private logger: Logger;
|
||||
|
||||
constructor(
|
||||
private aiService: AiService,
|
||||
private loggerService: LoggerService,
|
||||
) {
|
||||
this.logger = this.loggerService.getLogger('file-info');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -322,34 +317,6 @@ export class FileInfoService {
|
||||
return mime;
|
||||
}
|
||||
|
||||
/**
|
||||
* ビデオファイルにビデオトラックがあるかどうかチェック
|
||||
* (ない場合:m4a, webmなど)
|
||||
*
|
||||
* @param path ファイルパス
|
||||
* @returns ビデオトラックがあるかどうか(エラー発生時は常に`true`を返す)
|
||||
*/
|
||||
@bindThis
|
||||
private hasVideoTrackOnVideoFile(path: string): Promise<boolean> {
|
||||
const sublogger = this.logger.createSubLogger('ffprobe');
|
||||
sublogger.info(`Checking the video file. File path: ${path}`);
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
FFmpeg.ffprobe(path, (err, metadata) => {
|
||||
if (err) {
|
||||
sublogger.warn(`Could not check the video file. Returns true. File path: ${path}`, err);
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
resolve(metadata.streams.some((stream) => stream.codec_type === 'video'));
|
||||
});
|
||||
} catch (err) {
|
||||
sublogger.warn(`Could not check the video file. Returns true. File path: ${path}`, err as Error);
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect MIME Type and extension
|
||||
*/
|
||||
@@ -372,20 +339,6 @@ export class FileInfoService {
|
||||
return TYPE_SVG;
|
||||
}
|
||||
|
||||
if ((type.mime.startsWith('video') || type.mime === 'application/ogg') && !(await this.hasVideoTrackOnVideoFile(path))) {
|
||||
const newMime = `audio/${type.mime.split('/')[1]}`;
|
||||
if (newMime === 'audio/mp4') {
|
||||
return {
|
||||
mime: 'audio/mp4',
|
||||
ext: 'm4a',
|
||||
};
|
||||
}
|
||||
return {
|
||||
mime: newMime,
|
||||
ext: type.ext,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
mime: this.fixMime(type.mime),
|
||||
ext: type.ext,
|
||||
|
@@ -16,6 +16,6 @@ export class LoggerService {
|
||||
|
||||
@bindThis
|
||||
public getLogger(domain: string, color?: KEYWORD | undefined, store?: boolean) {
|
||||
return new Logger(domain, color, store);
|
||||
return new Logger(domain);
|
||||
}
|
||||
}
|
||||
|
@@ -6,11 +6,10 @@
|
||||
import { URL } from 'node:url';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as parse5 from 'parse5';
|
||||
import { Window, XMLSerializer } from 'happy-dom';
|
||||
import { Window } from 'happy-dom';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
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';
|
||||
@@ -34,8 +33,6 @@ export class MfmService {
|
||||
// some AP servers like Pixelfed use br tags as well as newlines
|
||||
html = html.replace(/<br\s?\/?>\r?\n/gi, '\n');
|
||||
|
||||
const normalizedHashtagNames = hashtagNames == null ? undefined : new Set<string>(hashtagNames.map(x => normalizeForSearch(x)));
|
||||
|
||||
const dom = parse5.parseFragment(html);
|
||||
|
||||
let text = '';
|
||||
@@ -88,7 +85,7 @@ export class MfmService {
|
||||
const href = node.attrs.find(x => x.name === 'href');
|
||||
|
||||
// ハッシュタグ
|
||||
if (normalizedHashtagNames && href && normalizedHashtagNames.has(normalizeForSearch(txt))) {
|
||||
if (hashtagNames && href && hashtagNames.map(x => x.toLowerCase()).includes(txt.toLowerCase())) {
|
||||
text += txt;
|
||||
// メンション
|
||||
} else if (txt.startsWith('@') && !(rel && rel.value.startsWith('me '))) {
|
||||
@@ -247,8 +244,6 @@ export class MfmService {
|
||||
|
||||
const doc = window.document;
|
||||
|
||||
const body = doc.createElement('p');
|
||||
|
||||
function appendChildren(children: mfm.MfmNode[], targetElement: any): void {
|
||||
if (children) {
|
||||
for (const child of children.map(x => (handlers as any)[x.type](x))) targetElement.appendChild(child);
|
||||
@@ -459,8 +454,8 @@ export class MfmService {
|
||||
},
|
||||
};
|
||||
|
||||
appendChildren(nodes, body);
|
||||
appendChildren(nodes, doc.body);
|
||||
|
||||
return new XMLSerializer().serializeToString(body);
|
||||
return `<p>${doc.body.innerHTML}</p>`;
|
||||
}
|
||||
}
|
||||
|
@@ -306,7 +306,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||
}
|
||||
|
||||
// Check blocking
|
||||
if (this.isRenote(data) && !this.isQuote(data)) {
|
||||
if (data.renote && !this.isQuote(data)) {
|
||||
if (data.renote.userHost === null) {
|
||||
if (data.renote.userId !== user.id) {
|
||||
const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id);
|
||||
@@ -641,7 +641,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||
}
|
||||
|
||||
// If it is renote
|
||||
if (this.isRenote(data)) {
|
||||
if (data.renote) {
|
||||
const type = this.isQuote(data) ? 'quote' : 'renote';
|
||||
|
||||
// Notify
|
||||
@@ -725,20 +725,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private isRenote(note: Option): note is Option & { renote: MiNote } {
|
||||
return note.renote != null;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private isQuote(note: Option & { renote: MiNote }): note is Option & { renote: MiNote } & (
|
||||
{ text: string } | { cw: string } | { reply: MiNote } | { poll: IPoll } | { files: MiDriveFile[] }
|
||||
) {
|
||||
// NOTE: SYNC WITH misc/is-quote.ts
|
||||
return note.text != null ||
|
||||
note.reply != null ||
|
||||
note.cw != null ||
|
||||
note.poll != null ||
|
||||
(note.files != null && note.files.length > 0);
|
||||
private isQuote(note: Option): note is Option & { renote: MiNote } {
|
||||
// sync with misc/is-quote.ts
|
||||
return !!note.renote && (!!note.text || !!note.cw || (!!note.files && !!note.files.length) || !!note.poll);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -806,7 +795,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||
private async renderNoteOrRenoteActivity(data: Option, note: MiNote) {
|
||||
if (data.localOnly) return null;
|
||||
|
||||
const content = this.isRenote(data) && !this.isQuote(data)
|
||||
const content = data.renote && !this.isQuote(data)
|
||||
? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note)
|
||||
: this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note);
|
||||
|
||||
|
@@ -24,7 +24,7 @@ import { bindThis } from '@/decorators.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { SearchService } from '@/core/SearchService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||
import { isPureRenote } from '@/misc/is-pure-renote.js';
|
||||
|
||||
@Injectable()
|
||||
export class NoteDeleteService {
|
||||
@@ -79,7 +79,7 @@ export class NoteDeleteService {
|
||||
let renote: MiNote | null = null;
|
||||
|
||||
// if deleted note is renote
|
||||
if (isRenote(note) && !isQuote(note)) {
|
||||
if (isPureRenote(note)) {
|
||||
renote = await this.notesRepository.findOneBy({
|
||||
id: note.renoteId,
|
||||
});
|
||||
|
@@ -101,7 +101,7 @@ export class PushNotificationService implements OnApplicationShutdown {
|
||||
type,
|
||||
body: (type === 'notification' || type === 'unreadAntennaNote') ? truncateBody(type, body) : body,
|
||||
userId,
|
||||
dateTime: Date.now(),
|
||||
dateTime: (new Date()).getTime(),
|
||||
}), {
|
||||
proxy: this.config.proxy,
|
||||
}).catch((err: any) => {
|
||||
|
@@ -205,79 +205,45 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||
private evalCond(user: MiUser, roles: MiRole[], value: RoleCondFormulaValue): boolean {
|
||||
try {
|
||||
switch (value.type) {
|
||||
// ~かつ~
|
||||
case 'and': {
|
||||
return value.values.every(v => this.evalCond(user, roles, v));
|
||||
}
|
||||
// ~または~
|
||||
case 'or': {
|
||||
return value.values.some(v => this.evalCond(user, roles, v));
|
||||
}
|
||||
// ~ではない
|
||||
case 'not': {
|
||||
return !this.evalCond(user, roles, value.value);
|
||||
}
|
||||
// マニュアルロールがアサインされている
|
||||
case 'roleAssignedTo': {
|
||||
return roles.some(r => r.id === value.roleId);
|
||||
}
|
||||
// ローカルユーザのみ
|
||||
case 'isLocal': {
|
||||
return this.userEntityService.isLocalUser(user);
|
||||
}
|
||||
// リモートユーザのみ
|
||||
case 'isRemote': {
|
||||
return this.userEntityService.isRemoteUser(user);
|
||||
}
|
||||
// サスペンド済みユーザである
|
||||
case 'isSuspended': {
|
||||
return user.isSuspended;
|
||||
}
|
||||
// 鍵アカウントユーザである
|
||||
case 'isLocked': {
|
||||
return user.isLocked;
|
||||
}
|
||||
// botユーザである
|
||||
case 'isBot': {
|
||||
return user.isBot;
|
||||
}
|
||||
// 猫である
|
||||
case 'isCat': {
|
||||
return user.isCat;
|
||||
}
|
||||
// 「ユーザを見つけやすくする」が有効なアカウント
|
||||
case 'isExplorable': {
|
||||
return user.isExplorable;
|
||||
}
|
||||
// ユーザが作成されてから指定期間経過した
|
||||
case 'createdLessThan': {
|
||||
return this.idService.parse(user.id).date.getTime() > (Date.now() - (value.sec * 1000));
|
||||
}
|
||||
// ユーザが作成されてから指定期間経っていない
|
||||
case 'createdMoreThan': {
|
||||
return this.idService.parse(user.id).date.getTime() < (Date.now() - (value.sec * 1000));
|
||||
}
|
||||
// フォロワー数が指定値以下
|
||||
case 'followersLessThanOrEq': {
|
||||
return user.followersCount <= value.value;
|
||||
}
|
||||
// フォロワー数が指定値以上
|
||||
case 'followersMoreThanOrEq': {
|
||||
return user.followersCount >= value.value;
|
||||
}
|
||||
// フォロー数が指定値以下
|
||||
case 'followingLessThanOrEq': {
|
||||
return user.followingCount <= value.value;
|
||||
}
|
||||
// フォロー数が指定値以上
|
||||
case 'followingMoreThanOrEq': {
|
||||
return user.followingCount >= value.value;
|
||||
}
|
||||
// ノート数が指定値以下
|
||||
case 'notesLessThanOrEq': {
|
||||
return user.notesCount <= value.value;
|
||||
}
|
||||
// ノート数が指定値以上
|
||||
case 'notesMoreThanOrEq': {
|
||||
return user.notesCount >= value.value;
|
||||
}
|
||||
|
@@ -511,12 +511,6 @@ export class UserFollowingService implements OnModuleInit {
|
||||
if (blocking) throw new Error('blocking');
|
||||
if (blocked) throw new Error('blocked');
|
||||
|
||||
// Remove old follow requests before creating a new one.
|
||||
await this.followRequestsRepository.delete({
|
||||
followeeId: followee.id,
|
||||
followerId: follower.id,
|
||||
});
|
||||
|
||||
const followRequest = await this.followRequestsRepository.insert({
|
||||
id: this.idService.gen(),
|
||||
followerId: follower.id,
|
||||
|
@@ -10,7 +10,7 @@ import {
|
||||
generateRegistrationOptions, verifyAuthenticationResponse,
|
||||
verifyRegistrationResponse,
|
||||
} from '@simplewebauthn/server';
|
||||
import { AttestationFormat, isoCBOR, isoUint8Array } from '@simplewebauthn/server/helpers';
|
||||
import { AttestationFormat, isoCBOR } from '@simplewebauthn/server/helpers';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { UserSecurityKeysRepository } from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
@@ -49,7 +49,7 @@ export class WebAuthnService {
|
||||
const instance = await this.metaService.fetch();
|
||||
return {
|
||||
origin: this.config.url,
|
||||
rpId: this.config.hostname,
|
||||
rpId: this.config.host,
|
||||
rpName: instance.name ?? this.config.host,
|
||||
rpIcon: instance.iconUrl ?? undefined,
|
||||
};
|
||||
@@ -65,12 +65,13 @@ export class WebAuthnService {
|
||||
const registrationOptions = await generateRegistrationOptions({
|
||||
rpName: relyingParty.rpName,
|
||||
rpID: relyingParty.rpId,
|
||||
userID: isoUint8Array.fromUTF8String(userId),
|
||||
userID: userId,
|
||||
userName: userName,
|
||||
userDisplayName: userDisplayName,
|
||||
attestationType: 'indirect',
|
||||
excludeCredentials: keys.map(key => (<{ id: string; transports?: AuthenticatorTransportFuture[]; }>{
|
||||
id: key.id,
|
||||
excludeCredentials: keys.map(key => (<PublicKeyCredentialDescriptorFuture>{
|
||||
id: Buffer.from(key.id, 'base64url'),
|
||||
type: 'public-key',
|
||||
transports: key.transports ?? undefined,
|
||||
})),
|
||||
authenticatorSelection: {
|
||||
@@ -86,7 +87,7 @@ export class WebAuthnService {
|
||||
|
||||
@bindThis
|
||||
public async verifyRegistration(userId: MiUser['id'], response: RegistrationResponseJSON): Promise<{
|
||||
credentialID: string;
|
||||
credentialID: Uint8Array;
|
||||
credentialPublicKey: Uint8Array;
|
||||
attestationObject: Uint8Array;
|
||||
fmt: AttestationFormat;
|
||||
@@ -143,7 +144,6 @@ export class WebAuthnService {
|
||||
|
||||
@bindThis
|
||||
public async initiateAuthentication(userId: MiUser['id']): Promise<PublicKeyCredentialRequestOptionsJSON> {
|
||||
const relyingParty = await this.getRelyingParty();
|
||||
const keys = await this.userSecurityKeysRepository.findBy({
|
||||
userId: userId,
|
||||
});
|
||||
@@ -153,9 +153,9 @@ export class WebAuthnService {
|
||||
}
|
||||
|
||||
const authenticationOptions = await generateAuthenticationOptions({
|
||||
rpID: relyingParty.rpId,
|
||||
allowCredentials: keys.map(key => (<{ id: string; transports?: AuthenticatorTransportFuture[]; }>{
|
||||
id: key.id,
|
||||
allowCredentials: keys.map(key => (<PublicKeyCredentialDescriptorFuture>{
|
||||
id: Buffer.from(key.id, 'base64url'),
|
||||
type: 'public-key',
|
||||
transports: key.transports ?? undefined,
|
||||
})),
|
||||
userVerification: 'preferred',
|
||||
@@ -219,7 +219,7 @@ export class WebAuthnService {
|
||||
expectedOrigin: relyingParty.origin,
|
||||
expectedRPID: relyingParty.rpId,
|
||||
authenticator: {
|
||||
credentialID: key.id,
|
||||
credentialID: Buffer.from(key.id, 'base64url'),
|
||||
credentialPublicKey: Buffer.from(key.publicKey, 'base64url'),
|
||||
counter: key.counter,
|
||||
transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined,
|
||||
|
@@ -28,9 +28,8 @@ 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 { LdSignatureService } from './LdSignatureService.js';
|
||||
import { ApMfmService } from './ApMfmService.js';
|
||||
import { CONTEXT } from './misc/contexts.js';
|
||||
import type { IAccept, IActivity, IAdd, IAnnounce, IApDocument, IApEmoji, IApHashtag, IApImage, IApMention, IBlock, ICreate, IDelete, IFlag, IFollow, IKey, ILike, IMove, IObject, IPost, IQuestion, IReject, IRemove, ITombstone, IUndo, IUpdate } from './type.js';
|
||||
|
||||
@Injectable()
|
||||
@@ -57,7 +56,7 @@ export class ApRendererService {
|
||||
private customEmojiService: CustomEmojiService,
|
||||
private userEntityService: UserEntityService,
|
||||
private driveFileEntityService: DriveFileEntityService,
|
||||
private jsonLdService: JsonLdService,
|
||||
private ldSignatureService: LdSignatureService,
|
||||
private userKeypairService: UserKeypairService,
|
||||
private apMfmService: ApMfmService,
|
||||
private mfmService: MfmService,
|
||||
@@ -167,7 +166,6 @@ export class ApRendererService {
|
||||
mediaType: file.webpublicType ?? file.type,
|
||||
url: this.driveFileEntityService.getPublicUrl(file),
|
||||
name: file.comment,
|
||||
sensitive: file.isSensitive,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -619,16 +617,48 @@ export class ApRendererService {
|
||||
x.id = `${this.config.url}/${randomUUID()}`;
|
||||
}
|
||||
|
||||
return Object.assign({ '@context': CONTEXT }, x as T & { id: string });
|
||||
return Object.assign({
|
||||
'@context': [
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
'https://w3id.org/security/v1',
|
||||
{
|
||||
Key: 'sec:Key',
|
||||
// as non-standards
|
||||
manuallyApprovesFollowers: 'as:manuallyApprovesFollowers',
|
||||
sensitive: 'as:sensitive',
|
||||
Hashtag: 'as:Hashtag',
|
||||
quoteUrl: 'as:quoteUrl',
|
||||
// Mastodon
|
||||
toot: 'http://joinmastodon.org/ns#',
|
||||
Emoji: 'toot:Emoji',
|
||||
featured: 'toot:featured',
|
||||
discoverable: 'toot:discoverable',
|
||||
// schema
|
||||
schema: 'http://schema.org#',
|
||||
PropertyValue: 'schema:PropertyValue',
|
||||
value: 'schema:value',
|
||||
// Misskey
|
||||
misskey: 'https://misskey-hub.net/ns#',
|
||||
'_misskey_content': 'misskey:_misskey_content',
|
||||
'_misskey_quote': 'misskey:_misskey_quote',
|
||||
'_misskey_reaction': 'misskey:_misskey_reaction',
|
||||
'_misskey_votes': 'misskey:_misskey_votes',
|
||||
'_misskey_summary': 'misskey:_misskey_summary',
|
||||
'isCat': 'misskey:isCat',
|
||||
// vcard
|
||||
vcard: 'http://www.w3.org/2006/vcard/ns#',
|
||||
},
|
||||
],
|
||||
}, x as T & { id: string });
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async attachLdSignature(activity: any, user: { id: MiUser['id']; host: null; }): Promise<IActivity> {
|
||||
const keypair = await this.userKeypairService.getUserKeypair(user.id);
|
||||
|
||||
const jsonLd = this.jsonLdService.use();
|
||||
jsonLd.debug = false;
|
||||
activity = await jsonLd.signRsaSignature2017(activity, keypair.privateKey, `${this.config.url}/users/${user.id}#main-key`);
|
||||
const ldSignature = this.ldSignatureService.use();
|
||||
ldSignature.debug = false;
|
||||
activity = await ldSignature.signRsaSignature2017(activity, keypair.privateKey, `${this.config.url}/users/${user.id}#main-key`);
|
||||
|
||||
return activity;
|
||||
}
|
||||
|
@@ -7,14 +7,14 @@ import * as crypto from 'node:crypto';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { CONTEXT, PRELOADED_CONTEXTS } from './misc/contexts.js';
|
||||
import { CONTEXTS } from './misc/contexts.js';
|
||||
import { validateContentTypeSetAsJsonLD } from './misc/validator.js';
|
||||
import type { JsonLdDocument } from 'jsonld';
|
||||
import type { JsonLd as JsonLdObject, RemoteDocument } from 'jsonld/jsonld-spec.js';
|
||||
import type { JsonLd, RemoteDocument } from 'jsonld/jsonld-spec.js';
|
||||
|
||||
// RsaSignature2017 implementation is based on https://github.com/transmute-industries/RsaSignature2017
|
||||
// RsaSignature2017 based from https://github.com/transmute-industries/RsaSignature2017
|
||||
|
||||
class JsonLd {
|
||||
class LdSignature {
|
||||
public debug = false;
|
||||
public preLoad = true;
|
||||
public loderTimeout = 5000;
|
||||
@@ -89,18 +89,10 @@ class JsonLd {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async compact(data: any, context: any = CONTEXT): Promise<JsonLdDocument> {
|
||||
public async normalize(data: JsonLdDocument): Promise<string> {
|
||||
const customLoader = this.getLoader();
|
||||
// XXX: Importing jsonld dynamically since Jest frequently fails to import it statically
|
||||
// https://github.com/misskey-dev/misskey/pull/9894#discussion_r1103753595
|
||||
return (await import('jsonld')).default.compact(data, context, {
|
||||
documentLoader: customLoader,
|
||||
});
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async normalize(data: JsonLdDocument): Promise<string> {
|
||||
const customLoader = this.getLoader();
|
||||
return (await import('jsonld')).default.normalize(data, {
|
||||
documentLoader: customLoader,
|
||||
});
|
||||
@@ -112,11 +104,11 @@ class JsonLd {
|
||||
if (!/^https?:\/\//.test(url)) throw new Error(`Invalid URL ${url}`);
|
||||
|
||||
if (this.preLoad) {
|
||||
if (url in PRELOADED_CONTEXTS) {
|
||||
if (url in CONTEXTS) {
|
||||
if (this.debug) console.debug(`HIT: ${url}`);
|
||||
return {
|
||||
contextUrl: undefined,
|
||||
document: PRELOADED_CONTEXTS[url],
|
||||
document: CONTEXTS[url],
|
||||
documentUrl: url,
|
||||
};
|
||||
}
|
||||
@@ -133,7 +125,7 @@ class JsonLd {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async fetchDocument(url: string): Promise<JsonLdObject> {
|
||||
private async fetchDocument(url: string): Promise<JsonLd> {
|
||||
const json = await this.httpRequestService.send(
|
||||
url,
|
||||
{
|
||||
@@ -154,7 +146,7 @@ class JsonLd {
|
||||
}
|
||||
});
|
||||
|
||||
return json as JsonLdObject;
|
||||
return json as JsonLd;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -166,14 +158,14 @@ class JsonLd {
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class JsonLdService {
|
||||
export class LdSignatureService {
|
||||
constructor(
|
||||
private httpRequestService: HttpRequestService,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public use(): JsonLd {
|
||||
return new JsonLd(this.httpRequestService);
|
||||
public use(): LdSignature {
|
||||
return new LdSignature(this.httpRequestService);
|
||||
}
|
||||
}
|
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { Context, JsonLd } from 'jsonld/jsonld-spec.js';
|
||||
import type { JsonLd } from 'jsonld/jsonld-spec.js';
|
||||
|
||||
/* eslint:disable:quotemark indent */
|
||||
const id_v1 = {
|
||||
@@ -526,42 +526,7 @@ const activitystreams = {
|
||||
},
|
||||
} satisfies JsonLd;
|
||||
|
||||
const context_iris = [
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
'https://w3id.org/security/v1',
|
||||
];
|
||||
|
||||
const extension_context_definition = {
|
||||
Key: 'sec:Key',
|
||||
// as non-standards
|
||||
manuallyApprovesFollowers: 'as:manuallyApprovesFollowers',
|
||||
sensitive: 'as:sensitive',
|
||||
Hashtag: 'as:Hashtag',
|
||||
quoteUrl: 'as:quoteUrl',
|
||||
// Mastodon
|
||||
toot: 'http://joinmastodon.org/ns#',
|
||||
Emoji: 'toot:Emoji',
|
||||
featured: 'toot:featured',
|
||||
discoverable: 'toot:discoverable',
|
||||
// schema
|
||||
schema: 'http://schema.org#',
|
||||
PropertyValue: 'schema:PropertyValue',
|
||||
value: 'schema:value',
|
||||
// Misskey
|
||||
misskey: 'https://misskey-hub.net/ns#',
|
||||
'_misskey_content': 'misskey:_misskey_content',
|
||||
'_misskey_quote': 'misskey:_misskey_quote',
|
||||
'_misskey_reaction': 'misskey:_misskey_reaction',
|
||||
'_misskey_votes': 'misskey:_misskey_votes',
|
||||
'_misskey_summary': 'misskey:_misskey_summary',
|
||||
'isCat': 'misskey:isCat',
|
||||
// vcard
|
||||
vcard: 'http://www.w3.org/2006/vcard/ns#',
|
||||
} satisfies Context;
|
||||
|
||||
export const CONTEXT: (string | Context)[] = [...context_iris, extension_context_definition];
|
||||
|
||||
export const PRELOADED_CONTEXTS: Record<string, JsonLd> = {
|
||||
export const CONTEXTS: Record<string, JsonLd> = {
|
||||
'https://w3id.org/identity/v1': id_v1,
|
||||
'https://w3id.org/security/v1': security_v1,
|
||||
'https://www.w3.org/ns/activitystreams': activitystreams,
|
||||
|
@@ -17,7 +17,7 @@ import { bindThis } from '@/decorators.js';
|
||||
import { checkHttps } from '@/misc/check-https.js';
|
||||
import { ApResolverService } from '../ApResolverService.js';
|
||||
import { ApLoggerService } from '../ApLoggerService.js';
|
||||
import { isDocument, type IObject } from '../type.js';
|
||||
import type { IObject } from '../type.js';
|
||||
|
||||
@Injectable()
|
||||
export class ApImageService {
|
||||
@@ -39,7 +39,7 @@ export class ApImageService {
|
||||
* Imageを作成します。
|
||||
*/
|
||||
@bindThis
|
||||
public async createImage(actor: MiRemoteUser, value: string | IObject): Promise<MiDriveFile | null> {
|
||||
public async createImage(actor: MiRemoteUser, value: string | IObject): Promise<MiDriveFile> {
|
||||
// 投稿者が凍結されていたらスキップ
|
||||
if (actor.isSuspended) {
|
||||
throw new Error('actor has been suspended');
|
||||
@@ -47,18 +47,16 @@ export class ApImageService {
|
||||
|
||||
const image = await this.apResolverService.createResolver().resolve(value);
|
||||
|
||||
if (!isDocument(image)) return null;
|
||||
|
||||
if (image.url == null) {
|
||||
return null;
|
||||
throw new Error('invalid image: url not provided');
|
||||
}
|
||||
|
||||
if (typeof image.url !== 'string') {
|
||||
return null;
|
||||
throw new Error('invalid image: unexpected type of url: ' + JSON.stringify(image.url, null, 2));
|
||||
}
|
||||
|
||||
if (!checkHttps(image.url)) {
|
||||
return null;
|
||||
throw new Error('invalid image: unexpected schema of url: ' + image.url);
|
||||
}
|
||||
|
||||
this.logger.info(`Creating the Image: ${image.url}`);
|
||||
@@ -88,11 +86,12 @@ export class ApImageService {
|
||||
/**
|
||||
* Imageを解決します。
|
||||
*
|
||||
* ImageをリモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
||||
* Misskeyに対象のImageが登録されていればそれを返し、そうでなければ
|
||||
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
||||
*/
|
||||
@bindThis
|
||||
public async resolveImage(actor: MiRemoteUser, value: string | IObject): Promise<MiDriveFile | null> {
|
||||
// TODO: Misskeyに対象のImageが登録されていればそれを返す
|
||||
public async resolveImage(actor: MiRemoteUser, value: string | IObject): Promise<MiDriveFile> {
|
||||
// TODO
|
||||
|
||||
// リモートサーバーからフェッチしてきて登録
|
||||
return await this.createImage(actor, value);
|
||||
|
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||
import promiseLimit from 'promise-limit';
|
||||
import { In } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { PollsRepository, EmojisRepository } from '@/models/_.js';
|
||||
@@ -128,7 +129,7 @@ export class ApNoteService {
|
||||
value,
|
||||
object,
|
||||
});
|
||||
throw err;
|
||||
throw new Error('invalid note');
|
||||
}
|
||||
|
||||
const note = object as IPost;
|
||||
@@ -208,13 +209,15 @@ export class ApNoteService {
|
||||
}
|
||||
|
||||
// 添付ファイル
|
||||
const files: MiDriveFile[] = [];
|
||||
|
||||
for (const attach of toArray(note.attachment)) {
|
||||
attach.sensitive ??= note.sensitive;
|
||||
const file = await this.apImageService.resolveImage(actor, attach);
|
||||
if (file) files.push(file);
|
||||
}
|
||||
// TODO: attachmentは必ずしもImageではない
|
||||
// TODO: attachmentは必ずしも配列ではない
|
||||
const limit = promiseLimit<MiDriveFile>(2);
|
||||
const files = (await Promise.all(toArray(note.attachment).map(attach => (
|
||||
limit(() => this.apImageService.resolveImage(actor, {
|
||||
...attach,
|
||||
sensitive: note.sensitive, // Noteがsensitiveなら添付もsensitiveにする
|
||||
}))
|
||||
))));
|
||||
|
||||
// リプライ
|
||||
const reply: MiNote | null = note.inReplyTo
|
||||
|
@@ -25,7 +25,6 @@ export interface IObject {
|
||||
endTime?: Date;
|
||||
icon?: any;
|
||||
image?: any;
|
||||
mediaType?: string;
|
||||
url?: ApObject | string;
|
||||
href?: string;
|
||||
tag?: IObject | IObject[];
|
||||
@@ -241,14 +240,14 @@ export interface IKey extends IObject {
|
||||
}
|
||||
|
||||
export interface IApDocument extends IObject {
|
||||
type: 'Audio' | 'Document' | 'Image' | 'Page' | 'Video';
|
||||
type: 'Document';
|
||||
name: string | null;
|
||||
mediaType: string;
|
||||
}
|
||||
|
||||
export const isDocument = (object: IObject): object is IApDocument =>
|
||||
['Audio', 'Document', 'Image', 'Page', 'Video'].includes(getApType(object));
|
||||
|
||||
export interface IApImage extends IApDocument {
|
||||
export interface IApImage extends IObject {
|
||||
type: 'Image';
|
||||
name: string | null;
|
||||
}
|
||||
|
||||
export interface ICreate extends IActivity {
|
||||
|
@@ -459,15 +459,13 @@ export default abstract class Chart<T extends Schema> {
|
||||
}
|
||||
}
|
||||
|
||||
// bake cardinality
|
||||
// bake unique count
|
||||
for (const [k, v] of Object.entries(finalDiffs)) {
|
||||
if (this.schema[k].uniqueIncrement) {
|
||||
const name = COLUMN_PREFIX + k.replaceAll('.', COLUMN_DELIMITER) as keyof Columns<T>;
|
||||
const tempColumnName = UNIQUE_TEMP_COLUMN_PREFIX + k.replaceAll('.', COLUMN_DELIMITER) as keyof TempColumnsForUnique<T>;
|
||||
const cardinalityOfHour = new Set([...(v as string[]), ...(logHour[tempColumnName] as unknown as string[])]).size;
|
||||
const cardinalityOfDay = new Set([...(v as string[]), ...(logDay[tempColumnName] as unknown as string[])]).size;
|
||||
queryForHour[name] = cardinalityOfHour;
|
||||
queryForDay[name] = cardinalityOfDay;
|
||||
queryForHour[name] = new Set([...(v as string[]), ...(logHour[tempColumnName] as unknown as string[])]).size;
|
||||
queryForDay[name] = new Set([...(v as string[]), ...(logDay[tempColumnName] as unknown as string[])]).size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -639,7 +637,7 @@ export default abstract class Chart<T extends Schema> {
|
||||
// 要求された範囲にログがひとつもなかったら
|
||||
if (logs.length === 0) {
|
||||
// もっとも新しいログを持ってくる
|
||||
// (すくなくともひとつログが無いと補間できないため)
|
||||
// (すくなくともひとつログが無いと隙間埋めできないため)
|
||||
const recentLog = await repository.findOne({
|
||||
where: group ? {
|
||||
group: group,
|
||||
@@ -656,7 +654,7 @@ export default abstract class Chart<T extends Schema> {
|
||||
// 要求された範囲の最も古い箇所に位置するログが存在しなかったら
|
||||
} else if (!isTimeSame(new Date(logs.at(-1)!.date * 1000), gt)) {
|
||||
// 要求された範囲の最も古い箇所時点での最も新しいログを持ってきて末尾に追加する
|
||||
// (補間できないため)
|
||||
// (隙間埋めできないため)
|
||||
const outdatedLog = await repository.findOne({
|
||||
where: {
|
||||
date: LessThan(Chart.dateToTimestamp(gt)),
|
||||
@@ -685,7 +683,7 @@ export default abstract class Chart<T extends Schema> {
|
||||
if (log) {
|
||||
chart.unshift(this.convertRawRecord(log));
|
||||
} else {
|
||||
// 補間
|
||||
// 隙間埋め
|
||||
const latest = logs.find(l => isTimeBefore(new Date(l.date * 1000), current));
|
||||
const data = latest ? this.convertRawRecord(latest) : null;
|
||||
chart.unshift(this.getNewLog(data));
|
||||
|
@@ -39,7 +39,6 @@ export class AntennaEntityService {
|
||||
caseSensitive: antenna.caseSensitive,
|
||||
localOnly: antenna.localOnly,
|
||||
notify: antenna.notify,
|
||||
excludeBots: antenna.excludeBots,
|
||||
withReplies: antenna.withReplies,
|
||||
withFile: antenna.withFile,
|
||||
isActive: antenna.isActive,
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { ClipNotesRepository, ClipFavoritesRepository, ClipsRepository, MiUser } from '@/models/_.js';
|
||||
import type { ClipFavoritesRepository, ClipsRepository, MiUser } from '@/models/_.js';
|
||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import type { } from '@/models/Blocking.js';
|
||||
@@ -20,9 +20,6 @@ export class ClipEntityService {
|
||||
@Inject(DI.clipsRepository)
|
||||
private clipsRepository: ClipsRepository,
|
||||
|
||||
@Inject(DI.clipNotesRepository)
|
||||
private clipNotesRepository: ClipNotesRepository,
|
||||
|
||||
@Inject(DI.clipFavoritesRepository)
|
||||
private clipFavoritesRepository: ClipFavoritesRepository,
|
||||
|
||||
@@ -50,7 +47,6 @@ 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,
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -248,7 +248,7 @@ export class DriveFileEntityService {
|
||||
folder: opts.detail && file.folderId ? this.driveFolderEntityService.pack(file.folderId, {
|
||||
detail: true,
|
||||
}) : null,
|
||||
userId: file.userId,
|
||||
userId: opts.withUser ? file.userId : null,
|
||||
user: (opts.withUser && file.userId) ? this.userEntityService.pack(file.userId) : null,
|
||||
});
|
||||
}
|
||||
|
@@ -111,7 +111,6 @@ export class MetaEntityService {
|
||||
policies: { ...DEFAULT_POLICIES, ...instance.policies },
|
||||
|
||||
mediaProxy: this.config.mediaProxy,
|
||||
enableUrlPreview: instance.urlPreviewEnabled,
|
||||
};
|
||||
|
||||
return packed;
|
||||
|
@@ -333,7 +333,6 @@ export class NoteEntityService implements OnModuleInit {
|
||||
visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined,
|
||||
renoteCount: note.renoteCount,
|
||||
repliesCount: note.repliesCount,
|
||||
reactionCount: Object.values(note.reactions).reduce((a, b) => a + b, 0),
|
||||
reactions: this.reactionService.convertLegacyReactions(note.reactions),
|
||||
reactionEmojis: this.customEmojiService.populateEmojis(reactionEmojiNames, host),
|
||||
reactionAndUserPairCache: opts.withReactionAndUserPairCache ? note.reactionAndUserPairCache : undefined,
|
||||
|
@@ -7,7 +7,6 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import _Ajv from 'ajv';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import { In } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
@@ -15,30 +14,9 @@ import type { Promiseable } from '@/misc/prelude/await-all.js';
|
||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||
import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js';
|
||||
import type { MiLocalUser, MiPartialLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js';
|
||||
import {
|
||||
birthdaySchema,
|
||||
descriptionSchema,
|
||||
localUsernameSchema,
|
||||
locationSchema,
|
||||
nameSchema,
|
||||
passwordSchema,
|
||||
} from '@/models/User.js';
|
||||
import type {
|
||||
BlockingsRepository,
|
||||
FollowingsRepository,
|
||||
FollowRequestsRepository,
|
||||
MiFollowing,
|
||||
MiUserNotePining,
|
||||
MiUserProfile,
|
||||
MutingsRepository,
|
||||
NoteUnreadsRepository,
|
||||
RenoteMutingsRepository,
|
||||
UserMemoRepository,
|
||||
UserNotePiningsRepository,
|
||||
UserProfilesRepository,
|
||||
UserSecurityKeysRepository,
|
||||
UsersRepository,
|
||||
} from '@/models/_.js';
|
||||
import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/User.js';
|
||||
import { MiNotification } from '@/models/Notification.js';
|
||||
import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, UserNotePiningsRepository, UserProfilesRepository, AnnouncementReadsRepository, AnnouncementsRepository, MiUserProfile, RenoteMutingsRepository, UserMemoRepository } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
||||
@@ -68,23 +46,11 @@ function isRemoteUser(user: MiUser | { host: MiUser['host'] }): boolean {
|
||||
return !isLocalUser(user);
|
||||
}
|
||||
|
||||
export type UserRelation = {
|
||||
id: MiUser['id']
|
||||
following: MiFollowing | null,
|
||||
isFollowing: boolean
|
||||
isFollowed: boolean
|
||||
hasPendingFollowRequestFromYou: boolean
|
||||
hasPendingFollowRequestToYou: boolean
|
||||
isBlocking: boolean
|
||||
isBlocked: boolean
|
||||
isMuted: boolean
|
||||
isRenoteMuted: boolean
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class UserEntityService implements OnModuleInit {
|
||||
private apPersonService: ApPersonService;
|
||||
private noteEntityService: NoteEntityService;
|
||||
private driveFileEntityService: DriveFileEntityService;
|
||||
private pageEntityService: PageEntityService;
|
||||
private customEmojiService: CustomEmojiService;
|
||||
private announcementService: AnnouncementService;
|
||||
@@ -123,6 +89,9 @@ export class UserEntityService implements OnModuleInit {
|
||||
@Inject(DI.renoteMutingsRepository)
|
||||
private renoteMutingsRepository: RenoteMutingsRepository,
|
||||
|
||||
@Inject(DI.driveFilesRepository)
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
@Inject(DI.noteUnreadsRepository)
|
||||
private noteUnreadsRepository: NoteUnreadsRepository,
|
||||
|
||||
@@ -132,6 +101,12 @@ export class UserEntityService implements OnModuleInit {
|
||||
@Inject(DI.userProfilesRepository)
|
||||
private userProfilesRepository: UserProfilesRepository,
|
||||
|
||||
@Inject(DI.announcementReadsRepository)
|
||||
private announcementReadsRepository: AnnouncementReadsRepository,
|
||||
|
||||
@Inject(DI.announcementsRepository)
|
||||
private announcementsRepository: AnnouncementsRepository,
|
||||
|
||||
@Inject(DI.userMemosRepository)
|
||||
private userMemosRepository: UserMemoRepository,
|
||||
) {
|
||||
@@ -140,6 +115,7 @@ export class UserEntityService implements OnModuleInit {
|
||||
onModuleInit() {
|
||||
this.apPersonService = this.moduleRef.get('ApPersonService');
|
||||
this.noteEntityService = this.moduleRef.get('NoteEntityService');
|
||||
this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService');
|
||||
this.pageEntityService = this.moduleRef.get('PageEntityService');
|
||||
this.customEmojiService = this.moduleRef.get('CustomEmojiService');
|
||||
this.announcementService = this.moduleRef.get('AnnouncementService');
|
||||
@@ -162,7 +138,7 @@ export class UserEntityService implements OnModuleInit {
|
||||
public isRemoteUser = isRemoteUser;
|
||||
|
||||
@bindThis
|
||||
public async getRelation(me: MiUser['id'], target: MiUser['id']): Promise<UserRelation> {
|
||||
public async getRelation(me: MiUser['id'], target: MiUser['id']) {
|
||||
const [
|
||||
following,
|
||||
isFollowed,
|
||||
@@ -235,59 +211,6 @@ export class UserEntityService implements OnModuleInit {
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getRelations(me: MiUser['id'], targets: MiUser['id'][]): Promise<Map<MiUser['id'], UserRelation>> {
|
||||
const [
|
||||
followers,
|
||||
followees,
|
||||
followersRequests,
|
||||
followeesRequests,
|
||||
blockers,
|
||||
blockees,
|
||||
muters,
|
||||
renoteMuters,
|
||||
] = await Promise.all([
|
||||
this.followingsRepository.findBy({ followerId: me })
|
||||
.then(f => new Map(f.map(it => [it.followeeId, it]))),
|
||||
this.followingsRepository.findBy({ followeeId: me })
|
||||
.then(it => it.map(it => it.followerId)),
|
||||
this.followRequestsRepository.findBy({ followerId: me })
|
||||
.then(it => it.map(it => it.followeeId)),
|
||||
this.followRequestsRepository.findBy({ followeeId: me })
|
||||
.then(it => it.map(it => it.followerId)),
|
||||
this.blockingsRepository.findBy({ blockerId: me })
|
||||
.then(it => it.map(it => it.blockeeId)),
|
||||
this.blockingsRepository.findBy({ blockeeId: me })
|
||||
.then(it => it.map(it => it.blockerId)),
|
||||
this.mutingsRepository.findBy({ muterId: me })
|
||||
.then(it => it.map(it => it.muteeId)),
|
||||
this.renoteMutingsRepository.findBy({ muterId: me })
|
||||
.then(it => it.map(it => it.muteeId)),
|
||||
]);
|
||||
|
||||
return new Map(
|
||||
targets.map(target => {
|
||||
const following = followers.get(target) ?? null;
|
||||
|
||||
return [
|
||||
target,
|
||||
{
|
||||
id: target,
|
||||
following: following,
|
||||
isFollowing: following != null,
|
||||
isFollowed: followees.includes(target),
|
||||
hasPendingFollowRequestFromYou: followersRequests.includes(target),
|
||||
hasPendingFollowRequestToYou: followeesRequests.includes(target),
|
||||
isBlocking: blockers.includes(target),
|
||||
isBlocked: blockees.includes(target),
|
||||
isMuted: muters.includes(target),
|
||||
isRenoteMuted: renoteMuters.includes(target),
|
||||
},
|
||||
];
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getHasUnreadAntenna(userId: MiUser['id']): Promise<boolean> {
|
||||
/*
|
||||
@@ -380,9 +303,6 @@ export class UserEntityService implements OnModuleInit {
|
||||
schema?: S,
|
||||
includeSecrets?: boolean,
|
||||
userProfile?: MiUserProfile,
|
||||
userRelations?: Map<MiUser['id'], UserRelation>,
|
||||
userMemos?: Map<MiUser['id'], string | null>,
|
||||
pinNotes?: Map<MiUser['id'], MiUserNotePining[]>,
|
||||
},
|
||||
): Promise<Packed<S>> {
|
||||
const opts = Object.assign({
|
||||
@@ -397,41 +317,13 @@ export class UserEntityService implements OnModuleInit {
|
||||
const isMe = meId === user.id;
|
||||
const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false;
|
||||
|
||||
const profile = isDetailed
|
||||
? (opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id }))
|
||||
: null;
|
||||
|
||||
let relation: UserRelation | null = null;
|
||||
if (meId && !isMe && isDetailed) {
|
||||
if (opts.userRelations) {
|
||||
relation = opts.userRelations.get(user.id) ?? null;
|
||||
} else {
|
||||
relation = await this.getRelation(meId, user.id);
|
||||
}
|
||||
}
|
||||
|
||||
let memo: string | null = null;
|
||||
if (isDetailed && meId) {
|
||||
if (opts.userMemos) {
|
||||
memo = opts.userMemos.get(user.id) ?? null;
|
||||
} else {
|
||||
memo = await this.userMemosRepository.findOneBy({ userId: meId, targetUserId: user.id })
|
||||
.then(row => row?.memo ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
let pins: MiUserNotePining[] = [];
|
||||
if (isDetailed) {
|
||||
if (opts.pinNotes) {
|
||||
pins = opts.pinNotes.get(user.id) ?? [];
|
||||
} else {
|
||||
pins = await this.userNotePiningsRepository.createQueryBuilder('pin')
|
||||
.where('pin.userId = :userId', { userId: user.id })
|
||||
.innerJoinAndSelect('pin.note', 'note')
|
||||
.orderBy('pin.id', 'DESC')
|
||||
.getMany();
|
||||
}
|
||||
}
|
||||
const relation = meId && !isMe && isDetailed ? await this.getRelation(meId, user.id) : null;
|
||||
const pins = isDetailed ? await this.userNotePiningsRepository.createQueryBuilder('pin')
|
||||
.where('pin.userId = :userId', { userId: user.id })
|
||||
.innerJoinAndSelect('pin.note', 'note')
|
||||
.orderBy('pin.id', 'DESC')
|
||||
.getMany() : [];
|
||||
const profile = isDetailed ? (opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id })) : null;
|
||||
|
||||
const followingCount = profile == null ? null :
|
||||
(profile.followingVisibility === 'public') || isMe ? user.followingCount :
|
||||
@@ -524,7 +416,9 @@ export class UserEntityService implements OnModuleInit {
|
||||
twoFactorEnabled: profile!.twoFactorEnabled,
|
||||
usePasswordLessLogin: profile!.usePasswordLessLogin,
|
||||
securityKeys: profile!.twoFactorEnabled
|
||||
? this.userSecurityKeysRepository.countBy({ userId: user.id }).then(result => result >= 1)
|
||||
? this.userSecurityKeysRepository.countBy({
|
||||
userId: user.id,
|
||||
}).then(result => result >= 1)
|
||||
: false,
|
||||
roles: this.roleService.getUserRoles(user.id).then(roles => roles.filter(role => role.isPublic).sort((a, b) => b.displayOrder - a.displayOrder).map(role => ({
|
||||
id: role.id,
|
||||
@@ -536,7 +430,10 @@ export class UserEntityService implements OnModuleInit {
|
||||
isAdministrator: role.isAdministrator,
|
||||
displayOrder: role.displayOrder,
|
||||
}))),
|
||||
memo: memo,
|
||||
memo: meId == null ? null : await this.userMemosRepository.findOneBy({
|
||||
userId: meId,
|
||||
targetUserId: user.id,
|
||||
}).then(row => row?.memo ?? null),
|
||||
moderationNote: iAmModerator ? (profile!.moderationNote ?? '') : undefined,
|
||||
} : {}),
|
||||
|
||||
@@ -617,7 +514,7 @@ export class UserEntityService implements OnModuleInit {
|
||||
return await awaitAll(packed);
|
||||
}
|
||||
|
||||
public async packMany<S extends 'MeDetailed' | 'UserDetailedNotMe' | 'UserDetailed' | 'UserLite' = 'UserLite'>(
|
||||
public packMany<S extends 'MeDetailed' | 'UserDetailedNotMe' | 'UserDetailed' | 'UserLite' = 'UserLite'>(
|
||||
users: (MiUser['id'] | MiUser)[],
|
||||
me?: { id: MiUser['id'] } | null | undefined,
|
||||
options?: {
|
||||
@@ -625,70 +522,6 @@ export class UserEntityService implements OnModuleInit {
|
||||
includeSecrets?: boolean,
|
||||
},
|
||||
): Promise<Packed<S>[]> {
|
||||
// -- IDのみの要素を補完して完全なエンティティ一覧を作る
|
||||
|
||||
const _users = users.filter((user): user is MiUser => typeof user !== 'string');
|
||||
if (_users.length !== users.length) {
|
||||
_users.push(
|
||||
...await this.usersRepository.findBy({
|
||||
id: In(users.filter((user): user is string => typeof user === 'string')),
|
||||
}),
|
||||
);
|
||||
}
|
||||
const _userIds = _users.map(u => u.id);
|
||||
|
||||
// -- 特に前提条件のない値群を取得
|
||||
|
||||
const profilesMap = await this.userProfilesRepository.findBy({ userId: In(_userIds) })
|
||||
.then(profiles => new Map(profiles.map(p => [p.userId, p])));
|
||||
|
||||
// -- 実行者の有無や指定スキーマの種別によって要否が異なる値群を取得
|
||||
|
||||
let userRelations: Map<MiUser['id'], UserRelation> = new Map();
|
||||
let userMemos: Map<MiUser['id'], string | null> = new Map();
|
||||
let pinNotes: Map<MiUser['id'], MiUserNotePining[]> = new Map();
|
||||
|
||||
if (options?.schema !== 'UserLite') {
|
||||
const meId = me ? me.id : null;
|
||||
if (meId) {
|
||||
userMemos = await this.userMemosRepository.findBy({ userId: meId })
|
||||
.then(memos => new Map(memos.map(memo => [memo.targetUserId, memo.memo])));
|
||||
|
||||
if (_userIds.length > 0) {
|
||||
userRelations = await this.getRelations(meId, _userIds);
|
||||
pinNotes = await this.userNotePiningsRepository.createQueryBuilder('pin')
|
||||
.where('pin.userId IN (:...userIds)', { userIds: _userIds })
|
||||
.innerJoinAndSelect('pin.note', 'note')
|
||||
.getMany()
|
||||
.then(pinsNotes => {
|
||||
const map = new Map<MiUser['id'], MiUserNotePining[]>();
|
||||
for (const note of pinsNotes) {
|
||||
const notes = map.get(note.userId) ?? [];
|
||||
notes.push(note);
|
||||
map.set(note.userId, notes);
|
||||
}
|
||||
for (const [, notes] of map.entries()) {
|
||||
// pack側ではDESCで取得しているので、それに合わせて降順に並び替えておく
|
||||
notes.sort((a, b) => b.id.localeCompare(a.id));
|
||||
}
|
||||
return map;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
_users.map(u => this.pack(
|
||||
u,
|
||||
me,
|
||||
{
|
||||
...options,
|
||||
userProfile: profilesMap.get(u.id),
|
||||
userRelations: userRelations,
|
||||
userMemos: userMemos,
|
||||
pinNotes: pinNotes,
|
||||
},
|
||||
)),
|
||||
);
|
||||
return Promise.all(users.map(u => this.pack(u, me, options)));
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ const envOption = {
|
||||
noDaemons: false,
|
||||
disableClustering: false,
|
||||
verbose: false,
|
||||
logJson: false,
|
||||
withLogTime: false,
|
||||
quiet: false,
|
||||
};
|
||||
|
@@ -50,6 +50,19 @@ export default class Logger {
|
||||
return;
|
||||
}
|
||||
|
||||
if (envOption.logJson) {
|
||||
console.log(JSON.stringify({
|
||||
time: new Date().toISOString(),
|
||||
level: level,
|
||||
message: message,
|
||||
data: data,
|
||||
important: important,
|
||||
context: [this.context].concat(subContexts).join('.'),
|
||||
cluster: cluster.isPrimary ? 'primary' : `worker-${cluster.worker!.id}`,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
const time = dateFormat(new Date(), 'HH:mm:ss');
|
||||
const worker = cluster.isPrimary ? '*' : cluster.worker!.id;
|
||||
const l =
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { onRequestHookHandler } from 'fastify';
|
||||
|
||||
export const handleRequestRedirectToOmitSearch: onRequestHookHandler = (request, reply, done) => {
|
||||
|
@@ -8,8 +8,9 @@
|
||||
* https://en.wikipedia.org/wiki/Identicon
|
||||
*/
|
||||
|
||||
import { createCanvas } from '@napi-rs/canvas';
|
||||
import * as p from 'pureimage';
|
||||
import gen from 'random-seed';
|
||||
import type { WriteStream } from 'node:fs';
|
||||
|
||||
const size = 128; // px
|
||||
const n = 5; // resolution
|
||||
@@ -44,9 +45,9 @@ const sideN = Math.floor(n / 2);
|
||||
/**
|
||||
* Generate buffer of an identicon by seed
|
||||
*/
|
||||
export async function genIdenticon(seed: string): Promise<Buffer> {
|
||||
export function genIdenticon(seed: string, stream: WriteStream): Promise<void> {
|
||||
const rand = gen.create(seed);
|
||||
const canvas = createCanvas(size, size);
|
||||
const canvas = p.make(size, size, undefined);
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
const bgColors = colors[rand(colors.length)];
|
||||
@@ -100,5 +101,5 @@ export async function genIdenticon(seed: string): Promise<Buffer> {
|
||||
}
|
||||
}
|
||||
|
||||
return await canvas.encode('png');
|
||||
return p.encodePNGToStream(canvas, stream);
|
||||
}
|
||||
|
10
packages/backend/src/misc/is-pure-renote.ts
Normal file
10
packages/backend/src/misc/is-pure-renote.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { MiNote } from '@/models/Note.js';
|
||||
|
||||
export function isPureRenote(note: MiNote): note is MiNote & { renoteId: NonNullable<MiNote['renoteId']> } {
|
||||
if (!note.renoteId) return false;
|
||||
|
||||
if (note.text) return false; // it's quoted with text
|
||||
if (note.fileIds.length !== 0) return false; // it's quoted with files
|
||||
if (note.hasPoll) return false; // it's quoted with poll
|
||||
return true;
|
||||
}
|
12
packages/backend/src/misc/is-quote.ts
Normal file
12
packages/backend/src/misc/is-quote.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { MiNote } from '@/models/Note.js';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function(note: MiNote): boolean {
|
||||
// sync with NoteCreateService.isQuote
|
||||
return note.renoteId != null && (note.text != null || note.hasPoll || (note.fileIds != null && note.fileIds.length > 0));
|
||||
}
|
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { MiNote } from '@/models/Note.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
|
||||
type Renote =
|
||||
MiNote & {
|
||||
renoteId: NonNullable<MiNote['renoteId']>
|
||||
};
|
||||
|
||||
type Quote =
|
||||
Renote & ({
|
||||
text: NonNullable<MiNote['text']>
|
||||
} | {
|
||||
cw: NonNullable<MiNote['cw']>
|
||||
} | {
|
||||
replyId: NonNullable<MiNote['replyId']>
|
||||
reply: NonNullable<MiNote['reply']>
|
||||
} | {
|
||||
hasPoll: true
|
||||
});
|
||||
|
||||
export function isRenote(note: MiNote): note is Renote {
|
||||
return note.renoteId != null;
|
||||
}
|
||||
|
||||
export function isQuote(note: Renote): note is Quote {
|
||||
// NOTE: SYNC WITH NoteCreateService.isQuote
|
||||
return note.text != null ||
|
||||
note.cw != null ||
|
||||
note.replyId != null ||
|
||||
note.hasPoll ||
|
||||
note.fileIds.length > 0;
|
||||
}
|
||||
|
||||
type PackedRenote =
|
||||
Packed<'Note'> & {
|
||||
renoteId: NonNullable<Packed<'Note'>['renoteId']>
|
||||
};
|
||||
|
||||
type PackedQuote =
|
||||
PackedRenote & ({
|
||||
text: NonNullable<Packed<'Note'>['text']>
|
||||
} | {
|
||||
cw: NonNullable<Packed<'Note'>['cw']>
|
||||
} | {
|
||||
replyId: NonNullable<Packed<'Note'>['replyId']>
|
||||
} | {
|
||||
poll: NonNullable<Packed<'Note'>['poll']>
|
||||
} | {
|
||||
fileIds: NonNullable<Packed<'Note'>['fileIds']>
|
||||
});
|
||||
|
||||
export function isRenotePacked(note: Packed<'Note'>): note is PackedRenote {
|
||||
return note.renoteId != null;
|
||||
}
|
||||
|
||||
export function isQuotePacked(note: PackedRenote): note is PackedQuote {
|
||||
return note.text != null ||
|
||||
note.cw != null ||
|
||||
note.replyId != null ||
|
||||
note.poll != null ||
|
||||
(note.fileIds != null && note.fileIds.length > 0);
|
||||
}
|
@@ -48,7 +48,6 @@ import {
|
||||
packedRoleCondFormulaValueCreatedSchema,
|
||||
packedRoleCondFormulaFollowersOrFollowingOrNotesSchema,
|
||||
packedRoleCondFormulaValueSchema,
|
||||
packedRoleCondFormulaValueUserSettingBooleanSchema,
|
||||
} from '@/models/json-schema/role.js';
|
||||
import { packedAdSchema } from '@/models/json-schema/ad.js';
|
||||
import { packedReversiGameLiteSchema, packedReversiGameDetailedSchema } from '@/models/json-schema/reversi-game.js';
|
||||
@@ -98,7 +97,6 @@ export const refs = {
|
||||
RoleCondFormulaLogics: packedRoleCondFormulaLogicsSchema,
|
||||
RoleCondFormulaValueNot: packedRoleCondFormulaValueNot,
|
||||
RoleCondFormulaValueIsLocalOrRemote: packedRoleCondFormulaValueIsLocalOrRemoteSchema,
|
||||
RoleCondFormulaValueUserSettingBooleanSchema: packedRoleCondFormulaValueUserSettingBooleanSchema,
|
||||
RoleCondFormulaValueAssignedRole: packedRoleCondFormulaValueAssignedRoleSchema,
|
||||
RoleCondFormulaValueCreated: packedRoleCondFormulaValueCreatedSchema,
|
||||
RoleCondFormulaFollowersOrFollowingOrNotes: packedRoleCondFormulaFollowersOrFollowingOrNotesSchema,
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export type FetchFunction<K, V> = (key: K) => Promise<V>;
|
||||
|
||||
type ResolveReject<V> = Parameters<ConstructorParameters<typeof Promise<V>>[0]>;
|
||||
|
@@ -72,11 +72,6 @@ export class MiAntenna {
|
||||
})
|
||||
public caseSensitive: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public excludeBots: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
|
@@ -277,6 +277,12 @@ export class MiMeta {
|
||||
})
|
||||
public enableSensitiveMediaDetectionForVideos: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
nullable: true,
|
||||
})
|
||||
public summalyProxy: string | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
@@ -582,36 +588,4 @@ export class MiMeta {
|
||||
default: 0,
|
||||
})
|
||||
public notesPerOneAd: number;
|
||||
|
||||
@Column('boolean', {
|
||||
default: true,
|
||||
})
|
||||
public urlPreviewEnabled: boolean;
|
||||
|
||||
@Column('integer', {
|
||||
default: 10000,
|
||||
})
|
||||
public urlPreviewTimeout: number;
|
||||
|
||||
@Column('bigint', {
|
||||
default: 1024 * 1024 * 10,
|
||||
})
|
||||
public urlPreviewMaximumContentLength: number;
|
||||
|
||||
@Column('boolean', {
|
||||
default: true,
|
||||
})
|
||||
public urlPreviewRequireContentLength: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
nullable: true,
|
||||
})
|
||||
public urlPreviewSummaryProxyUrl: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
nullable: true,
|
||||
})
|
||||
public urlPreviewUserAgent: string | null;
|
||||
}
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { id } from './util/id.js';
|
||||
import { MiUser } from './User.js';
|
||||
|
@@ -6,149 +6,69 @@
|
||||
import { Entity, Column, PrimaryColumn } from 'typeorm';
|
||||
import { id } from './util/id.js';
|
||||
|
||||
/**
|
||||
* ~かつ~
|
||||
* 複数の条件を同時に満たす場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueAnd = {
|
||||
type: 'and';
|
||||
values: RoleCondFormulaValue[];
|
||||
};
|
||||
|
||||
/**
|
||||
* ~または~
|
||||
* 複数の条件のうち、いずれかを満たす場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueOr = {
|
||||
type: 'or';
|
||||
values: RoleCondFormulaValue[];
|
||||
};
|
||||
|
||||
/**
|
||||
* ~ではない
|
||||
* 条件を満たさない場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueNot = {
|
||||
type: 'not';
|
||||
value: RoleCondFormulaValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* ローカルユーザーのみ成立とする
|
||||
*/
|
||||
type CondFormulaValueIsLocal = {
|
||||
type: 'isLocal';
|
||||
};
|
||||
|
||||
/**
|
||||
* リモートユーザーのみ成立とする
|
||||
*/
|
||||
type CondFormulaValueIsRemote = {
|
||||
type: 'isRemote';
|
||||
};
|
||||
|
||||
/**
|
||||
* 既に指定のマニュアルロールにアサインされている場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueRoleAssignedTo = {
|
||||
type: 'roleAssignedTo';
|
||||
roleId: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* サスペンド済みアカウントの場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueIsSuspended = {
|
||||
type: 'isSuspended';
|
||||
};
|
||||
|
||||
/**
|
||||
* 鍵アカウントの場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueIsLocked = {
|
||||
type: 'isLocked';
|
||||
};
|
||||
|
||||
/**
|
||||
* botアカウントの場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueIsBot = {
|
||||
type: 'isBot';
|
||||
};
|
||||
|
||||
/**
|
||||
* 猫アカウントの場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueIsCat = {
|
||||
type: 'isCat';
|
||||
};
|
||||
|
||||
/**
|
||||
* 「ユーザを見つけやすくする」が有効なアカウントの場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueIsExplorable = {
|
||||
type: 'isExplorable';
|
||||
};
|
||||
|
||||
/**
|
||||
* ユーザが作成されてから指定期間経過した場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueCreatedLessThan = {
|
||||
type: 'createdLessThan';
|
||||
sec: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* ユーザが作成されてから指定期間経っていない場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueCreatedMoreThan = {
|
||||
type: 'createdMoreThan';
|
||||
sec: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* フォロワー数が指定値以下の場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueFollowersLessThanOrEq = {
|
||||
type: 'followersLessThanOrEq';
|
||||
value: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* フォロワー数が指定値以上の場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueFollowersMoreThanOrEq = {
|
||||
type: 'followersMoreThanOrEq';
|
||||
value: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* フォロー数が指定値以下の場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueFollowingLessThanOrEq = {
|
||||
type: 'followingLessThanOrEq';
|
||||
value: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* フォロー数が指定値以上の場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueFollowingMoreThanOrEq = {
|
||||
type: 'followingMoreThanOrEq';
|
||||
value: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* 投稿数が指定値以下の場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueNotesLessThanOrEq = {
|
||||
type: 'notesLessThanOrEq';
|
||||
value: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* 投稿数が指定値以上の場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueNotesMoreThanOrEq = {
|
||||
type: 'notesMoreThanOrEq';
|
||||
value: number;
|
||||
@@ -160,11 +80,6 @@ export type RoleCondFormulaValue = { id: string } & (
|
||||
CondFormulaValueNot |
|
||||
CondFormulaValueIsLocal |
|
||||
CondFormulaValueIsRemote |
|
||||
CondFormulaValueIsSuspended |
|
||||
CondFormulaValueIsLocked |
|
||||
CondFormulaValueIsBot |
|
||||
CondFormulaValueIsCat |
|
||||
CondFormulaValueIsExplorable |
|
||||
CondFormulaValueRoleAssignedTo |
|
||||
CondFormulaValueCreatedLessThan |
|
||||
CondFormulaValueCreatedMoreThan |
|
||||
|
@@ -76,11 +76,6 @@ export const packedAntennaSchema = {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
excludeBots: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
default: false,
|
||||
},
|
||||
withReplies: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
|
@@ -52,9 +52,5 @@ export const packedClipSchema = {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
notesCount: {
|
||||
type: 'integer',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
@@ -207,10 +207,6 @@ export const packedMetaLiteSchema = {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
enableUrlPreview: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
backgroundImageUrl: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user