Merge remote-tracking branch 'kisaragi/refactor/kill-any/backend-test' into refactor/kill-any/backend-test
# Conflicts: # packages/backend/test/e2e/move.ts # packages/backend/test/utils.ts
This commit is contained in:
commit
54ee8834d1
|
@ -1,5 +1,3 @@
|
|||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Misskey",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"dockerComposeFile": "compose.yml",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspace",
|
||||
"features": {
|
||||
|
|
|
@ -7,7 +7,7 @@ Dockerfile
|
|||
build/
|
||||
built/
|
||||
db/
|
||||
docker-compose.yml
|
||||
.devcontainer/compose.yml
|
||||
node_modules/
|
||||
packages/*/node_modules
|
||||
redis/
|
||||
|
@ -28,4 +28,4 @@ fluent-emojis/
|
|||
|
||||
.idea/
|
||||
packages/*/.vscode/
|
||||
packages/backend/test/docker-compose.yml
|
||||
packages/backend/test/compose.yml
|
||||
|
|
|
@ -37,7 +37,7 @@ jobs:
|
|||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
|
|
@ -48,7 +48,7 @@ jobs:
|
|||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Build and Push to Docker Hub
|
||||
id: build
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
|
|
@ -22,7 +22,7 @@ jobs:
|
|||
sudo dpkg -i dockle.deb
|
||||
- run: |
|
||||
cp .config/docker_example.env .config/docker.env
|
||||
cp ./docker-compose_example.yml ./docker-compose.yml
|
||||
cp ./compose_example.yml ./compose.yml
|
||||
- run: |
|
||||
docker compose up -d web
|
||||
docker tag "$(docker compose images web | awk 'OFS=":" {print $4}' | tail -n +2)" misskey-web:latest
|
||||
|
|
|
@ -10,14 +10,14 @@ on:
|
|||
- packages/frontend/**
|
||||
- packages/sw/**
|
||||
- packages/misskey-js/**
|
||||
- packages/shared/.eslintrc.js
|
||||
- packages/shared/eslint.config.js
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/backend/**
|
||||
- packages/frontend/**
|
||||
- packages/sw/**
|
||||
- packages/misskey-js/**
|
||||
- packages/shared/.eslintrc.js
|
||||
- packages/shared/eslint.config.js
|
||||
|
||||
jobs:
|
||||
pnpm_install:
|
||||
|
|
|
@ -3,7 +3,7 @@ name: "Release Manager: sync changelog with PR"
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- release/**
|
||||
- develop
|
||||
paths:
|
||||
- 'CHANGELOG.md'
|
||||
|
||||
|
@ -20,17 +20,19 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# headがrelease/かつopenのPRを1つ取得
|
||||
# headが$GITHUB_REF_NAME, baseが$STABLE_BRANCHかつopenのPRを1つ取得
|
||||
- name: Get PR
|
||||
run: |
|
||||
echo "pr_number=$(gh pr list --limit 1 --head "$GITHUB_REF_NAME" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT
|
||||
echo "pr_number=$(gh pr list --limit 1 --search "head:$GITHUB_REF_NAME base:$STABLE_BRANCH is:open" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT
|
||||
id: get_pr
|
||||
env:
|
||||
STABLE_BRANCH: ${{ vars.STABLE_BRANCH }}
|
||||
- name: Get target version
|
||||
uses: misskey-dev/release-manager-actions/.github/actions/get-target-version@v1
|
||||
uses: misskey-dev/release-manager-actions/.github/actions/get-target-version@v2
|
||||
id: v
|
||||
# CHANGELOG.mdの内容を取得
|
||||
- name: Get changelog
|
||||
uses: misskey-dev/release-manager-actions/.github/actions/get-changelog@v1
|
||||
uses: misskey-dev/release-manager-actions/.github/actions/get-changelog@v2
|
||||
with:
|
||||
version: ${{ steps.v.outputs.target_version }}
|
||||
id: changelog
|
||||
|
@ -39,5 +41,5 @@ jobs:
|
|||
run: |
|
||||
gh pr edit "$PR_NUMBER" --body "$CHANGELOG"
|
||||
env:
|
||||
CHANGELOG: ${{ steps.changelog.outputs.changelog }}
|
||||
PR_NUMBER: ${{ steps.get_pr.outputs.pr_number }}
|
||||
CHANGELOG: ${{ steps.changelog.outputs.changelog }}
|
||||
|
|
|
@ -33,18 +33,21 @@ jobs:
|
|||
pr_number: ${{ steps.get_pr.outputs.pr_number }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# headがrelease/かつopenのPRを1つ取得
|
||||
# headが$GITHUB_REF_NAME, baseが$STABLE_BRANCHかつopenのPRを1つ取得
|
||||
- name: Get PRs
|
||||
run: |
|
||||
echo "pr_number=$(gh pr list --limit 1 --search "head:release/ is:open" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT
|
||||
echo "pr_number=$(gh pr list --limit 1 --search "head:$GITHUB_REF_NAME base:$STABLE_BRANCH is:open" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT
|
||||
id: get_pr
|
||||
env:
|
||||
STABLE_BRANCH: ${{ vars.STABLE_BRANCH }}
|
||||
|
||||
merge:
|
||||
uses: misskey-dev/release-manager-actions/.github/workflows/merge.yml@v1
|
||||
uses: misskey-dev/release-manager-actions/.github/workflows/merge.yml@v2
|
||||
needs: get-pr
|
||||
if: ${{ needs.get-pr.outputs.pr_number != '' && inputs.merge == true }}
|
||||
with:
|
||||
pr_number: ${{ needs.get-pr.outputs.pr_number }}
|
||||
user: 'github-actions[bot]'
|
||||
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
|
||||
# Text to prepend to the changelog
|
||||
# The first line must be `## Unreleased`
|
||||
|
@ -65,15 +68,14 @@ jobs:
|
|||
secrets:
|
||||
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
|
||||
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
||||
RULESET_EDIT_APP_ID: ${{ secrets.RULESET_EDIT_APP_ID }}
|
||||
RULESET_EDIT_APP_PRIVATE_KEY: ${{ secrets.RULESET_EDIT_APP_PRIVATE_KEY }}
|
||||
|
||||
create-prerelease:
|
||||
uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v1
|
||||
uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v2
|
||||
needs: get-pr
|
||||
if: ${{ needs.get-pr.outputs.pr_number != '' && inputs.merge != true }}
|
||||
with:
|
||||
pr_number: ${{ needs.get-pr.outputs.pr_number }}
|
||||
user: 'github-actions[bot]'
|
||||
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
|
||||
use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }}
|
||||
indent: ${{ vars.INDENT }}
|
||||
|
@ -82,10 +84,11 @@ jobs:
|
|||
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
||||
|
||||
create-target:
|
||||
uses: misskey-dev/release-manager-actions/.github/workflows/create-target.yml@v1
|
||||
uses: misskey-dev/release-manager-actions/.github/workflows/create-target.yml@v2
|
||||
needs: get-pr
|
||||
if: ${{ needs.get-pr.outputs.pr_number == '' }}
|
||||
with:
|
||||
user: 'github-actions[bot]'
|
||||
# The script for version increment.
|
||||
# process.env.CURRENT_VERSION: The current version.
|
||||
#
|
||||
|
@ -118,8 +121,7 @@ jobs:
|
|||
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
|
||||
use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }}
|
||||
indent: ${{ vars.INDENT }}
|
||||
stable_branch: ${{ vars.STABLE_BRANCH }}
|
||||
secrets:
|
||||
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
|
||||
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
||||
RULESET_EDIT_APP_ID: ${{ secrets.RULESET_EDIT_APP_ID }}
|
||||
RULESET_EDIT_APP_PRIVATE_KEY: ${{ secrets.RULESET_EDIT_APP_PRIVATE_KEY }}
|
||||
|
|
|
@ -16,23 +16,26 @@ jobs:
|
|||
check:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
ref: ${{ steps.get_pr.outputs.ref }}
|
||||
head: ${{ steps.get_pr.outputs.head }}
|
||||
base: ${{ steps.get_pr.outputs.base }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# PR情報を取得
|
||||
- name: Get PR
|
||||
run: |
|
||||
pr_json=$(gh pr view "$PR_NUMBER" --json isDraft,headRefName)
|
||||
echo "ref=$(echo $pr_json | jq -r '.headRefName')" >> $GITHUB_OUTPUT
|
||||
pr_json=$(gh pr view "$PR_NUMBER" --json isDraft,headRefName,baseRefName)
|
||||
echo "head=$(echo $pr_json | jq -r '.headRefName')" >> $GITHUB_OUTPUT
|
||||
echo "base=$(echo $pr_json | jq -r '.baseRefName')" >> $GITHUB_OUTPUT
|
||||
id: get_pr
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
release:
|
||||
uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v1
|
||||
uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v2
|
||||
needs: check
|
||||
if: startsWith(needs.check.outputs.ref, 'release/')
|
||||
if: needs.check.outputs.head == github.event.repository.default_branch && needs.check.outputs.base == vars.STABLE_BRANCH
|
||||
with:
|
||||
pr_number: ${{ github.event.pull_request.number }}
|
||||
user: 'github-actions[bot]'
|
||||
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
|
||||
use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }}
|
||||
indent: ${{ vars.INDENT }}
|
||||
|
|
|
@ -88,7 +88,7 @@ jobs:
|
|||
if [ "$BRANCH" = "misskey-dev:$HEAD_REF" ]; then
|
||||
BRANCH="$HEAD_REF"
|
||||
fi
|
||||
pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static --branch-name $BRANCH $(echo "$CHROMATIC_PARAMETER")
|
||||
pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static --branch-name "$BRANCH" $(echo "$CHROMATIC_PARAMETER")
|
||||
env:
|
||||
HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
||||
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
|
|
|
@ -35,8 +35,8 @@ coverage
|
|||
!/.config/example.yml
|
||||
!/.config/docker_example.yml
|
||||
!/.config/docker_example.env
|
||||
docker-compose.yml
|
||||
!/.devcontainer/docker-compose.yml
|
||||
.devcontainer/compose.yml
|
||||
!/.devcontainer/compose.yml
|
||||
|
||||
# misskey
|
||||
/build
|
||||
|
|
26
CHANGELOG.md
26
CHANGELOG.md
|
@ -3,15 +3,39 @@
|
|||
### General
|
||||
- Feat: 通報を受けた際、または解決した際に、予め登録した宛先に通知を飛ばせるように(mail or webhook) #13705
|
||||
- Fix: 配信停止したインスタンス一覧が見れなくなる問題を修正
|
||||
- Fix: Dockerコンテナの立ち上げ時に`pnpm`のインストールで固まることがある問題
|
||||
|
||||
### Client
|
||||
- Fix: `/about#federation` ページなどで各インスタンスのチャートが表示されなくなっていた問題を修正
|
||||
- Fix: ユーザーページの追加情報のラベルを投稿者のサーバーの絵文字で表示する (#13968)
|
||||
- Fix: リバーシの対局を正しく共有できないことがある問題を修正
|
||||
- Fix: コントロールパネルでベースロールのポリシーを編集してもUI上では変更が反映されない問題を修正
|
||||
- Fix: アンテナの編集画面のボタンに隙間を追加
|
||||
- Fix: テーマプレビューが見れない問題を修正
|
||||
- Fix: ショートカットキーが連打できる問題を修正
|
||||
(Cherry-picked from https://github.com/taiyme/misskey/pull/234)
|
||||
|
||||
### Server
|
||||
- チャート生成時にinstance.suspentionStateに置き換えられたinstance.isSuspendedが参照されてしまう問題を修正
|
||||
- Feat: レートリミット制限に引っかかったときに`Retry-After`ヘッダーを返すように (#13949)
|
||||
- Enhance: エンドポイント`clips/update`の必須項目を`clipId`のみに
|
||||
- Enhance: エンドポイント`admin/roles/update`の必須項目を`roleId`のみに
|
||||
- Enhance: エンドポイント`pages/update`の必須項目を`pageId`のみに
|
||||
- Enhance: エンドポイント`gallery/posts/update`の必須項目を`postId`のみに
|
||||
- Enhance: エンドポイント`i/webhook/update`の必須項目を`webhookId`のみに
|
||||
- Enhance: エンドポイント`admin/ad/update`の必須項目を`id`のみに
|
||||
- Fix: チャート生成時にinstance.suspentionStateに置き換えられたinstance.isSuspendedが参照されてしまう問題を修正
|
||||
- Fix: ユーザーのフィードページのMFMをHTMLに展開するように (#14006)
|
||||
- Fix: アンテナ・クリップ・リスト・ウェブフックがロールポリシーの上限より一つ多く作れてしまうのを修正 (#14036)
|
||||
- Fix: notRespondingSinceが実装される前に不通になったインスタンスが自動的に配信停止にならない (#14059)
|
||||
- Fix: FTT有効時、タイムライン用エンドポイントで`sinceId`にキャッシュ内最古のものより古いものを指定した場合に正しく結果が返ってこない問題を修正
|
||||
- Fix: 自分以外のクリップ内のノート個数が見えることがあるのを修正
|
||||
- Fix: 空文字列のリアクションはフォールバックされるように
|
||||
- Fix: リノートにリアクションできないように
|
||||
- Fix: ユーザー名の前後に空白文字列がある場合は省略するように
|
||||
- Fix: プロフィール編集時に名前を空白文字列のみにできる問題を修正
|
||||
|
||||
### Misskey.js
|
||||
- Feat: `/drive/files/create` のリクエストに対応(`multipart/form-data`に対応)
|
||||
|
||||
## 2024.5.0
|
||||
|
||||
|
|
|
@ -165,7 +165,7 @@ cp .github/misskey/test.yml .config/
|
|||
```
|
||||
Prepare DB/Redis for testing.
|
||||
```
|
||||
docker compose -f packages/backend/test/docker-compose.yml up
|
||||
docker compose -f packages/backend/test/compose.yml up
|
||||
```
|
||||
Alternatively, prepare an empty (data can be erased) DB and edit `.config/test.yml`.
|
||||
|
||||
|
|
|
@ -82,6 +82,10 @@ RUN apt-get update \
|
|||
USER misskey
|
||||
WORKDIR /misskey
|
||||
|
||||
# add package.json to add pnpm
|
||||
COPY --chown=misskey:misskey ./package.json ./package.json
|
||||
RUN corepack install
|
||||
|
||||
COPY --chown=misskey:misskey --from=target-builder /misskey/node_modules ./node_modules
|
||||
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/backend/node_modules ./packages/backend/node_modules
|
||||
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-js/node_modules ./packages/misskey-js/node_modules
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
version: "3"
|
||||
|
||||
# このconfigは、 dockerでMisskey本体を起動せず、 redisとpostgresql などだけを起動します
|
||||
|
||||
services:
|
|
@ -1,5 +1,3 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
web:
|
||||
build: .
|
|
@ -9761,7 +9761,7 @@ export interface Locale extends ILocale {
|
|||
"_dataSaver": {
|
||||
"_media": {
|
||||
/**
|
||||
* メディアの読み込み
|
||||
* メディアの読み込みを無効化
|
||||
*/
|
||||
"title": string;
|
||||
/**
|
||||
|
@ -9771,7 +9771,7 @@ export interface Locale extends ILocale {
|
|||
};
|
||||
"_avatar": {
|
||||
/**
|
||||
* アイコン画像
|
||||
* アイコン画像のアニメーションを無効化
|
||||
*/
|
||||
"title": string;
|
||||
/**
|
||||
|
@ -9781,7 +9781,7 @@ export interface Locale extends ILocale {
|
|||
};
|
||||
"_urlPreview": {
|
||||
/**
|
||||
* URLプレビューのサムネイル
|
||||
* URLプレビューのサムネイルを非表示
|
||||
*/
|
||||
"title": string;
|
||||
/**
|
||||
|
@ -9791,7 +9791,7 @@ export interface Locale extends ILocale {
|
|||
};
|
||||
"_code": {
|
||||
/**
|
||||
* コードハイライト
|
||||
* コードハイライトを非表示
|
||||
*/
|
||||
"title": string;
|
||||
/**
|
||||
|
|
|
@ -52,7 +52,11 @@ const primaries = {
|
|||
const clean = (text) => text.replace(new RegExp(String.fromCodePoint(0x08), 'g'), '');
|
||||
|
||||
export function build() {
|
||||
const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(new URL(`${c}.yml`, import.meta.url), 'utf-8'))) || {}, a), {});
|
||||
// vitestの挙動を調整するため、一度ローカル変数化する必要がある
|
||||
// https://github.com/vitest-dev/vitest/issues/3988#issuecomment-1686599577
|
||||
// https://github.com/misskey-dev/misskey/pull/14057#issuecomment-2192833785
|
||||
const metaUrl = import.meta.url;
|
||||
const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(new URL(`${c}.yml`, metaUrl), 'utf-8'))) || {}, a), {});
|
||||
|
||||
// 空文字列が入ることがあり、フォールバックが動作しなくなるのでプロパティごと消す
|
||||
const removeEmpty = (obj) => {
|
||||
|
|
|
@ -2599,16 +2599,16 @@ _externalResourceInstaller:
|
|||
|
||||
_dataSaver:
|
||||
_media:
|
||||
title: "メディアの読み込み"
|
||||
title: "メディアの読み込みを無効化"
|
||||
description: "画像・動画が自動で読み込まれるのを防止します。隠れている画像・動画はタップすると読み込まれます。"
|
||||
_avatar:
|
||||
title: "アイコン画像"
|
||||
title: "アイコン画像のアニメーションを無効化"
|
||||
description: "アイコン画像のアニメーションが停止します。アニメーション画像は通常の画像よりファイルサイズが大きいことがあるので、データ通信量をさらに削減できます。"
|
||||
_urlPreview:
|
||||
title: "URLプレビューのサムネイル"
|
||||
title: "URLプレビューのサムネイルを非表示"
|
||||
description: "URLプレビューのサムネイル画像が読み込まれなくなります。"
|
||||
_code:
|
||||
title: "コードハイライト"
|
||||
title: "コードハイライトを非表示"
|
||||
description: "MFMなどでコードハイライト記法が使われている場合、タップするまで読み込まれなくなります。コードハイライトではハイライトする言語ごとにその定義ファイルを読み込む必要がありますが、それらが自動で読み込まれなくなるため、通信量の削減が見込めます。"
|
||||
|
||||
_hemisphere:
|
||||
|
|
20
package.json
20
package.json
|
@ -55,20 +55,22 @@
|
|||
"js-yaml": "4.1.0",
|
||||
"postcss": "8.4.38",
|
||||
"tar": "6.2.1",
|
||||
"terser": "5.30.3",
|
||||
"typescript": "5.4.5",
|
||||
"esbuild": "0.20.2",
|
||||
"terser": "5.31.1",
|
||||
"typescript": "5.5.3",
|
||||
"esbuild": "0.22.0",
|
||||
"glob": "10.3.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.12.7",
|
||||
"@typescript-eslint/eslint-plugin": "7.7.1",
|
||||
"@typescript-eslint/parser": "7.7.1",
|
||||
"@misskey-dev/eslint-plugin": "2.0.2",
|
||||
"@types/node": "20.14.9",
|
||||
"@typescript-eslint/eslint-plugin": "7.15.0",
|
||||
"@typescript-eslint/parser": "7.15.0",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "13.7.3",
|
||||
"eslint": "8.57.0",
|
||||
"cypress": "13.13.0",
|
||||
"eslint": "9.6.0",
|
||||
"globals": "15.7.0",
|
||||
"ncp": "2.0.0",
|
||||
"start-server-and-test": "2.0.3"
|
||||
"start-server-and-test": "2.0.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tensorflow/tfjs-core": "4.4.0"
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
node_modules
|
||||
/built
|
||||
/.eslintrc.js
|
||||
/@types/**/*
|
|
@ -1,32 +0,0 @@
|
|||
module.exports = {
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json', './test/tsconfig.json'],
|
||||
},
|
||||
extends: [
|
||||
'../shared/.eslintrc.js',
|
||||
],
|
||||
rules: {
|
||||
'import/order': ['warn', {
|
||||
'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
|
||||
'pathGroups': [
|
||||
{
|
||||
'pattern': '@/**',
|
||||
'group': 'external',
|
||||
'position': 'after'
|
||||
}
|
||||
],
|
||||
}],
|
||||
'no-restricted-globals': [
|
||||
'error',
|
||||
{
|
||||
'name': '__dirname',
|
||||
'message': 'Not in ESModule. Use `import.meta.url` instead.'
|
||||
},
|
||||
{
|
||||
'name': '__filename',
|
||||
'message': 'Not in ESModule. Use `import.meta.url` instead.'
|
||||
}
|
||||
]
|
||||
},
|
||||
};
|
|
@ -0,0 +1,46 @@
|
|||
import tsParser from '@typescript-eslint/parser';
|
||||
import sharedConfig from '../shared/eslint.config.js';
|
||||
|
||||
export default [
|
||||
...sharedConfig,
|
||||
{
|
||||
ignores: ['**/node_modules', 'built', '@types/**/*'],
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: tsParser,
|
||||
project: ['./tsconfig.json', './test/tsconfig.json'],
|
||||
sourceType: 'module',
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'import/order': ['warn', {
|
||||
groups: [
|
||||
'builtin',
|
||||
'external',
|
||||
'internal',
|
||||
'parent',
|
||||
'sibling',
|
||||
'index',
|
||||
'object',
|
||||
'type',
|
||||
],
|
||||
pathGroups: [{
|
||||
pattern: '@/**',
|
||||
group: 'external',
|
||||
position: 'after',
|
||||
}],
|
||||
}],
|
||||
'no-restricted-globals': ['error', {
|
||||
name: '__dirname',
|
||||
message: 'Not in ESModule. Use `import.meta.url` instead.',
|
||||
}, {
|
||||
name: '__filename',
|
||||
message: 'Not in ESModule. Use `import.meta.url` instead.',
|
||||
}],
|
||||
},
|
||||
},
|
||||
];
|
|
@ -65,43 +65,43 @@
|
|||
"utf-8-validate": "6.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.412.0",
|
||||
"@aws-sdk/lib-storage": "3.412.0",
|
||||
"@bull-board/api": "5.17.0",
|
||||
"@bull-board/fastify": "5.17.0",
|
||||
"@bull-board/ui": "5.17.0",
|
||||
"@aws-sdk/client-s3": "3.600.0",
|
||||
"@aws-sdk/lib-storage": "3.600.0",
|
||||
"@bull-board/api": "5.20.5",
|
||||
"@bull-board/fastify": "5.20.5",
|
||||
"@bull-board/ui": "5.20.5",
|
||||
"@discordapp/twemoji": "15.0.3",
|
||||
"@fastify/accepts": "4.3.0",
|
||||
"@fastify/cookie": "9.3.1",
|
||||
"@fastify/cors": "9.0.1",
|
||||
"@fastify/express": "3.0.0",
|
||||
"@fastify/http-proxy": "9.5.0",
|
||||
"@fastify/multipart": "8.2.0",
|
||||
"@fastify/static": "7.0.3",
|
||||
"@fastify/multipart": "8.3.0",
|
||||
"@fastify/static": "7.0.4",
|
||||
"@fastify/view": "9.1.0",
|
||||
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
||||
"@misskey-dev/summaly": "5.1.0",
|
||||
"@napi-rs/canvas": "^0.1.52",
|
||||
"@nestjs/common": "10.3.8",
|
||||
"@nestjs/core": "10.3.8",
|
||||
"@nestjs/testing": "10.3.8",
|
||||
"@napi-rs/canvas": "^0.1.53",
|
||||
"@nestjs/common": "10.3.10",
|
||||
"@nestjs/core": "10.3.10",
|
||||
"@nestjs/testing": "10.3.10",
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@sentry/node": "^8.5.0",
|
||||
"@sentry/profiling-node": "^8.5.0",
|
||||
"@sentry/node": "8.13.0",
|
||||
"@sentry/profiling-node": "8.13.0",
|
||||
"@simplewebauthn/server": "10.0.0",
|
||||
"@sinonjs/fake-timers": "11.2.2",
|
||||
"@smithy/node-http-handler": "2.5.0",
|
||||
"@swc/cli": "0.3.12",
|
||||
"@swc/core": "1.4.17",
|
||||
"@swc/core": "1.6.6",
|
||||
"@twemoji/parser": "15.1.1",
|
||||
"accepts": "1.3.8",
|
||||
"ajv": "8.13.0",
|
||||
"ajv": "8.16.0",
|
||||
"archiver": "7.0.1",
|
||||
"async-mutex": "0.5.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"blurhash": "2.0.5",
|
||||
"body-parser": "1.20.2",
|
||||
"bullmq": "5.7.8",
|
||||
"bullmq": "5.8.3",
|
||||
"cacheable-lookup": "7.0.0",
|
||||
"cbor": "9.0.2",
|
||||
"chalk": "5.3.0",
|
||||
|
@ -112,27 +112,27 @@
|
|||
"content-disposition": "0.5.4",
|
||||
"date-fns": "2.30.0",
|
||||
"deep-email-validator": "0.1.21",
|
||||
"fastify": "4.26.2",
|
||||
"fastify": "4.28.1",
|
||||
"fastify-raw-body": "4.3.0",
|
||||
"feed": "4.2.2",
|
||||
"file-type": "19.0.0",
|
||||
"fluent-ffmpeg": "2.1.2",
|
||||
"fluent-ffmpeg": "2.1.3",
|
||||
"form-data": "4.0.0",
|
||||
"got": "14.2.1",
|
||||
"got": "14.4.1",
|
||||
"happy-dom": "10.0.3",
|
||||
"hpagent": "1.2.0",
|
||||
"htmlescape": "1.1.1",
|
||||
"http-link-header": "1.1.3",
|
||||
"ioredis": "5.4.1",
|
||||
"ip-cidr": "3.1.0",
|
||||
"ip-cidr": "4.0.1",
|
||||
"ipaddr.js": "2.2.0",
|
||||
"is-svg": "5.0.0",
|
||||
"is-svg": "5.0.1",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsdom": "24.0.0",
|
||||
"jsdom": "24.1.0",
|
||||
"json5": "2.2.3",
|
||||
"jsonld": "8.3.2",
|
||||
"jsrsasign": "11.1.0",
|
||||
"meilisearch": "0.38.0",
|
||||
"meilisearch": "0.41.0",
|
||||
"mfm-js": "0.24.0",
|
||||
"microformats-parser": "2.0.2",
|
||||
"mime-types": "2.1.35",
|
||||
|
@ -142,24 +142,24 @@
|
|||
"nanoid": "5.0.7",
|
||||
"nested-property": "4.0.0",
|
||||
"node-fetch": "3.3.2",
|
||||
"nodemailer": "6.9.13",
|
||||
"nodemailer": "6.9.14",
|
||||
"nsfwjs": "2.4.2",
|
||||
"oauth": "0.10.0",
|
||||
"oauth2orize": "1.12.0",
|
||||
"oauth2orize-pkce": "0.1.2",
|
||||
"os-utils": "0.0.14",
|
||||
"otpauth": "9.2.3",
|
||||
"otpauth": "9.3.1",
|
||||
"parse5": "7.1.2",
|
||||
"pg": "8.11.5",
|
||||
"pg": "8.12.0",
|
||||
"pkce-challenge": "4.1.0",
|
||||
"probe-image-size": "7.2.3",
|
||||
"promise-limit": "2.7.0",
|
||||
"pug": "3.0.2",
|
||||
"pug": "3.0.3",
|
||||
"punycode": "2.3.1",
|
||||
"qrcode": "1.5.3",
|
||||
"random-seed": "0.3.0",
|
||||
"ratelimiter": "3.4.1",
|
||||
"re2": "1.21.2",
|
||||
"re2": "1.21.3",
|
||||
"redis-lock": "0.1.4",
|
||||
"reflect-metadata": "0.2.2",
|
||||
"rename": "1.0.4",
|
||||
|
@ -167,27 +167,26 @@
|
|||
"rxjs": "7.8.1",
|
||||
"sanitize-html": "2.13.0",
|
||||
"secure-json-parse": "2.7.0",
|
||||
"sharp": "0.33.3",
|
||||
"sharp": "0.33.4",
|
||||
"slacc": "0.0.10",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"systeminformation": "5.22.7",
|
||||
"systeminformation": "5.22.11",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tmp": "0.2.3",
|
||||
"tsc-alias": "1.8.8",
|
||||
"tsc-alias": "1.8.10",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typeorm": "0.3.20",
|
||||
"typescript": "5.4.5",
|
||||
"typescript": "5.5.3",
|
||||
"ulid": "2.3.0",
|
||||
"vary": "1.1.2",
|
||||
"web-push": "3.6.7",
|
||||
"ws": "8.17.0",
|
||||
"ws": "8.17.1",
|
||||
"xev": "3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.7.0",
|
||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||
"@nestjs/platform-express": "10.3.8",
|
||||
"@nestjs/platform-express": "10.3.10",
|
||||
"@simplewebauthn/types": "10.0.0",
|
||||
"@swc/jest": "0.2.36",
|
||||
"@types/accepts": "1.3.7",
|
||||
|
@ -197,22 +196,21 @@
|
|||
"@types/color-convert": "2.0.3",
|
||||
"@types/content-disposition": "0.5.8",
|
||||
"@types/fluent-ffmpeg": "2.1.24",
|
||||
"@types/htmlescape": "^1.1.3",
|
||||
"@types/http-link-header": "1.0.5",
|
||||
"@types/htmlescape": "1.1.3",
|
||||
"@types/http-link-header": "1.0.7",
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/jsdom": "21.1.6",
|
||||
"@types/jsonld": "1.5.13",
|
||||
"@types/jsdom": "21.1.7",
|
||||
"@types/jsonld": "1.5.14",
|
||||
"@types/jsrsasign": "10.5.14",
|
||||
"@types/mime-types": "2.1.4",
|
||||
"@types/ms": "0.7.34",
|
||||
"@types/node": "20.12.7",
|
||||
"@types/node-fetch": "3.0.3",
|
||||
"@types/node": "20.14.9",
|
||||
"@types/nodemailer": "6.4.15",
|
||||
"@types/oauth": "0.9.4",
|
||||
"@types/oauth": "0.9.5",
|
||||
"@types/oauth2orize": "1.11.5",
|
||||
"@types/oauth2orize-pkce": "0.1.2",
|
||||
"@types/pg": "8.11.5",
|
||||
"@types/pg": "8.11.6",
|
||||
"@types/pug": "2.0.10",
|
||||
"@types/punycode": "2.1.4",
|
||||
"@types/qrcode": "1.5.5",
|
||||
|
@ -228,18 +226,17 @@
|
|||
"@types/vary": "1.1.3",
|
||||
"@types/web-push": "3.6.3",
|
||||
"@types/ws": "8.5.10",
|
||||
"@typescript-eslint/eslint-plugin": "7.7.1",
|
||||
"@typescript-eslint/parser": "7.7.1",
|
||||
"aws-sdk-client-mock": "3.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "7.15.0",
|
||||
"@typescript-eslint/parser": "7.15.0",
|
||||
"aws-sdk-client-mock": "4.0.1",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"execa": "8.0.1",
|
||||
"fkill": "^9.0.0",
|
||||
"execa": "9.2.0",
|
||||
"fkill": "9.0.0",
|
||||
"jest": "29.7.0",
|
||||
"jest-mock": "29.7.0",
|
||||
"nodemon": "3.1.0",
|
||||
"nodemon": "3.1.4",
|
||||
"pid-port": "1.0.0",
|
||||
"simple-oauth2": "5.0.0"
|
||||
"simple-oauth2": "5.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ function execStart() {
|
|||
|
||||
async function killProc() {
|
||||
if (backendProcess) {
|
||||
backendProcess.catch(() => {}); // backendProcess.kill()によって発生する例外を無視するためにcatch()を呼び出す
|
||||
backendProcess.kill();
|
||||
await new Promise(resolve => backendProcess.on('exit', resolve));
|
||||
backendProcess = undefined;
|
||||
|
@ -46,6 +47,7 @@ async function killProc() {
|
|||
],
|
||||
{
|
||||
stdio: [process.stdin, process.stdout, process.stderr, 'ipc'],
|
||||
serialization: "json",
|
||||
})
|
||||
.on('message', async (message) => {
|
||||
if (message.type === 'exit') {
|
||||
|
|
|
@ -10,7 +10,6 @@ import sanitizeHtml from 'sanitize-html';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { GlobalEvents, GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import type {
|
||||
AbuseReportNotificationRecipientRepository,
|
||||
MiAbuseReportNotificationRecipient,
|
||||
|
@ -91,7 +90,7 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
|
|||
const recipientEMailAddresses = await this.fetchEMailRecipients().then(it => it
|
||||
.filter(it => it.isActive && it.userProfile?.emailVerified)
|
||||
.map(it => it.userProfile?.email)
|
||||
.filter(isNotNull),
|
||||
.filter(x => x != null),
|
||||
);
|
||||
|
||||
// 送信先の鮮度を保つため、毎回取得する
|
||||
|
@ -138,7 +137,7 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
|
|||
.then(it => it
|
||||
.filter(it => it.isActive && it.systemWebhookId && it.method === 'webhook')
|
||||
.map(it => it.systemWebhookId)
|
||||
.filter(isNotNull));
|
||||
.filter(x => x != null));
|
||||
for (const webhookId of recipientWebhookIds) {
|
||||
await Promise.all(
|
||||
abuseReports.map(it => {
|
||||
|
@ -340,7 +339,7 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
|
|||
@bindThis
|
||||
private async removeUnauthorizedRecipientUsers(recipients: MiAbuseReportNotificationRecipient[]): Promise<MiAbuseReportNotificationRecipient[]> {
|
||||
const userRecipients = recipients.filter(it => it.userId !== null);
|
||||
const recipientUserIds = new Set(userRecipients.map(it => it.userId).filter(isNotNull));
|
||||
const recipientUserIds = new Set(userRecipients.map(it => it.userId).filter(x => x != null));
|
||||
if (recipientUserIds.size <= 0) {
|
||||
// ユーザが通知先として設定されていない場合、この関数での処理を行うべきレコードが無い
|
||||
return recipients;
|
||||
|
|
|
@ -55,9 +55,6 @@ export class FanoutTimelineEndpointService {
|
|||
|
||||
@bindThis
|
||||
private async getMiNotes(ps: TimelineOptions): Promise<MiNote[]> {
|
||||
let noteIds: string[];
|
||||
let shouldFallbackToDb = false;
|
||||
|
||||
// 呼び出し元と以下の処理をシンプルにするためにdbFallbackを置き換える
|
||||
if (!ps.useDbFallback) ps.dbFallback = () => Promise.resolve([]);
|
||||
|
||||
|
@ -67,12 +64,11 @@ export class FanoutTimelineEndpointService {
|
|||
const redisResult = await this.fanoutTimelineService.getMulti(ps.redisTimelines, ps.untilId, ps.sinceId);
|
||||
|
||||
// TODO: いい感じにgetMulti内でソート済だからuniqするときにredisResultが全てソート済なのを利用して再ソートを避けたい
|
||||
const redisResultIds = Array.from(new Set(redisResult.flat(1)));
|
||||
const redisResultIds = Array.from(new Set(redisResult.flat(1))).sort(idCompare);
|
||||
|
||||
redisResultIds.sort(idCompare);
|
||||
noteIds = redisResultIds.slice(0, ps.limit);
|
||||
|
||||
shouldFallbackToDb = shouldFallbackToDb || (noteIds.length === 0);
|
||||
let noteIds = redisResultIds.slice(0, ps.limit);
|
||||
const oldestNoteId = ascending ? redisResultIds[0] : redisResultIds[redisResultIds.length - 1];
|
||||
const shouldFallbackToDb = noteIds.length === 0 || ps.sinceId != null && ps.sinceId < oldestNoteId;
|
||||
|
||||
if (!shouldFallbackToDb) {
|
||||
let filter = ps.noteFilter ?? (_note => true);
|
||||
|
|
|
@ -13,10 +13,12 @@ import { intersperse } from '@/misc/prelude/array.js';
|
|||
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||
import type { IMentionedRemoteUsers } from '@/models/Note.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import * as TreeAdapter from '../../node_modules/parse5/dist/tree-adapters/default.js';
|
||||
import type { DefaultTreeAdapterMap } from 'parse5';
|
||||
import type * as mfm from 'mfm-js';
|
||||
|
||||
const treeAdapter = TreeAdapter.defaultTreeAdapter;
|
||||
const treeAdapter = parse5.defaultTreeAdapter;
|
||||
type Node = DefaultTreeAdapterMap['node'];
|
||||
type ChildNode = DefaultTreeAdapterMap['childNode'];
|
||||
|
||||
const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/;
|
||||
const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/;
|
||||
|
@ -46,7 +48,7 @@ export class MfmService {
|
|||
|
||||
return text.trim();
|
||||
|
||||
function getText(node: TreeAdapter.Node): string {
|
||||
function getText(node: Node): string {
|
||||
if (treeAdapter.isTextNode(node)) return node.value;
|
||||
if (!treeAdapter.isElementNode(node)) return '';
|
||||
if (node.nodeName === 'br') return '\n';
|
||||
|
@ -58,7 +60,7 @@ export class MfmService {
|
|||
return '';
|
||||
}
|
||||
|
||||
function appendChildren(childNodes: TreeAdapter.ChildNode[]): void {
|
||||
function appendChildren(childNodes: ChildNode[]): void {
|
||||
if (childNodes) {
|
||||
for (const n of childNodes) {
|
||||
analyze(n);
|
||||
|
@ -66,14 +68,16 @@ export class MfmService {
|
|||
}
|
||||
}
|
||||
|
||||
function analyze(node: TreeAdapter.Node) {
|
||||
function analyze(node: Node) {
|
||||
if (treeAdapter.isTextNode(node)) {
|
||||
text += node.value;
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip comment or document type node
|
||||
if (!treeAdapter.isElementNode(node)) return;
|
||||
if (!treeAdapter.isElementNode(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (node.nodeName) {
|
||||
case 'br': {
|
||||
|
@ -81,8 +85,7 @@ export class MfmService {
|
|||
break;
|
||||
}
|
||||
|
||||
case 'a':
|
||||
{
|
||||
case 'a': {
|
||||
const txt = getText(node);
|
||||
const rel = node.attrs.find(x => x.name === 'rel');
|
||||
const href = node.attrs.find(x => x.name === 'href');
|
||||
|
@ -90,7 +93,7 @@ export class MfmService {
|
|||
// ハッシュタグ
|
||||
if (normalizedHashtagNames && href && normalizedHashtagNames.has(normalizeForSearch(txt))) {
|
||||
text += txt;
|
||||
// メンション
|
||||
// メンション
|
||||
} else if (txt.startsWith('@') && !(rel && rel.value.startsWith('me '))) {
|
||||
const part = txt.split('@');
|
||||
|
||||
|
@ -102,7 +105,7 @@ export class MfmService {
|
|||
} else if (part.length === 3) {
|
||||
text += txt;
|
||||
}
|
||||
// その他
|
||||
// その他
|
||||
} else {
|
||||
const generateLink = () => {
|
||||
if (!href && !txt) {
|
||||
|
@ -130,8 +133,7 @@ export class MfmService {
|
|||
break;
|
||||
}
|
||||
|
||||
case 'h1':
|
||||
{
|
||||
case 'h1': {
|
||||
text += '【';
|
||||
appendChildren(node.childNodes);
|
||||
text += '】\n';
|
||||
|
@ -139,16 +141,14 @@ export class MfmService {
|
|||
}
|
||||
|
||||
case 'b':
|
||||
case 'strong':
|
||||
{
|
||||
case 'strong': {
|
||||
text += '**';
|
||||
appendChildren(node.childNodes);
|
||||
text += '**';
|
||||
break;
|
||||
}
|
||||
|
||||
case 'small':
|
||||
{
|
||||
case 'small': {
|
||||
text += '<small>';
|
||||
appendChildren(node.childNodes);
|
||||
text += '</small>';
|
||||
|
@ -156,8 +156,7 @@ export class MfmService {
|
|||
}
|
||||
|
||||
case 's':
|
||||
case 'del':
|
||||
{
|
||||
case 'del': {
|
||||
text += '~~';
|
||||
appendChildren(node.childNodes);
|
||||
text += '~~';
|
||||
|
@ -165,8 +164,7 @@ export class MfmService {
|
|||
}
|
||||
|
||||
case 'i':
|
||||
case 'em':
|
||||
{
|
||||
case 'em': {
|
||||
text += '<i>';
|
||||
appendChildren(node.childNodes);
|
||||
text += '</i>';
|
||||
|
@ -207,8 +205,7 @@ export class MfmService {
|
|||
case 'h3':
|
||||
case 'h4':
|
||||
case 'h5':
|
||||
case 'h6':
|
||||
{
|
||||
case 'h6': {
|
||||
text += '\n\n';
|
||||
appendChildren(node.childNodes);
|
||||
break;
|
||||
|
@ -221,8 +218,7 @@ export class MfmService {
|
|||
case 'article':
|
||||
case 'li':
|
||||
case 'dt':
|
||||
case 'dd':
|
||||
{
|
||||
case 'dd': {
|
||||
text += '\n';
|
||||
appendChildren(node.childNodes);
|
||||
break;
|
||||
|
|
|
@ -59,7 +59,6 @@ import { UtilityService } from '@/core/UtilityService.js';
|
|||
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||
import { isReply } from '@/misc/is-reply.js';
|
||||
import { trackPromise } from '@/misc/promise-tracker.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
|
||||
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
||||
|
@ -839,7 +838,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
const mentions = extractMentions(tokens);
|
||||
let mentionedUsers = (await Promise.all(mentions.map(m =>
|
||||
this.remoteUserResolveService.resolveUser(m.username, m.host ?? user.host).catch(() => null),
|
||||
))).filter(isNotNull);
|
||||
))).filter(x => x != null);
|
||||
|
||||
// Drop duplicate users
|
||||
mentionedUsers = mentionedUsers.filter((u, i, self) =>
|
||||
|
|
|
@ -29,6 +29,7 @@ import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
|||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { FeaturedService } from '@/core/FeaturedService.js';
|
||||
import { trackPromise } from '@/misc/promise-tracker.js';
|
||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||
|
||||
const FALLBACK = '\u2764';
|
||||
const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
|
||||
|
@ -117,11 +118,16 @@ export class ReactionService {
|
|||
throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.');
|
||||
}
|
||||
|
||||
// Check if note is Renote
|
||||
if (isRenote(note) && !isQuote(note)) {
|
||||
throw new IdentifiableError('12c35529-3c79-4327-b1cc-e2cf63a71925', 'You cannot react to Renote.');
|
||||
}
|
||||
|
||||
let reaction = _reaction ?? FALLBACK;
|
||||
|
||||
if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && (user.host != null))) {
|
||||
reaction = '\u2764';
|
||||
} else if (_reaction) {
|
||||
} else if (_reaction != null) {
|
||||
const custom = reaction.match(isCustomEmojiRegexp);
|
||||
if (custom) {
|
||||
const reacterHost = this.utilityService.toPunyNullable(user.host);
|
||||
|
|
|
@ -8,7 +8,6 @@ import promiseLimit from 'promise-limit';
|
|||
import type { MiRemoteUser, MiUser } from '@/models/User.js';
|
||||
import { concat, unique } from '@/misc/prelude/array.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { getApIds } from './type.js';
|
||||
import { ApPersonService } from './models/ApPersonService.js';
|
||||
import type { ApObject } from './type.js';
|
||||
|
@ -41,7 +40,7 @@ export class ApAudienceService {
|
|||
const limit = promiseLimit<MiUser | null>(2);
|
||||
const mentionedUsers = (await Promise.all(
|
||||
others.map(id => limit(() => this.apPersonService.resolvePerson(id, resolver).catch(() => null))),
|
||||
)).filter(isNotNull);
|
||||
)).filter(x => x != null);
|
||||
|
||||
if (toGroups.public.length > 0) {
|
||||
return {
|
||||
|
|
|
@ -27,7 +27,6 @@ import { QueueService } from '@/core/QueueService.js';
|
|||
import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { MiRemoteUser } from '@/models/User.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { AbuseReportService } from '@/core/AbuseReportService.js';
|
||||
import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
|
||||
|
@ -538,7 +537,7 @@ export class ApInboxService {
|
|||
const userIds = uris
|
||||
.filter(uri => uri.startsWith(this.config.url + '/users/'))
|
||||
.map(uri => uri.split('/').at(-1))
|
||||
.filter(isNotNull);
|
||||
.filter(x => x != null);
|
||||
const users = await this.usersRepository.findBy({
|
||||
id: In(userIds),
|
||||
});
|
||||
|
|
|
@ -26,7 +26,6 @@ import type { MiUserKeypair } from '@/models/UserKeypair.js';
|
|||
import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { JsonLdService } from './JsonLdService.js';
|
||||
import { ApMfmService } from './ApMfmService.js';
|
||||
|
@ -317,7 +316,7 @@ export class ApRendererService {
|
|||
const getPromisedFiles = async (ids: string[]): Promise<MiDriveFile[]> => {
|
||||
if (ids.length === 0) return [];
|
||||
const items = await this.driveFilesRepository.findBy({ id: In(ids) });
|
||||
return ids.map(id => items.find(item => item.id === id)).filter(isNotNull);
|
||||
return ids.map(id => items.find(item => item.id === id)).filter(x => x != null);
|
||||
};
|
||||
|
||||
let inReplyTo;
|
||||
|
@ -686,7 +685,7 @@ export class ApRendererService {
|
|||
if (names.length === 0) return [];
|
||||
|
||||
const allEmojis = await this.customEmojiService.localEmojisCache.fetch();
|
||||
const emojis = names.map(name => allEmojis.get(name)).filter(isNotNull);
|
||||
const emojis = names.map(name => allEmojis.get(name)).filter(x => x != null);
|
||||
|
||||
return emojis;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import promiseLimit from 'promise-limit';
|
|||
import type { MiUser } from '@/models/_.js';
|
||||
import { toArray, unique } from '@/misc/prelude/array.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { isMention } from '../type.js';
|
||||
import { Resolver } from '../ApResolverService.js';
|
||||
import { ApPersonService } from './ApPersonService.js';
|
||||
|
@ -28,7 +27,7 @@ export class ApMentionService {
|
|||
const limit = promiseLimit<MiUser | null>(2);
|
||||
const mentionedUsers = (await Promise.all(
|
||||
hrefs.map(x => limit(() => this.apPersonService.resolvePerson(x, resolver).catch(() => null))),
|
||||
)).filter(isNotNull);
|
||||
)).filter(x => x != null);
|
||||
|
||||
return mentionedUsers;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ import { UtilityService } from '@/core/UtilityService.js';
|
|||
import { bindThis } from '@/decorators.js';
|
||||
import { checkHttps } from '@/misc/check-https.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js';
|
||||
import { ApLoggerService } from '../ApLoggerService.js';
|
||||
import { ApMfmService } from '../ApMfmService.js';
|
||||
|
@ -253,7 +252,7 @@ export class ApNoteService {
|
|||
}
|
||||
};
|
||||
|
||||
const uris = unique([note._misskey_quote, note.quoteUrl].filter(isNotNull));
|
||||
const uris = unique([note._misskey_quote, note.quoteUrl].filter(x => x != null));
|
||||
const results = await Promise.all(uris.map(tryResolveNote));
|
||||
|
||||
quote = results.filter((x): x is { status: 'ok', res: MiNote } => x.status === 'ok').map(x => x.res).at(0);
|
||||
|
|
|
@ -38,7 +38,6 @@ import { MetaService } from '@/core/MetaService.js';
|
|||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||
import type { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||
import { checkHttps } from '@/misc/check-https.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { getApId, getApType, getOneApHrefNullable, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js';
|
||||
import { extractApHashtags } from './tag.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
|
@ -637,7 +636,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
|
||||
// とりあえずidを別の時間で生成して順番を維持
|
||||
let td = 0;
|
||||
for (const note of featuredNotes.filter(isNotNull)) {
|
||||
for (const note of featuredNotes.filter(x => x != null)) {
|
||||
td -= 1000;
|
||||
transactionalEntityManager.insert(MiUserNotePining, {
|
||||
id: this.idService.gen(Date.now() + td),
|
||||
|
|
|
@ -10,7 +10,6 @@ import type { Config } from '@/config.js';
|
|||
import type { IPoll } from '@/models/Poll.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { isQuestion } from '../type.js';
|
||||
import { ApLoggerService } from '../ApLoggerService.js';
|
||||
import { ApResolverService } from '../ApResolverService.js';
|
||||
|
@ -52,7 +51,7 @@ export class ApQuestionService {
|
|||
|
||||
const choices = question[multiple ? 'anyOf' : 'oneOf']
|
||||
?.map((x) => x.name)
|
||||
.filter(isNotNull)
|
||||
.filter(x => x != null)
|
||||
?? [];
|
||||
|
||||
const votes = question[multiple ? 'anyOf' : 'oneOf']?.map((x) => x.replies?.totalItems ?? x._misskey_votes ?? 0);
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
*/
|
||||
|
||||
import { toArray } from '@/misc/prelude/array.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { isHashtag } from '../type.js';
|
||||
import type { IObject, IApHashtag } from '../type.js';
|
||||
|
||||
|
@ -16,7 +15,7 @@ export function extractApHashtags(tags: IObject | IObject[] | null | undefined):
|
|||
return hashtags.map(tag => {
|
||||
const m = tag.name.match(/^#(.+)/);
|
||||
return m ? m[1] : null;
|
||||
}).filter(isNotNull);
|
||||
}).filter(x => x != null);
|
||||
}
|
||||
|
||||
export function extractApHashtagObjects(tags: IObject | IObject[] | null | undefined): IApHashtag[] {
|
||||
|
|
|
@ -11,7 +11,6 @@ import { bindThis } from '@/decorators.js';
|
|||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { Packed } from '@/misc/json-schema.js';
|
||||
import { SystemWebhookEntityService } from '@/core/entities/SystemWebhookEntityService.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
|
||||
@Injectable()
|
||||
export class AbuseReportNotificationRecipientEntityService {
|
||||
|
@ -66,13 +65,13 @@ export class AbuseReportNotificationRecipientEntityService {
|
|||
);
|
||||
}
|
||||
|
||||
const userIds = objs.map(it => it.userId).filter(isNotNull);
|
||||
const userIds = objs.map(it => it.userId).filter(x => x != null);
|
||||
const users: Map<string, Packed<'UserLite'>> = (userIds.length > 0)
|
||||
? await this.userEntityService.packMany(userIds)
|
||||
.then(it => new Map(it.map(it => [it.id, it])))
|
||||
: new Map();
|
||||
|
||||
const systemWebhookIds = objs.map(it => it.systemWebhookId).filter(isNotNull);
|
||||
const systemWebhookIds = objs.map(it => it.systemWebhookId).filter(x => x != null);
|
||||
const systemWebhooks: Map<string, Packed<'SystemWebhook'>> = (systemWebhookIds.length > 0)
|
||||
? await this.systemWebhookEntityService.packMany(systemWebhookIds)
|
||||
.then(it => new Map(it.map(it => [it.id, it])))
|
||||
|
|
|
@ -10,7 +10,6 @@ import { awaitAll } from '@/misc/prelude/await-all.js';
|
|||
import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
|
||||
|
@ -63,7 +62,7 @@ export class AbuseUserReportEntityService {
|
|||
) {
|
||||
const _reporters = reports.map(({ reporter, reporterId }) => reporter ?? reporterId);
|
||||
const _targetUsers = reports.map(({ targetUser, targetUserId }) => targetUser ?? targetUserId);
|
||||
const _assignees = reports.map(({ assignee, assigneeId }) => assignee ?? assigneeId).filter(isNotNull);
|
||||
const _assignees = reports.map(({ assignee, assigneeId }) => assignee ?? assigneeId).filter(x => x != null);
|
||||
const _userMap = await this.userEntityService.packMany(
|
||||
[..._reporters, ..._targetUsers, ..._assignees],
|
||||
null,
|
||||
|
|
|
@ -53,7 +53,7 @@ export class ClipEntityService {
|
|||
isPublic: clip.isPublic,
|
||||
favoritedCount: await this.clipFavoritesRepository.countBy({ clipId: clip.id }),
|
||||
isFavorited: meId ? await this.clipFavoritesRepository.exists({ where: { clipId: clip.id, userId: meId } }) : undefined,
|
||||
notesCount: meId ? await this.clipNotesRepository.countBy({ clipId: clip.id }) : undefined,
|
||||
notesCount: (meId === clip.userId) ? await this.clipNotesRepository.countBy({ clipId: clip.id }) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ import { appendQuery, query } from '@/misc/prelude/url.js';
|
|||
import { deepClone } from '@/misc/clone.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isMimeImage } from '@/misc/is-mime-image.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { UtilityService } from '../UtilityService.js';
|
||||
import { VideoProcessingService } from '../VideoProcessingService.js';
|
||||
|
@ -261,11 +260,11 @@ export class DriveFileEntityService {
|
|||
files: MiDriveFile[],
|
||||
options?: PackOptions,
|
||||
): Promise<Packed<'DriveFile'>[]> {
|
||||
const _user = files.map(({ user, userId }) => user ?? userId).filter(isNotNull);
|
||||
const _user = files.map(({ user, userId }) => user ?? userId).filter(x => x != null);
|
||||
const _userMap = await this.userEntityService.packMany(_user)
|
||||
.then(users => new Map(users.map(user => [user.id, user])));
|
||||
const items = await Promise.all(files.map(f => this.packNullable(f, options, f.userId ? { packedUser: _userMap.get(f.userId) } : {})));
|
||||
return items.filter(isNotNull);
|
||||
return items.filter(x => x != null);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
@ -290,6 +289,6 @@ export class DriveFileEntityService {
|
|||
): Promise<Packed<'DriveFile'>[]> {
|
||||
if (fileIds.length === 0) return [];
|
||||
const filesMap = await this.packManyByIdsMap(fileIds, options);
|
||||
return fileIds.map(id => filesMap.get(id)).filter(isNotNull);
|
||||
return fileIds.map(id => filesMap.get(id)).filter(x => x != null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ import type { MiUser } from '@/models/User.js';
|
|||
import type { MiRegistrationTicket } from '@/models/RegistrationTicket.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
|
||||
@Injectable()
|
||||
|
@ -59,8 +58,8 @@ export class InviteCodeEntityService {
|
|||
tickets: MiRegistrationTicket[],
|
||||
me: { id: MiUser['id'] },
|
||||
) {
|
||||
const _createdBys = tickets.map(({ createdBy, createdById }) => createdBy ?? createdById).filter(isNotNull);
|
||||
const _usedBys = tickets.map(({ usedBy, usedById }) => usedBy ?? usedById).filter(isNotNull);
|
||||
const _createdBys = tickets.map(({ createdBy, createdById }) => createdBy ?? createdById).filter(x => x != null);
|
||||
const _usedBys = tickets.map(({ usedBy, usedById }) => usedBy ?? usedById).filter(x => x != null);
|
||||
const _userMap = await this.userEntityService.packMany([..._createdBys, ..._usedBys], me)
|
||||
.then(users => new Map(users.map(u => [u.id, u])));
|
||||
return Promise.all(
|
||||
|
|
|
@ -14,7 +14,6 @@ import type { MiNote } from '@/models/Note.js';
|
|||
import type { MiNoteReaction } from '@/models/NoteReaction.js';
|
||||
import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { DebounceLoader } from '@/misc/loader.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
|
@ -276,7 +275,7 @@ export class NoteEntityService implements OnModuleInit {
|
|||
packedFiles.set(k, v);
|
||||
}
|
||||
}
|
||||
return fileIds.map(id => packedFiles.get(id)).filter(isNotNull);
|
||||
return fileIds.map(id => packedFiles.get(id)).filter(x => x != null);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
@ -449,12 +448,12 @@ export class NoteEntityService implements OnModuleInit {
|
|||
|
||||
await this.customEmojiService.prefetchEmojis(this.aggregateNoteEmojis(notes));
|
||||
// TODO: 本当は renote とか reply がないのに renoteId とか replyId があったらここで解決しておく
|
||||
const fileIds = notes.map(n => [n.fileIds, n.renote?.fileIds, n.reply?.fileIds]).flat(2).filter(isNotNull);
|
||||
const fileIds = notes.map(n => [n.fileIds, n.renote?.fileIds, n.reply?.fileIds]).flat(2).filter(x => x != null);
|
||||
const packedFiles = fileIds.length > 0 ? await this.driveFileEntityService.packManyByIdsMap(fileIds) : new Map();
|
||||
const users = [
|
||||
...notes.map(({ user, userId }) => user ?? userId),
|
||||
...notes.map(({ replyUserId }) => replyUserId).filter(isNotNull),
|
||||
...notes.map(({ renoteUserId }) => renoteUserId).filter(isNotNull),
|
||||
...notes.map(({ replyUserId }) => replyUserId).filter(x => x != null),
|
||||
...notes.map(({ renoteUserId }) => renoteUserId).filter(x => x != null),
|
||||
];
|
||||
const packedUsers = await this.userEntityService.packMany(users, me)
|
||||
.then(users => new Map(users.map(u => [u.id, u])));
|
||||
|
|
|
@ -13,7 +13,6 @@ import type { MiGroupedNotification, MiNotification } from '@/models/Notificatio
|
|||
import type { MiNote } from '@/models/Note.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { FilterUnionByProperty, groupedNotificationTypes } from '@/types.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { RoleEntityService } from './RoleEntityService.js';
|
||||
|
@ -103,7 +102,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
user,
|
||||
reaction: reaction.reaction,
|
||||
};
|
||||
}))).filter(r => isNotNull(r.user));
|
||||
}))).filter(r => r.user != null);
|
||||
// if all users have been deleted, don't show this notification
|
||||
if (reactions.length === 0) {
|
||||
return null;
|
||||
|
@ -124,7 +123,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
}
|
||||
|
||||
return this.userEntityService.pack(userId, { id: meId });
|
||||
}))).filter(isNotNull);
|
||||
}))).filter(x => x != null);
|
||||
// if all users have been deleted, don't show this notification
|
||||
if (users.length === 0) {
|
||||
return null;
|
||||
|
@ -181,7 +180,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
|
||||
validNotifications = await this.#filterValidNotifier(validNotifications, meId);
|
||||
|
||||
const noteIds = validNotifications.map(x => 'noteId' in x ? x.noteId : null).filter(isNotNull);
|
||||
const noteIds = validNotifications.map(x => 'noteId' in x ? x.noteId : null).filter(x => x != null);
|
||||
const notes = noteIds.length > 0 ? await this.notesRepository.find({
|
||||
where: { id: In(noteIds) },
|
||||
relations: ['user', 'reply', 'reply.user', 'renote', 'renote.user'],
|
||||
|
@ -223,7 +222,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
);
|
||||
});
|
||||
|
||||
return (await Promise.all(packPromises)).filter(isNotNull);
|
||||
return (await Promise.all(packPromises)).filter(x => x != null);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
@ -305,7 +304,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
this.cacheService.userProfileCache.fetch(meId).then(p => new Set(p.mutedInstances)),
|
||||
]);
|
||||
|
||||
const notifierIds = notifications.map(notification => 'notifierId' in notification ? notification.notifierId : null).filter(isNotNull);
|
||||
const notifierIds = notifications.map(notification => 'notifierId' in notification ? notification.notifierId : null).filter(x => x != null);
|
||||
const notifiers = notifierIds.length > 0 ? await this.usersRepository.find({
|
||||
where: { id: In(notifierIds) },
|
||||
}) : [];
|
||||
|
@ -313,7 +312,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
const filteredNotifications = ((await Promise.all(notifications.map(async (notification) => {
|
||||
const isValid = this.#validateNotifier(notification, userIdsWhoMeMuting, userMutedInstances, notifiers);
|
||||
return isValid ? notification : null;
|
||||
}))) as [T | null] ).filter(isNotNull);
|
||||
}))) as [T | null] ).filter(x => x != null);
|
||||
|
||||
return filteredNotifications;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ import type { MiPage } from '@/models/Page.js';
|
|||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
import { DriveFileEntityService } from './DriveFileEntityService.js';
|
||||
|
||||
|
@ -106,7 +105,7 @@ export class PageEntityService {
|
|||
script: page.script,
|
||||
eyeCatchingImageId: page.eyeCatchingImageId,
|
||||
eyeCatchingImage: page.eyeCatchingImageId ? await this.driveFileEntityService.pack(page.eyeCatchingImageId) : null,
|
||||
attachedFiles: this.driveFileEntityService.packMany((await Promise.all(attachedFiles)).filter(isNotNull)),
|
||||
attachedFiles: this.driveFileEntityService.packMany((await Promise.all(attachedFiles)).filter(x => x != null)),
|
||||
likedCount: page.likedCount,
|
||||
isLiked: meId ? await this.pageLikesRepository.exists({ where: { pageId: page.id, userId: meId } }) : undefined,
|
||||
});
|
||||
|
|
|
@ -47,7 +47,6 @@ import { IdService } from '@/core/IdService.js';
|
|||
import type { AnnouncementService } from '@/core/AnnouncementService.js';
|
||||
import type { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
import type { NoteEntityService } from './NoteEntityService.js';
|
||||
import type { DriveFileEntityService } from './DriveFileEntityService.js';
|
||||
|
@ -514,7 +513,7 @@ export class UserEntityService implements OnModuleInit {
|
|||
movedTo: user.movedToUri ? this.apPersonService.resolvePerson(user.movedToUri).then(user => user.id).catch(() => null) : null,
|
||||
alsoKnownAs: user.alsoKnownAs
|
||||
? Promise.all(user.alsoKnownAs.map(uri => this.apPersonService.fetchPerson(uri).then(user => user?.id).catch(() => null)))
|
||||
.then(xs => xs.length === 0 ? null : xs.filter(isNotNull))
|
||||
.then(xs => xs.length === 0 ? null : xs.filter(x => x != null))
|
||||
: null,
|
||||
createdAt: this.idService.parse(user.id).date.toISOString(),
|
||||
updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null,
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export function isNotNull<T extends NonNullable<unknown>>(input: T | undefined | null): input is T {
|
||||
return input != null;
|
||||
}
|
|
@ -65,44 +65,6 @@ export function maximum(xs: number[]): number {
|
|||
return Math.max(...xs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits an array based on the equivalence relation.
|
||||
* The concatenation of the result is equal to the argument.
|
||||
*/
|
||||
export function groupBy<T>(f: EndoRelation<T>, xs: T[]): T[][] {
|
||||
const groups = [] as T[][];
|
||||
for (const x of xs) {
|
||||
const lastGroup = groups.at(-1);
|
||||
if (lastGroup !== undefined && f(lastGroup[0], x)) {
|
||||
lastGroup.push(x);
|
||||
} else {
|
||||
groups.push([x]);
|
||||
}
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits an array based on the equivalence relation induced by the function.
|
||||
* The concatenation of the result is equal to the argument.
|
||||
*/
|
||||
export function groupOn<T, S>(f: (x: T) => S, xs: T[]): T[][] {
|
||||
return groupBy((a, b) => f(a) === f(b), xs);
|
||||
}
|
||||
|
||||
export function groupByX<T>(collections: T[], keySelector: (x: T) => string) {
|
||||
return collections.reduce((obj: Record<string, T[]>, item: T) => {
|
||||
const key = keySelector(item);
|
||||
if (!Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
obj[key] = [];
|
||||
}
|
||||
|
||||
obj[key].push(item);
|
||||
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two arrays by lexicographical order
|
||||
*/
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export function gcd(a: number, b: number): number {
|
||||
return b === 0 ? a : gcd(b, a % b);
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export interface IMaybe<T> {
|
||||
isJust(): this is IJust<T>;
|
||||
}
|
||||
|
||||
export interface IJust<T> extends IMaybe<T> {
|
||||
get(): T;
|
||||
}
|
||||
|
||||
export function just<T>(value: T): IJust<T> {
|
||||
return {
|
||||
isJust: () => true,
|
||||
get: () => value,
|
||||
};
|
||||
}
|
||||
|
||||
export function nothing<T>(): IMaybe<T> {
|
||||
return {
|
||||
isJust: () => false,
|
||||
};
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export function concat(xs: string[]): string {
|
||||
return xs.join('');
|
||||
}
|
||||
|
||||
export function capitalize(s: string): string {
|
||||
return toUpperCase(s.charAt(0)) + toLowerCase(s.slice(1));
|
||||
}
|
||||
|
||||
export function toUpperCase(s: string): string {
|
||||
return s.toUpperCase();
|
||||
}
|
||||
|
||||
export function toLowerCase(s: string): string {
|
||||
return s.toLowerCase();
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export const fallback = Symbol('fallback');
|
|
@ -20,7 +20,7 @@ export const packedDriveFileSchema = {
|
|||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
example: 'lenna.jpg',
|
||||
example: '192.jpg',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
|
|
|
@ -109,6 +109,12 @@ export class DeliverProcessorService {
|
|||
suspensionState: 'autoSuspendedForNotResponding',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// isNotRespondingがtrueでnotRespondingSinceがnullの場合はnotRespondingSinceをセット
|
||||
// notRespondingSinceは新たな機能なので、それ以前のデータにはnotRespondingSinceがない場合がある
|
||||
this.federatedInstanceService.update(i.id, {
|
||||
notRespondingSince: new Date(),
|
||||
});
|
||||
}
|
||||
|
||||
this.apRequestChart.deliverFail();
|
||||
|
|
|
@ -40,7 +40,7 @@ export const paramDef = {
|
|||
startsAt: { type: 'integer' },
|
||||
dayOfWeek: { type: 'integer' },
|
||||
},
|
||||
required: ['id', 'memo', 'url', 'imageUrl', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'dayOfWeek'],
|
||||
required: ['id'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
|
@ -63,8 +63,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
ratio: ps.ratio,
|
||||
memo: ps.memo,
|
||||
imageUrl: ps.imageUrl,
|
||||
expiresAt: new Date(ps.expiresAt),
|
||||
startsAt: new Date(ps.startsAt),
|
||||
expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : undefined,
|
||||
startsAt: ps.startsAt ? new Date(ps.startsAt) : undefined,
|
||||
dayOfWeek: ps.dayOfWeek,
|
||||
});
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ export const meta = {
|
|||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
example: 'lenna.jpg',
|
||||
example: '192.jpg',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { RolesRepository } from '@/models/_.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
|
@ -50,19 +49,6 @@ export const paramDef = {
|
|||
},
|
||||
required: [
|
||||
'roleId',
|
||||
'name',
|
||||
'description',
|
||||
'color',
|
||||
'iconUrl',
|
||||
'target',
|
||||
'condFormula',
|
||||
'isPublic',
|
||||
'isModerator',
|
||||
'isAdministrator',
|
||||
'asBadge',
|
||||
'canEditMembersByModerator',
|
||||
'displayOrder',
|
||||
'policies',
|
||||
],
|
||||
} as const;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
|
||||
import { ClipService } from '@/core/ClipService.js';
|
||||
|
@ -41,7 +41,7 @@ export const paramDef = {
|
|||
isPublic: { type: 'boolean' },
|
||||
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
|
||||
},
|
||||
required: ['clipId', 'name'],
|
||||
required: ['clipId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
|
|
|
@ -95,15 +95,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
// Check if the circular reference will occur
|
||||
const checkCircle = async (folderId: string): Promise<boolean> => {
|
||||
// Fetch folder
|
||||
const folder2 = await this.driveFoldersRepository.findOneBy({
|
||||
const folder2 = await this.driveFoldersRepository.findOneByOrFail({
|
||||
id: folderId,
|
||||
});
|
||||
|
||||
if (folder2!.id === folder!.id) {
|
||||
if (folder2.id === folder.id) {
|
||||
return true;
|
||||
} else if (folder2!.parentId) {
|
||||
return await checkCircle(folder2!.parentId);
|
||||
} else if (folder2.parentId) {
|
||||
return await checkCircle(folder2.parentId);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ import type { MiDriveFile } from '@/models/DriveFile.js';
|
|||
import { IdService } from '@/core/IdService.js';
|
||||
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['gallery'],
|
||||
|
@ -70,7 +69,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
id: fileId,
|
||||
userId: me.id,
|
||||
}),
|
||||
))).filter(isNotNull);
|
||||
))).filter(x => x != null);
|
||||
|
||||
if (files.length === 0) {
|
||||
throw new Error();
|
||||
|
|
|
@ -10,7 +10,6 @@ import type { DriveFilesRepository, GalleryPostsRepository } from '@/models/_.js
|
|||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['gallery'],
|
||||
|
@ -48,7 +47,7 @@ export const paramDef = {
|
|||
} },
|
||||
isSensitive: { type: 'boolean', default: false },
|
||||
},
|
||||
required: ['postId', 'title', 'fileIds'],
|
||||
required: ['postId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
|
@ -63,15 +62,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private galleryPostEntityService: GalleryPostEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const files = (await Promise.all(ps.fileIds.map(fileId =>
|
||||
this.driveFilesRepository.findOneBy({
|
||||
id: fileId,
|
||||
userId: me.id,
|
||||
}),
|
||||
))).filter(isNotNull);
|
||||
let files: Array<MiDriveFile> | undefined;
|
||||
|
||||
if (files.length === 0) {
|
||||
throw new Error();
|
||||
if (ps.fileIds) {
|
||||
files = (await Promise.all(ps.fileIds.map(fileId =>
|
||||
this.driveFilesRepository.findOneBy({
|
||||
id: fileId,
|
||||
userId: me.id,
|
||||
}),
|
||||
))).filter(x => x != null);
|
||||
|
||||
if (files.length === 0) {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
await this.galleryPostsRepository.update({
|
||||
|
@ -82,7 +85,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
title: ps.title,
|
||||
description: ps.description,
|
||||
isSensitive: ps.isSensitive,
|
||||
fileIds: files.map(file => file.id),
|
||||
fileIds: files ? files.map(file => file.id) : undefined,
|
||||
});
|
||||
|
||||
const post = await this.galleryPostsRepository.findOneByOrFail({ id: ps.postId });
|
||||
|
|
|
@ -257,7 +257,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||
|
||||
if (ps.name !== undefined) updates.name = ps.name;
|
||||
if (ps.name !== undefined) {
|
||||
if (ps.name === null) {
|
||||
updates.name = null;
|
||||
} else {
|
||||
const trimmedName = ps.name.trim();
|
||||
updates.name = trimmedName === '' ? null : trimmedName;
|
||||
}
|
||||
}
|
||||
if (ps.description !== undefined) profileUpdates.description = ps.description;
|
||||
if (ps.lang !== undefined) profileUpdates.lang = ps.lang;
|
||||
if (ps.location !== undefined) profileUpdates.location = ps.location;
|
||||
|
|
|
@ -34,13 +34,13 @@ export const paramDef = {
|
|||
webhookId: { type: 'string', format: 'misskey:id' },
|
||||
name: { type: 'string', minLength: 1, maxLength: 100 },
|
||||
url: { type: 'string', minLength: 1, maxLength: 1024 },
|
||||
secret: { type: 'string', maxLength: 1024, default: '' },
|
||||
secret: { type: 'string', nullable: true, maxLength: 1024 },
|
||||
on: { type: 'array', items: {
|
||||
type: 'string', enum: webhookEventTypes,
|
||||
} },
|
||||
active: { type: 'boolean' },
|
||||
},
|
||||
required: ['webhookId', 'name', 'url', 'on', 'active'],
|
||||
required: ['webhookId'],
|
||||
} as const;
|
||||
|
||||
// TODO: ロジックをサービスに切り出す
|
||||
|
@ -66,7 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
await this.webhooksRepository.update(webhook.id, {
|
||||
name: ps.name,
|
||||
url: ps.url,
|
||||
secret: ps.secret,
|
||||
secret: ps.secret === null ? '' : ps.secret,
|
||||
on: ps.on,
|
||||
active: ps.active,
|
||||
});
|
||||
|
|
|
@ -36,6 +36,12 @@ export const meta = {
|
|||
code: 'YOU_HAVE_BEEN_BLOCKED',
|
||||
id: '20ef5475-9f38-4e4c-bd33-de6d979498ec',
|
||||
},
|
||||
|
||||
cannotReactToRenote: {
|
||||
message: 'You cannot react to Renote.',
|
||||
code: 'CANNOT_REACT_TO_RENOTE',
|
||||
id: 'eaccdc08-ddef-43fe-908f-d108faad57f5',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
@ -62,6 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
await this.reactionService.create(me, note, ps.reaction).catch(err => {
|
||||
if (err.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') throw new ApiError(meta.errors.alreadyReacted);
|
||||
if (err.id === 'e70412a4-7197-4726-8e74-f3e0deb92aa7') throw new ApiError(meta.errors.youHaveBeenBlocked);
|
||||
if (err.id === '12c35529-3c79-4327-b1cc-e2cf63a71925') throw new ApiError(meta.errors.cannotReactToRenote);
|
||||
throw err;
|
||||
});
|
||||
return;
|
||||
|
|
|
@ -70,7 +70,7 @@ export const paramDef = {
|
|||
alignCenter: { type: 'boolean' },
|
||||
hideTitleWhenPinned: { type: 'boolean' },
|
||||
},
|
||||
required: ['pageId', 'title', 'name', 'content', 'variables', 'script'],
|
||||
required: ['pageId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
|
@ -91,9 +91,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new ApiError(meta.errors.accessDenied);
|
||||
}
|
||||
|
||||
let eyeCatchingImage = null;
|
||||
if (ps.eyeCatchingImageId != null) {
|
||||
eyeCatchingImage = await this.driveFilesRepository.findOneBy({
|
||||
const eyeCatchingImage = await this.driveFilesRepository.findOneBy({
|
||||
id: ps.eyeCatchingImageId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
@ -116,23 +115,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
await this.pagesRepository.update(page.id, {
|
||||
updatedAt: new Date(),
|
||||
title: ps.title,
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
name: ps.name === undefined ? page.name : ps.name,
|
||||
name: ps.name,
|
||||
summary: ps.summary === undefined ? page.summary : ps.summary,
|
||||
content: ps.content,
|
||||
variables: ps.variables,
|
||||
script: ps.script,
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter,
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned,
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
font: ps.font === undefined ? page.font : ps.font,
|
||||
eyeCatchingImageId: ps.eyeCatchingImageId === null
|
||||
? null
|
||||
: ps.eyeCatchingImageId === undefined
|
||||
? page.eyeCatchingImageId
|
||||
: eyeCatchingImage!.id,
|
||||
alignCenter: ps.alignCenter,
|
||||
hideTitleWhenPinned: ps.hideTitleWhenPinned,
|
||||
font: ps.font,
|
||||
eyeCatchingImageId: ps.eyeCatchingImageId,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
|
|||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['users'],
|
||||
|
@ -53,7 +52,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
host: acct.host ?? IsNull(),
|
||||
})));
|
||||
|
||||
return await this.userEntityService.packMany(users.filter(isNotNull), me, { schema: 'UserDetailed' });
|
||||
return await this.userEntityService.packMany(users.filter(x => x != null), me, { schema: 'UserDetailed' });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
|||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { MfmService } from "@/core/MfmService.js";
|
||||
import { parse as mfmParse } from 'mfm-js';
|
||||
|
||||
@Injectable()
|
||||
export class FeedService {
|
||||
|
@ -33,6 +35,7 @@ export class FeedService {
|
|||
private userEntityService: UserEntityService,
|
||||
private driveFileEntityService: DriveFileEntityService,
|
||||
private idService: IdService,
|
||||
private mfmService: MfmService,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -76,13 +79,14 @@ export class FeedService {
|
|||
id: In(note.fileIds),
|
||||
}) : [];
|
||||
const file = files.find(file => file.type.startsWith('image/'));
|
||||
const text = note.text;
|
||||
|
||||
feed.addItem({
|
||||
title: `New note by ${author.name}`,
|
||||
link: `${this.config.url}/notes/${note.id}`,
|
||||
date: this.idService.parse(note.id).date,
|
||||
description: note.cw ?? undefined,
|
||||
content: note.text ?? undefined,
|
||||
content: text ? this.mfmService.toHtml(mfmParse(text), JSON.parse(note.mentionedRemoteUsers)) ?? undefined : undefined,
|
||||
image: file ? this.driveFileEntityService.getPublicUrl(file) : undefined,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -29,7 +29,8 @@
|
|||
|
||||
let forceError = localStorage.getItem('forceError');
|
||||
if (forceError != null) {
|
||||
renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.')
|
||||
renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.');
|
||||
return;
|
||||
}
|
||||
|
||||
//#region Detect language & fetch translations
|
||||
|
@ -155,7 +156,12 @@
|
|||
document.head.appendChild(css);
|
||||
}
|
||||
|
||||
function renderError(code, details) {
|
||||
async function renderError(code, details) {
|
||||
// Cannot set property 'innerHTML' of null を回避
|
||||
if (document.readyState === 'loading') {
|
||||
await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve));
|
||||
}
|
||||
|
||||
let errorsElement = document.getElementById('errors');
|
||||
|
||||
if (!errorsElement) {
|
||||
|
@ -314,6 +320,6 @@
|
|||
#errorInfo {
|
||||
width: 50%;
|
||||
}
|
||||
`)
|
||||
}`)
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
module.exports = {
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
},
|
||||
extends: [
|
||||
'../../shared/.eslintrc.js',
|
||||
],
|
||||
rules: {
|
||||
'import/order': ['warn', {
|
||||
'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
|
||||
'pathGroups': [
|
||||
{
|
||||
'pattern': '@/**',
|
||||
'group': 'external',
|
||||
'position': 'after'
|
||||
}
|
||||
],
|
||||
}],
|
||||
'no-restricted-globals': [
|
||||
'error',
|
||||
{
|
||||
'name': '__dirname',
|
||||
'message': 'Not in ESModule. Use `import.meta.url` instead.'
|
||||
},
|
||||
{
|
||||
'name': '__filename',
|
||||
'message': 'Not in ESModule. Use `import.meta.url` instead.'
|
||||
}
|
||||
]
|
||||
},
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
import tsParser from '@typescript-eslint/parser';
|
||||
import sharedConfig from '../../shared/eslint.config.js';
|
||||
|
||||
export default [
|
||||
...sharedConfig,
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: tsParser,
|
||||
project: ['./tsconfig.json'],
|
||||
sourceType: 'module',
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'import/order': ['warn', {
|
||||
groups: [
|
||||
'builtin',
|
||||
'external',
|
||||
'internal',
|
||||
'parent',
|
||||
'sibling',
|
||||
'index',
|
||||
'object',
|
||||
'type',
|
||||
],
|
||||
pathGroups: [{
|
||||
pattern: '@/**',
|
||||
group: 'external',
|
||||
position: 'after',
|
||||
}],
|
||||
}],
|
||||
'no-restricted-globals': ['error', {
|
||||
name: '__dirname',
|
||||
message: 'Not in ESModule. Use `import.meta.url` instead.',
|
||||
}, {
|
||||
name: '__filename',
|
||||
message: 'Not in ESModule. Use `import.meta.url` instead.',
|
||||
}],
|
||||
},
|
||||
},
|
||||
];
|
|
@ -1,11 +0,0 @@
|
|||
module.exports = {
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
},
|
||||
extends: ['../.eslintrc.cjs'],
|
||||
env: {
|
||||
node: true,
|
||||
jest: true,
|
||||
},
|
||||
};
|
|
@ -1,5 +1,3 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
redistest:
|
||||
image: redis:7
|
|
@ -23,7 +23,7 @@ describe('Drive', () => {
|
|||
|
||||
const marker = Math.random().toString();
|
||||
|
||||
const url = 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg';
|
||||
const url = 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/192.jpg';
|
||||
|
||||
const catcher = makeStreamCatcher(
|
||||
alice,
|
||||
|
@ -41,14 +41,14 @@ describe('Drive', () => {
|
|||
const file = await catcher;
|
||||
|
||||
assert.strictEqual(res.status, 204);
|
||||
assert.strictEqual(file.name, 'Lenna.jpg');
|
||||
assert.strictEqual(file.name, '192.jpg');
|
||||
assert.strictEqual(file.type, 'image/jpeg');
|
||||
});
|
||||
|
||||
test('ローカルからアップロードできる', async () => {
|
||||
// APIレスポンスを直接使用するので utils.js uploadFile が通過することで成功とする
|
||||
|
||||
const res = await uploadFile(alice, { path: 'Lenna.jpg', name: 'テスト画像' });
|
||||
const res = await uploadFile(alice, { path: '192.jpg', name: 'テスト画像' });
|
||||
|
||||
assert.strictEqual(res.body?.name, 'テスト画像.jpg');
|
||||
assert.strictEqual(res.body.type, 'image/jpeg');
|
||||
|
|
|
@ -117,12 +117,21 @@ describe('Endpoints', () => {
|
|||
assert.strictEqual(res.body.birthday, myBirthday);
|
||||
});
|
||||
|
||||
test('名前を空白にできる', async () => {
|
||||
test('名前を空白のみにした場合nullになる', async () => {
|
||||
const res = await api('i/update', {
|
||||
name: ' ',
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.body.name, ' ');
|
||||
assert.strictEqual(res.body.name, null);
|
||||
});
|
||||
|
||||
test('名前の前後に空白(ホワイトスペース)を入れてもトリムされる', async () => {
|
||||
const res = await api('i/update', {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#white_space
|
||||
name: ' あ い う \u0009\u000b\u000c\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\ufeff',
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.body.name, 'あ い う');
|
||||
});
|
||||
|
||||
test('誕生日の設定を削除できる', async () => {
|
||||
|
@ -266,6 +275,67 @@ describe('Endpoints', () => {
|
|||
assert.strictEqual(res.status, 400);
|
||||
});
|
||||
|
||||
test('リノートにリアクションできない', async () => {
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
const bobRenote = await post(bob, { renoteId: bobNote.id });
|
||||
|
||||
const res = await api('notes/reactions/create', {
|
||||
noteId: bobRenote.id,
|
||||
reaction: '🚀',
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.code, 'CANNOT_REACT_TO_RENOTE');
|
||||
});
|
||||
|
||||
test('引用にリアクションできる', async () => {
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
const bobRenote = await post(bob, { text: 'hi again', renoteId: bobNote.id });
|
||||
|
||||
const res = await api('notes/reactions/create', {
|
||||
noteId: bobRenote.id,
|
||||
reaction: '🚀',
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 204);
|
||||
});
|
||||
|
||||
test('空文字列のリアクションは\u2764にフォールバックされる', async () => {
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
|
||||
const res = await api('notes/reactions/create', {
|
||||
noteId: bobNote.id,
|
||||
reaction: '',
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 204);
|
||||
|
||||
const reaction = await api('notes/reactions', {
|
||||
noteId: bobNote.id,
|
||||
});
|
||||
|
||||
assert.strictEqual(reaction.body.length, 1);
|
||||
assert.strictEqual(reaction.body[0].type, '\u2764');
|
||||
});
|
||||
|
||||
test('絵文字ではない文字列のリアクションは\u2764にフォールバックされる', async () => {
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
|
||||
const res = await api('notes/reactions/create', {
|
||||
noteId: bobNote.id,
|
||||
reaction: 'Hello!',
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 204);
|
||||
|
||||
const reaction = await api('notes/reactions', {
|
||||
noteId: bobNote.id,
|
||||
});
|
||||
|
||||
assert.strictEqual(reaction.body.length, 1);
|
||||
assert.strictEqual(reaction.body[0].type, '\u2764');
|
||||
});
|
||||
|
||||
test('空のパラメータで怒られる', async () => {
|
||||
// @ts-expect-error param must not be empty
|
||||
const res = await api('notes/reactions/create', {}, alice);
|
||||
|
@ -523,7 +593,7 @@ describe('Endpoints', () => {
|
|||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body!.name, 'Lenna.jpg');
|
||||
assert.strictEqual(res.body!.name, '192.jpg');
|
||||
});
|
||||
|
||||
test('ファイルに名前を付けられる', async () => {
|
||||
|
|
|
@ -153,6 +153,23 @@ describe('Webリソース', () => {
|
|||
path: path('nonexisting'),
|
||||
status: 404,
|
||||
}));
|
||||
|
||||
describe(' has entry such ', () => {
|
||||
beforeEach(() => {
|
||||
post(alice, { text: "**a**" })
|
||||
});
|
||||
|
||||
test('MFMを含まない。', async () => {
|
||||
const content = await simpleGet(path(alice.username), "*/*", undefined, res => res.text());
|
||||
const _body: unknown = content.body;
|
||||
// JSONフィードのときは改めて文字列化する
|
||||
const body: string = typeof (_body) === "object" ? JSON.stringify(_body) : _body as string;
|
||||
|
||||
if (body.includes("**a**")) {
|
||||
throw new Error("MFM shouldn't be included");
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
describe.each([{ path: '/api/foo' }])('$path', ({ path }) => {
|
||||
|
|
|
@ -7,12 +7,13 @@ import { INestApplicationContext } from '@nestjs/common';
|
|||
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import * as assert from 'assert';
|
||||
import { loadConfig } from '@/config.js';
|
||||
import { MiRepository, MiUser, UsersRepository, miRepository } from '@/models/_.js';
|
||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||
import { jobQueue } from '@/boot/common.js';
|
||||
import { api, castAsError, initTestDb, signup, sleep, successfulApiCall, uploadFile } from '../utils.js';
|
||||
import { api, castAsError, initTestDb, signup, successfulApiCall, uploadFile } from '../utils.js';
|
||||
import type * as misskey from 'misskey-js';
|
||||
|
||||
describe('Account Move', () => {
|
||||
|
@ -271,7 +272,7 @@ describe('Account Move', () => {
|
|||
|
||||
assert.strictEqual(move.status, 200);
|
||||
|
||||
await sleep(1000 * 3); // wait for jobs to finish
|
||||
await setTimeout(1000 * 3); // wait for jobs to finish
|
||||
|
||||
// Unfollow delayed?
|
||||
const aliceFollowings = await api('users/following', {
|
||||
|
@ -338,7 +339,7 @@ describe('Account Move', () => {
|
|||
});
|
||||
|
||||
test('Unfollowed after 10 sec (24 hours in production).', async () => {
|
||||
await sleep(1000 * 8);
|
||||
await setTimeout(1000 * 8);
|
||||
|
||||
const following = await api('users/following', {
|
||||
userId: alice.id,
|
||||
|
|
|
@ -41,7 +41,7 @@ describe('Note', () => {
|
|||
});
|
||||
|
||||
test('ファイルを添付できる', async () => {
|
||||
const file = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg');
|
||||
const file = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/192.jpg');
|
||||
|
||||
const res = await api('notes/create', {
|
||||
fileIds: [file.id],
|
||||
|
@ -53,7 +53,7 @@ describe('Note', () => {
|
|||
}, 1000 * 10);
|
||||
|
||||
test('他人のファイルで怒られる', async () => {
|
||||
const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg');
|
||||
const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/192.jpg');
|
||||
|
||||
const res = await api('notes/create', {
|
||||
text: 'test',
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { api, post, signup, sleep, waitFire } from '../utils.js';
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { api, post, signup, waitFire } from '../utils.js';
|
||||
import type * as misskey from 'misskey-js';
|
||||
|
||||
describe('Renote Mute', () => {
|
||||
|
@ -35,7 +36,7 @@ describe('Renote Mute', () => {
|
|||
const carolNote = await post(carol, { text: 'hi' });
|
||||
|
||||
// redisに追加されるのを待つ
|
||||
await sleep(100);
|
||||
await setTimeout(100);
|
||||
|
||||
const res = await api('notes/local-timeline', {}, alice);
|
||||
|
||||
|
@ -52,7 +53,7 @@ describe('Renote Mute', () => {
|
|||
const carolNote = await post(carol, { text: 'hi' });
|
||||
|
||||
// redisに追加されるのを待つ
|
||||
await sleep(100);
|
||||
await setTimeout(100);
|
||||
|
||||
const res = await api('notes/local-timeline', {}, alice);
|
||||
|
||||
|
@ -69,7 +70,7 @@ describe('Renote Mute', () => {
|
|||
const bobRenote = await post(bob, { renoteId: carolNote.id });
|
||||
|
||||
// redisに追加されるのを待つ
|
||||
await sleep(100);
|
||||
await setTimeout(100);
|
||||
|
||||
const res = await api('notes/local-timeline', {}, alice);
|
||||
|
||||
|
|
|
@ -7,17 +7,26 @@
|
|||
// pnpm jest -- e2e/timelines.ts
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { api, post, randomString, sendEnvUpdateRequest, signup, sleep, uploadUrl } from '../utils.js';
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { Redis } from 'ioredis';
|
||||
import { loadConfig } from '@/config.js';
|
||||
import { api, post, randomString, sendEnvUpdateRequest, signup, uploadUrl } from '../utils.js';
|
||||
|
||||
function genHost() {
|
||||
return randomString() + '.example.com';
|
||||
}
|
||||
|
||||
function waitForPushToTl() {
|
||||
return sleep(500);
|
||||
return setTimeout(500);
|
||||
}
|
||||
|
||||
let redisForTimelines: Redis;
|
||||
|
||||
describe('Timelines', () => {
|
||||
beforeAll(() => {
|
||||
redisForTimelines = new Redis(loadConfig().redisForTimelines);
|
||||
});
|
||||
|
||||
describe('Home TL', () => {
|
||||
test.concurrent('自分の visibility: followers なノートが含まれる', async () => {
|
||||
const [alice] = await Promise.all([signup()]);
|
||||
|
@ -36,7 +45,7 @@ describe('Timelines', () => {
|
|||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
|
||||
|
@ -52,7 +61,7 @@ describe('Timelines', () => {
|
|||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
|
||||
|
@ -69,7 +78,7 @@ describe('Timelines', () => {
|
|||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||
|
||||
|
@ -86,7 +95,7 @@ describe('Timelines', () => {
|
|||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await api('following/update', { userId: bob.id, withReplies: true }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||
|
||||
|
@ -103,7 +112,7 @@ describe('Timelines', () => {
|
|||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await api('following/update', { userId: bob.id, withReplies: true }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] });
|
||||
|
||||
|
@ -120,7 +129,7 @@ describe('Timelines', () => {
|
|||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await api('following/update', { userId: bob.id, withReplies: true }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const carolNote = await post(carol, { text: 'hi', visibility: 'followers' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||
|
||||
|
@ -139,7 +148,7 @@ describe('Timelines', () => {
|
|||
await api('following/create', { userId: carol.id }, alice);
|
||||
await api('following/create', { userId: carol.id }, bob);
|
||||
await api('following/update', { userId: bob.id, withReplies: true }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const carolNote = await post(carol, { text: 'hi', visibility: 'followers' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||
|
||||
|
@ -158,7 +167,7 @@ describe('Timelines', () => {
|
|||
await api('following/create', { userId: bob.id }, alice);
|
||||
await api('following/create', { userId: carol.id }, alice);
|
||||
await api('following/update', { userId: bob.id, withReplies: true }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] });
|
||||
|
||||
|
@ -174,7 +183,7 @@ describe('Timelines', () => {
|
|||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const bobNote1 = await post(bob, { text: 'hi' });
|
||||
const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
|
||||
|
||||
|
@ -190,7 +199,7 @@ describe('Timelines', () => {
|
|||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const aliceNote = await post(alice, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
|
||||
|
||||
|
@ -220,7 +229,7 @@ describe('Timelines', () => {
|
|||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { renoteId: carolNote.id });
|
||||
|
||||
|
@ -236,7 +245,7 @@ describe('Timelines', () => {
|
|||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { renoteId: carolNote.id });
|
||||
|
||||
|
@ -254,7 +263,7 @@ describe('Timelines', () => {
|
|||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
|
||||
|
||||
|
@ -272,7 +281,7 @@ describe('Timelines', () => {
|
|||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
@ -287,7 +296,7 @@ describe('Timelines', () => {
|
|||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await api('mute/create', { userId: carol.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
|
||||
|
||||
|
@ -305,7 +314,7 @@ describe('Timelines', () => {
|
|||
await api('following/create', { userId: bob.id }, alice);
|
||||
await api('following/update', { userId: bob.id, withReplies: true }, alice);
|
||||
await api('mute/create', { userId: carol.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||
|
||||
|
@ -351,7 +360,7 @@ describe('Timelines', () => {
|
|||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const [bobFile, carolFile] = await Promise.all([
|
||||
uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'),
|
||||
uploadUrl(carol, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'),
|
||||
|
@ -376,7 +385,7 @@ describe('Timelines', () => {
|
|||
|
||||
const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body);
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
@ -403,7 +412,7 @@ describe('Timelines', () => {
|
|||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
@ -430,7 +439,7 @@ describe('Timelines', () => {
|
|||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
@ -558,7 +567,7 @@ describe('Timelines', () => {
|
|||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('following/create', { userId: carol.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const carolNote = await post(carol, { text: 'hi', visibility: 'home' });
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
|
||||
|
@ -574,7 +583,7 @@ describe('Timelines', () => {
|
|||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('mute/create', { userId: carol.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
|
||||
|
@ -591,7 +600,7 @@ describe('Timelines', () => {
|
|||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await api('mute/create', { userId: carol.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
|
||||
|
||||
|
@ -609,7 +618,7 @@ describe('Timelines', () => {
|
|||
await api('following/create', { userId: bob.id }, alice);
|
||||
await api('following/update', { userId: bob.id, withReplies: true }, alice);
|
||||
await api('mute/create', { userId: carol.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||
|
||||
|
@ -625,7 +634,7 @@ describe('Timelines', () => {
|
|||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const aliceNote = await post(alice, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
|
||||
|
||||
|
@ -695,7 +704,7 @@ describe('Timelines', () => {
|
|||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
@ -709,7 +718,7 @@ describe('Timelines', () => {
|
|||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const aliceNote = await post(alice, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
|
||||
|
||||
|
@ -812,7 +821,7 @@ describe('Timelines', () => {
|
|||
|
||||
const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
@ -827,7 +836,7 @@ describe('Timelines', () => {
|
|||
|
||||
const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
@ -842,7 +851,7 @@ describe('Timelines', () => {
|
|||
|
||||
const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
@ -857,7 +866,7 @@ describe('Timelines', () => {
|
|||
|
||||
const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||
|
||||
|
@ -873,7 +882,7 @@ describe('Timelines', () => {
|
|||
|
||||
const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const bobNote1 = await post(bob, { text: 'hi' });
|
||||
const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
|
||||
|
||||
|
@ -891,7 +900,7 @@ describe('Timelines', () => {
|
|||
const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await api('users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: false }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const aliceNote = await post(alice, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
|
||||
|
||||
|
@ -908,7 +917,7 @@ describe('Timelines', () => {
|
|||
const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await api('users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: false }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||
|
||||
|
@ -925,7 +934,7 @@ describe('Timelines', () => {
|
|||
const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await api('users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: true }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||
|
||||
|
@ -942,7 +951,7 @@ describe('Timelines', () => {
|
|||
await api('following/create', { userId: bob.id }, alice);
|
||||
const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
@ -958,7 +967,7 @@ describe('Timelines', () => {
|
|||
await api('following/create', { userId: bob.id }, alice);
|
||||
const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
@ -974,7 +983,7 @@ describe('Timelines', () => {
|
|||
|
||||
const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('users/lists/push', { listId: list.id, userId: alice.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
@ -991,7 +1000,7 @@ describe('Timelines', () => {
|
|||
const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body);
|
||||
const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
@ -1023,7 +1032,7 @@ describe('Timelines', () => {
|
|||
|
||||
const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
@ -1040,7 +1049,7 @@ describe('Timelines', () => {
|
|||
const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await api('users/lists/push', { listId: list.id, userId: carol.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
@ -1080,7 +1089,7 @@ describe('Timelines', () => {
|
|||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||
|
||||
await api('following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
@ -1220,7 +1229,7 @@ describe('Timelines', () => {
|
|||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('mute/create', { userId: carol.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
|
||||
|
||||
|
@ -1235,7 +1244,7 @@ describe('Timelines', () => {
|
|||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||
|
||||
await api('mute/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
await setTimeout(1000);
|
||||
const bobNote1 = await post(bob, { text: 'hi' });
|
||||
const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
|
||||
const bobNote3 = await post(bob, { text: 'hi', renoteId: bobNote1.id });
|
||||
|
@ -1272,6 +1281,33 @@ describe('Timelines', () => {
|
|||
|
||||
assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
|
||||
});
|
||||
|
||||
/** @see https://github.com/misskey-dev/misskey/issues/14000 */
|
||||
test.concurrent('FTT: sinceId にキャッシュより古いノートを指定しても、sinceId による絞り込みが正しく動作する', async () => {
|
||||
const alice = await signup();
|
||||
const noteSince = await post(alice, { text: 'Note where id will be `sinceId`.' });
|
||||
const note1 = await post(alice, { text: '1' });
|
||||
const note2 = await post(alice, { text: '2' });
|
||||
await redisForTimelines.del('list:userTimeline:' + alice.id);
|
||||
const note3 = await post(alice, { text: '3' });
|
||||
|
||||
const res = await api('users/notes', { userId: alice.id, sinceId: noteSince.id });
|
||||
assert.deepStrictEqual(res.body, [note1, note2, note3]);
|
||||
});
|
||||
|
||||
test.concurrent('FTT: sinceId にキャッシュより古いノートを指定しても、sinceId と untilId による絞り込みが正しく動作する', async () => {
|
||||
const alice = await signup();
|
||||
const noteSince = await post(alice, { text: 'Note where id will be `sinceId`.' });
|
||||
const note1 = await post(alice, { text: '1' });
|
||||
const note2 = await post(alice, { text: '2' });
|
||||
await redisForTimelines.del('list:userTimeline:' + alice.id);
|
||||
const note3 = await post(alice, { text: '3' });
|
||||
const noteUntil = await post(alice, { text: 'Note where id will be `untilId`.' });
|
||||
await post(alice, { text: '4' });
|
||||
|
||||
const res = await api('users/notes', { userId: alice.id, sinceId: noteSince.id, untilId: noteUntil.id });
|
||||
assert.deepStrictEqual(res.body, [note3, note2, note1]);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: リノートミュート済みユーザーのテスト
|
||||
|
|
|
@ -17,8 +17,8 @@ describe('users/notes', () => {
|
|||
|
||||
beforeAll(async () => {
|
||||
alice = await signup({ username: 'alice' });
|
||||
const jpg = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg');
|
||||
const png = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.png');
|
||||
const jpg = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/192.jpg');
|
||||
const png = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/192.png');
|
||||
jpgNote = await post(alice, {
|
||||
fileIds: [jpg.id],
|
||||
});
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import globals from 'globals';
|
||||
import tsParser from '@typescript-eslint/parser';
|
||||
import sharedConfig from '../../shared/eslint.config.js';
|
||||
|
||||
export default [
|
||||
...sharedConfig,
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
},
|
||||
parserOptions: {
|
||||
parser: tsParser,
|
||||
project: ['./tsconfig.json'],
|
||||
sourceType: 'module',
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { just, nothing } from '../../src/misc/prelude/maybe.js';
|
||||
|
||||
describe('just', () => {
|
||||
test('has a value', () => {
|
||||
assert.deepStrictEqual(just(3).isJust(), true);
|
||||
});
|
||||
|
||||
test('has the inverse called get', () => {
|
||||
assert.deepStrictEqual(just(3).get(), 3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('nothing', () => {
|
||||
test('has no value', () => {
|
||||
assert.deepStrictEqual(nothing().isJust(), false);
|
||||
});
|
||||
});
|
Binary file not shown.
After Width: | Height: | Size: 5.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
Before Width: | Height: | Size: 25 KiB |
Binary file not shown.
Before Width: | Height: | Size: 463 KiB |
|
@ -83,21 +83,21 @@ describe('FileInfoService', () => {
|
|||
|
||||
describe('IMAGE', () => {
|
||||
test('Generic JPEG', async () => {
|
||||
const path = `${resources}/Lenna.jpg`;
|
||||
const path = `${resources}/192.jpg`;
|
||||
const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
delete info.sensitive;
|
||||
delete info.porn;
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 25360,
|
||||
md5: '091b3f259662aa31e2ffef4519951168',
|
||||
size: 5131,
|
||||
md5: '8c9ed0677dd2b8f9f7472c3af247e5e3',
|
||||
type: {
|
||||
mime: 'image/jpeg',
|
||||
ext: 'jpg',
|
||||
},
|
||||
width: 512,
|
||||
height: 512,
|
||||
width: 192,
|
||||
height: 192,
|
||||
orientation: undefined,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { jest } from '@jest/globals';
|
||||
import { ModuleMocker } from 'jest-mock';
|
||||
import { Test } from '@nestjs/testing';
|
||||
|
@ -29,7 +30,6 @@ import { secureRndstr } from '@/misc/secure-rndstr.js';
|
|||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { RoleCondFormulaValue } from '@/models/Role.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { sleep } from '../utils.js';
|
||||
import type { TestingModule } from '@nestjs/testing';
|
||||
import type { MockFunctionMetadata } from 'jest-mock';
|
||||
|
||||
|
@ -278,7 +278,7 @@ describe('RoleService', () => {
|
|||
|
||||
// ストリーミング経由で反映されるまでちょっと待つ
|
||||
clock.uninstall();
|
||||
await sleep(100);
|
||||
await setTimeout(100);
|
||||
|
||||
const resultAfter25hAgain = await roleService.getUserPolicies(user.id);
|
||||
expect(resultAfter25hAgain.canManageCustomEmojis).toBe(true);
|
||||
|
@ -807,7 +807,7 @@ describe('RoleService', () => {
|
|||
await roleService.assign(user.id, role.id);
|
||||
|
||||
clock.uninstall();
|
||||
await sleep(100);
|
||||
await setTimeout(100);
|
||||
|
||||
const assignments = await roleAssignmentsRepository.find({
|
||||
where: {
|
||||
|
@ -835,7 +835,7 @@ describe('RoleService', () => {
|
|||
await roleService.assign(user.id, role.id);
|
||||
|
||||
clock.uninstall();
|
||||
await sleep(100);
|
||||
await setTimeout(100);
|
||||
|
||||
const assignments = await roleAssignmentsRepository.find({
|
||||
where: {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { afterEach, beforeEach, describe, expect, jest } from '@jest/globals';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { MiUser } from '@/models/User.js';
|
||||
|
@ -16,7 +17,7 @@ import { DI } from '@/di-symbols.js';
|
|||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
||||
import { randomString, sleep } from '../utils.js';
|
||||
import { randomString } from '../utils.js';
|
||||
|
||||
describe('SystemWebhookService', () => {
|
||||
let app: TestingModule;
|
||||
|
@ -358,7 +359,7 @@ describe('SystemWebhookService', () => {
|
|||
);
|
||||
|
||||
// redisでの配信経由で更新されるのでちょっと待つ
|
||||
await sleep(500);
|
||||
await setTimeout(500);
|
||||
|
||||
const fetchedWebhooks = await service.fetchActiveSystemWebhooks();
|
||||
expect(fetchedWebhooks).toEqual([webhook]);
|
||||
|
@ -377,7 +378,7 @@ describe('SystemWebhookService', () => {
|
|||
);
|
||||
|
||||
// redisでの配信経由で更新されるのでちょっと待つ
|
||||
await sleep(500);
|
||||
await setTimeout(500);
|
||||
|
||||
const fetchedWebhooks = await service.fetchActiveSystemWebhooks();
|
||||
expect(fetchedWebhooks).toEqual([]);
|
||||
|
@ -407,7 +408,7 @@ describe('SystemWebhookService', () => {
|
|||
);
|
||||
|
||||
// redisでの配信経由で更新されるのでちょっと待つ
|
||||
await sleep(500);
|
||||
await setTimeout(500);
|
||||
|
||||
const fetchedWebhooks = await service.fetchActiveSystemWebhooks();
|
||||
expect(fetchedWebhooks).toEqual([webhook2]);
|
||||
|
@ -434,7 +435,7 @@ describe('SystemWebhookService', () => {
|
|||
);
|
||||
|
||||
// redisでの配信経由で更新されるのでちょっと待つ
|
||||
await sleep(500);
|
||||
await setTimeout(500);
|
||||
|
||||
const fetchedWebhooks = await service.fetchActiveSystemWebhooks();
|
||||
expect(fetchedWebhooks.length).toEqual(0);
|
||||
|
@ -457,7 +458,7 @@ describe('SystemWebhookService', () => {
|
|||
);
|
||||
|
||||
// redisでの配信経由で更新されるのでちょっと待つ
|
||||
await sleep(500);
|
||||
await setTimeout(500);
|
||||
|
||||
const fetchedWebhooks = await service.fetchActiveSystemWebhooks();
|
||||
expect(fetchedWebhooks).toEqual([webhook2]);
|
||||
|
@ -481,7 +482,7 @@ describe('SystemWebhookService', () => {
|
|||
);
|
||||
|
||||
// redisでの配信経由で更新されるのでちょっと待つ
|
||||
await sleep(500);
|
||||
await setTimeout(500);
|
||||
|
||||
const fetchedWebhooks = await service.fetchActiveSystemWebhooks();
|
||||
expect(fetchedWebhooks.length).toEqual(0);
|
||||
|
@ -504,7 +505,7 @@ describe('SystemWebhookService', () => {
|
|||
);
|
||||
|
||||
// redisでの配信経由で更新されるのでちょっと待つ
|
||||
await sleep(500);
|
||||
await setTimeout(500);
|
||||
|
||||
const fetchedWebhooks = await service.fetchActiveSystemWebhooks();
|
||||
expect(fetchedWebhooks.length).toEqual(0);
|
||||
|
|
|
@ -17,6 +17,7 @@ import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/val
|
|||
import { entities } from '../src/postgres.js';
|
||||
import { loadConfig } from '../src/config.js';
|
||||
import type * as misskey from 'misskey-js';
|
||||
import { type Response } from 'node-fetch';
|
||||
import { ApiError } from "@/server/api/error.js";
|
||||
|
||||
export { server as startServer, jobQueue as startJobQueue } from '@/boot/common.js';
|
||||
|
@ -299,7 +300,7 @@ export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadO
|
|||
body: misskey.entities.DriveFile | null
|
||||
}> => {
|
||||
const absPath = path == null
|
||||
? new URL('resources/Lenna.jpg', import.meta.url)
|
||||
? new URL('resources/192.jpg', import.meta.url)
|
||||
: isAbsolute(path.toString())
|
||||
? new URL(path)
|
||||
: new URL(path, new URL('resources/', import.meta.url));
|
||||
|
@ -457,7 +458,7 @@ export type SimpleGetResponse = {
|
|||
type: string | null,
|
||||
location: string | null
|
||||
};
|
||||
export const simpleGet = async (path: string, accept = '*/*', cookie: any = undefined): Promise<SimpleGetResponse> => {
|
||||
export const simpleGet = async (path: string, accept = '*/*', cookie: any = undefined, bodyExtractor: (res: Response) => Promise<string | null> = _ => Promise.resolve(null)): Promise<SimpleGetResponse> => {
|
||||
const res = await relativeFetch(path, {
|
||||
headers: {
|
||||
Accept: accept,
|
||||
|
@ -485,7 +486,7 @@ export const simpleGet = async (path: string, accept = '*/*', cookie: any = unde
|
|||
const body =
|
||||
jsonTypes.includes(res.headers.get('content-type') ?? '') ? await res.json() :
|
||||
htmlTypes.includes(res.headers.get('content-type') ?? '') ? new JSDOM(await res.text()) :
|
||||
null;
|
||||
await bodyExtractor(res);
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
|
@ -607,14 +608,6 @@ export async function initTestDb(justBorrow = false, initEntities?: any[]) {
|
|||
return db;
|
||||
}
|
||||
|
||||
export function sleep(msec: number) {
|
||||
return new Promise<void>(res => {
|
||||
setTimeout(() => {
|
||||
res();
|
||||
}, msec);
|
||||
});
|
||||
}
|
||||
|
||||
export async function sendEnvUpdateRequest(params: { key: string, value?: string }) {
|
||||
const res = await fetch(
|
||||
`http://localhost:${port + 1000}/env`,
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
'node': false,
|
||||
},
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
'parser': '@typescript-eslint/parser',
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
extraFileExtensions: ['.vue'],
|
||||
},
|
||||
extends: [
|
||||
'../shared/.eslintrc.js',
|
||||
'plugin:vue/vue3-recommended',
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/no-empty-interface': [
|
||||
'error',
|
||||
{
|
||||
'allowSingleExtends': true,
|
||||
},
|
||||
],
|
||||
// window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため
|
||||
// e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため
|
||||
'id-denylist': ['error', 'window', 'e'],
|
||||
'no-shadow': ['warn'],
|
||||
'vue/attributes-order': ['error', {
|
||||
'alphabetical': false,
|
||||
}],
|
||||
'vue/no-use-v-if-with-v-for': ['error', {
|
||||
'allowUsingIterationVar': false,
|
||||
}],
|
||||
'vue/no-ref-as-operand': 'error',
|
||||
'vue/no-multi-spaces': ['error', {
|
||||
'ignoreProperties': false,
|
||||
}],
|
||||
'vue/no-v-html': 'warn',
|
||||
'vue/order-in-components': 'error',
|
||||
'vue/html-indent': ['warn', 'tab', {
|
||||
'attribute': 1,
|
||||
'baseIndent': 0,
|
||||
'closeBracket': 0,
|
||||
'alignAttributesVertically': true,
|
||||
'ignores': [],
|
||||
}],
|
||||
'vue/html-closing-bracket-spacing': ['warn', {
|
||||
'startTag': 'never',
|
||||
'endTag': 'never',
|
||||
'selfClosingTag': 'never',
|
||||
}],
|
||||
'vue/multi-word-component-names': 'warn',
|
||||
'vue/require-v-for-key': 'warn',
|
||||
'vue/no-unused-components': 'warn',
|
||||
'vue/no-unused-vars': 'warn',
|
||||
'vue/no-dupe-keys': 'warn',
|
||||
'vue/valid-v-for': 'warn',
|
||||
'vue/return-in-computed-property': 'warn',
|
||||
'vue/no-setup-props-destructure': 'warn',
|
||||
'vue/max-attributes-per-line': 'off',
|
||||
'vue/html-self-closing': 'off',
|
||||
'vue/singleline-html-element-content-newline': 'off',
|
||||
'vue/v-on-event-hyphenation': ['error', 'never', { autofix: true }],
|
||||
'vue/attribute-hyphenation': ['error', 'never'],
|
||||
},
|
||||
globals: {
|
||||
// Node.js
|
||||
'module': false,
|
||||
'require': false,
|
||||
'__dirname': false,
|
||||
|
||||
// Misskey
|
||||
'_DEV_': false,
|
||||
'_LANGS_': false,
|
||||
'_VERSION_': false,
|
||||
'_ENV_': false,
|
||||
'_PERF_PREFIX_': false,
|
||||
'_DATA_TRANSFER_DRIVE_FILE_': false,
|
||||
'_DATA_TRANSFER_DRIVE_FOLDER_': false,
|
||||
'_DATA_TRANSFER_DECK_COLUMN_': false,
|
||||
},
|
||||
};
|
|
@ -47,7 +47,6 @@ await fs.readFile(
|
|||
)
|
||||
)
|
||||
.map((path) => path.replace(/(?:(?<=\.stories)\.(?:impl|meta)|\.msw)(?=\.ts$)/g, ''))
|
||||
.map((path) => (path.startsWith('.') ? path : `./${path}`))
|
||||
);
|
||||
if (
|
||||
micromatch(Array.from(modules), [
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { DefaultBodyType, HttpResponse, HttpResponseResolver, JsonBodyType, PathParams, http } from 'msw';
|
||||
import seedrandom from 'seedrandom';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
function getChartArray(seed: string, limit: number, option?: { accumulate?: boolean, mul?: number }): number[] {
|
||||
const rng = seedrandom(seed);
|
||||
const max = Math.floor(option?.mul ?? 250 * rng());
|
||||
let accumulation = 0;
|
||||
const array: number[] = [];
|
||||
for (let i = 0; i < limit; i++) {
|
||||
const num = Math.floor((max + 1) * rng());
|
||||
if (option?.accumulate) {
|
||||
accumulation += num;
|
||||
array.unshift(accumulation);
|
||||
} else {
|
||||
array.push(num);
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
export function getChartResolver(fields: string[], option?: { accumulate?: boolean, mulMap?: Record<string, number> }): HttpResponseResolver<PathParams, DefaultBodyType, JsonBodyType> {
|
||||
return ({ request }) => {
|
||||
action(`GET ${request.url}`)();
|
||||
const limitParam = new URL(request.url).searchParams.get('limit');
|
||||
const limit = limitParam ? parseInt(limitParam) : 30;
|
||||
const res = {};
|
||||
for (const field of fields) {
|
||||
const layers = field.split('.');
|
||||
let current = res;
|
||||
while (layers.length > 1) {
|
||||
const currentKey = layers.shift()!;
|
||||
if (current[currentKey] == null) current[currentKey] = {};
|
||||
current = current[currentKey];
|
||||
}
|
||||
current[layers[0]] = getChartArray(field, limit, {
|
||||
accumulate: option?.accumulate,
|
||||
mul: option?.mulMap != null && field in option.mulMap ? option.mulMap[field] : undefined,
|
||||
});
|
||||
}
|
||||
return HttpResponse.json(res);
|
||||
};
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { FORCE_REMOUNT } from '@storybook/core-events';
|
||||
import { FORCE_RE_RENDER, FORCE_REMOUNT } from '@storybook/core-events';
|
||||
import { addons } from '@storybook/preview-api';
|
||||
import { type Preview, setup } from '@storybook/vue3';
|
||||
import isChromatic from 'chromatic/isChromatic';
|
||||
|
@ -16,7 +16,7 @@ import '../src/style.scss';
|
|||
|
||||
const appInitialized = Symbol();
|
||||
|
||||
let lastStory = null;
|
||||
let lastStory: string | null = null;
|
||||
let moduleInitialized = false;
|
||||
let unobserve = () => {};
|
||||
let misskeyOS = null;
|
||||
|
@ -110,7 +110,7 @@ const preview = {
|
|||
}).catch(() => {});
|
||||
Promise.all([resetIndexedDBPromise, resetDefaultStorePromise]).then(() => {
|
||||
initLocalStorage();
|
||||
channel.emit(FORCE_REMOUNT, { storyId: context.id });
|
||||
channel.emit(FORCE_RE_RENDER, { storyId: context.id });
|
||||
});
|
||||
}
|
||||
const story = Story();
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
import globals from 'globals';
|
||||
import tsParser from '@typescript-eslint/parser';
|
||||
import parser from 'vue-eslint-parser';
|
||||
import pluginVue from 'eslint-plugin-vue';
|
||||
import pluginMisskey from '@misskey-dev/eslint-plugin';
|
||||
import sharedConfig from '../shared/eslint.config.js';
|
||||
|
||||
export default [
|
||||
...sharedConfig,
|
||||
{
|
||||
files: ['src/**/*.vue'],
|
||||
...pluginMisskey.configs.typescript,
|
||||
},
|
||||
...pluginVue.configs['flat/recommended'],
|
||||
{
|
||||
files: ['src/**/*.{ts,vue}'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...Object.fromEntries(Object.entries(globals.node).map(([key]) => [key, 'off'])),
|
||||
...globals.browser,
|
||||
|
||||
// Node.js
|
||||
module: false,
|
||||
require: false,
|
||||
__dirname: false,
|
||||
|
||||
// Misskey
|
||||
_DEV_: false,
|
||||
_LANGS_: false,
|
||||
_VERSION_: false,
|
||||
_ENV_: false,
|
||||
_PERF_PREFIX_: false,
|
||||
_DATA_TRANSFER_DRIVE_FILE_: false,
|
||||
_DATA_TRANSFER_DRIVE_FOLDER_: false,
|
||||
_DATA_TRANSFER_DECK_COLUMN_: false,
|
||||
},
|
||||
parser,
|
||||
parserOptions: {
|
||||
extraFileExtensions: ['.vue'],
|
||||
parser: tsParser,
|
||||
project: ['./tsconfig.json'],
|
||||
sourceType: 'module',
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-empty-interface': ['error', {
|
||||
allowSingleExtends: true,
|
||||
}],
|
||||
// window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため
|
||||
// e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため
|
||||
'id-denylist': ['error', 'window', 'e'],
|
||||
'no-shadow': ['warn'],
|
||||
'vue/attributes-order': ['error', {
|
||||
alphabetical: false,
|
||||
}],
|
||||
'vue/no-use-v-if-with-v-for': ['error', {
|
||||
allowUsingIterationVar: false,
|
||||
}],
|
||||
'vue/no-ref-as-operand': 'error',
|
||||
'vue/no-multi-spaces': ['error', {
|
||||
ignoreProperties: false,
|
||||
}],
|
||||
'vue/no-v-html': 'warn',
|
||||
'vue/order-in-components': 'error',
|
||||
'vue/html-indent': ['warn', 'tab', {
|
||||
attribute: 1,
|
||||
baseIndent: 0,
|
||||
closeBracket: 0,
|
||||
alignAttributesVertically: true,
|
||||
ignores: [],
|
||||
}],
|
||||
'vue/html-closing-bracket-spacing': ['warn', {
|
||||
startTag: 'never',
|
||||
endTag: 'never',
|
||||
selfClosingTag: 'never',
|
||||
}],
|
||||
'vue/multi-word-component-names': 'warn',
|
||||
'vue/require-v-for-key': 'warn',
|
||||
'vue/no-unused-components': 'warn',
|
||||
'vue/no-unused-vars': 'warn',
|
||||
'vue/no-dupe-keys': 'warn',
|
||||
'vue/valid-v-for': 'warn',
|
||||
'vue/return-in-computed-property': 'warn',
|
||||
'vue/no-setup-props-reactivity-loss': 'warn',
|
||||
'vue/max-attributes-per-line': 'off',
|
||||
'vue/html-self-closing': 'off',
|
||||
'vue/singleline-html-element-content-newline': 'off',
|
||||
'vue/v-on-event-hyphenation': ['error', 'never', {
|
||||
autofix: true,
|
||||
}],
|
||||
'vue/attribute-hyphenation': ['error', 'never'],
|
||||
},
|
||||
},
|
||||
];
|
|
@ -22,24 +22,24 @@
|
|||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||
"@misskey-dev/browser-image-resizer": "2024.1.0",
|
||||
"@rollup/plugin-json": "6.1.0",
|
||||
"@rollup/plugin-replace": "5.0.5",
|
||||
"@rollup/plugin-replace": "5.0.7",
|
||||
"@rollup/pluginutils": "5.1.0",
|
||||
"@syuilo/aiscript": "0.18.0",
|
||||
"@tabler/icons-webfont": "3.3.0",
|
||||
"@twemoji/parser": "15.1.1",
|
||||
"@vitejs/plugin-vue": "5.0.4",
|
||||
"@vue/compiler-sfc": "3.4.26",
|
||||
"@vitejs/plugin-vue": "5.0.5",
|
||||
"@vue/compiler-sfc": "3.4.31",
|
||||
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.9",
|
||||
"astring": "1.8.6",
|
||||
"broadcast-channel": "7.0.0",
|
||||
"buraha": "0.0.1",
|
||||
"canvas-confetti": "1.9.3",
|
||||
"chart.js": "4.4.2",
|
||||
"chart.js": "4.4.3",
|
||||
"chartjs-adapter-date-fns": "3.0.0",
|
||||
"chartjs-chart-matrix": "2.0.1",
|
||||
"chartjs-plugin-gradient": "0.6.1",
|
||||
"chartjs-plugin-zoom": "2.0.1",
|
||||
"chromatic": "11.3.0",
|
||||
"chromatic": "11.5.4",
|
||||
"compare-versions": "6.1.0",
|
||||
"cropperjs": "2.0.0-beta.5",
|
||||
"date-fns": "2.30.0",
|
||||
|
@ -55,89 +55,87 @@
|
|||
"misskey-bubble-game": "workspace:*",
|
||||
"misskey-js": "workspace:*",
|
||||
"misskey-reversi": "workspace:*",
|
||||
"photoswipe": "5.4.3",
|
||||
"photoswipe": "5.4.4",
|
||||
"punycode": "2.3.1",
|
||||
"rollup": "4.17.2",
|
||||
"rollup": "4.18.0",
|
||||
"sanitize-html": "2.13.0",
|
||||
"sass": "1.76.0",
|
||||
"shiki": "1.4.0",
|
||||
"sass": "1.77.6",
|
||||
"shiki": "1.10.0",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"textarea-caret": "3.1.0",
|
||||
"three": "0.164.1",
|
||||
"throttle-debounce": "5.0.0",
|
||||
"three": "0.165.0",
|
||||
"throttle-debounce": "5.0.2",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tsc-alias": "1.8.8",
|
||||
"tsc-alias": "1.8.10",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typescript": "5.4.5",
|
||||
"uuid": "9.0.1",
|
||||
"v-code-diff": "1.11.0",
|
||||
"vite": "5.2.11",
|
||||
"vue": "3.4.26",
|
||||
"typescript": "5.5.3",
|
||||
"uuid": "10.0.0",
|
||||
"v-code-diff": "1.12.0",
|
||||
"vite": "5.3.2",
|
||||
"vue": "3.4.31",
|
||||
"vuedraggable": "next"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||
"@misskey-dev/summaly": "5.1.0",
|
||||
"@storybook/addon-actions": "8.0.9",
|
||||
"@storybook/addon-essentials": "8.0.9",
|
||||
"@storybook/addon-interactions": "8.0.9",
|
||||
"@storybook/addon-links": "8.0.9",
|
||||
"@storybook/addon-mdx-gfm": "8.0.9",
|
||||
"@storybook/addon-storysource": "8.0.9",
|
||||
"@storybook/blocks": "8.0.9",
|
||||
"@storybook/components": "8.0.9",
|
||||
"@storybook/core-events": "8.0.9",
|
||||
"@storybook/manager-api": "8.0.9",
|
||||
"@storybook/preview-api": "8.0.9",
|
||||
"@storybook/react": "8.0.9",
|
||||
"@storybook/react-vite": "8.0.9",
|
||||
"@storybook/test": "8.0.9",
|
||||
"@storybook/theming": "8.0.9",
|
||||
"@storybook/types": "8.0.9",
|
||||
"@storybook/vue3": "8.0.9",
|
||||
"@storybook/vue3-vite": "8.0.9",
|
||||
"@testing-library/vue": "8.0.3",
|
||||
"@storybook/addon-actions": "8.1.11",
|
||||
"@storybook/addon-essentials": "8.1.11",
|
||||
"@storybook/addon-interactions": "8.1.11",
|
||||
"@storybook/addon-links": "8.1.11",
|
||||
"@storybook/addon-mdx-gfm": "8.1.11",
|
||||
"@storybook/addon-storysource": "8.1.11",
|
||||
"@storybook/blocks": "8.1.11",
|
||||
"@storybook/components": "8.1.11",
|
||||
"@storybook/core-events": "8.1.11",
|
||||
"@storybook/manager-api": "8.1.11",
|
||||
"@storybook/preview-api": "8.1.11",
|
||||
"@storybook/react": "8.1.11",
|
||||
"@storybook/react-vite": "8.1.11",
|
||||
"@storybook/test": "8.1.11",
|
||||
"@storybook/theming": "8.1.11",
|
||||
"@storybook/types": "8.1.11",
|
||||
"@storybook/vue3": "8.1.11",
|
||||
"@storybook/vue3-vite": "8.1.11",
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/escape-regexp": "0.0.3",
|
||||
"@types/estree": "1.0.5",
|
||||
"@types/matter-js": "0.19.6",
|
||||
"@types/micromatch": "4.0.7",
|
||||
"@types/node": "20.12.7",
|
||||
"@types/micromatch": "4.0.9",
|
||||
"@types/node": "20.14.9",
|
||||
"@types/punycode": "2.1.4",
|
||||
"@types/sanitize-html": "2.11.0",
|
||||
"@types/seedrandom": "3.0.8",
|
||||
"@types/throttle-debounce": "5.0.2",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@types/uuid": "9.0.8",
|
||||
"@types/uuid": "10.0.0",
|
||||
"@types/ws": "8.5.10",
|
||||
"@typescript-eslint/eslint-plugin": "7.7.1",
|
||||
"@typescript-eslint/parser": "7.7.1",
|
||||
"@vitest/coverage-v8": "0.34.6",
|
||||
"@vue/runtime-core": "3.4.26",
|
||||
"acorn": "8.11.3",
|
||||
"@typescript-eslint/eslint-plugin": "7.15.0",
|
||||
"@typescript-eslint/parser": "7.15.0",
|
||||
"@vitest/coverage-v8": "1.6.0",
|
||||
"@vue/runtime-core": "3.4.31",
|
||||
"acorn": "8.12.0",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "13.8.1",
|
||||
"eslint": "8.57.0",
|
||||
"cypress": "13.13.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-vue": "9.25.0",
|
||||
"eslint-plugin-vue": "9.26.0",
|
||||
"fast-glob": "3.3.2",
|
||||
"happy-dom": "10.0.3",
|
||||
"intersection-observer": "0.12.2",
|
||||
"micromatch": "4.0.5",
|
||||
"msw": "2.2.14",
|
||||
"msw-storybook-addon": "2.0.1",
|
||||
"nodemon": "3.1.0",
|
||||
"prettier": "3.2.5",
|
||||
"micromatch": "4.0.7",
|
||||
"msw": "2.3.1",
|
||||
"msw-storybook-addon": "2.0.2",
|
||||
"nodemon": "3.1.4",
|
||||
"prettier": "3.3.2",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"seedrandom": "3.0.5",
|
||||
"start-server-and-test": "2.0.3",
|
||||
"storybook": "8.0.9",
|
||||
"start-server-and-test": "2.0.4",
|
||||
"storybook": "8.1.11",
|
||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
||||
"vite-plugin-turbosnap": "1.0.3",
|
||||
"vitest": "0.34.6",
|
||||
"vitest": "1.6.0",
|
||||
"vitest-fetch-mock": "0.2.2",
|
||||
"vue-component-type-helpers": "2.0.16",
|
||||
"vue-eslint-parser": "9.4.2",
|
||||
"vue-tsc": "2.0.16"
|
||||
"vue-component-type-helpers": "2.0.24",
|
||||
"vue-eslint-parser": "9.4.3",
|
||||
"vue-tsc": "2.0.24"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -184,10 +184,12 @@ export async function refreshAccount() {
|
|||
|
||||
export async function login(token: Account['token'], redirect?: string) {
|
||||
const showing = ref(true);
|
||||
popup(defineAsyncComponent(() => import('@/components/MkWaitingDialog.vue')), {
|
||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkWaitingDialog.vue')), {
|
||||
success: false,
|
||||
showing: showing,
|
||||
}, {}, 'closed');
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
if (_DEV_) console.log('logging as token ', token);
|
||||
const me = await fetchAccount(token, undefined, true)
|
||||
.catch(reason => {
|
||||
|
@ -223,21 +225,23 @@ export async function openAccountMenu(opts: {
|
|||
if (!$i) return;
|
||||
|
||||
function showSigninDialog() {
|
||||
popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {}, {
|
||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {}, {
|
||||
done: res => {
|
||||
addAccount(res.id, res.i);
|
||||
success();
|
||||
},
|
||||
}, 'closed');
|
||||
closed: () => dispose(),
|
||||
});
|
||||
}
|
||||
|
||||
function createAccount() {
|
||||
popup(defineAsyncComponent(() => import('@/components/MkSignupDialog.vue')), {}, {
|
||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSignupDialog.vue')), {}, {
|
||||
done: res => {
|
||||
addAccount(res.id, res.i);
|
||||
switchAccountWithToken(res.i);
|
||||
},
|
||||
}, 'closed');
|
||||
closed: () => dispose(),
|
||||
});
|
||||
}
|
||||
|
||||
async function switchAccount(account: Misskey.entities.UserDetailed) {
|
||||
|
|
|
@ -13,7 +13,6 @@ import * as sound from '@/scripts/sound.js';
|
|||
import { $i, signout, updateAccount } from '@/account.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { ColdDeviceStorage, defaultStore } from '@/store.js';
|
||||
import { makeHotkey } from '@/scripts/hotkey.js';
|
||||
import { reactionPicker } from '@/scripts/reaction-picker.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { claimAchievement, claimedAchievements } from '@/scripts/achievements.js';
|
||||
|
@ -21,6 +20,7 @@ import { initializeSw } from '@/scripts/initialize-sw.js';
|
|||
import { deckStore } from '@/ui/deck/deck-store.js';
|
||||
import { emojiPicker } from '@/scripts/emoji-picker.js';
|
||||
import { mainRouter } from '@/router/main.js';
|
||||
import { type Keymap, makeHotkey } from '@/scripts/hotkey.js';
|
||||
|
||||
export async function mainBoot() {
|
||||
const { isClientUpdated } = await common(() => createApp(
|
||||
|
@ -35,7 +35,9 @@ export async function mainBoot() {
|
|||
emojiPicker.init();
|
||||
|
||||
if (isClientUpdated && $i) {
|
||||
popup(defineAsyncComponent(() => import('@/components/MkUpdated.vue')), {}, {}, 'closed');
|
||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkUpdated.vue')), {}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
}
|
||||
|
||||
const stream = useStream();
|
||||
|
@ -67,14 +69,6 @@ export async function mainBoot() {
|
|||
});
|
||||
}
|
||||
|
||||
const hotkeys = {
|
||||
'd': (): void => {
|
||||
defaultStore.set('darkMode', !defaultStore.state.darkMode);
|
||||
},
|
||||
's': (): void => {
|
||||
mainRouter.push('/search');
|
||||
},
|
||||
};
|
||||
try {
|
||||
if (defaultStore.state.enableSeasonalScreenEffect) {
|
||||
const month = new Date().getMonth() + 1;
|
||||
|
@ -96,34 +90,37 @@ export async function mainBoot() {
|
|||
}).render();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error(error);
|
||||
console.error('Failed to initialise the seasonal screen effect canvas context:', error);
|
||||
}
|
||||
|
||||
if ($i) {
|
||||
// only add post shortcuts if logged in
|
||||
hotkeys['p|n'] = post;
|
||||
|
||||
defaultStore.loaded.then(() => {
|
||||
if (defaultStore.state.accountSetupWizard !== -1) {
|
||||
popup(defineAsyncComponent(() => import('@/components/MkUserSetupDialog.vue')), {}, {}, 'closed');
|
||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkUserSetupDialog.vue')), {}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
for (const announcement of ($i.unreadAnnouncements ?? []).filter(x => x.display === 'dialog')) {
|
||||
popup(defineAsyncComponent(() => import('@/components/MkAnnouncementDialog.vue')), {
|
||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkAnnouncementDialog.vue')), {
|
||||
announcement,
|
||||
}, {}, 'closed');
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
}
|
||||
|
||||
stream.on('announcementCreated', (ev) => {
|
||||
const announcement = ev.announcement;
|
||||
if (announcement.display === 'dialog') {
|
||||
popup(defineAsyncComponent(() => import('@/components/MkAnnouncementDialog.vue')), {
|
||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkAnnouncementDialog.vue')), {
|
||||
announcement,
|
||||
}, {}, 'closed');
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -247,13 +244,17 @@ export async function mainBoot() {
|
|||
const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo');
|
||||
if (neverShowDonationInfo !== 'true' && (createdAt.getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !location.pathname.startsWith('/miauth')) {
|
||||
if (latestDonationInfoShownAt == null || (new Date(latestDonationInfoShownAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 30)))) {
|
||||
popup(defineAsyncComponent(() => import('@/components/MkDonation.vue')), {}, {}, 'closed');
|
||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkDonation.vue')), {}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const modifiedVersionMustProminentlyOfferInAgplV3Section13Read = miLocalStorage.getItem('modifiedVersionMustProminentlyOfferInAgplV3Section13Read');
|
||||
if (modifiedVersionMustProminentlyOfferInAgplV3Section13Read !== 'true' && instance.repositoryUrl !== 'https://github.com/misskey-dev/misskey') {
|
||||
popup(defineAsyncComponent(() => import('@/components/MkSourceCodeAvailablePopup.vue')), {}, {}, 'closed');
|
||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSourceCodeAvailablePopup.vue')), {}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
}
|
||||
|
||||
if ('Notification' in window) {
|
||||
|
@ -322,7 +323,19 @@ export async function mainBoot() {
|
|||
}
|
||||
|
||||
// shortcut
|
||||
document.addEventListener('keydown', makeHotkey(hotkeys));
|
||||
const keymap = {
|
||||
'p|n': () => {
|
||||
if ($i == null) return;
|
||||
post();
|
||||
},
|
||||
'd': () => {
|
||||
defaultStore.set('darkMode', !defaultStore.state.darkMode);
|
||||
},
|
||||
's': () => {
|
||||
mainRouter.push('/search');
|
||||
},
|
||||
} as const satisfies Keymap;
|
||||
document.addEventListener('keydown', makeHotkey(keymap), { passive: false });
|
||||
|
||||
initializeSw();
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue