mirror of
https://github.com/misskey-dev/misskey.git
synced 2024-12-29 09:58:36 +01:00
Merge branch 'develop' into feature/default-post-target-detect-from-path
This commit is contained in:
commit
e9c1a62d99
102 changed files with 2930 additions and 513 deletions
|
@ -103,6 +103,14 @@ redis:
|
||||||
# #prefix: example-prefix
|
# #prefix: example-prefix
|
||||||
# #db: 1
|
# #db: 1
|
||||||
|
|
||||||
|
#redisForReactions:
|
||||||
|
# host: redis
|
||||||
|
# port: 6379
|
||||||
|
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
|
||||||
|
# #pass: example-pass
|
||||||
|
# #prefix: example-prefix
|
||||||
|
# #db: 1
|
||||||
|
|
||||||
# ┌───────────────────────────┐
|
# ┌───────────────────────────┐
|
||||||
#───┘ MeiliSearch configuration └─────────────────────────────
|
#───┘ MeiliSearch configuration └─────────────────────────────
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,14 @@ redis:
|
||||||
# #prefix: example-prefix
|
# #prefix: example-prefix
|
||||||
# #db: 1
|
# #db: 1
|
||||||
|
|
||||||
|
#redisForReactions:
|
||||||
|
# host: redis
|
||||||
|
# port: 6379
|
||||||
|
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
|
||||||
|
# #pass: example-pass
|
||||||
|
# #prefix: example-prefix
|
||||||
|
# #db: 1
|
||||||
|
|
||||||
# ┌───────────────────────────┐
|
# ┌───────────────────────────┐
|
||||||
#───┘ MeiliSearch configuration └─────────────────────────────
|
#───┘ MeiliSearch configuration └─────────────────────────────
|
||||||
|
|
||||||
|
|
|
@ -172,6 +172,16 @@ redis:
|
||||||
# # You can specify more ioredis options...
|
# # You can specify more ioredis options...
|
||||||
# #username: example-username
|
# #username: example-username
|
||||||
|
|
||||||
|
#redisForReactions:
|
||||||
|
# host: localhost
|
||||||
|
# port: 6379
|
||||||
|
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
|
||||||
|
# #pass: example-pass
|
||||||
|
# #prefix: example-prefix
|
||||||
|
# #db: 1
|
||||||
|
# # You can specify more ioredis options...
|
||||||
|
# #username: example-username
|
||||||
|
|
||||||
# ┌───────────────────────────┐
|
# ┌───────────────────────────┐
|
||||||
#───┘ MeiliSearch configuration └─────────────────────────────
|
#───┘ MeiliSearch configuration └─────────────────────────────
|
||||||
|
|
||||||
|
|
|
@ -103,6 +103,14 @@ redis:
|
||||||
# #prefix: example-prefix
|
# #prefix: example-prefix
|
||||||
# #db: 1
|
# #db: 1
|
||||||
|
|
||||||
|
#redisForReactions:
|
||||||
|
# host: redis
|
||||||
|
# port: 6379
|
||||||
|
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
|
||||||
|
# #pass: example-pass
|
||||||
|
# #prefix: example-prefix
|
||||||
|
# #db: 1
|
||||||
|
|
||||||
# ┌───────────────────────────┐
|
# ┌───────────────────────────┐
|
||||||
#───┘ MeiliSearch configuration └─────────────────────────────
|
#───┘ MeiliSearch configuration └─────────────────────────────
|
||||||
|
|
||||||
|
|
2
.github/workflows/api-misskey-js.yml
vendored
2
.github/workflows/api-misskey-js.yml
vendored
|
@ -21,7 +21,7 @@ jobs:
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
2
.github/workflows/changelog-check.yml
vendored
2
.github/workflows/changelog-check.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
||||||
- name: Checkout head
|
- name: Checkout head
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.1
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ jobs:
|
||||||
|
|
||||||
- name: setup node
|
- name: setup node
|
||||||
id: setup-node
|
id: setup-node
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
|
2
.github/workflows/get-api-diff.yml
vendored
2
.github/workflows/get-api-diff.yml
vendored
|
@ -33,7 +33,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
6
.github/workflows/lint.yml
vendored
6
.github/workflows/lint.yml
vendored
|
@ -33,7 +33,7 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: true
|
submodules: true
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4.0.3
|
- uses: actions/setup-node@v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
@ -62,7 +62,7 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: true
|
submodules: true
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4.0.3
|
- uses: actions/setup-node@v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
@ -92,7 +92,7 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: true
|
submodules: true
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4.0.3
|
- uses: actions/setup-node@v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
2
.github/workflows/locale.yml
vendored
2
.github/workflows/locale.yml
vendored
|
@ -19,7 +19,7 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: true
|
submodules: true
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4.0.3
|
- uses: actions/setup-node@v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
2
.github/workflows/on-release-created.yml
vendored
2
.github/workflows/on-release-created.yml
vendored
|
@ -26,7 +26,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
33
.github/workflows/report-api-diff.yml
vendored
33
.github/workflows/report-api-diff.yml
vendored
|
@ -70,18 +70,27 @@ jobs:
|
||||||
- id: out-diff
|
- id: out-diff
|
||||||
name: Build diff Comment
|
name: Build diff Comment
|
||||||
run: |
|
run: |
|
||||||
cat <<- EOF > ./output.md
|
HEADER="このPRによるapi.jsonの差分"
|
||||||
このPRによるapi.jsonの差分
|
FOOTER="[Get diff files from Workflow Page](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})"
|
||||||
<details>
|
DIFF_BYTES="$(stat ./api.json.diff -c '%s' | tr -d '\n')"
|
||||||
<summary>差分はこちら</summary>
|
|
||||||
|
echo "$HEADER" > ./output.md
|
||||||
\`\`\`diff
|
|
||||||
$(cat ./api.json.diff)
|
if (( "$DIFF_BYTES" <= 1 )); then
|
||||||
\`\`\`
|
echo '差分はありません。' >> ./output.md
|
||||||
</details>
|
else
|
||||||
|
cat <<- EOF >> ./output.md
|
||||||
[Get diff files from Workflow Page](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})
|
<details>
|
||||||
EOF
|
<summary>差分はこちら</summary>
|
||||||
|
|
||||||
|
\`\`\`diff
|
||||||
|
$(cat ./api.json.diff)
|
||||||
|
\`\`\`
|
||||||
|
</details>
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$FOOTER" >> ./output.md
|
||||||
- uses: thollander/actions-comment-pull-request@v2
|
- uses: thollander/actions-comment-pull-request@v2
|
||||||
with:
|
with:
|
||||||
pr_number: ${{ steps.load-pr-num.outputs.pr-number }}
|
pr_number: ${{ steps.load-pr-num.outputs.pr-number }}
|
||||||
|
|
2
.github/workflows/storybook.yml
vendored
2
.github/workflows/storybook.yml
vendored
|
@ -41,7 +41,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js 20.x
|
- name: Use Node.js 20.x
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
4
.github/workflows/test-backend.yml
vendored
4
.github/workflows/test-backend.yml
vendored
|
@ -46,7 +46,7 @@ jobs:
|
||||||
- name: Install FFmpeg
|
- name: Install FFmpeg
|
||||||
uses: FedericoCarboni/setup-ffmpeg@v3
|
uses: FedericoCarboni/setup-ffmpeg@v3
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
@ -93,7 +93,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
4
.github/workflows/test-frontend.yml
vendored
4
.github/workflows/test-frontend.yml
vendored
|
@ -35,7 +35,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
@ -90,7 +90,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
2
.github/workflows/test-misskey-js.yml
vendored
2
.github/workflows/test-misskey-js.yml
vendored
|
@ -31,7 +31,7 @@ jobs:
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
|
|
||||||
- name: Setup Node.js ${{ matrix.node-version }}
|
- name: Setup Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
2
.github/workflows/test-production.yml
vendored
2
.github/workflows/test-production.yml
vendored
|
@ -25,7 +25,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
2
.github/workflows/validate-api-json.yml
vendored
2
.github/workflows/validate-api-json.yml
vendored
|
@ -27,7 +27,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -65,6 +65,10 @@ temp
|
||||||
tsdoc-metadata.json
|
tsdoc-metadata.json
|
||||||
misskey-assets
|
misskey-assets
|
||||||
|
|
||||||
|
# Vite temporary files
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
||||||
|
|
||||||
# blender backups
|
# blender backups
|
||||||
*.blend1
|
*.blend1
|
||||||
*.blend2
|
*.blend2
|
||||||
|
|
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -1,25 +1,35 @@
|
||||||
## Unreleased
|
## 2024.9.0
|
||||||
|
|
||||||
### General
|
### General
|
||||||
-
|
- Feat: UserWebhookとSystemWebhookのテスト送信機能を追加 (#14445)
|
||||||
|
- Enhance: ユーザーによるコンテンツインポートの可否をロールポリシーで制御できるように
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Feat: ノート単体・ユーザーのノート・クリップのノートの埋め込み機能
|
- Feat: ノート単体・ユーザーのノート・クリップのノートの埋め込み機能
|
||||||
- 埋め込みコードやウェブサイトへの実装方法の詳細はMisskey Hubに掲載予定です
|
- 埋め込みコードやウェブサイトへの実装方法の詳細は https://misskey-hub.net/docs/for-users/features/embed/ をご覧ください
|
||||||
- Enhance: サイズ制限を超過するファイルをアップロードしようとした際にエラーを出すように
|
- Enhance: サイズ制限を超過するファイルをアップロードしようとした際にエラーを出すように
|
||||||
- Enhance: アイコンデコレーション管理画面にプレビューを追加
|
- Enhance: アイコンデコレーション管理画面にプレビューを追加
|
||||||
- Enhance: コントロールパネル内のファイル一覧でセンシティブなファイルを区別しやすく
|
- Enhance: コントロールパネル内のファイル一覧でセンシティブなファイルを区別しやすく
|
||||||
|
- Enhance: ScratchpadにUIインスペクターを追加
|
||||||
- Enhance: どこで投稿フォームを開いてもお気に入りに登録したチャンネルにノートできるように
|
- Enhance: どこで投稿フォームを開いてもお気に入りに登録したチャンネルにノートできるように
|
||||||
- Enhance: チャンネルのページを開いている間はデフォルトの公開範囲がそのチャンネルになるように
|
- Enhance: チャンネルのページを開いている間はデフォルトの公開範囲がそのチャンネルになるように
|
||||||
- Fix: サーバーメトリクスが2つ以上あるとリロード直後の表示がおかしくなる問題を修正
|
- Fix: サーバーメトリクスが2つ以上あるとリロード直後の表示がおかしくなる問題を修正
|
||||||
- Fix: 月の違う同じ日はセパレータが表示されないのを修正
|
- Fix: 月の違う同じ日はセパレータが表示されないのを修正
|
||||||
|
- Fix: 縦横比が極端なカスタム絵文字を表示する際にレイアウトが崩れる箇所があるのを修正
|
||||||
|
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/725)
|
||||||
|
- Fix: 設定変更時のリロード確認ダイアログが複数個表示されることがある問題を修正
|
||||||
|
- Fix: ファイルの詳細ページのファイルの説明で改行が正しく表示されない問題を修正
|
||||||
|
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/bde6bb0bd2e8b0d027e724d2acdb8ae0585a8110)
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
|
- Feat: Misskey® Reactions Buffering Technology™ (RBT)により、リアクションの作成負荷を低減することが可能に
|
||||||
- Fix: アンテナの書き込み時にキーワードが与えられなかった場合のエラーをApiErrorとして投げるように
|
- Fix: アンテナの書き込み時にキーワードが与えられなかった場合のエラーをApiErrorとして投げるように
|
||||||
- この変更により、公式フロントエンドでは入力の不備が内部エラーとして報告される代わりに一般的なエラーダイアログで報告されます
|
- この変更により、公式フロントエンドでは入力の不備が内部エラーとして報告される代わりに一般的なエラーダイアログで報告されます
|
||||||
- Fix: ファイルがサイズの制限を超えてアップロードされた際にエラーを返さなかった問題を修正
|
- Fix: ファイルがサイズの制限を超えてアップロードされた際にエラーを返さなかった問題を修正
|
||||||
- Fix: 外部ページを解析する際に、ページに紐づけられた関連リソースも読み込まれてしまう問題を修正
|
- Fix: 外部ページを解析する際に、ページに紐づけられた関連リソースも読み込まれてしまう問題を修正
|
||||||
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/26e0412fbb91447c37e8fb06ffb0487346063bb8)
|
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/26e0412fbb91447c37e8fb06ffb0487346063bb8)
|
||||||
|
- Fix: `Retry-After`ヘッダーが送信されなかった問題を修正
|
||||||
|
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/8a982c61c01909e7540ff1be9f019df07c3f0624)
|
||||||
|
|
||||||
## 2024.8.0
|
## 2024.8.0
|
||||||
|
|
||||||
|
|
|
@ -124,6 +124,14 @@ redis:
|
||||||
# #prefix: example-prefix
|
# #prefix: example-prefix
|
||||||
# #db: 1
|
# #db: 1
|
||||||
|
|
||||||
|
#redisForReactions:
|
||||||
|
# host: redis
|
||||||
|
# port: 6379
|
||||||
|
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
|
||||||
|
# #pass: example-pass
|
||||||
|
# #prefix: example-prefix
|
||||||
|
# #db: 1
|
||||||
|
|
||||||
# ┌───────────────────────────┐
|
# ┌───────────────────────────┐
|
||||||
#───┘ MeiliSearch configuration └─────────────────────────────
|
#───┘ MeiliSearch configuration └─────────────────────────────
|
||||||
|
|
||||||
|
|
38
locales/index.d.ts
vendored
38
locales/index.d.ts
vendored
|
@ -2384,6 +2384,14 @@ export interface Locale extends ILocale {
|
||||||
* スクラッチパッドは、AiScriptの実験環境を提供します。Misskeyと対話するコードの記述、実行、結果の確認ができます。
|
* スクラッチパッドは、AiScriptの実験環境を提供します。Misskeyと対話するコードの記述、実行、結果の確認ができます。
|
||||||
*/
|
*/
|
||||||
"scratchpadDescription": string;
|
"scratchpadDescription": string;
|
||||||
|
/**
|
||||||
|
* UIインスペクター
|
||||||
|
*/
|
||||||
|
"uiInspector": string;
|
||||||
|
/**
|
||||||
|
* メモリ上に存在しているUIコンポーネントのインスタンスの一覧を見ることができます。UIコンポーネントはUi:C:系関数により生成されます。
|
||||||
|
*/
|
||||||
|
"uiInspectorDescription": string;
|
||||||
/**
|
/**
|
||||||
* 出力
|
* 出力
|
||||||
*/
|
*/
|
||||||
|
@ -3121,7 +3129,7 @@ export interface Locale extends ILocale {
|
||||||
*/
|
*/
|
||||||
"narrow": string;
|
"narrow": string;
|
||||||
/**
|
/**
|
||||||
* 設定はページリロード後に反映されます。今すぐリロードしますか?
|
* 設定はページリロード後に反映されます。
|
||||||
*/
|
*/
|
||||||
"reloadToApplySetting": string;
|
"reloadToApplySetting": string;
|
||||||
/**
|
/**
|
||||||
|
@ -5575,6 +5583,10 @@ export interface Locale extends ILocale {
|
||||||
* 有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。
|
* 有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。
|
||||||
*/
|
*/
|
||||||
"fanoutTimelineDbFallbackDescription": string;
|
"fanoutTimelineDbFallbackDescription": string;
|
||||||
|
/**
|
||||||
|
* 有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。
|
||||||
|
*/
|
||||||
|
"reactionsBufferingDescription": string;
|
||||||
/**
|
/**
|
||||||
* 問い合わせ先URL
|
* 問い合わせ先URL
|
||||||
*/
|
*/
|
||||||
|
@ -6754,6 +6766,26 @@ export interface Locale extends ILocale {
|
||||||
* アイコンデコレーションの最大取付個数
|
* アイコンデコレーションの最大取付個数
|
||||||
*/
|
*/
|
||||||
"avatarDecorationLimit": string;
|
"avatarDecorationLimit": string;
|
||||||
|
/**
|
||||||
|
* アンテナのインポートを許可
|
||||||
|
*/
|
||||||
|
"canImportAntennas": string;
|
||||||
|
/**
|
||||||
|
* ブロックのインポートを許可
|
||||||
|
*/
|
||||||
|
"canImportBlocking": string;
|
||||||
|
/**
|
||||||
|
* フォローのインポートを許可
|
||||||
|
*/
|
||||||
|
"canImportFollowing": string;
|
||||||
|
/**
|
||||||
|
* ミュートのインポートを許可
|
||||||
|
*/
|
||||||
|
"canImportMuting": string;
|
||||||
|
/**
|
||||||
|
* リストのインポートを許可
|
||||||
|
*/
|
||||||
|
"canImportUserLists": string;
|
||||||
};
|
};
|
||||||
"_condition": {
|
"_condition": {
|
||||||
/**
|
/**
|
||||||
|
@ -9489,6 +9521,10 @@ export interface Locale extends ILocale {
|
||||||
* Webhookを削除しますか?
|
* Webhookを削除しますか?
|
||||||
*/
|
*/
|
||||||
"deleteConfirm": string;
|
"deleteConfirm": string;
|
||||||
|
/**
|
||||||
|
* スイッチの右にあるボタンをクリックするとダミーのデータを使用したテスト用Webhookを送信できます。
|
||||||
|
*/
|
||||||
|
"testRemarks": string;
|
||||||
};
|
};
|
||||||
"_abuseReport": {
|
"_abuseReport": {
|
||||||
"_notificationRecipient": {
|
"_notificationRecipient": {
|
||||||
|
|
|
@ -592,6 +592,8 @@ ascendingOrder: "昇順"
|
||||||
descendingOrder: "降順"
|
descendingOrder: "降順"
|
||||||
scratchpad: "スクラッチパッド"
|
scratchpad: "スクラッチパッド"
|
||||||
scratchpadDescription: "スクラッチパッドは、AiScriptの実験環境を提供します。Misskeyと対話するコードの記述、実行、結果の確認ができます。"
|
scratchpadDescription: "スクラッチパッドは、AiScriptの実験環境を提供します。Misskeyと対話するコードの記述、実行、結果の確認ができます。"
|
||||||
|
uiInspector: "UIインスペクター"
|
||||||
|
uiInspectorDescription: "メモリ上に存在しているUIコンポーネントのインスタンスの一覧を見ることができます。UIコンポーネントはUi:C:系関数により生成されます。"
|
||||||
output: "出力"
|
output: "出力"
|
||||||
script: "スクリプト"
|
script: "スクリプト"
|
||||||
disablePagesScript: "Pagesのスクリプトを無効にする"
|
disablePagesScript: "Pagesのスクリプトを無効にする"
|
||||||
|
@ -776,7 +778,7 @@ left: "左"
|
||||||
center: "中央"
|
center: "中央"
|
||||||
wide: "広い"
|
wide: "広い"
|
||||||
narrow: "狭い"
|
narrow: "狭い"
|
||||||
reloadToApplySetting: "設定はページリロード後に反映されます。今すぐリロードしますか?"
|
reloadToApplySetting: "設定はページリロード後に反映されます。"
|
||||||
needReloadToApply: "反映には再起動が必要です。"
|
needReloadToApply: "反映には再起動が必要です。"
|
||||||
showTitlebar: "タイトルバーを表示する"
|
showTitlebar: "タイトルバーを表示する"
|
||||||
clearCache: "キャッシュをクリア"
|
clearCache: "キャッシュをクリア"
|
||||||
|
@ -1409,6 +1411,7 @@ _serverSettings:
|
||||||
fanoutTimelineDescription: "有効にすると、各種タイムラインを取得する際のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。サーバーのメモリ容量が少ない場合、または動作が不安定な場合は無効にすることができます。"
|
fanoutTimelineDescription: "有効にすると、各種タイムラインを取得する際のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。サーバーのメモリ容量が少ない場合、または動作が不安定な場合は無効にすることができます。"
|
||||||
fanoutTimelineDbFallback: "データベースへのフォールバック"
|
fanoutTimelineDbFallback: "データベースへのフォールバック"
|
||||||
fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。"
|
fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。"
|
||||||
|
reactionsBufferingDescription: "有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。"
|
||||||
inquiryUrl: "問い合わせ先URL"
|
inquiryUrl: "問い合わせ先URL"
|
||||||
inquiryUrlDescription: "サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定します。"
|
inquiryUrlDescription: "サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定します。"
|
||||||
|
|
||||||
|
@ -1745,6 +1748,11 @@ _role:
|
||||||
canSearchNotes: "ノート検索の利用"
|
canSearchNotes: "ノート検索の利用"
|
||||||
canUseTranslator: "翻訳機能の利用"
|
canUseTranslator: "翻訳機能の利用"
|
||||||
avatarDecorationLimit: "アイコンデコレーションの最大取付個数"
|
avatarDecorationLimit: "アイコンデコレーションの最大取付個数"
|
||||||
|
canImportAntennas: "アンテナのインポートを許可"
|
||||||
|
canImportBlocking: "ブロックのインポートを許可"
|
||||||
|
canImportFollowing: "フォローのインポートを許可"
|
||||||
|
canImportMuting: "ミュートのインポートを許可"
|
||||||
|
canImportUserLists: "リストのインポートを許可"
|
||||||
_condition:
|
_condition:
|
||||||
roleAssignedTo: "マニュアルロールにアサイン済み"
|
roleAssignedTo: "マニュアルロールにアサイン済み"
|
||||||
isLocal: "ローカルユーザー"
|
isLocal: "ローカルユーザー"
|
||||||
|
@ -2515,6 +2523,7 @@ _webhookSettings:
|
||||||
abuseReportResolved: "ユーザーからの通報を処理したとき"
|
abuseReportResolved: "ユーザーからの通報を処理したとき"
|
||||||
userCreated: "ユーザーが作成されたとき"
|
userCreated: "ユーザーが作成されたとき"
|
||||||
deleteConfirm: "Webhookを削除しますか?"
|
deleteConfirm: "Webhookを削除しますか?"
|
||||||
|
testRemarks: "スイッチの右にあるボタンをクリックするとダミーのデータを使用したテスト用Webhookを送信できます。"
|
||||||
|
|
||||||
_abuseReport:
|
_abuseReport:
|
||||||
_notificationRecipient:
|
_notificationRecipient:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2024.8.0",
|
"version": "2024.9.0-alpha.2",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class ReactionsBuffering1726804538569 {
|
||||||
|
name = 'ReactionsBuffering1726804538569'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "enableReactionsBuffering" boolean NOT NULL DEFAULT false`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableReactionsBuffering"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -132,6 +132,7 @@
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"jsonld": "8.3.2",
|
"jsonld": "8.3.2",
|
||||||
"jsrsasign": "11.1.0",
|
"jsrsasign": "11.1.0",
|
||||||
|
"juice": "11.0.0",
|
||||||
"meilisearch": "0.41.0",
|
"meilisearch": "0.41.0",
|
||||||
"mfm-js": "0.24.0",
|
"mfm-js": "0.24.0",
|
||||||
"microformats-parser": "2.0.2",
|
"microformats-parser": "2.0.2",
|
||||||
|
|
|
@ -78,11 +78,19 @@ const $redisForTimelines: Provider = {
|
||||||
inject: [DI.config],
|
inject: [DI.config],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const $redisForReactions: Provider = {
|
||||||
|
provide: DI.redisForReactions,
|
||||||
|
useFactory: (config: Config) => {
|
||||||
|
return new Redis.Redis(config.redisForReactions);
|
||||||
|
},
|
||||||
|
inject: [DI.config],
|
||||||
|
};
|
||||||
|
|
||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
imports: [RepositoryModule],
|
imports: [RepositoryModule],
|
||||||
providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines],
|
providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions],
|
||||||
exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, RepositoryModule],
|
exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions, RepositoryModule],
|
||||||
})
|
})
|
||||||
export class GlobalModule implements OnApplicationShutdown {
|
export class GlobalModule implements OnApplicationShutdown {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -91,6 +99,7 @@ export class GlobalModule implements OnApplicationShutdown {
|
||||||
@Inject(DI.redisForPub) private redisForPub: Redis.Redis,
|
@Inject(DI.redisForPub) private redisForPub: Redis.Redis,
|
||||||
@Inject(DI.redisForSub) private redisForSub: Redis.Redis,
|
@Inject(DI.redisForSub) private redisForSub: Redis.Redis,
|
||||||
@Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis,
|
@Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis,
|
||||||
|
@Inject(DI.redisForReactions) private redisForReactions: Redis.Redis,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
public async dispose(): Promise<void> {
|
public async dispose(): Promise<void> {
|
||||||
|
@ -103,6 +112,7 @@ export class GlobalModule implements OnApplicationShutdown {
|
||||||
this.redisForPub.disconnect(),
|
this.redisForPub.disconnect(),
|
||||||
this.redisForSub.disconnect(),
|
this.redisForSub.disconnect(),
|
||||||
this.redisForTimelines.disconnect(),
|
this.redisForTimelines.disconnect(),
|
||||||
|
this.redisForReactions.disconnect(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ type Source = {
|
||||||
redisForPubsub?: RedisOptionsSource;
|
redisForPubsub?: RedisOptionsSource;
|
||||||
redisForJobQueue?: RedisOptionsSource;
|
redisForJobQueue?: RedisOptionsSource;
|
||||||
redisForTimelines?: RedisOptionsSource;
|
redisForTimelines?: RedisOptionsSource;
|
||||||
|
redisForReactions?: RedisOptionsSource;
|
||||||
meilisearch?: {
|
meilisearch?: {
|
||||||
host: string;
|
host: string;
|
||||||
port: string;
|
port: string;
|
||||||
|
@ -171,6 +172,7 @@ export type Config = {
|
||||||
redisForPubsub: RedisOptions & RedisOptionsSource;
|
redisForPubsub: RedisOptions & RedisOptionsSource;
|
||||||
redisForJobQueue: RedisOptions & RedisOptionsSource;
|
redisForJobQueue: RedisOptions & RedisOptionsSource;
|
||||||
redisForTimelines: RedisOptions & RedisOptionsSource;
|
redisForTimelines: RedisOptions & RedisOptionsSource;
|
||||||
|
redisForReactions: RedisOptions & RedisOptionsSource;
|
||||||
sentryForBackend: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; } | undefined;
|
sentryForBackend: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; } | undefined;
|
||||||
sentryForFrontend: { options: Partial<Sentry.NodeOptions> } | undefined;
|
sentryForFrontend: { options: Partial<Sentry.NodeOptions> } | undefined;
|
||||||
perChannelMaxNoteCacheCount: number;
|
perChannelMaxNoteCacheCount: number;
|
||||||
|
@ -251,6 +253,7 @@ export function loadConfig(): Config {
|
||||||
redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis,
|
redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis,
|
||||||
redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis,
|
redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis,
|
||||||
redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
|
redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
|
||||||
|
redisForReactions: config.redisForReactions ? convertRedisOptions(config.redisForReactions, host) : redis,
|
||||||
sentryForBackend: config.sentryForBackend,
|
sentryForBackend: config.sentryForBackend,
|
||||||
sentryForFrontend: config.sentryForFrontend,
|
sentryForFrontend: config.sentryForFrontend,
|
||||||
id: config.id,
|
id: config.id,
|
||||||
|
|
|
@ -8,6 +8,8 @@ export const MAX_NOTE_TEXT_LENGTH = 3000;
|
||||||
export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min
|
export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min
|
||||||
export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days
|
export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days
|
||||||
|
|
||||||
|
export const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
|
||||||
|
|
||||||
//#region hard limits
|
//#region hard limits
|
||||||
// If you change DB_* values, you must also change the DB schema.
|
// If you change DB_* values, you must also change the DB schema.
|
||||||
|
|
||||||
|
|
|
@ -123,11 +123,14 @@ export class AntennaService implements OnApplicationShutdown {
|
||||||
if (antenna.src === 'home') {
|
if (antenna.src === 'home') {
|
||||||
// TODO
|
// TODO
|
||||||
} else if (antenna.src === 'list') {
|
} else if (antenna.src === 'list') {
|
||||||
const listUsers = (await this.userListMembershipsRepository.findBy({
|
if (antenna.userListId == null) return false;
|
||||||
userListId: antenna.userListId!,
|
const exists = await this.userListMembershipsRepository.exists({
|
||||||
})).map(x => x.userId);
|
where: {
|
||||||
|
userListId: antenna.userListId,
|
||||||
if (!listUsers.includes(note.userId)) return false;
|
userId: note.userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!exists) return false;
|
||||||
} else if (antenna.src === 'users') {
|
} else if (antenna.src === 'users') {
|
||||||
const accts = antenna.users.map(x => {
|
const accts = antenna.users.map(x => {
|
||||||
const { username, host } = Acct.parse(x);
|
const { username, host } = Acct.parse(x);
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
|
import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
|
||||||
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
||||||
import { UserSearchService } from '@/core/UserSearchService.js';
|
import { UserSearchService } from '@/core/UserSearchService.js';
|
||||||
|
import { WebhookTestService } from '@/core/WebhookTestService.js';
|
||||||
import { AccountMoveService } from './AccountMoveService.js';
|
import { AccountMoveService } from './AccountMoveService.js';
|
||||||
import { AccountUpdateService } from './AccountUpdateService.js';
|
import { AccountUpdateService } from './AccountUpdateService.js';
|
||||||
import { AiService } from './AiService.js';
|
import { AiService } from './AiService.js';
|
||||||
|
@ -49,6 +50,7 @@ import { PollService } from './PollService.js';
|
||||||
import { PushNotificationService } from './PushNotificationService.js';
|
import { PushNotificationService } from './PushNotificationService.js';
|
||||||
import { QueryService } from './QueryService.js';
|
import { QueryService } from './QueryService.js';
|
||||||
import { ReactionService } from './ReactionService.js';
|
import { ReactionService } from './ReactionService.js';
|
||||||
|
import { ReactionsBufferingService } from './ReactionsBufferingService.js';
|
||||||
import { RelayService } from './RelayService.js';
|
import { RelayService } from './RelayService.js';
|
||||||
import { RoleService } from './RoleService.js';
|
import { RoleService } from './RoleService.js';
|
||||||
import { S3Service } from './S3Service.js';
|
import { S3Service } from './S3Service.js';
|
||||||
|
@ -192,6 +194,7 @@ const $ProxyAccountService: Provider = { provide: 'ProxyAccountService', useExis
|
||||||
const $PushNotificationService: Provider = { provide: 'PushNotificationService', useExisting: PushNotificationService };
|
const $PushNotificationService: Provider = { provide: 'PushNotificationService', useExisting: PushNotificationService };
|
||||||
const $QueryService: Provider = { provide: 'QueryService', useExisting: QueryService };
|
const $QueryService: Provider = { provide: 'QueryService', useExisting: QueryService };
|
||||||
const $ReactionService: Provider = { provide: 'ReactionService', useExisting: ReactionService };
|
const $ReactionService: Provider = { provide: 'ReactionService', useExisting: ReactionService };
|
||||||
|
const $ReactionsBufferingService: Provider = { provide: 'ReactionsBufferingService', useExisting: ReactionsBufferingService };
|
||||||
const $RelayService: Provider = { provide: 'RelayService', useExisting: RelayService };
|
const $RelayService: Provider = { provide: 'RelayService', useExisting: RelayService };
|
||||||
const $RoleService: Provider = { provide: 'RoleService', useExisting: RoleService };
|
const $RoleService: Provider = { provide: 'RoleService', useExisting: RoleService };
|
||||||
const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service };
|
const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service };
|
||||||
|
@ -211,6 +214,7 @@ const $UserAuthService: Provider = { provide: 'UserAuthService', useExisting: Us
|
||||||
const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', useExisting: VideoProcessingService };
|
const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', useExisting: VideoProcessingService };
|
||||||
const $UserWebhookService: Provider = { provide: 'UserWebhookService', useExisting: UserWebhookService };
|
const $UserWebhookService: Provider = { provide: 'UserWebhookService', useExisting: UserWebhookService };
|
||||||
const $SystemWebhookService: Provider = { provide: 'SystemWebhookService', useExisting: SystemWebhookService };
|
const $SystemWebhookService: Provider = { provide: 'SystemWebhookService', useExisting: SystemWebhookService };
|
||||||
|
const $WebhookTestService: Provider = { provide: 'WebhookTestService', useExisting: WebhookTestService };
|
||||||
const $UtilityService: Provider = { provide: 'UtilityService', useExisting: UtilityService };
|
const $UtilityService: Provider = { provide: 'UtilityService', useExisting: UtilityService };
|
||||||
const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: FileInfoService };
|
const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: FileInfoService };
|
||||||
const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService };
|
const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService };
|
||||||
|
@ -340,6 +344,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
PushNotificationService,
|
PushNotificationService,
|
||||||
QueryService,
|
QueryService,
|
||||||
ReactionService,
|
ReactionService,
|
||||||
|
ReactionsBufferingService,
|
||||||
RelayService,
|
RelayService,
|
||||||
RoleService,
|
RoleService,
|
||||||
S3Service,
|
S3Service,
|
||||||
|
@ -359,6 +364,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
VideoProcessingService,
|
VideoProcessingService,
|
||||||
UserWebhookService,
|
UserWebhookService,
|
||||||
SystemWebhookService,
|
SystemWebhookService,
|
||||||
|
WebhookTestService,
|
||||||
UtilityService,
|
UtilityService,
|
||||||
FileInfoService,
|
FileInfoService,
|
||||||
SearchService,
|
SearchService,
|
||||||
|
@ -484,6 +490,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$PushNotificationService,
|
$PushNotificationService,
|
||||||
$QueryService,
|
$QueryService,
|
||||||
$ReactionService,
|
$ReactionService,
|
||||||
|
$ReactionsBufferingService,
|
||||||
$RelayService,
|
$RelayService,
|
||||||
$RoleService,
|
$RoleService,
|
||||||
$S3Service,
|
$S3Service,
|
||||||
|
@ -503,6 +510,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$VideoProcessingService,
|
$VideoProcessingService,
|
||||||
$UserWebhookService,
|
$UserWebhookService,
|
||||||
$SystemWebhookService,
|
$SystemWebhookService,
|
||||||
|
$WebhookTestService,
|
||||||
$UtilityService,
|
$UtilityService,
|
||||||
$FileInfoService,
|
$FileInfoService,
|
||||||
$SearchService,
|
$SearchService,
|
||||||
|
@ -629,6 +637,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
PushNotificationService,
|
PushNotificationService,
|
||||||
QueryService,
|
QueryService,
|
||||||
ReactionService,
|
ReactionService,
|
||||||
|
ReactionsBufferingService,
|
||||||
RelayService,
|
RelayService,
|
||||||
RoleService,
|
RoleService,
|
||||||
S3Service,
|
S3Service,
|
||||||
|
@ -648,6 +657,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
VideoProcessingService,
|
VideoProcessingService,
|
||||||
UserWebhookService,
|
UserWebhookService,
|
||||||
SystemWebhookService,
|
SystemWebhookService,
|
||||||
|
WebhookTestService,
|
||||||
UtilityService,
|
UtilityService,
|
||||||
FileInfoService,
|
FileInfoService,
|
||||||
SearchService,
|
SearchService,
|
||||||
|
@ -772,6 +782,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$PushNotificationService,
|
$PushNotificationService,
|
||||||
$QueryService,
|
$QueryService,
|
||||||
$ReactionService,
|
$ReactionService,
|
||||||
|
$ReactionsBufferingService,
|
||||||
$RelayService,
|
$RelayService,
|
||||||
$RoleService,
|
$RoleService,
|
||||||
$S3Service,
|
$S3Service,
|
||||||
|
@ -791,6 +802,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$VideoProcessingService,
|
$VideoProcessingService,
|
||||||
$UserWebhookService,
|
$UserWebhookService,
|
||||||
$SystemWebhookService,
|
$SystemWebhookService,
|
||||||
|
$WebhookTestService,
|
||||||
$UtilityService,
|
$UtilityService,
|
||||||
$FileInfoService,
|
$FileInfoService,
|
||||||
$SearchService,
|
$SearchService,
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
import { URLSearchParams } from 'node:url';
|
import { URLSearchParams } from 'node:url';
|
||||||
import * as nodemailer from 'nodemailer';
|
import * as nodemailer from 'nodemailer';
|
||||||
|
import juice from 'juice';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { validate as validateEmail } from 'deep-email-validator';
|
import { validate as validateEmail } from 'deep-email-validator';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
@ -61,14 +62,7 @@ export class EmailService {
|
||||||
} : undefined,
|
} : undefined,
|
||||||
} as any);
|
} as any);
|
||||||
|
|
||||||
try {
|
const htmlContent = `<!doctype html>
|
||||||
// TODO: htmlサニタイズ
|
|
||||||
const info = await transporter.sendMail({
|
|
||||||
from: meta.email!,
|
|
||||||
to: to,
|
|
||||||
subject: subject,
|
|
||||||
text: text,
|
|
||||||
html: `<!doctype html>
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
@ -147,7 +141,18 @@ export class EmailService {
|
||||||
<a href="${ this.config.url }">${ this.config.host }</a>
|
<a href="${ this.config.url }">${ this.config.host }</a>
|
||||||
</nav>
|
</nav>
|
||||||
</body>
|
</body>
|
||||||
</html>`,
|
</html>`;
|
||||||
|
|
||||||
|
const inlinedHtml = juice(htmlContent);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// TODO: htmlサニタイズ
|
||||||
|
const info = await transporter.sendMail({
|
||||||
|
from: meta.email!,
|
||||||
|
to: to,
|
||||||
|
subject: subject,
|
||||||
|
text: text,
|
||||||
|
html: inlinedHtml,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.info(`Message sent: ${info.messageId}`);
|
this.logger.info(`Message sent: ${info.messageId}`);
|
||||||
|
|
|
@ -87,6 +87,12 @@ export class QueueService {
|
||||||
repeat: { pattern: '*/5 * * * *' },
|
repeat: { pattern: '*/5 * * * *' },
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.systemQueue.add('bakeBufferedReactions', {
|
||||||
|
}, {
|
||||||
|
repeat: { pattern: '0 0 * * *' },
|
||||||
|
removeOnComplete: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -452,10 +458,15 @@ export class QueueService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see UserWebhookDeliverJobData
|
* @see UserWebhookDeliverJobData
|
||||||
* @see WebhookDeliverProcessorService
|
* @see UserWebhookDeliverProcessorService
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public userWebhookDeliver(webhook: MiWebhook, type: typeof webhookEventTypes[number], content: unknown) {
|
public userWebhookDeliver(
|
||||||
|
webhook: MiWebhook,
|
||||||
|
type: typeof webhookEventTypes[number],
|
||||||
|
content: unknown,
|
||||||
|
opts?: { attempts?: number },
|
||||||
|
) {
|
||||||
const data: UserWebhookDeliverJobData = {
|
const data: UserWebhookDeliverJobData = {
|
||||||
type,
|
type,
|
||||||
content,
|
content,
|
||||||
|
@ -468,7 +479,7 @@ export class QueueService {
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.userWebhookDeliverQueue.add(webhook.id, data, {
|
return this.userWebhookDeliverQueue.add(webhook.id, data, {
|
||||||
attempts: 4,
|
attempts: opts?.attempts ?? 4,
|
||||||
backoff: {
|
backoff: {
|
||||||
type: 'custom',
|
type: 'custom',
|
||||||
},
|
},
|
||||||
|
@ -479,10 +490,15 @@ export class QueueService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see SystemWebhookDeliverJobData
|
* @see SystemWebhookDeliverJobData
|
||||||
* @see WebhookDeliverProcessorService
|
* @see SystemWebhookDeliverProcessorService
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public systemWebhookDeliver(webhook: MiSystemWebhook, type: SystemWebhookEventType, content: unknown) {
|
public systemWebhookDeliver(
|
||||||
|
webhook: MiSystemWebhook,
|
||||||
|
type: SystemWebhookEventType,
|
||||||
|
content: unknown,
|
||||||
|
opts?: { attempts?: number },
|
||||||
|
) {
|
||||||
const data: SystemWebhookDeliverJobData = {
|
const data: SystemWebhookDeliverJobData = {
|
||||||
type,
|
type,
|
||||||
content,
|
content,
|
||||||
|
@ -494,7 +510,7 @@ export class QueueService {
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.systemWebhookDeliverQueue.add(webhook.id, data, {
|
return this.systemWebhookDeliverQueue.add(webhook.id, data, {
|
||||||
attempts: 4,
|
attempts: opts?.attempts ?? 4,
|
||||||
backoff: {
|
backoff: {
|
||||||
type: 'custom',
|
type: 'custom',
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import * as Redis from 'ioredis';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository } from '@/models/_.js';
|
import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository } from '@/models/_.js';
|
||||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
|
@ -30,9 +29,10 @@ import { RoleService } from '@/core/RoleService.js';
|
||||||
import { FeaturedService } from '@/core/FeaturedService.js';
|
import { FeaturedService } from '@/core/FeaturedService.js';
|
||||||
import { trackPromise } from '@/misc/promise-tracker.js';
|
import { trackPromise } from '@/misc/promise-tracker.js';
|
||||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||||
|
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
|
||||||
|
import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.js';
|
||||||
|
|
||||||
const FALLBACK = '\u2764';
|
const FALLBACK = '\u2764';
|
||||||
const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
|
|
||||||
|
|
||||||
const legacies: Record<string, string> = {
|
const legacies: Record<string, string> = {
|
||||||
'like': '👍',
|
'like': '👍',
|
||||||
|
@ -71,9 +71,6 @@ const decodeCustomEmojiRegexp = /^:([\w+-]+)(?:@([\w.-]+))?:$/;
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ReactionService {
|
export class ReactionService {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.redis)
|
|
||||||
private redisClient: Redis.Redis,
|
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@ -93,6 +90,7 @@ export class ReactionService {
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
private userBlockingService: UserBlockingService,
|
private userBlockingService: UserBlockingService,
|
||||||
|
private reactionsBufferingService: ReactionsBufferingService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private featuredService: FeaturedService,
|
private featuredService: FeaturedService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
|
@ -174,7 +172,6 @@ export class ReactionService {
|
||||||
reaction,
|
reaction,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create reaction
|
|
||||||
try {
|
try {
|
||||||
await this.noteReactionsRepository.insert(record);
|
await this.noteReactionsRepository.insert(record);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -198,16 +195,20 @@ export class ReactionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment reactions count
|
// Increment reactions count
|
||||||
const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`;
|
if (meta.enableReactionsBuffering) {
|
||||||
await this.notesRepository.createQueryBuilder().update()
|
await this.reactionsBufferingService.create(note.id, user.id, reaction, note.reactionAndUserPairCache);
|
||||||
.set({
|
} else {
|
||||||
reactions: () => sql,
|
const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`;
|
||||||
...(note.reactionAndUserPairCache.length < PER_NOTE_REACTION_USER_PAIR_CACHE_MAX ? {
|
await this.notesRepository.createQueryBuilder().update()
|
||||||
reactionAndUserPairCache: () => `array_append("reactionAndUserPairCache", '${user.id}/${reaction}')`,
|
.set({
|
||||||
} : {}),
|
reactions: () => sql,
|
||||||
})
|
...(note.reactionAndUserPairCache.length < PER_NOTE_REACTION_USER_PAIR_CACHE_MAX ? {
|
||||||
.where('id = :id', { id: note.id })
|
reactionAndUserPairCache: () => `array_append("reactionAndUserPairCache", '${user.id}/${reaction}')`,
|
||||||
.execute();
|
} : {}),
|
||||||
|
})
|
||||||
|
.where('id = :id', { id: note.id })
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
// 30%の確率、セルフではない、3日以内に投稿されたノートの場合ハイライト用ランキング更新
|
// 30%の確率、セルフではない、3日以内に投稿されたノートの場合ハイライト用ランキング更新
|
||||||
if (
|
if (
|
||||||
|
@ -304,15 +305,21 @@ export class ReactionService {
|
||||||
throw new IdentifiableError('60527ec9-b4cb-4a88-a6bd-32d3ad26817d', 'not reacted');
|
throw new IdentifiableError('60527ec9-b4cb-4a88-a6bd-32d3ad26817d', 'not reacted');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const meta = await this.metaService.fetch();
|
||||||
|
|
||||||
// Decrement reactions count
|
// Decrement reactions count
|
||||||
const sql = `jsonb_set("reactions", '{${exist.reaction}}', (COALESCE("reactions"->>'${exist.reaction}', '0')::int - 1)::text::jsonb)`;
|
if (meta.enableReactionsBuffering) {
|
||||||
await this.notesRepository.createQueryBuilder().update()
|
await this.reactionsBufferingService.delete(note.id, user.id, exist.reaction);
|
||||||
.set({
|
} else {
|
||||||
reactions: () => sql,
|
const sql = `jsonb_set("reactions", '{${exist.reaction}}', (COALESCE("reactions"->>'${exist.reaction}', '0')::int - 1)::text::jsonb)`;
|
||||||
reactionAndUserPairCache: () => `array_remove("reactionAndUserPairCache", '${user.id}/${exist.reaction}')`,
|
await this.notesRepository.createQueryBuilder().update()
|
||||||
})
|
.set({
|
||||||
.where('id = :id', { id: note.id })
|
reactions: () => sql,
|
||||||
.execute();
|
reactionAndUserPairCache: () => `array_remove("reactionAndUserPairCache", '${user.id}/${exist.reaction}')`,
|
||||||
|
})
|
||||||
|
.where('id = :id', { id: note.id })
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
this.globalEventService.publishNoteStream(note.id, 'unreacted', {
|
this.globalEventService.publishNoteStream(note.id, 'unreacted', {
|
||||||
reaction: this.decodeReaction(exist.reaction).reaction,
|
reaction: this.decodeReaction(exist.reaction).reaction,
|
||||||
|
|
162
packages/backend/src/core/ReactionsBufferingService.ts
Normal file
162
packages/backend/src/core/ReactionsBufferingService.ts
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import * as Redis from 'ioredis';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import type { MiNote } from '@/models/Note.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import type { MiUser, NotesRepository } from '@/models/_.js';
|
||||||
|
import type { Config } from '@/config.js';
|
||||||
|
import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.js';
|
||||||
|
|
||||||
|
const REDIS_DELTA_PREFIX = 'reactionsBufferDeltas';
|
||||||
|
const REDIS_PAIR_PREFIX = 'reactionsBufferPairs';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ReactionsBufferingService {
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.config)
|
||||||
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.redisForReactions)
|
||||||
|
private redisForReactions: Redis.Redis, // TODO: 専用のRedisインスタンスにする
|
||||||
|
|
||||||
|
@Inject(DI.notesRepository)
|
||||||
|
private notesRepository: NotesRepository,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async create(noteId: MiNote['id'], userId: MiUser['id'], reaction: string, currentPairs: string[]): Promise<void> {
|
||||||
|
const pipeline = this.redisForReactions.pipeline();
|
||||||
|
pipeline.hincrby(`${REDIS_DELTA_PREFIX}:${noteId}`, reaction, 1);
|
||||||
|
for (let i = 0; i < currentPairs.length; i++) {
|
||||||
|
pipeline.zadd(`${REDIS_PAIR_PREFIX}:${noteId}`, i, currentPairs[i]);
|
||||||
|
}
|
||||||
|
pipeline.zadd(`${REDIS_PAIR_PREFIX}:${noteId}`, Date.now(), `${userId}/${reaction}`);
|
||||||
|
pipeline.zremrangebyrank(`${REDIS_PAIR_PREFIX}:${noteId}`, 0, -(PER_NOTE_REACTION_USER_PAIR_CACHE_MAX + 1));
|
||||||
|
await pipeline.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async delete(noteId: MiNote['id'], userId: MiUser['id'], reaction: string): Promise<void> {
|
||||||
|
const pipeline = this.redisForReactions.pipeline();
|
||||||
|
pipeline.hincrby(`${REDIS_DELTA_PREFIX}:${noteId}`, reaction, -1);
|
||||||
|
pipeline.zrem(`${REDIS_PAIR_PREFIX}:${noteId}`, `${userId}/${reaction}`);
|
||||||
|
// TODO: 「消した要素一覧」も持っておかないとcreateされた時に上書きされて復活する
|
||||||
|
await pipeline.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async get(noteId: MiNote['id']): Promise<{
|
||||||
|
deltas: Record<string, number>;
|
||||||
|
pairs: ([MiUser['id'], string])[];
|
||||||
|
}> {
|
||||||
|
const pipeline = this.redisForReactions.pipeline();
|
||||||
|
pipeline.hgetall(`${REDIS_DELTA_PREFIX}:${noteId}`);
|
||||||
|
pipeline.zrange(`${REDIS_PAIR_PREFIX}:${noteId}`, 0, -1);
|
||||||
|
const results = await pipeline.exec();
|
||||||
|
|
||||||
|
const resultDeltas = results![0][1] as Record<string, string>;
|
||||||
|
const resultPairs = results![1][1] as string[];
|
||||||
|
|
||||||
|
const deltas = {} as Record<string, number>;
|
||||||
|
for (const [name, count] of Object.entries(resultDeltas)) {
|
||||||
|
deltas[name] = parseInt(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pairs = resultPairs.map(x => x.split('/') as [MiUser['id'], string]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
deltas,
|
||||||
|
pairs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async getMany(noteIds: MiNote['id'][]): Promise<Map<MiNote['id'], {
|
||||||
|
deltas: Record<string, number>;
|
||||||
|
pairs: ([MiUser['id'], string])[];
|
||||||
|
}>> {
|
||||||
|
const map = new Map<MiNote['id'], {
|
||||||
|
deltas: Record<string, number>;
|
||||||
|
pairs: ([MiUser['id'], string])[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const pipeline = this.redisForReactions.pipeline();
|
||||||
|
for (const noteId of noteIds) {
|
||||||
|
pipeline.hgetall(`${REDIS_DELTA_PREFIX}:${noteId}`);
|
||||||
|
pipeline.zrange(`${REDIS_PAIR_PREFIX}:${noteId}`, 0, -1);
|
||||||
|
}
|
||||||
|
const results = await pipeline.exec();
|
||||||
|
|
||||||
|
const opsForEachNotes = 2;
|
||||||
|
for (let i = 0; i < noteIds.length; i++) {
|
||||||
|
const noteId = noteIds[i];
|
||||||
|
const resultDeltas = results![i * opsForEachNotes][1] as Record<string, string>;
|
||||||
|
const resultPairs = results![i * opsForEachNotes + 1][1] as string[];
|
||||||
|
|
||||||
|
const deltas = {} as Record<string, number>;
|
||||||
|
for (const [name, count] of Object.entries(resultDeltas)) {
|
||||||
|
deltas[name] = parseInt(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pairs = resultPairs.map(x => x.split('/') as [MiUser['id'], string]);
|
||||||
|
|
||||||
|
map.set(noteId, {
|
||||||
|
deltas,
|
||||||
|
pairs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: scanは重い可能性があるので、別途 bufferedNoteIds を直接Redis上に持っておいてもいいかもしれない
|
||||||
|
@bindThis
|
||||||
|
public async bake(): Promise<void> {
|
||||||
|
const bufferedNoteIds = [];
|
||||||
|
let cursor = '0';
|
||||||
|
do {
|
||||||
|
// https://github.com/redis/ioredis#transparent-key-prefixing
|
||||||
|
const result = await this.redisForReactions.scan(
|
||||||
|
cursor,
|
||||||
|
'MATCH',
|
||||||
|
`${this.config.redis.prefix}:${REDIS_DELTA_PREFIX}:*`,
|
||||||
|
'COUNT',
|
||||||
|
'1000');
|
||||||
|
|
||||||
|
cursor = result[0];
|
||||||
|
bufferedNoteIds.push(...result[1].map(x => x.replace(`${this.config.redis.prefix}:${REDIS_DELTA_PREFIX}:`, '')));
|
||||||
|
} while (cursor !== '0');
|
||||||
|
|
||||||
|
const bufferedMap = await this.getMany(bufferedNoteIds);
|
||||||
|
|
||||||
|
// clear
|
||||||
|
const pipeline = this.redisForReactions.pipeline();
|
||||||
|
for (const noteId of bufferedNoteIds) {
|
||||||
|
pipeline.del(`${REDIS_DELTA_PREFIX}:${noteId}`);
|
||||||
|
pipeline.del(`${REDIS_PAIR_PREFIX}:${noteId}`);
|
||||||
|
}
|
||||||
|
await pipeline.exec();
|
||||||
|
|
||||||
|
// TODO: SQL一個にまとめたい
|
||||||
|
for (const [noteId, buffered] of bufferedMap) {
|
||||||
|
const sql = Object.entries(buffered.deltas)
|
||||||
|
.map(([reaction, count]) =>
|
||||||
|
`jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + ${count})::text::jsonb)`)
|
||||||
|
.join(' || ');
|
||||||
|
|
||||||
|
this.notesRepository.createQueryBuilder().update()
|
||||||
|
.set({
|
||||||
|
reactions: () => sql,
|
||||||
|
reactionAndUserPairCache: buffered.pairs.map(x => x.join('/')),
|
||||||
|
})
|
||||||
|
.where('id = :id', { id: noteId })
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -58,6 +58,11 @@ export type RolePolicies = {
|
||||||
userEachUserListsLimit: number;
|
userEachUserListsLimit: number;
|
||||||
rateLimitFactor: number;
|
rateLimitFactor: number;
|
||||||
avatarDecorationLimit: number;
|
avatarDecorationLimit: number;
|
||||||
|
canImportAntennas: boolean;
|
||||||
|
canImportBlocking: boolean;
|
||||||
|
canImportFollowing: boolean;
|
||||||
|
canImportMuting: boolean;
|
||||||
|
canImportUserLists: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_POLICIES: RolePolicies = {
|
export const DEFAULT_POLICIES: RolePolicies = {
|
||||||
|
@ -87,6 +92,11 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
||||||
userEachUserListsLimit: 50,
|
userEachUserListsLimit: 50,
|
||||||
rateLimitFactor: 1,
|
rateLimitFactor: 1,
|
||||||
avatarDecorationLimit: 1,
|
avatarDecorationLimit: 1,
|
||||||
|
canImportAntennas: true,
|
||||||
|
canImportBlocking: true,
|
||||||
|
canImportFollowing: true,
|
||||||
|
canImportMuting: true,
|
||||||
|
canImportUserLists: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -387,6 +397,11 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
userEachUserListsLimit: calc('userEachUserListsLimit', vs => Math.max(...vs)),
|
userEachUserListsLimit: calc('userEachUserListsLimit', vs => Math.max(...vs)),
|
||||||
rateLimitFactor: calc('rateLimitFactor', vs => Math.max(...vs)),
|
rateLimitFactor: calc('rateLimitFactor', vs => Math.max(...vs)),
|
||||||
avatarDecorationLimit: calc('avatarDecorationLimit', vs => Math.max(...vs)),
|
avatarDecorationLimit: calc('avatarDecorationLimit', vs => Math.max(...vs)),
|
||||||
|
canImportAntennas: calc('canImportAntennas', vs => vs.some(v => v === true)),
|
||||||
|
canImportBlocking: calc('canImportBlocking', vs => vs.some(v => v === true)),
|
||||||
|
canImportFollowing: calc('canImportFollowing', vs => vs.some(v => v === true)),
|
||||||
|
canImportMuting: calc('canImportMuting', vs => vs.some(v => v === true)),
|
||||||
|
canImportUserLists: calc('canImportUserLists', vs => vs.some(v => v === true)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ export class SystemWebhookService implements OnApplicationShutdown {
|
||||||
* SystemWebhook の一覧を取得する.
|
* SystemWebhook の一覧を取得する.
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public async fetchSystemWebhooks(params?: {
|
public fetchSystemWebhooks(params?: {
|
||||||
ids?: MiSystemWebhook['id'][];
|
ids?: MiSystemWebhook['id'][];
|
||||||
isActive?: MiSystemWebhook['isActive'];
|
isActive?: MiSystemWebhook['isActive'];
|
||||||
on?: MiSystemWebhook['on'];
|
on?: MiSystemWebhook['on'];
|
||||||
|
@ -165,19 +165,24 @@ export class SystemWebhookService implements OnApplicationShutdown {
|
||||||
/**
|
/**
|
||||||
* SystemWebhook をWebhook配送キューに追加する
|
* SystemWebhook をWebhook配送キューに追加する
|
||||||
* @see QueueService.systemWebhookDeliver
|
* @see QueueService.systemWebhookDeliver
|
||||||
|
* // TODO: contentの型を厳格化する
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public async enqueueSystemWebhook(webhook: MiSystemWebhook | MiSystemWebhook['id'], type: SystemWebhookEventType, content: unknown) {
|
public async enqueueSystemWebhook<T extends SystemWebhookEventType>(
|
||||||
|
webhook: MiSystemWebhook | MiSystemWebhook['id'],
|
||||||
|
type: T,
|
||||||
|
content: unknown,
|
||||||
|
) {
|
||||||
const webhookEntity = typeof webhook === 'string'
|
const webhookEntity = typeof webhook === 'string'
|
||||||
? (await this.fetchActiveSystemWebhooks()).find(a => a.id === webhook)
|
? (await this.fetchActiveSystemWebhooks()).find(a => a.id === webhook)
|
||||||
: webhook;
|
: webhook;
|
||||||
if (!webhookEntity || !webhookEntity.isActive) {
|
if (!webhookEntity || !webhookEntity.isActive) {
|
||||||
this.logger.info(`Webhook is not active or not found : ${webhook}`);
|
this.logger.info(`SystemWebhook is not active or not found : ${webhook}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!webhookEntity.on.includes(type)) {
|
if (!webhookEntity.on.includes(type)) {
|
||||||
this.logger.info(`Webhook ${webhookEntity.id} is not listening to ${type}`);
|
this.logger.info(`SystemWebhook ${webhookEntity.id} is not listening to ${type}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
import type { WebhooksRepository } from '@/models/_.js';
|
import { type WebhooksRepository } from '@/models/_.js';
|
||||||
import type { MiWebhook } from '@/models/Webhook.js';
|
import { MiWebhook } from '@/models/Webhook.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { GlobalEvents } from '@/core/GlobalEventService.js';
|
import { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||||
|
@ -38,6 +38,31 @@ export class UserWebhookService implements OnApplicationShutdown {
|
||||||
return this.activeWebhooks;
|
return this.activeWebhooks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UserWebhook の一覧を取得する.
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
|
public fetchWebhooks(params?: {
|
||||||
|
ids?: MiWebhook['id'][];
|
||||||
|
isActive?: MiWebhook['active'];
|
||||||
|
on?: MiWebhook['on'];
|
||||||
|
}): Promise<MiWebhook[]> {
|
||||||
|
const query = this.webhooksRepository.createQueryBuilder('webhook');
|
||||||
|
if (params) {
|
||||||
|
if (params.ids && params.ids.length > 0) {
|
||||||
|
query.andWhere('webhook.id IN (:...ids)', { ids: params.ids });
|
||||||
|
}
|
||||||
|
if (params.isActive !== undefined) {
|
||||||
|
query.andWhere('webhook.active = :isActive', { isActive: params.isActive });
|
||||||
|
}
|
||||||
|
if (params.on && params.on.length > 0) {
|
||||||
|
query.andWhere(':on <@ webhook.on', { on: params.on });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return query.getMany();
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async onMessage(_: string, data: string): Promise<void> {
|
private async onMessage(_: string, data: string): Promise<void> {
|
||||||
const obj = JSON.parse(data);
|
const obj = JSON.parse(data);
|
||||||
|
|
434
packages/backend/src/core/WebhookTestService.ts
Normal file
434
packages/backend/src/core/WebhookTestService.ts
Normal file
|
@ -0,0 +1,434 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { MiAbuseUserReport, MiNote, MiUser, MiWebhook } from '@/models/_.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { MiSystemWebhook, type SystemWebhookEventType } from '@/models/SystemWebhook.js';
|
||||||
|
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
||||||
|
import { Packed } from '@/misc/json-schema.js';
|
||||||
|
import { type WebhookEventTypes } from '@/models/Webhook.js';
|
||||||
|
import { UserWebhookService } from '@/core/UserWebhookService.js';
|
||||||
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
|
|
||||||
|
const oneDayMillis = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
function generateAbuseReport(override?: Partial<MiAbuseUserReport>): MiAbuseUserReport {
|
||||||
|
return {
|
||||||
|
id: 'dummy-abuse-report1',
|
||||||
|
targetUserId: 'dummy-target-user',
|
||||||
|
targetUser: null,
|
||||||
|
reporterId: 'dummy-reporter-user',
|
||||||
|
reporter: null,
|
||||||
|
assigneeId: null,
|
||||||
|
assignee: null,
|
||||||
|
resolved: false,
|
||||||
|
forwarded: false,
|
||||||
|
comment: 'This is a dummy report for testing purposes.',
|
||||||
|
targetUserHost: null,
|
||||||
|
reporterHost: null,
|
||||||
|
...override,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateDummyUser(override?: Partial<MiUser>): MiUser {
|
||||||
|
return {
|
||||||
|
id: 'dummy-user-1',
|
||||||
|
updatedAt: new Date(Date.now() - oneDayMillis * 7),
|
||||||
|
lastFetchedAt: new Date(Date.now() - oneDayMillis * 5),
|
||||||
|
lastActiveDate: new Date(Date.now() - oneDayMillis * 3),
|
||||||
|
hideOnlineStatus: false,
|
||||||
|
username: 'dummy1',
|
||||||
|
usernameLower: 'dummy1',
|
||||||
|
name: 'DummyUser1',
|
||||||
|
followersCount: 10,
|
||||||
|
followingCount: 5,
|
||||||
|
movedToUri: null,
|
||||||
|
movedAt: null,
|
||||||
|
alsoKnownAs: null,
|
||||||
|
notesCount: 30,
|
||||||
|
avatarId: null,
|
||||||
|
avatar: null,
|
||||||
|
bannerId: null,
|
||||||
|
banner: null,
|
||||||
|
avatarUrl: null,
|
||||||
|
bannerUrl: null,
|
||||||
|
avatarBlurhash: null,
|
||||||
|
bannerBlurhash: null,
|
||||||
|
avatarDecorations: [],
|
||||||
|
tags: [],
|
||||||
|
isSuspended: false,
|
||||||
|
isLocked: false,
|
||||||
|
isBot: false,
|
||||||
|
isCat: true,
|
||||||
|
isRoot: false,
|
||||||
|
isExplorable: true,
|
||||||
|
isHibernated: false,
|
||||||
|
isDeleted: false,
|
||||||
|
emojis: [],
|
||||||
|
host: null,
|
||||||
|
inbox: null,
|
||||||
|
sharedInbox: null,
|
||||||
|
featured: null,
|
||||||
|
uri: null,
|
||||||
|
followersUri: null,
|
||||||
|
token: null,
|
||||||
|
...override,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateDummyNote(override?: Partial<MiNote>): MiNote {
|
||||||
|
return {
|
||||||
|
id: 'dummy-note-1',
|
||||||
|
replyId: null,
|
||||||
|
reply: null,
|
||||||
|
renoteId: null,
|
||||||
|
renote: null,
|
||||||
|
threadId: null,
|
||||||
|
text: 'This is a dummy note for testing purposes.',
|
||||||
|
name: null,
|
||||||
|
cw: null,
|
||||||
|
userId: 'dummy-user-1',
|
||||||
|
user: null,
|
||||||
|
localOnly: true,
|
||||||
|
reactionAcceptance: 'likeOnly',
|
||||||
|
renoteCount: 10,
|
||||||
|
repliesCount: 5,
|
||||||
|
clippedCount: 0,
|
||||||
|
reactions: {},
|
||||||
|
visibility: 'public',
|
||||||
|
uri: null,
|
||||||
|
url: null,
|
||||||
|
fileIds: [],
|
||||||
|
attachedFileTypes: [],
|
||||||
|
visibleUserIds: [],
|
||||||
|
mentions: [],
|
||||||
|
mentionedRemoteUsers: '[]',
|
||||||
|
reactionAndUserPairCache: [],
|
||||||
|
emojis: [],
|
||||||
|
tags: [],
|
||||||
|
hasPoll: false,
|
||||||
|
channelId: null,
|
||||||
|
channel: null,
|
||||||
|
userHost: null,
|
||||||
|
replyUserId: null,
|
||||||
|
replyUserHost: null,
|
||||||
|
renoteUserId: null,
|
||||||
|
renoteUserHost: null,
|
||||||
|
...override,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toPackedNote(note: MiNote, detail = true, override?: Packed<'Note'>): Packed<'Note'> {
|
||||||
|
return {
|
||||||
|
id: note.id,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
deletedAt: null,
|
||||||
|
text: note.text,
|
||||||
|
cw: note.cw,
|
||||||
|
userId: note.userId,
|
||||||
|
user: toPackedUserLite(note.user ?? generateDummyUser()),
|
||||||
|
replyId: note.replyId,
|
||||||
|
renoteId: note.renoteId,
|
||||||
|
isHidden: false,
|
||||||
|
visibility: note.visibility,
|
||||||
|
mentions: note.mentions,
|
||||||
|
visibleUserIds: note.visibleUserIds,
|
||||||
|
fileIds: note.fileIds,
|
||||||
|
files: [],
|
||||||
|
tags: note.tags,
|
||||||
|
poll: null,
|
||||||
|
emojis: note.emojis,
|
||||||
|
channelId: note.channelId,
|
||||||
|
channel: note.channel,
|
||||||
|
localOnly: note.localOnly,
|
||||||
|
reactionAcceptance: note.reactionAcceptance,
|
||||||
|
reactionEmojis: {},
|
||||||
|
reactions: {},
|
||||||
|
reactionCount: 0,
|
||||||
|
renoteCount: note.renoteCount,
|
||||||
|
repliesCount: note.repliesCount,
|
||||||
|
uri: note.uri ?? undefined,
|
||||||
|
url: note.url ?? undefined,
|
||||||
|
reactionAndUserPairCache: note.reactionAndUserPairCache,
|
||||||
|
...(detail ? {
|
||||||
|
clippedCount: note.clippedCount,
|
||||||
|
reply: note.reply ? toPackedNote(note.reply, false) : null,
|
||||||
|
renote: note.renote ? toPackedNote(note.renote, true) : null,
|
||||||
|
myReaction: null,
|
||||||
|
} : {}),
|
||||||
|
...override,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toPackedUserLite(user: MiUser, override?: Packed<'UserLite'>): Packed<'UserLite'> {
|
||||||
|
return {
|
||||||
|
id: user.id,
|
||||||
|
name: user.name,
|
||||||
|
username: user.username,
|
||||||
|
host: user.host,
|
||||||
|
avatarUrl: user.avatarUrl,
|
||||||
|
avatarBlurhash: user.avatarBlurhash,
|
||||||
|
avatarDecorations: user.avatarDecorations.map(it => ({
|
||||||
|
id: it.id,
|
||||||
|
angle: it.angle,
|
||||||
|
flipH: it.flipH,
|
||||||
|
url: 'https://example.com/dummy-image001.png',
|
||||||
|
offsetX: it.offsetX,
|
||||||
|
offsetY: it.offsetY,
|
||||||
|
})),
|
||||||
|
isBot: user.isBot,
|
||||||
|
isCat: user.isCat,
|
||||||
|
emojis: user.emojis,
|
||||||
|
onlineStatus: 'active',
|
||||||
|
badgeRoles: [],
|
||||||
|
...override,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toPackedUserDetailedNotMe(user: MiUser, override?: Packed<'UserDetailedNotMe'>): Packed<'UserDetailedNotMe'> {
|
||||||
|
return {
|
||||||
|
...toPackedUserLite(user),
|
||||||
|
url: null,
|
||||||
|
uri: null,
|
||||||
|
movedTo: null,
|
||||||
|
alsoKnownAs: [],
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
updatedAt: user.updatedAt?.toISOString() ?? null,
|
||||||
|
lastFetchedAt: user.lastFetchedAt?.toISOString() ?? null,
|
||||||
|
bannerUrl: user.bannerUrl,
|
||||||
|
bannerBlurhash: user.bannerBlurhash,
|
||||||
|
isLocked: user.isLocked,
|
||||||
|
isSilenced: false,
|
||||||
|
isSuspended: user.isSuspended,
|
||||||
|
description: null,
|
||||||
|
location: null,
|
||||||
|
birthday: null,
|
||||||
|
lang: null,
|
||||||
|
fields: [],
|
||||||
|
verifiedLinks: [],
|
||||||
|
followersCount: user.followersCount,
|
||||||
|
followingCount: user.followingCount,
|
||||||
|
notesCount: user.notesCount,
|
||||||
|
pinnedNoteIds: [],
|
||||||
|
pinnedNotes: [],
|
||||||
|
pinnedPageId: null,
|
||||||
|
pinnedPage: null,
|
||||||
|
publicReactions: true,
|
||||||
|
followersVisibility: 'public',
|
||||||
|
followingVisibility: 'public',
|
||||||
|
twoFactorEnabled: false,
|
||||||
|
usePasswordLessLogin: false,
|
||||||
|
securityKeys: false,
|
||||||
|
roles: [],
|
||||||
|
memo: null,
|
||||||
|
moderationNote: undefined,
|
||||||
|
isFollowing: false,
|
||||||
|
isFollowed: false,
|
||||||
|
hasPendingFollowRequestFromYou: false,
|
||||||
|
hasPendingFollowRequestToYou: false,
|
||||||
|
isBlocking: false,
|
||||||
|
isBlocked: false,
|
||||||
|
isMuted: false,
|
||||||
|
isRenoteMuted: false,
|
||||||
|
notify: 'none',
|
||||||
|
withReplies: true,
|
||||||
|
...override,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const dummyUser1 = generateDummyUser();
|
||||||
|
const dummyUser2 = generateDummyUser({
|
||||||
|
id: 'dummy-user-2',
|
||||||
|
updatedAt: new Date(Date.now() - oneDayMillis * 30),
|
||||||
|
lastFetchedAt: new Date(Date.now() - oneDayMillis),
|
||||||
|
lastActiveDate: new Date(Date.now() - oneDayMillis),
|
||||||
|
username: 'dummy2',
|
||||||
|
usernameLower: 'dummy2',
|
||||||
|
name: 'DummyUser2',
|
||||||
|
followersCount: 40,
|
||||||
|
followingCount: 50,
|
||||||
|
notesCount: 900,
|
||||||
|
});
|
||||||
|
const dummyUser3 = generateDummyUser({
|
||||||
|
id: 'dummy-user-3',
|
||||||
|
updatedAt: new Date(Date.now() - oneDayMillis * 15),
|
||||||
|
lastFetchedAt: new Date(Date.now() - oneDayMillis * 2),
|
||||||
|
lastActiveDate: new Date(Date.now() - oneDayMillis * 2),
|
||||||
|
username: 'dummy3',
|
||||||
|
usernameLower: 'dummy3',
|
||||||
|
name: 'DummyUser3',
|
||||||
|
followersCount: 60,
|
||||||
|
followingCount: 70,
|
||||||
|
notesCount: 15900,
|
||||||
|
});
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WebhookTestService {
|
||||||
|
public static NoSuchWebhookError = class extends Error {};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private userWebhookService: UserWebhookService,
|
||||||
|
private systemWebhookService: SystemWebhookService,
|
||||||
|
private queueService: QueueService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UserWebhookのテスト送信を行う.
|
||||||
|
* 送信されるペイロードはいずれもダミーの値で、実際にはデータベース上に存在しない.
|
||||||
|
*
|
||||||
|
* また、この関数経由で送信されるWebhookは以下の設定を無視する.
|
||||||
|
* - Webhookそのものの有効・無効設定(active)
|
||||||
|
* - 送信対象イベント(on)に関する設定
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
|
public async testUserWebhook(
|
||||||
|
params: {
|
||||||
|
webhookId: MiWebhook['id'],
|
||||||
|
type: WebhookEventTypes,
|
||||||
|
override?: Partial<Omit<MiWebhook, 'id'>>,
|
||||||
|
},
|
||||||
|
sender: MiUser | null,
|
||||||
|
) {
|
||||||
|
const webhooks = await this.userWebhookService.fetchWebhooks({ ids: [params.webhookId] })
|
||||||
|
.then(it => it.filter(it => it.userId === sender?.id));
|
||||||
|
if (webhooks.length === 0) {
|
||||||
|
throw new WebhookTestService.NoSuchWebhookError();
|
||||||
|
}
|
||||||
|
|
||||||
|
const webhook = webhooks[0];
|
||||||
|
const send = (contents: unknown) => {
|
||||||
|
const merged = {
|
||||||
|
...webhook,
|
||||||
|
...params.override,
|
||||||
|
};
|
||||||
|
|
||||||
|
// テスト目的なのでUserWebhookServiceの機能を経由せず直接キューに追加する(チェック処理などをスキップする意図).
|
||||||
|
// また、Jobの試行回数も1回だけ.
|
||||||
|
this.queueService.userWebhookDeliver(merged, params.type, contents, { attempts: 1 });
|
||||||
|
};
|
||||||
|
|
||||||
|
const dummyNote1 = generateDummyNote({
|
||||||
|
userId: dummyUser1.id,
|
||||||
|
user: dummyUser1,
|
||||||
|
});
|
||||||
|
const dummyReply1 = generateDummyNote({
|
||||||
|
id: 'dummy-reply-1',
|
||||||
|
replyId: dummyNote1.id,
|
||||||
|
reply: dummyNote1,
|
||||||
|
userId: dummyUser1.id,
|
||||||
|
user: dummyUser1,
|
||||||
|
});
|
||||||
|
const dummyRenote1 = generateDummyNote({
|
||||||
|
id: 'dummy-renote-1',
|
||||||
|
renoteId: dummyNote1.id,
|
||||||
|
renote: dummyNote1,
|
||||||
|
userId: dummyUser2.id,
|
||||||
|
user: dummyUser2,
|
||||||
|
text: null,
|
||||||
|
});
|
||||||
|
const dummyMention1 = generateDummyNote({
|
||||||
|
id: 'dummy-mention-1',
|
||||||
|
userId: dummyUser1.id,
|
||||||
|
user: dummyUser1,
|
||||||
|
text: `@${dummyUser2.username} This is a mention to you.`,
|
||||||
|
mentions: [dummyUser2.id],
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (params.type) {
|
||||||
|
case 'note': {
|
||||||
|
send(toPackedNote(dummyNote1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'reply': {
|
||||||
|
send(toPackedNote(dummyReply1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'renote': {
|
||||||
|
send(toPackedNote(dummyRenote1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'mention': {
|
||||||
|
send(toPackedNote(dummyMention1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'follow': {
|
||||||
|
send(toPackedUserDetailedNotMe(dummyUser1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'followed': {
|
||||||
|
send(toPackedUserLite(dummyUser2));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'unfollow': {
|
||||||
|
send(toPackedUserDetailedNotMe(dummyUser3));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SystemWebhookのテスト送信を行う.
|
||||||
|
* 送信されるペイロードはいずれもダミーの値で、実際にはデータベース上に存在しない.
|
||||||
|
*
|
||||||
|
* また、この関数経由で送信されるWebhookは以下の設定を無視する.
|
||||||
|
* - Webhookそのものの有効・無効設定(isActive)
|
||||||
|
* - 送信対象イベント(on)に関する設定
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
|
public async testSystemWebhook(
|
||||||
|
params: {
|
||||||
|
webhookId: MiSystemWebhook['id'],
|
||||||
|
type: SystemWebhookEventType,
|
||||||
|
override?: Partial<Omit<MiSystemWebhook, 'id'>>,
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const webhooks = await this.systemWebhookService.fetchSystemWebhooks({ ids: [params.webhookId] });
|
||||||
|
if (webhooks.length === 0) {
|
||||||
|
throw new WebhookTestService.NoSuchWebhookError();
|
||||||
|
}
|
||||||
|
|
||||||
|
const webhook = webhooks[0];
|
||||||
|
const send = (contents: unknown) => {
|
||||||
|
const merged = {
|
||||||
|
...webhook,
|
||||||
|
...params.override,
|
||||||
|
};
|
||||||
|
|
||||||
|
// テスト目的なのでSystemWebhookServiceの機能を経由せず直接キューに追加する(チェック処理などをスキップする意図).
|
||||||
|
// また、Jobの試行回数も1回だけ.
|
||||||
|
this.queueService.systemWebhookDeliver(merged, params.type, contents, { attempts: 1 });
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (params.type) {
|
||||||
|
case 'abuseReport': {
|
||||||
|
send(generateAbuseReport({
|
||||||
|
targetUserId: dummyUser1.id,
|
||||||
|
targetUser: dummyUser1,
|
||||||
|
reporterId: dummyUser2.id,
|
||||||
|
reporter: dummyUser2,
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'abuseReportResolved': {
|
||||||
|
send(generateAbuseReport({
|
||||||
|
targetUserId: dummyUser1.id,
|
||||||
|
targetUser: dummyUser1,
|
||||||
|
reporterId: dummyUser2.id,
|
||||||
|
reporter: dummyUser2,
|
||||||
|
assigneeId: dummyUser3.id,
|
||||||
|
assignee: dummyUser3,
|
||||||
|
resolved: true,
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'userCreated': {
|
||||||
|
send(toPackedUserLite(dummyUser1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,24 +11,39 @@ import type { Packed } from '@/misc/json-schema.js';
|
||||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||||
import type { MiUser } from '@/models/User.js';
|
import type { MiUser } from '@/models/User.js';
|
||||||
import type { MiNote } from '@/models/Note.js';
|
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 type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository } from '@/models/_.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { DebounceLoader } from '@/misc/loader.js';
|
import { DebounceLoader } from '@/misc/loader.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
|
||||||
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
import type { OnModuleInit } from '@nestjs/common';
|
import type { OnModuleInit } from '@nestjs/common';
|
||||||
import type { CustomEmojiService } from '../CustomEmojiService.js';
|
import type { CustomEmojiService } from '../CustomEmojiService.js';
|
||||||
import type { ReactionService } from '../ReactionService.js';
|
import type { ReactionService } from '../ReactionService.js';
|
||||||
import type { UserEntityService } from './UserEntityService.js';
|
import type { UserEntityService } from './UserEntityService.js';
|
||||||
import type { DriveFileEntityService } from './DriveFileEntityService.js';
|
import type { DriveFileEntityService } from './DriveFileEntityService.js';
|
||||||
|
|
||||||
|
function mergeReactions(src: Record<string, number>, delta: Record<string, number>) {
|
||||||
|
const reactions = { ...src };
|
||||||
|
for (const [name, count] of Object.entries(delta)) {
|
||||||
|
if (reactions[name] != null) {
|
||||||
|
reactions[name] += count;
|
||||||
|
} else {
|
||||||
|
reactions[name] = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reactions;
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NoteEntityService implements OnModuleInit {
|
export class NoteEntityService implements OnModuleInit {
|
||||||
private userEntityService: UserEntityService;
|
private userEntityService: UserEntityService;
|
||||||
private driveFileEntityService: DriveFileEntityService;
|
private driveFileEntityService: DriveFileEntityService;
|
||||||
private customEmojiService: CustomEmojiService;
|
private customEmojiService: CustomEmojiService;
|
||||||
private reactionService: ReactionService;
|
private reactionService: ReactionService;
|
||||||
|
private reactionsBufferingService: ReactionsBufferingService;
|
||||||
private idService: IdService;
|
private idService: IdService;
|
||||||
|
private metaService: MetaService;
|
||||||
private noteLoader = new DebounceLoader(this.findNoteOrFail);
|
private noteLoader = new DebounceLoader(this.findNoteOrFail);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -59,6 +74,9 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
//private driveFileEntityService: DriveFileEntityService,
|
//private driveFileEntityService: DriveFileEntityService,
|
||||||
//private customEmojiService: CustomEmojiService,
|
//private customEmojiService: CustomEmojiService,
|
||||||
//private reactionService: ReactionService,
|
//private reactionService: ReactionService,
|
||||||
|
//private reactionsBufferingService: ReactionsBufferingService,
|
||||||
|
//private idService: IdService,
|
||||||
|
//private metaService: MetaService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +85,9 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService');
|
this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService');
|
||||||
this.customEmojiService = this.moduleRef.get('CustomEmojiService');
|
this.customEmojiService = this.moduleRef.get('CustomEmojiService');
|
||||||
this.reactionService = this.moduleRef.get('ReactionService');
|
this.reactionService = this.moduleRef.get('ReactionService');
|
||||||
|
this.reactionsBufferingService = this.moduleRef.get('ReactionsBufferingService');
|
||||||
this.idService = this.moduleRef.get('IdService');
|
this.idService = this.moduleRef.get('IdService');
|
||||||
|
this.metaService = this.moduleRef.get('MetaService');
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -287,6 +307,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
skipHide?: boolean;
|
skipHide?: boolean;
|
||||||
withReactionAndUserPairCache?: boolean;
|
withReactionAndUserPairCache?: boolean;
|
||||||
_hint_?: {
|
_hint_?: {
|
||||||
|
bufferdReactions: Map<MiNote['id'], { deltas: Record<string, number>; pairs: ([MiUser['id'], string])[] }> | null;
|
||||||
myReactions: Map<MiNote['id'], string | null>;
|
myReactions: Map<MiNote['id'], string | null>;
|
||||||
packedFiles: Map<MiNote['fileIds'][number], Packed<'DriveFile'> | null>;
|
packedFiles: Map<MiNote['fileIds'][number], Packed<'DriveFile'> | null>;
|
||||||
packedUsers: Map<MiUser['id'], Packed<'UserLite'>>
|
packedUsers: Map<MiUser['id'], Packed<'UserLite'>>
|
||||||
|
@ -303,6 +324,22 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
const note = typeof src === 'object' ? src : await this.noteLoader.load(src);
|
const note = typeof src === 'object' ? src : await this.noteLoader.load(src);
|
||||||
const host = note.userHost;
|
const host = note.userHost;
|
||||||
|
|
||||||
|
const meta = await this.metaService.fetch();
|
||||||
|
|
||||||
|
const bufferdReactions = opts._hint_?.bufferdReactions != null
|
||||||
|
? (opts._hint_.bufferdReactions.get(note.id) ?? { deltas: {}, pairs: [] })
|
||||||
|
: meta.enableReactionsBuffering
|
||||||
|
? await this.reactionsBufferingService.get(note.id)
|
||||||
|
: { deltas: {}, pairs: [] };
|
||||||
|
const reactions = mergeReactions(note.reactions, bufferdReactions.deltas ?? {});
|
||||||
|
for (const [name, count] of Object.entries(reactions)) {
|
||||||
|
if (count <= 0) {
|
||||||
|
delete reactions[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const reactionAndUserPairCache = note.reactionAndUserPairCache.concat(bufferdReactions.pairs.map(x => x.join('/')));
|
||||||
|
|
||||||
let text = note.text;
|
let text = note.text;
|
||||||
|
|
||||||
if (note.name && (note.url ?? note.uri)) {
|
if (note.name && (note.url ?? note.uri)) {
|
||||||
|
@ -315,7 +352,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
: await this.channelsRepository.findOneBy({ id: note.channelId })
|
: await this.channelsRepository.findOneBy({ id: note.channelId })
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const reactionEmojiNames = Object.keys(note.reactions)
|
const reactionEmojiNames = Object.keys(reactions)
|
||||||
.filter(x => x.startsWith(':') && x.includes('@') && !x.includes('@.')) // リモートカスタム絵文字のみ
|
.filter(x => x.startsWith(':') && x.includes('@') && !x.includes('@.')) // リモートカスタム絵文字のみ
|
||||||
.map(x => this.reactionService.decodeReaction(x).reaction.replaceAll(':', ''));
|
.map(x => this.reactionService.decodeReaction(x).reaction.replaceAll(':', ''));
|
||||||
const packedFiles = options?._hint_?.packedFiles;
|
const packedFiles = options?._hint_?.packedFiles;
|
||||||
|
@ -334,10 +371,10 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined,
|
visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined,
|
||||||
renoteCount: note.renoteCount,
|
renoteCount: note.renoteCount,
|
||||||
repliesCount: note.repliesCount,
|
repliesCount: note.repliesCount,
|
||||||
reactionCount: Object.values(note.reactions).reduce((a, b) => a + b, 0),
|
reactionCount: Object.values(reactions).reduce((a, b) => a + b, 0),
|
||||||
reactions: this.reactionService.convertLegacyReactions(note.reactions),
|
reactions: reactions,
|
||||||
reactionEmojis: this.customEmojiService.populateEmojis(reactionEmojiNames, host),
|
reactionEmojis: this.customEmojiService.populateEmojis(reactionEmojiNames, host),
|
||||||
reactionAndUserPairCache: opts.withReactionAndUserPairCache ? note.reactionAndUserPairCache : undefined,
|
reactionAndUserPairCache: opts.withReactionAndUserPairCache ? reactionAndUserPairCache : undefined,
|
||||||
emojis: host != null ? this.customEmojiService.populateEmojis(note.emojis, host) : undefined,
|
emojis: host != null ? this.customEmojiService.populateEmojis(note.emojis, host) : undefined,
|
||||||
tags: note.tags.length > 0 ? note.tags : undefined,
|
tags: note.tags.length > 0 ? note.tags : undefined,
|
||||||
fileIds: note.fileIds,
|
fileIds: note.fileIds,
|
||||||
|
@ -376,8 +413,12 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
|
|
||||||
poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
|
poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
|
||||||
|
|
||||||
...(meId && Object.keys(note.reactions).length > 0 ? {
|
...(meId && Object.keys(reactions).length > 0 ? {
|
||||||
myReaction: this.populateMyReaction(note, meId, options?._hint_),
|
myReaction: this.populateMyReaction({
|
||||||
|
id: note.id,
|
||||||
|
reactions: reactions,
|
||||||
|
reactionAndUserPairCache: reactionAndUserPairCache,
|
||||||
|
}, meId, options?._hint_),
|
||||||
} : {}),
|
} : {}),
|
||||||
} : {}),
|
} : {}),
|
||||||
});
|
});
|
||||||
|
@ -400,6 +441,10 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
) {
|
) {
|
||||||
if (notes.length === 0) return [];
|
if (notes.length === 0) return [];
|
||||||
|
|
||||||
|
const meta = await this.metaService.fetch();
|
||||||
|
|
||||||
|
const bufferdReactions = meta.enableReactionsBuffering ? await this.reactionsBufferingService.getMany(notes.map(x => x.id)) : null;
|
||||||
|
|
||||||
const meId = me ? me.id : null;
|
const meId = me ? me.id : null;
|
||||||
const myReactionsMap = new Map<MiNote['id'], string | null>();
|
const myReactionsMap = new Map<MiNote['id'], string | null>();
|
||||||
if (meId) {
|
if (meId) {
|
||||||
|
@ -410,23 +455,33 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
|
|
||||||
for (const note of notes) {
|
for (const note of notes) {
|
||||||
if (note.renote && (note.text == null && note.fileIds.length === 0)) { // pure renote
|
if (note.renote && (note.text == null && note.fileIds.length === 0)) { // pure renote
|
||||||
const reactionsCount = Object.values(note.renote.reactions).reduce((a, b) => a + b, 0);
|
const reactionsCount = Object.values(mergeReactions(note.renote.reactions, bufferdReactions?.get(note.renote.id)?.deltas ?? {})).reduce((a, b) => a + b, 0);
|
||||||
if (reactionsCount === 0) {
|
if (reactionsCount === 0) {
|
||||||
myReactionsMap.set(note.renote.id, null);
|
myReactionsMap.set(note.renote.id, null);
|
||||||
} else if (reactionsCount <= note.renote.reactionAndUserPairCache.length) {
|
} else if (reactionsCount <= note.renote.reactionAndUserPairCache.length + (bufferdReactions?.get(note.renote.id)?.pairs.length ?? 0)) {
|
||||||
const pair = note.renote.reactionAndUserPairCache.find(p => p.startsWith(meId));
|
const pairInBuffer = bufferdReactions?.get(note.renote.id)?.pairs.find(p => p[0] === meId);
|
||||||
myReactionsMap.set(note.renote.id, pair ? pair.split('/')[1] : null);
|
if (pairInBuffer) {
|
||||||
|
myReactionsMap.set(note.renote.id, pairInBuffer[1]);
|
||||||
|
} else {
|
||||||
|
const pair = note.renote.reactionAndUserPairCache.find(p => p.startsWith(meId));
|
||||||
|
myReactionsMap.set(note.renote.id, pair ? pair.split('/')[1] : null);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
idsNeedFetchMyReaction.add(note.renote.id);
|
idsNeedFetchMyReaction.add(note.renote.id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (note.id < oldId) {
|
if (note.id < oldId) {
|
||||||
const reactionsCount = Object.values(note.reactions).reduce((a, b) => a + b, 0);
|
const reactionsCount = Object.values(mergeReactions(note.reactions, bufferdReactions?.get(note.id)?.deltas ?? {})).reduce((a, b) => a + b, 0);
|
||||||
if (reactionsCount === 0) {
|
if (reactionsCount === 0) {
|
||||||
myReactionsMap.set(note.id, null);
|
myReactionsMap.set(note.id, null);
|
||||||
} else if (reactionsCount <= note.reactionAndUserPairCache.length) {
|
} else if (reactionsCount <= note.reactionAndUserPairCache.length + (bufferdReactions?.get(note.id)?.pairs.length ?? 0)) {
|
||||||
const pair = note.reactionAndUserPairCache.find(p => p.startsWith(meId));
|
const pairInBuffer = bufferdReactions?.get(note.id)?.pairs.find(p => p[0] === meId);
|
||||||
myReactionsMap.set(note.id, pair ? pair.split('/')[1] : null);
|
if (pairInBuffer) {
|
||||||
|
myReactionsMap.set(note.id, pairInBuffer[1]);
|
||||||
|
} else {
|
||||||
|
const pair = note.reactionAndUserPairCache.find(p => p.startsWith(meId));
|
||||||
|
myReactionsMap.set(note.id, pair ? pair.split('/')[1] : null);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
idsNeedFetchMyReaction.add(note.id);
|
idsNeedFetchMyReaction.add(note.id);
|
||||||
}
|
}
|
||||||
|
@ -461,6 +516,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
return await Promise.all(notes.map(n => this.pack(n, me, {
|
return await Promise.all(notes.map(n => this.pack(n, me, {
|
||||||
...options,
|
...options,
|
||||||
_hint_: {
|
_hint_: {
|
||||||
|
bufferdReactions,
|
||||||
myReactions: myReactionsMap,
|
myReactions: myReactionsMap,
|
||||||
packedFiles,
|
packedFiles,
|
||||||
packedUsers,
|
packedUsers,
|
||||||
|
|
|
@ -11,6 +11,7 @@ export const DI = {
|
||||||
redisForPub: Symbol('redisForPub'),
|
redisForPub: Symbol('redisForPub'),
|
||||||
redisForSub: Symbol('redisForSub'),
|
redisForSub: Symbol('redisForSub'),
|
||||||
redisForTimelines: Symbol('redisForTimelines'),
|
redisForTimelines: Symbol('redisForTimelines'),
|
||||||
|
redisForReactions: Symbol('redisForReactions'),
|
||||||
|
|
||||||
//#region Repositories
|
//#region Repositories
|
||||||
usersRepository: Symbol('usersRepository'),
|
usersRepository: Symbol('usersRepository'),
|
||||||
|
|
|
@ -589,6 +589,11 @@ export class MiMeta {
|
||||||
})
|
})
|
||||||
public perUserListTimelineCacheMax: number;
|
public perUserListTimelineCacheMax: number;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
public enableReactionsBuffering: boolean;
|
||||||
|
|
||||||
@Column('integer', {
|
@Column('integer', {
|
||||||
default: 0,
|
default: 0,
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { id } from './util/id.js';
|
||||||
import { MiUser } from './User.js';
|
import { MiUser } from './User.js';
|
||||||
|
|
||||||
export const webhookEventTypes = ['mention', 'unfollow', 'follow', 'followed', 'note', 'reply', 'renote', 'reaction'] as const;
|
export const webhookEventTypes = ['mention', 'unfollow', 'follow', 'followed', 'note', 'reply', 'renote', 'reaction'] as const;
|
||||||
|
export type WebhookEventTypes = typeof webhookEventTypes[number];
|
||||||
|
|
||||||
@Entity('webhook')
|
@Entity('webhook')
|
||||||
export class MiWebhook {
|
export class MiWebhook {
|
||||||
|
|
|
@ -272,6 +272,26 @@ export const packedRolePoliciesSchema = {
|
||||||
type: 'integer',
|
type: 'integer',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
canImportAntennas: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
canImportBlocking: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
canImportFollowing: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
canImportMuting: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
canImportUserLists: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { InboxProcessorService } from './processors/InboxProcessorService.js';
|
||||||
import { UserWebhookDeliverProcessorService } from './processors/UserWebhookDeliverProcessorService.js';
|
import { UserWebhookDeliverProcessorService } from './processors/UserWebhookDeliverProcessorService.js';
|
||||||
import { SystemWebhookDeliverProcessorService } from './processors/SystemWebhookDeliverProcessorService.js';
|
import { SystemWebhookDeliverProcessorService } from './processors/SystemWebhookDeliverProcessorService.js';
|
||||||
import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
|
import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
|
||||||
|
import { BakeBufferedReactionsProcessorService } from './processors/BakeBufferedReactionsProcessorService.js';
|
||||||
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
|
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
|
||||||
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
||||||
import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js';
|
import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js';
|
||||||
|
@ -51,6 +52,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
|
||||||
ResyncChartsProcessorService,
|
ResyncChartsProcessorService,
|
||||||
CleanChartsProcessorService,
|
CleanChartsProcessorService,
|
||||||
CheckExpiredMutingsProcessorService,
|
CheckExpiredMutingsProcessorService,
|
||||||
|
BakeBufferedReactionsProcessorService,
|
||||||
CleanProcessorService,
|
CleanProcessorService,
|
||||||
DeleteDriveFilesProcessorService,
|
DeleteDriveFilesProcessorService,
|
||||||
ExportCustomEmojisProcessorService,
|
ExportCustomEmojisProcessorService,
|
||||||
|
|
|
@ -39,6 +39,7 @@ import { TickChartsProcessorService } from './processors/TickChartsProcessorServ
|
||||||
import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js';
|
import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js';
|
||||||
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
|
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
|
||||||
import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
|
import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
|
||||||
|
import { BakeBufferedReactionsProcessorService } from './processors/BakeBufferedReactionsProcessorService.js';
|
||||||
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
||||||
import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
|
import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
|
||||||
import { QueueLoggerService } from './QueueLoggerService.js';
|
import { QueueLoggerService } from './QueueLoggerService.js';
|
||||||
|
@ -118,6 +119,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||||
private cleanChartsProcessorService: CleanChartsProcessorService,
|
private cleanChartsProcessorService: CleanChartsProcessorService,
|
||||||
private aggregateRetentionProcessorService: AggregateRetentionProcessorService,
|
private aggregateRetentionProcessorService: AggregateRetentionProcessorService,
|
||||||
private checkExpiredMutingsProcessorService: CheckExpiredMutingsProcessorService,
|
private checkExpiredMutingsProcessorService: CheckExpiredMutingsProcessorService,
|
||||||
|
private bakeBufferedReactionsProcessorService: BakeBufferedReactionsProcessorService,
|
||||||
private cleanProcessorService: CleanProcessorService,
|
private cleanProcessorService: CleanProcessorService,
|
||||||
) {
|
) {
|
||||||
this.logger = this.queueLoggerService.logger;
|
this.logger = this.queueLoggerService.logger;
|
||||||
|
@ -147,6 +149,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||||
case 'cleanCharts': return this.cleanChartsProcessorService.process();
|
case 'cleanCharts': return this.cleanChartsProcessorService.process();
|
||||||
case 'aggregateRetention': return this.aggregateRetentionProcessorService.process();
|
case 'aggregateRetention': return this.aggregateRetentionProcessorService.process();
|
||||||
case 'checkExpiredMutings': return this.checkExpiredMutingsProcessorService.process();
|
case 'checkExpiredMutings': return this.checkExpiredMutingsProcessorService.process();
|
||||||
|
case 'bakeBufferedReactions': return this.bakeBufferedReactionsProcessorService.process();
|
||||||
case 'clean': return this.cleanProcessorService.process();
|
case 'clean': return this.cleanProcessorService.process();
|
||||||
default: throw new Error(`unrecognized job type ${job.name} for system`);
|
default: throw new Error(`unrecognized job type ${job.name} for system`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import type Logger from '@/logger.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
|
||||||
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
|
import type * as Bull from 'bullmq';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BakeBufferedReactionsProcessorService {
|
||||||
|
private logger: Logger;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private reactionsBufferingService: ReactionsBufferingService,
|
||||||
|
private metaService: MetaService,
|
||||||
|
private queueLoggerService: QueueLoggerService,
|
||||||
|
) {
|
||||||
|
this.logger = this.queueLoggerService.logger.createSubLogger('bake-buffered-reactions');
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async process(): Promise<void> {
|
||||||
|
const meta = await this.metaService.fetch();
|
||||||
|
if (!meta.enableReactionsBuffering) {
|
||||||
|
this.logger.info('Reactions buffering is disabled. Skipping...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.info('Baking buffered reactions...');
|
||||||
|
|
||||||
|
await this.reactionsBufferingService.bake();
|
||||||
|
|
||||||
|
this.logger.succ('All buffered reactions baked.');
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,6 +27,9 @@ export class HealthServerService {
|
||||||
@Inject(DI.redisForTimelines)
|
@Inject(DI.redisForTimelines)
|
||||||
private redisForTimelines: Redis.Redis,
|
private redisForTimelines: Redis.Redis,
|
||||||
|
|
||||||
|
@Inject(DI.redisForReactions)
|
||||||
|
private redisForReactions: Redis.Redis,
|
||||||
|
|
||||||
@Inject(DI.db)
|
@Inject(DI.db)
|
||||||
private db: DataSource,
|
private db: DataSource,
|
||||||
|
|
||||||
|
@ -43,6 +46,7 @@ export class HealthServerService {
|
||||||
this.redisForPub.ping(),
|
this.redisForPub.ping(),
|
||||||
this.redisForSub.ping(),
|
this.redisForSub.ping(),
|
||||||
this.redisForTimelines.ping(),
|
this.redisForTimelines.ping(),
|
||||||
|
this.redisForReactions.ping(),
|
||||||
this.db.query('SELECT 1'),
|
this.db.query('SELECT 1'),
|
||||||
...(this.meilisearch ? [this.meilisearch.health()] : []),
|
...(this.meilisearch ? [this.meilisearch.health()] : []),
|
||||||
]).then(() => 200, () => 503));
|
]).then(() => 200, () => 503));
|
||||||
|
|
|
@ -64,15 +64,6 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||||
let statusCode = err.httpStatusCode;
|
let statusCode = err.httpStatusCode;
|
||||||
if (err.httpStatusCode === 401) {
|
if (err.httpStatusCode === 401) {
|
||||||
reply.header('WWW-Authenticate', 'Bearer realm="Misskey"');
|
reply.header('WWW-Authenticate', 'Bearer realm="Misskey"');
|
||||||
} else if (err.kind === 'client') {
|
|
||||||
reply.header('WWW-Authenticate', `Bearer realm="Misskey", error="invalid_request", error_description="${err.message}"`);
|
|
||||||
statusCode = statusCode ?? 400;
|
|
||||||
} else if (err.kind === 'permission') {
|
|
||||||
// (ROLE_PERMISSION_DENIEDは関係ない)
|
|
||||||
if (err.code === 'PERMISSION_DENIED') {
|
|
||||||
reply.header('WWW-Authenticate', `Bearer realm="Misskey", error="insufficient_scope", error_description="${err.message}"`);
|
|
||||||
}
|
|
||||||
statusCode = statusCode ?? 403;
|
|
||||||
} else if (err.code === 'RATE_LIMIT_EXCEEDED') {
|
} else if (err.code === 'RATE_LIMIT_EXCEEDED') {
|
||||||
const info: unknown = err.info;
|
const info: unknown = err.info;
|
||||||
const unixEpochInSeconds = Date.now();
|
const unixEpochInSeconds = Date.now();
|
||||||
|
@ -83,6 +74,15 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||||
} else {
|
} else {
|
||||||
this.logger.warn(`rate limit information has unexpected type ${typeof(err.info?.reset)}`);
|
this.logger.warn(`rate limit information has unexpected type ${typeof(err.info?.reset)}`);
|
||||||
}
|
}
|
||||||
|
} else if (err.kind === 'client') {
|
||||||
|
reply.header('WWW-Authenticate', `Bearer realm="Misskey", error="invalid_request", error_description="${err.message}"`);
|
||||||
|
statusCode = statusCode ?? 400;
|
||||||
|
} else if (err.kind === 'permission') {
|
||||||
|
// (ROLE_PERMISSION_DENIEDは関係ない)
|
||||||
|
if (err.code === 'PERMISSION_DENIED') {
|
||||||
|
reply.header('WWW-Authenticate', `Bearer realm="Misskey", error="insufficient_scope", error_description="${err.message}"`);
|
||||||
|
}
|
||||||
|
statusCode = statusCode ?? 403;
|
||||||
} else if (!statusCode) {
|
} else if (!statusCode) {
|
||||||
statusCode = 500;
|
statusCode = 500;
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,6 +92,7 @@ import * as ep___admin_systemWebhook_delete from './endpoints/admin/system-webho
|
||||||
import * as ep___admin_systemWebhook_list from './endpoints/admin/system-webhook/list.js';
|
import * as ep___admin_systemWebhook_list from './endpoints/admin/system-webhook/list.js';
|
||||||
import * as ep___admin_systemWebhook_show from './endpoints/admin/system-webhook/show.js';
|
import * as ep___admin_systemWebhook_show from './endpoints/admin/system-webhook/show.js';
|
||||||
import * as ep___admin_systemWebhook_update from './endpoints/admin/system-webhook/update.js';
|
import * as ep___admin_systemWebhook_update from './endpoints/admin/system-webhook/update.js';
|
||||||
|
import * as ep___admin_systemWebhook_test from './endpoints/admin/system-webhook/test.js';
|
||||||
import * as ep___announcements from './endpoints/announcements.js';
|
import * as ep___announcements from './endpoints/announcements.js';
|
||||||
import * as ep___announcements_show from './endpoints/announcements/show.js';
|
import * as ep___announcements_show from './endpoints/announcements/show.js';
|
||||||
import * as ep___antennas_create from './endpoints/antennas/create.js';
|
import * as ep___antennas_create from './endpoints/antennas/create.js';
|
||||||
|
@ -258,6 +259,7 @@ import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
|
||||||
import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
|
import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
|
||||||
import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js';
|
import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js';
|
||||||
import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js';
|
import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js';
|
||||||
|
import * as ep___i_webhooks_test from './endpoints/i/webhooks/test.js';
|
||||||
import * as ep___invite_create from './endpoints/invite/create.js';
|
import * as ep___invite_create from './endpoints/invite/create.js';
|
||||||
import * as ep___invite_delete from './endpoints/invite/delete.js';
|
import * as ep___invite_delete from './endpoints/invite/delete.js';
|
||||||
import * as ep___invite_list from './endpoints/invite/list.js';
|
import * as ep___invite_list from './endpoints/invite/list.js';
|
||||||
|
@ -475,6 +477,7 @@ const $admin_systemWebhook_delete: Provider = { provide: 'ep:admin/system-webhoo
|
||||||
const $admin_systemWebhook_list: Provider = { provide: 'ep:admin/system-webhook/list', useClass: ep___admin_systemWebhook_list.default };
|
const $admin_systemWebhook_list: Provider = { provide: 'ep:admin/system-webhook/list', useClass: ep___admin_systemWebhook_list.default };
|
||||||
const $admin_systemWebhook_show: Provider = { provide: 'ep:admin/system-webhook/show', useClass: ep___admin_systemWebhook_show.default };
|
const $admin_systemWebhook_show: Provider = { provide: 'ep:admin/system-webhook/show', useClass: ep___admin_systemWebhook_show.default };
|
||||||
const $admin_systemWebhook_update: Provider = { provide: 'ep:admin/system-webhook/update', useClass: ep___admin_systemWebhook_update.default };
|
const $admin_systemWebhook_update: Provider = { provide: 'ep:admin/system-webhook/update', useClass: ep___admin_systemWebhook_update.default };
|
||||||
|
const $admin_systemWebhook_test: Provider = { provide: 'ep:admin/system-webhook/test', useClass: ep___admin_systemWebhook_test.default };
|
||||||
const $announcements: Provider = { provide: 'ep:announcements', useClass: ep___announcements.default };
|
const $announcements: Provider = { provide: 'ep:announcements', useClass: ep___announcements.default };
|
||||||
const $announcements_show: Provider = { provide: 'ep:announcements/show', useClass: ep___announcements_show.default };
|
const $announcements_show: Provider = { provide: 'ep:announcements/show', useClass: ep___announcements_show.default };
|
||||||
const $antennas_create: Provider = { provide: 'ep:antennas/create', useClass: ep___antennas_create.default };
|
const $antennas_create: Provider = { provide: 'ep:antennas/create', useClass: ep___antennas_create.default };
|
||||||
|
@ -641,6 +644,7 @@ const $i_webhooks_list: Provider = { provide: 'ep:i/webhooks/list', useClass: ep
|
||||||
const $i_webhooks_show: Provider = { provide: 'ep:i/webhooks/show', useClass: ep___i_webhooks_show.default };
|
const $i_webhooks_show: Provider = { provide: 'ep:i/webhooks/show', useClass: ep___i_webhooks_show.default };
|
||||||
const $i_webhooks_update: Provider = { provide: 'ep:i/webhooks/update', useClass: ep___i_webhooks_update.default };
|
const $i_webhooks_update: Provider = { provide: 'ep:i/webhooks/update', useClass: ep___i_webhooks_update.default };
|
||||||
const $i_webhooks_delete: Provider = { provide: 'ep:i/webhooks/delete', useClass: ep___i_webhooks_delete.default };
|
const $i_webhooks_delete: Provider = { provide: 'ep:i/webhooks/delete', useClass: ep___i_webhooks_delete.default };
|
||||||
|
const $i_webhooks_test: Provider = { provide: 'ep:i/webhooks/test', useClass: ep___i_webhooks_test.default };
|
||||||
const $invite_create: Provider = { provide: 'ep:invite/create', useClass: ep___invite_create.default };
|
const $invite_create: Provider = { provide: 'ep:invite/create', useClass: ep___invite_create.default };
|
||||||
const $invite_delete: Provider = { provide: 'ep:invite/delete', useClass: ep___invite_delete.default };
|
const $invite_delete: Provider = { provide: 'ep:invite/delete', useClass: ep___invite_delete.default };
|
||||||
const $invite_list: Provider = { provide: 'ep:invite/list', useClass: ep___invite_list.default };
|
const $invite_list: Provider = { provide: 'ep:invite/list', useClass: ep___invite_list.default };
|
||||||
|
@ -862,6 +866,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||||
$admin_systemWebhook_list,
|
$admin_systemWebhook_list,
|
||||||
$admin_systemWebhook_show,
|
$admin_systemWebhook_show,
|
||||||
$admin_systemWebhook_update,
|
$admin_systemWebhook_update,
|
||||||
|
$admin_systemWebhook_test,
|
||||||
$announcements,
|
$announcements,
|
||||||
$announcements_show,
|
$announcements_show,
|
||||||
$antennas_create,
|
$antennas_create,
|
||||||
|
@ -1028,6 +1033,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||||
$i_webhooks_show,
|
$i_webhooks_show,
|
||||||
$i_webhooks_update,
|
$i_webhooks_update,
|
||||||
$i_webhooks_delete,
|
$i_webhooks_delete,
|
||||||
|
$i_webhooks_test,
|
||||||
$invite_create,
|
$invite_create,
|
||||||
$invite_delete,
|
$invite_delete,
|
||||||
$invite_list,
|
$invite_list,
|
||||||
|
@ -1243,6 +1249,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||||
$admin_systemWebhook_list,
|
$admin_systemWebhook_list,
|
||||||
$admin_systemWebhook_show,
|
$admin_systemWebhook_show,
|
||||||
$admin_systemWebhook_update,
|
$admin_systemWebhook_update,
|
||||||
|
$admin_systemWebhook_test,
|
||||||
$announcements,
|
$announcements,
|
||||||
$announcements_show,
|
$announcements_show,
|
||||||
$antennas_create,
|
$antennas_create,
|
||||||
|
@ -1409,6 +1416,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||||
$i_webhooks_show,
|
$i_webhooks_show,
|
||||||
$i_webhooks_update,
|
$i_webhooks_update,
|
||||||
$i_webhooks_delete,
|
$i_webhooks_delete,
|
||||||
|
$i_webhooks_test,
|
||||||
$invite_create,
|
$invite_create,
|
||||||
$invite_delete,
|
$invite_delete,
|
||||||
$invite_list,
|
$invite_list,
|
||||||
|
|
|
@ -98,6 +98,7 @@ import * as ep___admin_systemWebhook_delete from './endpoints/admin/system-webho
|
||||||
import * as ep___admin_systemWebhook_list from './endpoints/admin/system-webhook/list.js';
|
import * as ep___admin_systemWebhook_list from './endpoints/admin/system-webhook/list.js';
|
||||||
import * as ep___admin_systemWebhook_show from './endpoints/admin/system-webhook/show.js';
|
import * as ep___admin_systemWebhook_show from './endpoints/admin/system-webhook/show.js';
|
||||||
import * as ep___admin_systemWebhook_update from './endpoints/admin/system-webhook/update.js';
|
import * as ep___admin_systemWebhook_update from './endpoints/admin/system-webhook/update.js';
|
||||||
|
import * as ep___admin_systemWebhook_test from './endpoints/admin/system-webhook/test.js';
|
||||||
import * as ep___announcements from './endpoints/announcements.js';
|
import * as ep___announcements from './endpoints/announcements.js';
|
||||||
import * as ep___announcements_show from './endpoints/announcements/show.js';
|
import * as ep___announcements_show from './endpoints/announcements/show.js';
|
||||||
import * as ep___antennas_create from './endpoints/antennas/create.js';
|
import * as ep___antennas_create from './endpoints/antennas/create.js';
|
||||||
|
@ -264,6 +265,7 @@ import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
|
||||||
import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
|
import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
|
||||||
import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js';
|
import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js';
|
||||||
import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js';
|
import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js';
|
||||||
|
import * as ep___i_webhooks_test from './endpoints/i/webhooks/test.js';
|
||||||
import * as ep___invite_create from './endpoints/invite/create.js';
|
import * as ep___invite_create from './endpoints/invite/create.js';
|
||||||
import * as ep___invite_delete from './endpoints/invite/delete.js';
|
import * as ep___invite_delete from './endpoints/invite/delete.js';
|
||||||
import * as ep___invite_list from './endpoints/invite/list.js';
|
import * as ep___invite_list from './endpoints/invite/list.js';
|
||||||
|
@ -479,6 +481,7 @@ const eps = [
|
||||||
['admin/system-webhook/list', ep___admin_systemWebhook_list],
|
['admin/system-webhook/list', ep___admin_systemWebhook_list],
|
||||||
['admin/system-webhook/show', ep___admin_systemWebhook_show],
|
['admin/system-webhook/show', ep___admin_systemWebhook_show],
|
||||||
['admin/system-webhook/update', ep___admin_systemWebhook_update],
|
['admin/system-webhook/update', ep___admin_systemWebhook_update],
|
||||||
|
['admin/system-webhook/test', ep___admin_systemWebhook_test],
|
||||||
['announcements', ep___announcements],
|
['announcements', ep___announcements],
|
||||||
['announcements/show', ep___announcements_show],
|
['announcements/show', ep___announcements_show],
|
||||||
['antennas/create', ep___antennas_create],
|
['antennas/create', ep___antennas_create],
|
||||||
|
@ -645,6 +648,7 @@ const eps = [
|
||||||
['i/webhooks/show', ep___i_webhooks_show],
|
['i/webhooks/show', ep___i_webhooks_show],
|
||||||
['i/webhooks/update', ep___i_webhooks_update],
|
['i/webhooks/update', ep___i_webhooks_update],
|
||||||
['i/webhooks/delete', ep___i_webhooks_delete],
|
['i/webhooks/delete', ep___i_webhooks_delete],
|
||||||
|
['i/webhooks/test', ep___i_webhooks_test],
|
||||||
['invite/create', ep___invite_create],
|
['invite/create', ep___invite_create],
|
||||||
['invite/delete', ep___invite_delete],
|
['invite/delete', ep___invite_delete],
|
||||||
['invite/list', ep___invite_list],
|
['invite/list', ep___invite_list],
|
||||||
|
|
|
@ -377,6 +377,10 @@ export const meta = {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
enableReactionsBuffering: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
notesPerOneAd: {
|
notesPerOneAd: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
@ -617,6 +621,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
perRemoteUserUserTimelineCacheMax: instance.perRemoteUserUserTimelineCacheMax,
|
perRemoteUserUserTimelineCacheMax: instance.perRemoteUserUserTimelineCacheMax,
|
||||||
perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax,
|
perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax,
|
||||||
perUserListTimelineCacheMax: instance.perUserListTimelineCacheMax,
|
perUserListTimelineCacheMax: instance.perUserListTimelineCacheMax,
|
||||||
|
enableReactionsBuffering: instance.enableReactionsBuffering,
|
||||||
notesPerOneAd: instance.notesPerOneAd,
|
notesPerOneAd: instance.notesPerOneAd,
|
||||||
summalyProxy: instance.urlPreviewSummaryProxyUrl,
|
summalyProxy: instance.urlPreviewSummaryProxyUrl,
|
||||||
urlPreviewEnabled: instance.urlPreviewEnabled,
|
urlPreviewEnabled: instance.urlPreviewEnabled,
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import ms from 'ms';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { WebhookTestService } from '@/core/WebhookTestService.js';
|
||||||
|
import { ApiError } from '@/server/api/error.js';
|
||||||
|
import { systemWebhookEventTypes } from '@/models/SystemWebhook.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['webhooks'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireModerator: true,
|
||||||
|
secure: true,
|
||||||
|
kind: 'read:admin:system-webhook',
|
||||||
|
|
||||||
|
limit: {
|
||||||
|
duration: ms('15min'),
|
||||||
|
max: 60,
|
||||||
|
},
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
noSuchWebhook: {
|
||||||
|
message: 'No such webhook.',
|
||||||
|
code: 'NO_SUCH_WEBHOOK',
|
||||||
|
id: '0c52149c-e913-18f8-5dc7-74870bfe0cf9',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
webhookId: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'misskey:id',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: 'string',
|
||||||
|
enum: systemWebhookEventTypes,
|
||||||
|
},
|
||||||
|
override: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
url: { type: 'string', nullable: false },
|
||||||
|
secret: { type: 'string', nullable: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['webhookId', 'type'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
private webhookTestService: WebhookTestService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps) => {
|
||||||
|
try {
|
||||||
|
await this.webhookTestService.testSystemWebhook({
|
||||||
|
webhookId: ps.webhookId,
|
||||||
|
type: ps.type,
|
||||||
|
override: ps.override,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof WebhookTestService.NoSuchWebhookError) {
|
||||||
|
throw new ApiError(meta.errors.noSuchWebhook);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -142,6 +142,7 @@ export const paramDef = {
|
||||||
perRemoteUserUserTimelineCacheMax: { type: 'integer' },
|
perRemoteUserUserTimelineCacheMax: { type: 'integer' },
|
||||||
perUserHomeTimelineCacheMax: { type: 'integer' },
|
perUserHomeTimelineCacheMax: { type: 'integer' },
|
||||||
perUserListTimelineCacheMax: { type: 'integer' },
|
perUserListTimelineCacheMax: { type: 'integer' },
|
||||||
|
enableReactionsBuffering: { type: 'boolean' },
|
||||||
notesPerOneAd: { type: 'integer' },
|
notesPerOneAd: { type: 'integer' },
|
||||||
silencedHosts: {
|
silencedHosts: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
@ -598,6 +599,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
set.perUserListTimelineCacheMax = ps.perUserListTimelineCacheMax;
|
set.perUserListTimelineCacheMax = ps.perUserListTimelineCacheMax;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.enableReactionsBuffering !== undefined) {
|
||||||
|
set.enableReactionsBuffering = ps.enableReactionsBuffering;
|
||||||
|
}
|
||||||
|
|
||||||
if (ps.notesPerOneAd !== undefined) {
|
if (ps.notesPerOneAd !== undefined) {
|
||||||
set.notesPerOneAd = ps.notesPerOneAd;
|
set.notesPerOneAd = ps.notesPerOneAd;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { ApiError } from '../../error.js';
|
||||||
export const meta = {
|
export const meta = {
|
||||||
secure: true,
|
secure: true,
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
requireRolePolicy: 'canImportAntennas',
|
||||||
prohibitMoved: true,
|
prohibitMoved: true,
|
||||||
|
|
||||||
limit: {
|
limit: {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { ApiError } from '../../error.js';
|
||||||
export const meta = {
|
export const meta = {
|
||||||
secure: true,
|
secure: true,
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
requireRolePolicy: 'canImportBlocking',
|
||||||
prohibitMoved: true,
|
prohibitMoved: true,
|
||||||
|
|
||||||
limit: {
|
limit: {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { ApiError } from '../../error.js';
|
||||||
export const meta = {
|
export const meta = {
|
||||||
secure: true,
|
secure: true,
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
requireRolePolicy: 'canImportFollowing',
|
||||||
prohibitMoved: true,
|
prohibitMoved: true,
|
||||||
limit: {
|
limit: {
|
||||||
duration: ms('1hour'),
|
duration: ms('1hour'),
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { ApiError } from '../../error.js';
|
||||||
export const meta = {
|
export const meta = {
|
||||||
secure: true,
|
secure: true,
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
requireRolePolicy: 'canImportMuting',
|
||||||
prohibitMoved: true,
|
prohibitMoved: true,
|
||||||
|
|
||||||
limit: {
|
limit: {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { ApiError } from '../../error.js';
|
||||||
export const meta = {
|
export const meta = {
|
||||||
secure: true,
|
secure: true,
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
requireRolePolicy: 'canImportUserLists',
|
||||||
prohibitMoved: true,
|
prohibitMoved: true,
|
||||||
limit: {
|
limit: {
|
||||||
duration: ms('1hour'),
|
duration: ms('1hour'),
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { DI } from '@/di-symbols.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { ApiError } from '@/server/api/error.js';
|
import { ApiError } from '@/server/api/error.js';
|
||||||
|
|
||||||
|
// TODO: UserWebhook schemaの適用
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['webhooks'],
|
tags: ['webhooks'],
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { webhookEventTypes } from '@/models/Webhook.js';
|
||||||
import type { WebhooksRepository } from '@/models/_.js';
|
import type { WebhooksRepository } from '@/models/_.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
|
||||||
|
// TODO: UserWebhook schemaの適用
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['webhooks', 'account'],
|
tags: ['webhooks', 'account'],
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import type { WebhooksRepository } from '@/models/_.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { ApiError } from '../../../error.js';
|
import { ApiError } from '../../../error.js';
|
||||||
|
|
||||||
|
// TODO: UserWebhook schemaの適用
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['webhooks'],
|
tags: ['webhooks'],
|
||||||
|
|
||||||
|
|
76
packages/backend/src/server/api/endpoints/i/webhooks/test.ts
Normal file
76
packages/backend/src/server/api/endpoints/i/webhooks/test.ts
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import ms from 'ms';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { webhookEventTypes } from '@/models/Webhook.js';
|
||||||
|
import { WebhookTestService } from '@/core/WebhookTestService.js';
|
||||||
|
import { ApiError } from '@/server/api/error.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['webhooks'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
secure: true,
|
||||||
|
kind: 'read:account',
|
||||||
|
|
||||||
|
limit: {
|
||||||
|
duration: ms('15min'),
|
||||||
|
max: 60,
|
||||||
|
},
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
noSuchWebhook: {
|
||||||
|
message: 'No such webhook.',
|
||||||
|
code: 'NO_SUCH_WEBHOOK',
|
||||||
|
id: '0c52149c-e913-18f8-5dc7-74870bfe0cf9',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
webhookId: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'misskey:id',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: 'string',
|
||||||
|
enum: webhookEventTypes,
|
||||||
|
},
|
||||||
|
override: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
url: { type: 'string' },
|
||||||
|
secret: { type: 'string' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['webhookId', 'type'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
private webhookTestService: WebhookTestService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
try {
|
||||||
|
await this.webhookTestService.testUserWebhook({
|
||||||
|
webhookId: ps.webhookId,
|
||||||
|
type: ps.type,
|
||||||
|
override: ps.override,
|
||||||
|
}, me);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof WebhookTestService.NoSuchWebhookError) {
|
||||||
|
throw new ApiError(meta.errors.noSuchWebhook);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
@ -6,6 +7,7 @@
|
||||||
import { setTimeout } from 'node:timers/promises';
|
import { setTimeout } from 'node:timers/promises';
|
||||||
import { afterEach, beforeEach, describe, expect, jest } from '@jest/globals';
|
import { afterEach, beforeEach, describe, expect, jest } from '@jest/globals';
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { randomString } from '../utils.js';
|
||||||
import { MiUser } from '@/models/User.js';
|
import { MiUser } from '@/models/User.js';
|
||||||
import { MiSystemWebhook, SystemWebhookEventType } from '@/models/SystemWebhook.js';
|
import { MiSystemWebhook, SystemWebhookEventType } from '@/models/SystemWebhook.js';
|
||||||
import { SystemWebhooksRepository, UsersRepository } from '@/models/_.js';
|
import { SystemWebhooksRepository, UsersRepository } from '@/models/_.js';
|
||||||
|
@ -17,7 +19,6 @@ import { DI } from '@/di-symbols.js';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
import { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
||||||
import { randomString } from '../utils.js';
|
|
||||||
|
|
||||||
describe('SystemWebhookService', () => {
|
describe('SystemWebhookService', () => {
|
||||||
let app: TestingModule;
|
let app: TestingModule;
|
||||||
|
@ -313,7 +314,7 @@ describe('SystemWebhookService', () => {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
on: ['abuseReport'],
|
on: ['abuseReport'],
|
||||||
});
|
});
|
||||||
await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' });
|
await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' } as any);
|
||||||
|
|
||||||
expect(queueService.systemWebhookDeliver).toHaveBeenCalled();
|
expect(queueService.systemWebhookDeliver).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
@ -323,7 +324,7 @@ describe('SystemWebhookService', () => {
|
||||||
isActive: false,
|
isActive: false,
|
||||||
on: ['abuseReport'],
|
on: ['abuseReport'],
|
||||||
});
|
});
|
||||||
await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' });
|
await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' } as any);
|
||||||
|
|
||||||
expect(queueService.systemWebhookDeliver).not.toHaveBeenCalled();
|
expect(queueService.systemWebhookDeliver).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
@ -337,8 +338,8 @@ describe('SystemWebhookService', () => {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
on: ['abuseReportResolved'],
|
on: ['abuseReportResolved'],
|
||||||
});
|
});
|
||||||
await service.enqueueSystemWebhook(webhook1.id, 'abuseReport', { foo: 'bar' });
|
await service.enqueueSystemWebhook(webhook1.id, 'abuseReport', { foo: 'bar' } as any);
|
||||||
await service.enqueueSystemWebhook(webhook2.id, 'abuseReport', { foo: 'bar' });
|
await service.enqueueSystemWebhook(webhook2.id, 'abuseReport', { foo: 'bar' } as any);
|
||||||
|
|
||||||
expect(queueService.systemWebhookDeliver).not.toHaveBeenCalled();
|
expect(queueService.systemWebhookDeliver).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
245
packages/backend/test/unit/UserWebhookService.ts
Normal file
245
packages/backend/test/unit/UserWebhookService.ts
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { afterEach, beforeEach, describe, expect, jest } from '@jest/globals';
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { randomString } from '../utils.js';
|
||||||
|
import { MiUser } from '@/models/User.js';
|
||||||
|
import { MiWebhook, UsersRepository, WebhooksRepository } from '@/models/_.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { GlobalModule } from '@/GlobalModule.js';
|
||||||
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
|
import { UserWebhookService } from '@/core/UserWebhookService.js';
|
||||||
|
|
||||||
|
describe('UserWebhookService', () => {
|
||||||
|
let app: TestingModule;
|
||||||
|
let service: UserWebhookService;
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
let usersRepository: UsersRepository;
|
||||||
|
let userWebhooksRepository: WebhooksRepository;
|
||||||
|
let idService: IdService;
|
||||||
|
let queueService: jest.Mocked<QueueService>;
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
let root: MiUser;
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
async function createUser(data: Partial<MiUser> = {}) {
|
||||||
|
return await usersRepository
|
||||||
|
.insert({
|
||||||
|
id: idService.gen(),
|
||||||
|
...data,
|
||||||
|
})
|
||||||
|
.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createWebhook(data: Partial<MiWebhook> = {}) {
|
||||||
|
return userWebhooksRepository
|
||||||
|
.insert({
|
||||||
|
id: idService.gen(),
|
||||||
|
name: randomString(),
|
||||||
|
on: ['mention'],
|
||||||
|
url: 'https://example.com',
|
||||||
|
secret: randomString(),
|
||||||
|
userId: root.id,
|
||||||
|
...data,
|
||||||
|
})
|
||||||
|
.then(x => userWebhooksRepository.findOneByOrFail(x.identifiers[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
async function beforeAllImpl() {
|
||||||
|
app = await Test
|
||||||
|
.createTestingModule({
|
||||||
|
imports: [
|
||||||
|
GlobalModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
UserWebhookService,
|
||||||
|
IdService,
|
||||||
|
LoggerService,
|
||||||
|
GlobalEventService,
|
||||||
|
{
|
||||||
|
provide: QueueService, useFactory: () => ({ systemWebhookDeliver: jest.fn() }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.compile();
|
||||||
|
|
||||||
|
usersRepository = app.get(DI.usersRepository);
|
||||||
|
userWebhooksRepository = app.get(DI.webhooksRepository);
|
||||||
|
|
||||||
|
service = app.get(UserWebhookService);
|
||||||
|
idService = app.get(IdService);
|
||||||
|
queueService = app.get(QueueService) as jest.Mocked<QueueService>;
|
||||||
|
|
||||||
|
app.enableShutdownHooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function afterAllImpl() {
|
||||||
|
await app.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function beforeEachImpl() {
|
||||||
|
root = await createUser({ isRoot: true, username: 'root', usernameLower: 'root' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function afterEachImpl() {
|
||||||
|
await usersRepository.delete({});
|
||||||
|
await userWebhooksRepository.delete({});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
describe('アプリを毎回作り直す必要のないグループ', () => {
|
||||||
|
beforeAll(beforeAllImpl);
|
||||||
|
afterAll(afterAllImpl);
|
||||||
|
beforeEach(beforeEachImpl);
|
||||||
|
afterEach(afterEachImpl);
|
||||||
|
|
||||||
|
describe('fetchSystemWebhooks', () => {
|
||||||
|
test('フィルタなし', async () => {
|
||||||
|
const webhook1 = await createWebhook({
|
||||||
|
active: true,
|
||||||
|
on: ['mention'],
|
||||||
|
});
|
||||||
|
const webhook2 = await createWebhook({
|
||||||
|
active: false,
|
||||||
|
on: ['mention'],
|
||||||
|
});
|
||||||
|
const webhook3 = await createWebhook({
|
||||||
|
active: true,
|
||||||
|
on: ['reply'],
|
||||||
|
});
|
||||||
|
const webhook4 = await createWebhook({
|
||||||
|
active: false,
|
||||||
|
on: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchedWebhooks = await service.fetchWebhooks();
|
||||||
|
expect(fetchedWebhooks).toEqual([webhook1, webhook2, webhook3, webhook4]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('activeのみ', async () => {
|
||||||
|
const webhook1 = await createWebhook({
|
||||||
|
active: true,
|
||||||
|
on: ['mention'],
|
||||||
|
});
|
||||||
|
const webhook2 = await createWebhook({
|
||||||
|
active: false,
|
||||||
|
on: ['mention'],
|
||||||
|
});
|
||||||
|
const webhook3 = await createWebhook({
|
||||||
|
active: true,
|
||||||
|
on: ['reply'],
|
||||||
|
});
|
||||||
|
const webhook4 = await createWebhook({
|
||||||
|
active: false,
|
||||||
|
on: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchedWebhooks = await service.fetchWebhooks({ isActive: true });
|
||||||
|
expect(fetchedWebhooks).toEqual([webhook1, webhook3]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('特定のイベントのみ', async () => {
|
||||||
|
const webhook1 = await createWebhook({
|
||||||
|
active: true,
|
||||||
|
on: ['mention'],
|
||||||
|
});
|
||||||
|
const webhook2 = await createWebhook({
|
||||||
|
active: false,
|
||||||
|
on: ['mention'],
|
||||||
|
});
|
||||||
|
const webhook3 = await createWebhook({
|
||||||
|
active: true,
|
||||||
|
on: ['reply'],
|
||||||
|
});
|
||||||
|
const webhook4 = await createWebhook({
|
||||||
|
active: false,
|
||||||
|
on: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchedWebhooks = await service.fetchWebhooks({ on: ['mention'] });
|
||||||
|
expect(fetchedWebhooks).toEqual([webhook1, webhook2]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('activeな特定のイベントのみ', async () => {
|
||||||
|
const webhook1 = await createWebhook({
|
||||||
|
active: true,
|
||||||
|
on: ['mention'],
|
||||||
|
});
|
||||||
|
const webhook2 = await createWebhook({
|
||||||
|
active: false,
|
||||||
|
on: ['mention'],
|
||||||
|
});
|
||||||
|
const webhook3 = await createWebhook({
|
||||||
|
active: true,
|
||||||
|
on: ['reply'],
|
||||||
|
});
|
||||||
|
const webhook4 = await createWebhook({
|
||||||
|
active: false,
|
||||||
|
on: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchedWebhooks = await service.fetchWebhooks({ on: ['mention'], isActive: true });
|
||||||
|
expect(fetchedWebhooks).toEqual([webhook1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ID指定', async () => {
|
||||||
|
const webhook1 = await createWebhook({
|
||||||
|
active: true,
|
||||||
|
on: ['mention'],
|
||||||
|
});
|
||||||
|
const webhook2 = await createWebhook({
|
||||||
|
active: false,
|
||||||
|
on: ['mention'],
|
||||||
|
});
|
||||||
|
const webhook3 = await createWebhook({
|
||||||
|
active: true,
|
||||||
|
on: ['reply'],
|
||||||
|
});
|
||||||
|
const webhook4 = await createWebhook({
|
||||||
|
active: false,
|
||||||
|
on: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchedWebhooks = await service.fetchWebhooks({ ids: [webhook1.id, webhook4.id] });
|
||||||
|
expect(fetchedWebhooks).toEqual([webhook1, webhook4]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ID指定(他条件とANDになるか見たい)', async () => {
|
||||||
|
const webhook1 = await createWebhook({
|
||||||
|
active: true,
|
||||||
|
on: ['mention'],
|
||||||
|
});
|
||||||
|
const webhook2 = await createWebhook({
|
||||||
|
active: false,
|
||||||
|
on: ['mention'],
|
||||||
|
});
|
||||||
|
const webhook3 = await createWebhook({
|
||||||
|
active: true,
|
||||||
|
on: ['reply'],
|
||||||
|
});
|
||||||
|
const webhook4 = await createWebhook({
|
||||||
|
active: false,
|
||||||
|
on: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchedWebhooks = await service.fetchWebhooks({ ids: [webhook1.id, webhook4.id], isActive: false });
|
||||||
|
expect(fetchedWebhooks).toEqual([webhook4]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
225
packages/backend/test/unit/WebhookTestService.ts
Normal file
225
packages/backend/test/unit/WebhookTestService.ts
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { beforeAll, describe, jest } from '@jest/globals';
|
||||||
|
import { WebhookTestService } from '@/core/WebhookTestService.js';
|
||||||
|
import { UserWebhookService } from '@/core/UserWebhookService.js';
|
||||||
|
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
||||||
|
import { GlobalModule } from '@/GlobalModule.js';
|
||||||
|
import { MiSystemWebhook, MiUser, MiWebhook, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
|
|
||||||
|
describe('WebhookTestService', () => {
|
||||||
|
let app: TestingModule;
|
||||||
|
let service: WebhookTestService;
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
let usersRepository: UsersRepository;
|
||||||
|
let userProfilesRepository: UserProfilesRepository;
|
||||||
|
let queueService: jest.Mocked<QueueService>;
|
||||||
|
let userWebhookService: jest.Mocked<UserWebhookService>;
|
||||||
|
let systemWebhookService: jest.Mocked<SystemWebhookService>;
|
||||||
|
let idService: IdService;
|
||||||
|
|
||||||
|
let root: MiUser;
|
||||||
|
let alice: MiUser;
|
||||||
|
|
||||||
|
async function createUser(data: Partial<MiUser> = {}) {
|
||||||
|
const user = await usersRepository
|
||||||
|
.insert({
|
||||||
|
id: idService.gen(),
|
||||||
|
...data,
|
||||||
|
})
|
||||||
|
.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
|
||||||
|
|
||||||
|
await userProfilesRepository.insert({
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
app = await Test.createTestingModule({
|
||||||
|
imports: [
|
||||||
|
GlobalModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
WebhookTestService,
|
||||||
|
IdService,
|
||||||
|
{
|
||||||
|
provide: QueueService, useFactory: () => ({
|
||||||
|
systemWebhookDeliver: jest.fn(),
|
||||||
|
userWebhookDeliver: jest.fn(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: UserWebhookService, useFactory: () => ({
|
||||||
|
fetchWebhooks: jest.fn(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: SystemWebhookService, useFactory: () => ({
|
||||||
|
fetchSystemWebhooks: jest.fn(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
usersRepository = app.get(DI.usersRepository);
|
||||||
|
userProfilesRepository = app.get(DI.userProfilesRepository);
|
||||||
|
|
||||||
|
service = app.get(WebhookTestService);
|
||||||
|
idService = app.get(IdService);
|
||||||
|
queueService = app.get(QueueService) as jest.Mocked<QueueService>;
|
||||||
|
userWebhookService = app.get(UserWebhookService) as jest.Mocked<UserWebhookService>;
|
||||||
|
systemWebhookService = app.get(SystemWebhookService) as jest.Mocked<SystemWebhookService>;
|
||||||
|
|
||||||
|
app.enableShutdownHooks();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
root = await createUser({ username: 'root', usernameLower: 'root', isRoot: true });
|
||||||
|
alice = await createUser({ username: 'alice', usernameLower: 'alice', isRoot: false });
|
||||||
|
|
||||||
|
userWebhookService.fetchWebhooks.mockReturnValue(Promise.resolve([
|
||||||
|
{ id: 'dummy-webhook', active: true, userId: alice.id } as MiWebhook,
|
||||||
|
]));
|
||||||
|
systemWebhookService.fetchSystemWebhooks.mockReturnValue(Promise.resolve([
|
||||||
|
{ id: 'dummy-webhook', isActive: true } as MiSystemWebhook,
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
queueService.systemWebhookDeliver.mockClear();
|
||||||
|
queueService.userWebhookDeliver.mockClear();
|
||||||
|
userWebhookService.fetchWebhooks.mockClear();
|
||||||
|
systemWebhookService.fetchSystemWebhooks.mockClear();
|
||||||
|
|
||||||
|
await usersRepository.delete({});
|
||||||
|
await userProfilesRepository.delete({});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await app.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
describe('testUserWebhook', () => {
|
||||||
|
test('note', async () => {
|
||||||
|
await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'note' }, alice);
|
||||||
|
|
||||||
|
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||||
|
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||||
|
expect(calls[1]).toBe('note');
|
||||||
|
expect((calls[2] as any).id).toBe('dummy-note-1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reply', async () => {
|
||||||
|
await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'reply' }, alice);
|
||||||
|
|
||||||
|
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||||
|
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||||
|
expect(calls[1]).toBe('reply');
|
||||||
|
expect((calls[2] as any).id).toBe('dummy-reply-1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renote', async () => {
|
||||||
|
await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'renote' }, alice);
|
||||||
|
|
||||||
|
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||||
|
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||||
|
expect(calls[1]).toBe('renote');
|
||||||
|
expect((calls[2] as any).id).toBe('dummy-renote-1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('mention', async () => {
|
||||||
|
await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'mention' }, alice);
|
||||||
|
|
||||||
|
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||||
|
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||||
|
expect(calls[1]).toBe('mention');
|
||||||
|
expect((calls[2] as any).id).toBe('dummy-mention-1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('follow', async () => {
|
||||||
|
await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'follow' }, alice);
|
||||||
|
|
||||||
|
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||||
|
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||||
|
expect(calls[1]).toBe('follow');
|
||||||
|
expect((calls[2] as any).id).toBe('dummy-user-1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('followed', async () => {
|
||||||
|
await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'followed' }, alice);
|
||||||
|
|
||||||
|
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||||
|
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||||
|
expect(calls[1]).toBe('followed');
|
||||||
|
expect((calls[2] as any).id).toBe('dummy-user-2');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unfollow', async () => {
|
||||||
|
await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'unfollow' }, alice);
|
||||||
|
|
||||||
|
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||||
|
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||||
|
expect(calls[1]).toBe('unfollow');
|
||||||
|
expect((calls[2] as any).id).toBe('dummy-user-3');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('NoSuchWebhookError', () => {
|
||||||
|
test('user not match', async () => {
|
||||||
|
userWebhookService.fetchWebhooks.mockClear();
|
||||||
|
userWebhookService.fetchWebhooks.mockReturnValue(Promise.resolve([
|
||||||
|
{ id: 'dummy-webhook', active: true } as MiWebhook,
|
||||||
|
]));
|
||||||
|
|
||||||
|
await expect(service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'note' }, root))
|
||||||
|
.rejects.toThrow(WebhookTestService.NoSuchWebhookError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('testSystemWebhook', () => {
|
||||||
|
test('abuseReport', async () => {
|
||||||
|
await service.testSystemWebhook({ webhookId: 'dummy-webhook', type: 'abuseReport' });
|
||||||
|
|
||||||
|
const calls = queueService.systemWebhookDeliver.mock.calls[0];
|
||||||
|
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||||
|
expect(calls[1]).toBe('abuseReport');
|
||||||
|
expect((calls[2] as any).id).toBe('dummy-abuse-report1');
|
||||||
|
expect((calls[2] as any).resolved).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('abuseReportResolved', async () => {
|
||||||
|
await service.testSystemWebhook({ webhookId: 'dummy-webhook', type: 'abuseReportResolved' });
|
||||||
|
|
||||||
|
const calls = queueService.systemWebhookDeliver.mock.calls[0];
|
||||||
|
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||||
|
expect(calls[1]).toBe('abuseReportResolved');
|
||||||
|
expect((calls[2] as any).id).toBe('dummy-abuse-report1');
|
||||||
|
expect((calls[2] as any).resolved).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('userCreated', async () => {
|
||||||
|
await service.testSystemWebhook({ webhookId: 'dummy-webhook', type: 'userCreated' });
|
||||||
|
|
||||||
|
const calls = queueService.systemWebhookDeliver.mock.calls[0];
|
||||||
|
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||||
|
expect(calls[1]).toBe('userCreated');
|
||||||
|
expect((calls[2] as any).id).toBe('dummy-user-1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -4,10 +4,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import type { MiUser } from '@/models/User.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { GlobalModule } from '@/GlobalModule.js';
|
import { GlobalModule } from '@/GlobalModule.js';
|
||||||
import { CoreModule } from '@/core/CoreModule.js';
|
import { CoreModule } from '@/core/CoreModule.js';
|
||||||
import type { MiUser } from '@/models/User.js';
|
|
||||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||||
import { genAidx } from '@/misc/id/aidx.js';
|
import { genAidx } from '@/misc/id/aidx.js';
|
||||||
import {
|
import {
|
||||||
|
@ -49,6 +49,7 @@ import { ApLoggerService } from '@/core/activitypub/ApLoggerService.js';
|
||||||
import { AccountMoveService } from '@/core/AccountMoveService.js';
|
import { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||||
import { ReactionService } from '@/core/ReactionService.js';
|
import { ReactionService } from '@/core/ReactionService.js';
|
||||||
import { NotificationService } from '@/core/NotificationService.js';
|
import { NotificationService } from '@/core/NotificationService.js';
|
||||||
|
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
|
||||||
|
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
|
@ -169,6 +170,7 @@ describe('UserEntityService', () => {
|
||||||
ApLoggerService,
|
ApLoggerService,
|
||||||
AccountMoveService,
|
AccountMoveService,
|
||||||
ReactionService,
|
ReactionService,
|
||||||
|
ReactionsBufferingService,
|
||||||
NotificationService,
|
NotificationService,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -98,6 +98,11 @@ export const ROLE_POLICIES = [
|
||||||
'userEachUserListsLimit',
|
'userEachUserListsLimit',
|
||||||
'rateLimitFactor',
|
'rateLimitFactor',
|
||||||
'avatarDecorationLimit',
|
'avatarDecorationLimit',
|
||||||
|
'canImportAntennas',
|
||||||
|
'canImportBlocking',
|
||||||
|
'canImportFollowing',
|
||||||
|
'canImportMuting',
|
||||||
|
'canImportUserLists',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
// なんか動かない
|
// なんか動かない
|
||||||
|
|
|
@ -611,6 +611,7 @@ defineExpose({
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
@ -717,7 +718,7 @@ defineExpose({
|
||||||
|
|
||||||
> .item {
|
> .item {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0;
|
padding: 0 3px;
|
||||||
width: var(--eachSize);
|
width: var(--eachSize);
|
||||||
height: var(--eachSize);
|
height: var(--eachSize);
|
||||||
contain: strict;
|
contain: strict;
|
||||||
|
|
|
@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:withTooltip="true"
|
:withTooltip="true"
|
||||||
:reaction="notification.reaction.replace(/^:(\w+):$/, ':$1@.:')"
|
:reaction="notification.reaction.replace(/^:(\w+):$/, ':$1@.:')"
|
||||||
:noStyle="true"
|
:noStyle="true"
|
||||||
style="width: 100%; height: 100%;"
|
style="width: 100%; height: 100% !important; object-fit: contain;"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -122,7 +122,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:withTooltip="true"
|
:withTooltip="true"
|
||||||
:reaction="reaction.reaction.replace(/^:(\w+):$/, ':$1@.:')"
|
:reaction="reaction.reaction.replace(/^:(\w+):$/, ':$1@.:')"
|
||||||
:noStyle="true"
|
:noStyle="true"
|
||||||
style="width: 100%; height: 100%;"
|
style="width: 100%; height: 100% !important; object-fit: contain;"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -36,6 +36,7 @@ const emit = defineEmits<{
|
||||||
.icon {
|
.icon {
|
||||||
display: block;
|
display: block;
|
||||||
width: 60px;
|
width: 60px;
|
||||||
|
max-height: 60px;
|
||||||
font-size: 60px; // unicodeな絵文字についてはwidthが効かないため
|
font-size: 60px; // unicodeな絵文字についてはwidthが効かないため
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
|
|
@ -63,6 +63,7 @@ function getReactionName(reaction: string): string {
|
||||||
.reactionIcon {
|
.reactionIcon {
|
||||||
display: block;
|
display: block;
|
||||||
width: 60px;
|
width: 60px;
|
||||||
|
max-height: 60px;
|
||||||
font-size: 60px; // unicodeな絵文字についてはwidthが効かないため
|
font-size: 60px; // unicodeな絵文字についてはwidthが効かないため
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|
|
@ -4,9 +4,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { defineAsyncComponent } from 'vue';
|
import { defineAsyncComponent } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
|
||||||
export type SystemWebhookEventType = 'abuseReport' | 'abuseReportResolved';
|
export type SystemWebhookEventType = Misskey.entities.SystemWebhook['on'][number];
|
||||||
|
|
||||||
export type MkSystemWebhookEditorProps = {
|
export type MkSystemWebhookEditorProps = {
|
||||||
mode: 'create' | 'edit';
|
mode: 'create' | 'edit';
|
||||||
|
|
|
@ -35,16 +35,31 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkFolder :defaultOpen="true">
|
<MkFolder :defaultOpen="true">
|
||||||
<template #label>{{ i18n.ts._webhookSettings.trigger }}</template>
|
<template #label>{{ i18n.ts._webhookSettings.trigger }}</template>
|
||||||
|
|
||||||
<div class="_gaps_s">
|
<div class="_gaps">
|
||||||
<MkSwitch v-model="events.abuseReport" :disabled="disabledEvents.abuseReport">
|
<div class="_gaps_s">
|
||||||
<template #label>{{ i18n.ts._webhookSettings._systemEvents.abuseReport }}</template>
|
<div :class="$style.switchBox">
|
||||||
</MkSwitch>
|
<MkSwitch v-model="events.abuseReport" :disabled="disabledEvents.abuseReport">
|
||||||
<MkSwitch v-model="events.abuseReportResolved" :disabled="disabledEvents.abuseReportResolved">
|
<template #label>{{ i18n.ts._webhookSettings._systemEvents.abuseReport }}</template>
|
||||||
<template #label>{{ i18n.ts._webhookSettings._systemEvents.abuseReportResolved }}</template>
|
</MkSwitch>
|
||||||
</MkSwitch>
|
<MkButton v-show="mode === 'edit'" transparent :class="$style.testButton" :disabled="!(isActive && events.abuseReport)" @click="test('abuseReport')"><i class="ti ti-send"></i></MkButton>
|
||||||
<MkSwitch v-model="events.userCreated" :disabled="disabledEvents.userCreated">
|
</div>
|
||||||
<template #label>{{ i18n.ts._webhookSettings._systemEvents.userCreated }}</template>
|
<div :class="$style.switchBox">
|
||||||
</MkSwitch>
|
<MkSwitch v-model="events.abuseReportResolved" :disabled="disabledEvents.abuseReportResolved">
|
||||||
|
<template #label>{{ i18n.ts._webhookSettings._systemEvents.abuseReportResolved }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkButton v-show="mode === 'edit'" transparent :class="$style.testButton" :disabled="!(isActive && events.abuseReportResolved)" @click="test('abuseReportResolved')"><i class="ti ti-send"></i></MkButton>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.switchBox">
|
||||||
|
<MkSwitch v-model="events.userCreated" :disabled="disabledEvents.userCreated">
|
||||||
|
<template #label>{{ i18n.ts._webhookSettings._systemEvents.userCreated }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkButton v-show="mode === 'edit'" transparent :class="$style.testButton" :disabled="!(isActive && events.userCreated)" @click="test('userCreated')"><i class="ti ti-send"></i></MkButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-show="mode === 'edit'" :class="$style.description">
|
||||||
|
{{ i18n.ts._webhookSettings.testRemarks }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
@ -66,6 +81,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref, shallowRef, toRefs } from 'vue';
|
import { computed, onMounted, ref, shallowRef, toRefs } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import {
|
import {
|
||||||
|
@ -180,6 +196,21 @@ async function loadingScope<T>(fn: () => Promise<T>): Promise<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function test(type: Misskey.entities.SystemWebhook['on'][number]): Promise<void> {
|
||||||
|
if (!id.value) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
await os.apiWithDialog('admin/system-webhook/test', {
|
||||||
|
webhookId: id.value,
|
||||||
|
type,
|
||||||
|
override: {
|
||||||
|
secret: secret.value,
|
||||||
|
url: url.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadingScope(async () => {
|
await loadingScope(async () => {
|
||||||
switch (mode.value) {
|
switch (mode.value) {
|
||||||
|
@ -235,4 +266,29 @@ onMounted(async () => {
|
||||||
-webkit-backdrop-filter: var(--blur, blur(15px));
|
-webkit-backdrop-filter: var(--blur, blur(15px));
|
||||||
backdrop-filter: var(--blur, blur(15px));
|
backdrop-filter: var(--blur, blur(15px));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.switchBox {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: start;
|
||||||
|
|
||||||
|
.testButton {
|
||||||
|
$buttonSize: 28px;
|
||||||
|
padding: 0;
|
||||||
|
width: $buttonSize;
|
||||||
|
min-width: $buttonSize;
|
||||||
|
max-width: $buttonSize;
|
||||||
|
height: $buttonSize;
|
||||||
|
margin-left: auto;
|
||||||
|
line-height: normal;
|
||||||
|
font-size: 90%;
|
||||||
|
border-radius: 9999px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: 0.85em;
|
||||||
|
padding: 8px 0 0 0;
|
||||||
|
color: var(--fgTransparentWeak);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -2,16 +2,15 @@
|
||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
|
||||||
import { StoryObj } from '@storybook/vue3';
|
import { StoryObj } from '@storybook/vue3';
|
||||||
import { expect, within } from '@storybook/test';
|
import { expect, within } from '@storybook/test';
|
||||||
import MkMisskeyFlavoredMarkdown from './MkMisskeyFlavoredMarkdown.js';
|
import MkMfm from './MkMfm.js';
|
||||||
export const Default = {
|
export const Default = {
|
||||||
render(args) {
|
render(args) {
|
||||||
return {
|
return {
|
||||||
components: {
|
components: {
|
||||||
MkMisskeyFlavoredMarkdown,
|
MkMfm,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
return {
|
return {
|
||||||
|
@ -25,7 +24,7 @@ export const Default = {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
template: '<MkMisskeyFlavoredMarkdown v-bind="props" />',
|
template: '<MkMfm v-bind="props" />',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async play({ canvasElement, args }) {
|
async play({ canvasElement, args }) {
|
||||||
|
@ -54,25 +53,25 @@ export const Default = {
|
||||||
parameters: {
|
parameters: {
|
||||||
layout: 'centered',
|
layout: 'centered',
|
||||||
},
|
},
|
||||||
} satisfies StoryObj<typeof MkMisskeyFlavoredMarkdown>;
|
} satisfies StoryObj<typeof MkMfm>;
|
||||||
export const Plain = {
|
export const Plain = {
|
||||||
...Default,
|
...Default,
|
||||||
args: {
|
args: {
|
||||||
...Default.args,
|
...Default.args,
|
||||||
plain: true,
|
plain: true,
|
||||||
},
|
},
|
||||||
} satisfies StoryObj<typeof MkMisskeyFlavoredMarkdown>;
|
} satisfies StoryObj<typeof MkMfm>;
|
||||||
export const Nowrap = {
|
export const Nowrap = {
|
||||||
...Default,
|
...Default,
|
||||||
args: {
|
args: {
|
||||||
...Default.args,
|
...Default.args,
|
||||||
nowrap: true,
|
nowrap: true,
|
||||||
},
|
},
|
||||||
} satisfies StoryObj<typeof MkMisskeyFlavoredMarkdown>;
|
} satisfies StoryObj<typeof MkMfm>;
|
||||||
export const IsNotNote = {
|
export const IsNotNote = {
|
||||||
...Default,
|
...Default,
|
||||||
args: {
|
args: {
|
||||||
...Default.args,
|
...Default.args,
|
||||||
isNote: false,
|
isNote: false,
|
||||||
},
|
},
|
||||||
} satisfies StoryObj<typeof MkMisskeyFlavoredMarkdown>;
|
} satisfies StoryObj<typeof MkMfm>;
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import { App } from 'vue';
|
import { App } from 'vue';
|
||||||
|
|
||||||
import Mfm from './global/MkMisskeyFlavoredMarkdown.js';
|
import Mfm from './global/MkMfm.js';
|
||||||
import MkA from './global/MkA.vue';
|
import MkA from './global/MkA.vue';
|
||||||
import MkAcct from './global/MkAcct.vue';
|
import MkAcct from './global/MkAcct.vue';
|
||||||
import MkAvatar from './global/MkAvatar.vue';
|
import MkAvatar from './global/MkAvatar.vue';
|
||||||
|
|
|
@ -36,6 +36,58 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
|
<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<MkFolder :defaultOpen="true">
|
||||||
|
<template #icon><i class="ti ti-bolt"></i></template>
|
||||||
|
<template #label>Misskey® Fan-out Timeline Technology™ (FTT)</template>
|
||||||
|
<template v-if="enableFanoutTimeline" #suffix>Enabled</template>
|
||||||
|
<template v-else #suffix>Disabled</template>
|
||||||
|
|
||||||
|
<div class="_gaps_m">
|
||||||
|
<MkSwitch v-model="enableFanoutTimeline">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
<template #caption>
|
||||||
|
<div>{{ i18n.ts._serverSettings.fanoutTimelineDescription }}</div>
|
||||||
|
<div><MkLink target="_blank" url="https://misskey-hub.net/docs/for-admin/features/ftt/">{{ i18n.ts.details }}</MkLink></div>
|
||||||
|
</template>
|
||||||
|
</MkSwitch>
|
||||||
|
|
||||||
|
<MkSwitch v-model="enableFanoutTimelineDbFallback">
|
||||||
|
<template #label>{{ i18n.ts._serverSettings.fanoutTimelineDbFallback }}</template>
|
||||||
|
<template #caption>{{ i18n.ts._serverSettings.fanoutTimelineDbFallbackDescription }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
|
||||||
|
<MkInput v-model="perLocalUserUserTimelineCacheMax" type="number">
|
||||||
|
<template #label>perLocalUserUserTimelineCacheMax</template>
|
||||||
|
</MkInput>
|
||||||
|
|
||||||
|
<MkInput v-model="perRemoteUserUserTimelineCacheMax" type="number">
|
||||||
|
<template #label>perRemoteUserUserTimelineCacheMax</template>
|
||||||
|
</MkInput>
|
||||||
|
|
||||||
|
<MkInput v-model="perUserHomeTimelineCacheMax" type="number">
|
||||||
|
<template #label>perUserHomeTimelineCacheMax</template>
|
||||||
|
</MkInput>
|
||||||
|
|
||||||
|
<MkInput v-model="perUserListTimelineCacheMax" type="number">
|
||||||
|
<template #label>perUserListTimelineCacheMax</template>
|
||||||
|
</MkInput>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder :defaultOpen="true">
|
||||||
|
<template #icon><i class="ti ti-bolt"></i></template>
|
||||||
|
<template #label>Misskey® Reactions Buffering Technology™ (RBT)<span class="_beta">{{ i18n.ts.beta }}</span></template>
|
||||||
|
<template v-if="enableReactionsBuffering" #suffix>Enabled</template>
|
||||||
|
<template v-else #suffix>Disabled</template>
|
||||||
|
|
||||||
|
<div class="_gaps_m">
|
||||||
|
<MkSwitch v-model="enableReactionsBuffering">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
<template #caption>{{ i18n.ts._serverSettings.reactionsBufferingDescription }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
</div>
|
</div>
|
||||||
</FormSuspense>
|
</FormSuspense>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
|
@ -52,11 +104,21 @@ import { fetchInstance } from '@/instance.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
import MkInput from '@/components/MkInput.vue';
|
||||||
|
import MkLink from '@/components/MkLink.vue';
|
||||||
|
|
||||||
const enableServerMachineStats = ref<boolean>(false);
|
const enableServerMachineStats = ref<boolean>(false);
|
||||||
const enableIdenticonGeneration = ref<boolean>(false);
|
const enableIdenticonGeneration = ref<boolean>(false);
|
||||||
const enableChartsForRemoteUser = ref<boolean>(false);
|
const enableChartsForRemoteUser = ref<boolean>(false);
|
||||||
const enableChartsForFederatedInstances = ref<boolean>(false);
|
const enableChartsForFederatedInstances = ref<boolean>(false);
|
||||||
|
const enableFanoutTimeline = ref<boolean>(false);
|
||||||
|
const enableFanoutTimelineDbFallback = ref<boolean>(false);
|
||||||
|
const perLocalUserUserTimelineCacheMax = ref<number>(0);
|
||||||
|
const perRemoteUserUserTimelineCacheMax = ref<number>(0);
|
||||||
|
const perUserHomeTimelineCacheMax = ref<number>(0);
|
||||||
|
const perUserListTimelineCacheMax = ref<number>(0);
|
||||||
|
const enableReactionsBuffering = ref<boolean>(false);
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
const meta = await misskeyApi('admin/meta');
|
const meta = await misskeyApi('admin/meta');
|
||||||
|
@ -64,6 +126,13 @@ async function init() {
|
||||||
enableIdenticonGeneration.value = meta.enableIdenticonGeneration;
|
enableIdenticonGeneration.value = meta.enableIdenticonGeneration;
|
||||||
enableChartsForRemoteUser.value = meta.enableChartsForRemoteUser;
|
enableChartsForRemoteUser.value = meta.enableChartsForRemoteUser;
|
||||||
enableChartsForFederatedInstances.value = meta.enableChartsForFederatedInstances;
|
enableChartsForFederatedInstances.value = meta.enableChartsForFederatedInstances;
|
||||||
|
enableFanoutTimeline.value = meta.enableFanoutTimeline;
|
||||||
|
enableFanoutTimelineDbFallback.value = meta.enableFanoutTimelineDbFallback;
|
||||||
|
perLocalUserUserTimelineCacheMax.value = meta.perLocalUserUserTimelineCacheMax;
|
||||||
|
perRemoteUserUserTimelineCacheMax.value = meta.perRemoteUserUserTimelineCacheMax;
|
||||||
|
perUserHomeTimelineCacheMax.value = meta.perUserHomeTimelineCacheMax;
|
||||||
|
perUserListTimelineCacheMax.value = meta.perUserListTimelineCacheMax;
|
||||||
|
enableReactionsBuffering.value = meta.enableReactionsBuffering;
|
||||||
}
|
}
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
|
@ -72,6 +141,13 @@ function save() {
|
||||||
enableIdenticonGeneration: enableIdenticonGeneration.value,
|
enableIdenticonGeneration: enableIdenticonGeneration.value,
|
||||||
enableChartsForRemoteUser: enableChartsForRemoteUser.value,
|
enableChartsForRemoteUser: enableChartsForRemoteUser.value,
|
||||||
enableChartsForFederatedInstances: enableChartsForFederatedInstances.value,
|
enableChartsForFederatedInstances: enableChartsForFederatedInstances.value,
|
||||||
|
enableFanoutTimeline: enableFanoutTimeline.value,
|
||||||
|
enableFanoutTimelineDbFallback: enableFanoutTimelineDbFallback.value,
|
||||||
|
perLocalUserUserTimelineCacheMax: perLocalUserUserTimelineCacheMax.value,
|
||||||
|
perRemoteUserUserTimelineCacheMax: perRemoteUserUserTimelineCacheMax.value,
|
||||||
|
perUserHomeTimelineCacheMax: perUserHomeTimelineCacheMax.value,
|
||||||
|
perUserListTimelineCacheMax: perUserListTimelineCacheMax.value,
|
||||||
|
enableReactionsBuffering: enableReactionsBuffering.value,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
fetchInstance(true);
|
fetchInstance(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -590,6 +590,106 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkRange>
|
</MkRange>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canImportAntennas, 'canImportAntennas'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.canImportAntennas }}</template>
|
||||||
|
<template #suffix>
|
||||||
|
<span v-if="role.policies.canImportAntennas.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||||
|
<span v-else>{{ role.policies.canImportAntennas.value ? i18n.ts.yes : i18n.ts.no }}</span>
|
||||||
|
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canImportAntennas)"></i></span>
|
||||||
|
</template>
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkSwitch v-model="role.policies.canImportAntennas.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkSwitch v-model="role.policies.canImportAntennas.value" :disabled="role.policies.canImportAntennas.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkRange v-model="role.policies.canImportAntennas.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||||
|
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||||
|
</MkRange>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canImportBlocking, 'canImportBlocking'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.canImportBlocking }}</template>
|
||||||
|
<template #suffix>
|
||||||
|
<span v-if="role.policies.canImportBlocking.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||||
|
<span v-else>{{ role.policies.canImportBlocking.value ? i18n.ts.yes : i18n.ts.no }}</span>
|
||||||
|
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canImportBlocking)"></i></span>
|
||||||
|
</template>
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkSwitch v-model="role.policies.canImportBlocking.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkSwitch v-model="role.policies.canImportBlocking.value" :disabled="role.policies.canImportBlocking.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkRange v-model="role.policies.canImportBlocking.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||||
|
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||||
|
</MkRange>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canImportFollowing, 'canImportFollowing'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.canImportFollowing }}</template>
|
||||||
|
<template #suffix>
|
||||||
|
<span v-if="role.policies.canImportFollowing.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||||
|
<span v-else>{{ role.policies.canImportFollowing.value ? i18n.ts.yes : i18n.ts.no }}</span>
|
||||||
|
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canImportFollowing)"></i></span>
|
||||||
|
</template>
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkSwitch v-model="role.policies.canImportFollowing.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkSwitch v-model="role.policies.canImportFollowing.value" :disabled="role.policies.canImportFollowing.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkRange v-model="role.policies.canImportFollowing.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||||
|
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||||
|
</MkRange>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canImportMuting, 'canImportMuting'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.canImportMuting }}</template>
|
||||||
|
<template #suffix>
|
||||||
|
<span v-if="role.policies.canImportMuting.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||||
|
<span v-else>{{ role.policies.canImportMuting.value ? i18n.ts.yes : i18n.ts.no }}</span>
|
||||||
|
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canImportMuting)"></i></span>
|
||||||
|
</template>
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkSwitch v-model="role.policies.canImportMuting.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkSwitch v-model="role.policies.canImportMuting.value" :disabled="role.policies.canImportMuting.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkRange v-model="role.policies.canImportMuting.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||||
|
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||||
|
</MkRange>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canImportUserLists, 'canImportUserLists'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.canImportUserLists }}</template>
|
||||||
|
<template #suffix>
|
||||||
|
<span v-if="role.policies.canImportUserLists.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||||
|
<span v-else>{{ role.policies.canImportUserLists.value ? i18n.ts.yes : i18n.ts.no }}</span>
|
||||||
|
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canImportUserLists)"></i></span>
|
||||||
|
</template>
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkSwitch v-model="role.policies.canImportUserLists.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkSwitch v-model="role.policies.canImportUserLists.value" :disabled="role.policies.canImportUserLists.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkRange v-model="role.policies.canImportUserLists.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||||
|
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||||
|
</MkRange>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
</div>
|
</div>
|
||||||
</FormSlot>
|
</FormSlot>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -214,6 +214,46 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkInput>
|
</MkInput>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canImportAntennas, 'canImportAntennas'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.canImportAntennas }}</template>
|
||||||
|
<template #suffix>{{ policies.canImportAntennas ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
|
<MkSwitch v-model="policies.canImportAntennas">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canImportBlocking, 'canImportBlocking'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.canImportBlocking }}</template>
|
||||||
|
<template #suffix>{{ policies.canImportBlocking ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
|
<MkSwitch v-model="policies.canImportBlocking">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canImportFollowing, 'canImportFollowing'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.canImportFollowing }}</template>
|
||||||
|
<template #suffix>{{ policies.canImportFollowing ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
|
<MkSwitch v-model="policies.canImportFollowing">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canImportMuting, 'canImportMuting'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.canImportMuting }}</template>
|
||||||
|
<template #suffix>{{ policies.canImportMuting ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
|
<MkSwitch v-model="policies.canImportMuting">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canImportUserLists, 'canImportUserList'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.canImportUserLists }}</template>
|
||||||
|
<template #suffix>{{ policies.canImportUserLists ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
|
<MkSwitch v-model="policies.canImportUserLists">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
<MkButton primary rounded @click="updateBaseRole">{{ i18n.ts.save }}</MkButton>
|
<MkButton primary rounded @click="updateBaseRole">{{ i18n.ts.save }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
|
@ -96,38 +96,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
||||||
<FormSection>
|
|
||||||
<template #label>Misskey® Fan-out Timeline Technology™ (FTT)</template>
|
|
||||||
|
|
||||||
<div class="_gaps_m">
|
|
||||||
<MkSwitch v-model="enableFanoutTimeline">
|
|
||||||
<template #label>{{ i18n.ts.enable }}</template>
|
|
||||||
<template #caption>{{ i18n.ts._serverSettings.fanoutTimelineDescription }}</template>
|
|
||||||
</MkSwitch>
|
|
||||||
|
|
||||||
<MkSwitch v-model="enableFanoutTimelineDbFallback">
|
|
||||||
<template #label>{{ i18n.ts._serverSettings.fanoutTimelineDbFallback }}</template>
|
|
||||||
<template #caption>{{ i18n.ts._serverSettings.fanoutTimelineDbFallbackDescription }}</template>
|
|
||||||
</MkSwitch>
|
|
||||||
|
|
||||||
<MkInput v-model="perLocalUserUserTimelineCacheMax" type="number">
|
|
||||||
<template #label>perLocalUserUserTimelineCacheMax</template>
|
|
||||||
</MkInput>
|
|
||||||
|
|
||||||
<MkInput v-model="perRemoteUserUserTimelineCacheMax" type="number">
|
|
||||||
<template #label>perRemoteUserUserTimelineCacheMax</template>
|
|
||||||
</MkInput>
|
|
||||||
|
|
||||||
<MkInput v-model="perUserHomeTimelineCacheMax" type="number">
|
|
||||||
<template #label>perUserHomeTimelineCacheMax</template>
|
|
||||||
</MkInput>
|
|
||||||
|
|
||||||
<MkInput v-model="perUserListTimelineCacheMax" type="number">
|
|
||||||
<template #label>perUserListTimelineCacheMax</template>
|
|
||||||
</MkInput>
|
|
||||||
</div>
|
|
||||||
</FormSection>
|
|
||||||
|
|
||||||
<FormSection>
|
<FormSection>
|
||||||
<template #label>{{ i18n.ts._ad.adsSettings }}</template>
|
<template #label>{{ i18n.ts._ad.adsSettings }}</template>
|
||||||
|
|
||||||
|
@ -236,12 +204,6 @@ const cacheRemoteSensitiveFiles = ref<boolean>(false);
|
||||||
const enableServiceWorker = ref<boolean>(false);
|
const enableServiceWorker = ref<boolean>(false);
|
||||||
const swPublicKey = ref<string | null>(null);
|
const swPublicKey = ref<string | null>(null);
|
||||||
const swPrivateKey = ref<string | null>(null);
|
const swPrivateKey = ref<string | null>(null);
|
||||||
const enableFanoutTimeline = ref<boolean>(false);
|
|
||||||
const enableFanoutTimelineDbFallback = ref<boolean>(false);
|
|
||||||
const perLocalUserUserTimelineCacheMax = ref<number>(0);
|
|
||||||
const perRemoteUserUserTimelineCacheMax = ref<number>(0);
|
|
||||||
const perUserHomeTimelineCacheMax = ref<number>(0);
|
|
||||||
const perUserListTimelineCacheMax = ref<number>(0);
|
|
||||||
const notesPerOneAd = ref<number>(0);
|
const notesPerOneAd = ref<number>(0);
|
||||||
const urlPreviewEnabled = ref<boolean>(true);
|
const urlPreviewEnabled = ref<boolean>(true);
|
||||||
const urlPreviewTimeout = ref<number>(10000);
|
const urlPreviewTimeout = ref<number>(10000);
|
||||||
|
@ -265,12 +227,6 @@ async function init(): Promise<void> {
|
||||||
enableServiceWorker.value = meta.enableServiceWorker;
|
enableServiceWorker.value = meta.enableServiceWorker;
|
||||||
swPublicKey.value = meta.swPublickey;
|
swPublicKey.value = meta.swPublickey;
|
||||||
swPrivateKey.value = meta.swPrivateKey;
|
swPrivateKey.value = meta.swPrivateKey;
|
||||||
enableFanoutTimeline.value = meta.enableFanoutTimeline;
|
|
||||||
enableFanoutTimelineDbFallback.value = meta.enableFanoutTimelineDbFallback;
|
|
||||||
perLocalUserUserTimelineCacheMax.value = meta.perLocalUserUserTimelineCacheMax;
|
|
||||||
perRemoteUserUserTimelineCacheMax.value = meta.perRemoteUserUserTimelineCacheMax;
|
|
||||||
perUserHomeTimelineCacheMax.value = meta.perUserHomeTimelineCacheMax;
|
|
||||||
perUserListTimelineCacheMax.value = meta.perUserListTimelineCacheMax;
|
|
||||||
notesPerOneAd.value = meta.notesPerOneAd;
|
notesPerOneAd.value = meta.notesPerOneAd;
|
||||||
urlPreviewEnabled.value = meta.urlPreviewEnabled;
|
urlPreviewEnabled.value = meta.urlPreviewEnabled;
|
||||||
urlPreviewTimeout.value = meta.urlPreviewTimeout;
|
urlPreviewTimeout.value = meta.urlPreviewTimeout;
|
||||||
|
@ -295,12 +251,6 @@ async function save() {
|
||||||
enableServiceWorker: enableServiceWorker.value,
|
enableServiceWorker: enableServiceWorker.value,
|
||||||
swPublicKey: swPublicKey.value,
|
swPublicKey: swPublicKey.value,
|
||||||
swPrivateKey: swPrivateKey.value,
|
swPrivateKey: swPrivateKey.value,
|
||||||
enableFanoutTimeline: enableFanoutTimeline.value,
|
|
||||||
enableFanoutTimelineDbFallback: enableFanoutTimelineDbFallback.value,
|
|
||||||
perLocalUserUserTimelineCacheMax: perLocalUserUserTimelineCacheMax.value,
|
|
||||||
perRemoteUserUserTimelineCacheMax: perRemoteUserUserTimelineCacheMax.value,
|
|
||||||
perUserHomeTimelineCacheMax: perUserHomeTimelineCacheMax.value,
|
|
||||||
perUserListTimelineCacheMax: perUserListTimelineCacheMax.value,
|
|
||||||
notesPerOneAd: notesPerOneAd.value,
|
notesPerOneAd: notesPerOneAd.value,
|
||||||
urlPreviewEnabled: urlPreviewEnabled.value,
|
urlPreviewEnabled: urlPreviewEnabled.value,
|
||||||
urlPreviewTimeout: urlPreviewTimeout.value,
|
urlPreviewTimeout: urlPreviewTimeout.value,
|
||||||
|
|
|
@ -4,33 +4,42 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="$style.main">
|
<MkFolder>
|
||||||
<span :class="$style.icon">
|
<template #label>{{ entity.name || entity.url }}</template>
|
||||||
<i v-if="!entity.isActive" class="ti ti-player-pause"/>
|
<template #icon>
|
||||||
<i v-else-if="entity.latestStatus === null" class="ti ti-circle"/>
|
<i v-if="!entity.isActive" class="ti ti-player-pause"/>
|
||||||
<i
|
<i v-else-if="entity.latestStatus === null" class="ti ti-circle"/>
|
||||||
v-else-if="[200, 201, 204].includes(entity.latestStatus)"
|
<i
|
||||||
class="ti ti-check"
|
v-else-if="[200, 201, 204].includes(entity.latestStatus)"
|
||||||
:style="{ color: 'var(--success)' }"
|
class="ti ti-check"
|
||||||
/>
|
:style="{ color: 'var(--success)' }"
|
||||||
<i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--error)' }"/>
|
/>
|
||||||
</span>
|
<i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--error)' }"/>
|
||||||
<span :class="$style.text">{{ entity.name || entity.url }}</span>
|
</template>
|
||||||
<span :class="$style.suffix">
|
<template #suffix>
|
||||||
<MkTime v-if="entity.latestSentAt" :time="entity.latestSentAt" style="margin-right: 8px"/>
|
<MkTime v-if="entity.latestSentAt" :time="entity.latestSentAt" style="margin-right: 8px"/>
|
||||||
<button :class="$style.suffixButton" @click="onEditClick">
|
<span v-else>-</span>
|
||||||
<i class="ti ti-settings"></i>
|
</template>
|
||||||
</button>
|
|
||||||
<button :class="$style.suffixButton" @click="onDeleteClick">
|
<div>
|
||||||
<i class="ti ti-trash"></i>
|
<div class="_buttons">
|
||||||
</button>
|
<MkButton @click="onEditClick">
|
||||||
</span>
|
<i class="ti ti-settings"></i> {{ i18n.ts.edit }}
|
||||||
</div>
|
</MkButton>
|
||||||
|
<MkButton danger @click="onDeleteClick">
|
||||||
|
<i class="ti ti-trash"></i> {{ i18n.ts.delete }}
|
||||||
|
</MkButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { entities } from 'misskey-js';
|
import { entities } from 'misskey-js';
|
||||||
import { toRefs } from 'vue';
|
import { toRefs } from 'vue';
|
||||||
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'edit', value: entities.SystemWebhook): void;
|
(ev: 'edit', value: entities.SystemWebhook): void;
|
||||||
|
@ -54,64 +63,10 @@ function onDeleteClick() {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style module lang="scss">
|
<style module lang="scss">
|
||||||
.main {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 10px 14px;
|
|
||||||
background: var(--buttonBg);
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
background: var(--buttonHoverBg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: var(--accent);
|
|
||||||
background: var(--buttonHoverBg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
margin-right: 0.75em;
|
margin-right: 0.75em;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--fgTransparentWeak);
|
color: var(--fgTransparentWeak);
|
||||||
}
|
}
|
||||||
|
|
||||||
.text {
|
|
||||||
flex-shrink: 1;
|
|
||||||
white-space: normal;
|
|
||||||
padding-right: 12px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.suffix {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gaps: 4px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: -8px;
|
|
||||||
opacity: 0.7;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.suffixButton {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
border-radius: 9999px;
|
|
||||||
margin-top: -8px;
|
|
||||||
margin-bottom: -8px;
|
|
||||||
padding: 8px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--buttonBg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -11,8 +11,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<MkSpacer :contentMax="900">
|
<MkSpacer :contentMax="900">
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<MkButton :class="$style.linkButton" full @click="onCreateWebhookClicked">
|
<MkButton primary @click="onCreateWebhookClicked">
|
||||||
{{ i18n.ts._webhookSettings.createWebhook }}
|
<i class="ti ti-plus"></i> {{ i18n.ts._webhookSettings.createWebhook }}
|
||||||
</MkButton>
|
</MkButton>
|
||||||
|
|
||||||
<FormSection>
|
<FormSection>
|
||||||
|
@ -89,8 +89,5 @@ definePageMetadata(() => ({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style module lang="scss">
|
<style module lang="scss">
|
||||||
.linkButton {
|
|
||||||
text-align: left;
|
|
||||||
padding: 10px 18px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -344,6 +344,7 @@ definePageMetadata(() => ({
|
||||||
> .img {
|
> .img {
|
||||||
width: 42px;
|
width: 42px;
|
||||||
height: 42px;
|
height: 42px;
|
||||||
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .body {
|
> .body {
|
||||||
|
@ -390,6 +391,7 @@ definePageMetadata(() => ({
|
||||||
> .img {
|
> .img {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .body {
|
> .body {
|
||||||
|
|
|
@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
</button>
|
</button>
|
||||||
<button class="_button" :class="$style.kvEditBtn" @click="describe()">
|
<button class="_button" :class="$style.kvEditBtn" @click="describe()">
|
||||||
<MkKeyValue>
|
<MkKeyValue :class="$style.multiline">
|
||||||
<template #key>{{ i18n.ts.description }}</template>
|
<template #key>{{ i18n.ts.description }}</template>
|
||||||
<template #value>{{ file.comment ? file.comment : `(${i18n.ts.none})` }}<i class="ti ti-pencil" :class="$style.kvEditIcon"></i></template>
|
<template #value>{{ file.comment ? file.comment : `(${i18n.ts.none})` }}<i class="ti ti-pencil" :class="$style.kvEditIcon"></i></template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
|
@ -313,6 +313,10 @@ onMounted(async () => {
|
||||||
padding: .5rem 1rem;
|
padding: .5rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.multiline {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.kvEditBtn {
|
.kvEditBtn {
|
||||||
text-align: start;
|
text-align: start;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -30,6 +30,24 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</MkContainer>
|
</MkContainer>
|
||||||
|
|
||||||
|
<MkContainer :foldable="true" :expanded="false">
|
||||||
|
<template #header>{{ i18n.ts.uiInspector }}</template>
|
||||||
|
<div :class="$style.uiInspector">
|
||||||
|
<div v-for="c in components" :key="c.value.id">
|
||||||
|
<div :class="$style.uiInspectorType">{{ c.value.type }}</div>
|
||||||
|
<div :class="$style.uiInspectorId">{{ c.value.id }}</div>
|
||||||
|
<button :class="$style.uiInspectorPropsToggle" @click="() => uiInspectorOpenedComponents.set(c, !uiInspectorOpenedComponents.get(c))">
|
||||||
|
<i v-if="uiInspectorOpenedComponents.get(c)" class="ti ti-chevron-up icon"></i>
|
||||||
|
<i v-else class="ti ti-chevron-down icon"></i>
|
||||||
|
</button>
|
||||||
|
<div v-if="uiInspectorOpenedComponents.get(c)">
|
||||||
|
<MkTextarea :modelValue="stringifyUiProps(c.value)" code readonly></MkTextarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.uiInspectorDescription">{{ i18n.ts.uiInspectorDescription }}</div>
|
||||||
|
</div>
|
||||||
|
</MkContainer>
|
||||||
|
|
||||||
<div class="">
|
<div class="">
|
||||||
{{ i18n.ts.scratchpadDescription }}
|
{{ i18n.ts.scratchpadDescription }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,6 +61,7 @@ import { onDeactivated, onUnmounted, Ref, ref, watch, computed } from 'vue';
|
||||||
import { Interpreter, Parser, utils } from '@syuilo/aiscript';
|
import { Interpreter, Parser, utils } from '@syuilo/aiscript';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
import MkTextarea from '@/components/MkTextarea.vue';
|
||||||
import MkCodeEditor from '@/components/MkCodeEditor.vue';
|
import MkCodeEditor from '@/components/MkCodeEditor.vue';
|
||||||
import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
|
import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
@ -61,6 +80,7 @@ const logs = ref<any[]>([]);
|
||||||
const root = ref<AsUiRoot>();
|
const root = ref<AsUiRoot>();
|
||||||
const components = ref<Ref<AsUiComponent>[]>([]);
|
const components = ref<Ref<AsUiComponent>[]>([]);
|
||||||
const uiKey = ref(0);
|
const uiKey = ref(0);
|
||||||
|
const uiInspectorOpenedComponents = ref(new Map<string, boolean>);
|
||||||
|
|
||||||
const saved = miLocalStorage.getItem('scratchpad');
|
const saved = miLocalStorage.getItem('scratchpad');
|
||||||
if (saved) {
|
if (saved) {
|
||||||
|
@ -71,6 +91,14 @@ watch(code, () => {
|
||||||
miLocalStorage.setItem('scratchpad', code.value);
|
miLocalStorage.setItem('scratchpad', code.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function stringifyUiProps(uiProps) {
|
||||||
|
return JSON.stringify(
|
||||||
|
{ ...uiProps, type: undefined, id: undefined },
|
||||||
|
(k, v) => typeof v === 'function' ? '<function>' : v,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
if (aiscript) aiscript.abort();
|
if (aiscript) aiscript.abort();
|
||||||
root.value = undefined;
|
root.value = undefined;
|
||||||
|
@ -192,4 +220,35 @@ definePageMetadata(() => ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.uiInspector {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uiInspectorType {
|
||||||
|
display: inline-block;
|
||||||
|
border: hidden;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: var(--panelHighlight);
|
||||||
|
padding: 2px 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uiInspectorId {
|
||||||
|
display: inline-block;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uiInspectorDescription {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uiInspectorPropsToggle {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -258,7 +258,7 @@ import { langs } from '@@/js/config.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
|
@ -270,16 +270,6 @@ const fontSize = ref(miLocalStorage.getItem('fontSize'));
|
||||||
const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
|
const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
|
||||||
const dataSaver = ref(defaultStore.state.dataSaver);
|
const dataSaver = ref(defaultStore.state.dataSaver);
|
||||||
|
|
||||||
async function reloadAsk() {
|
|
||||||
const { canceled } = await os.confirm({
|
|
||||||
type: 'info',
|
|
||||||
text: i18n.ts.reloadToApplySetting,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
unisonReload();
|
|
||||||
}
|
|
||||||
|
|
||||||
const hemisphere = computed(defaultStore.makeGetterSetter('hemisphere'));
|
const hemisphere = computed(defaultStore.makeGetterSetter('hemisphere'));
|
||||||
const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind'));
|
const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind'));
|
||||||
const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior'));
|
const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior'));
|
||||||
|
@ -369,7 +359,7 @@ watch([
|
||||||
confirmWhenRevealingSensitiveMedia,
|
confirmWhenRevealingSensitiveMedia,
|
||||||
contextMenu,
|
contextMenu,
|
||||||
], async () => {
|
], async () => {
|
||||||
await reloadAsk();
|
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
const emojiIndexLangs = ['en-US', 'ja-JP', 'ja-JP_hira'] as const;
|
const emojiIndexLangs = ['en-US', 'ja-JP', 'ja-JP_hira'] as const;
|
||||||
|
|
|
@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkButton primary :class="$style.button" inline @click="exportFollowing()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
<MkButton primary :class="$style.button" inline @click="exportFollowing()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
<MkFolder v-if="$i && !$i.movedTo">
|
<MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportFollowing">
|
||||||
<template #label>{{ i18n.ts.import }}</template>
|
<template #label>{{ i18n.ts.import }}</template>
|
||||||
<template #icon><i class="ti ti-upload"></i></template>
|
<template #icon><i class="ti ti-upload"></i></template>
|
||||||
<MkSwitch v-model="withReplies">
|
<MkSwitch v-model="withReplies">
|
||||||
|
@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #icon><i class="ti ti-download"></i></template>
|
<template #icon><i class="ti ti-download"></i></template>
|
||||||
<MkButton primary :class="$style.button" inline @click="exportUserLists()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
<MkButton primary :class="$style.button" inline @click="exportUserLists()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
<MkFolder v-if="$i && !$i.movedTo">
|
<MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportUserLists">
|
||||||
<template #label>{{ i18n.ts.import }}</template>
|
<template #label>{{ i18n.ts.import }}</template>
|
||||||
<template #icon><i class="ti ti-upload"></i></template>
|
<template #icon><i class="ti ti-upload"></i></template>
|
||||||
<MkButton primary :class="$style.button" inline @click="importUserLists($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
|
<MkButton primary :class="$style.button" inline @click="importUserLists($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
|
||||||
|
@ -78,7 +78,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #icon><i class="ti ti-download"></i></template>
|
<template #icon><i class="ti ti-download"></i></template>
|
||||||
<MkButton primary :class="$style.button" inline @click="exportMuting()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
<MkButton primary :class="$style.button" inline @click="exportMuting()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
<MkFolder v-if="$i && !$i.movedTo">
|
<MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportMuting">
|
||||||
<template #label>{{ i18n.ts.import }}</template>
|
<template #label>{{ i18n.ts.import }}</template>
|
||||||
<template #icon><i class="ti ti-upload"></i></template>
|
<template #icon><i class="ti ti-upload"></i></template>
|
||||||
<MkButton primary :class="$style.button" inline @click="importMuting($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
|
<MkButton primary :class="$style.button" inline @click="importMuting($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
|
||||||
|
@ -93,7 +93,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #icon><i class="ti ti-download"></i></template>
|
<template #icon><i class="ti ti-download"></i></template>
|
||||||
<MkButton primary :class="$style.button" inline @click="exportBlocking()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
<MkButton primary :class="$style.button" inline @click="exportBlocking()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
<MkFolder v-if="$i && !$i.movedTo">
|
<MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportBlocking">
|
||||||
<template #label>{{ i18n.ts.import }}</template>
|
<template #label>{{ i18n.ts.import }}</template>
|
||||||
<template #icon><i class="ti ti-upload"></i></template>
|
<template #icon><i class="ti ti-upload"></i></template>
|
||||||
<MkButton primary :class="$style.button" inline @click="importBlocking($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
|
<MkButton primary :class="$style.button" inline @click="importBlocking($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
|
||||||
|
@ -108,7 +108,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #icon><i class="ti ti-download"></i></template>
|
<template #icon><i class="ti ti-download"></i></template>
|
||||||
<MkButton primary :class="$style.button" inline @click="exportAntennas()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
<MkButton primary :class="$style.button" inline @click="exportAntennas()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
<MkFolder v-if="$i && !$i.movedTo">
|
<MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportAntennas">
|
||||||
<template #label>{{ i18n.ts.import }}</template>
|
<template #label>{{ i18n.ts.import }}</template>
|
||||||
<template #icon><i class="ti ti-upload"></i></template>
|
<template #icon><i class="ti ti-upload"></i></template>
|
||||||
<MkButton primary :class="$style.button" inline @click="importAntennas($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
|
<MkButton primary :class="$style.button" inline @click="importAntennas($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
|
||||||
|
|
|
@ -54,7 +54,7 @@ import MkContainer from '@/components/MkContainer.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { navbarItemDef } from '@/navbar.js';
|
import { navbarItemDef } from '@/navbar.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
|
||||||
|
@ -67,16 +67,6 @@ const items = ref(defaultStore.state.menu.map(x => ({
|
||||||
|
|
||||||
const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
|
const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
|
||||||
|
|
||||||
async function reloadAsk() {
|
|
||||||
const { canceled } = await os.confirm({
|
|
||||||
type: 'info',
|
|
||||||
text: i18n.ts.reloadToApplySetting,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
unisonReload();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addItem() {
|
async function addItem() {
|
||||||
const menu = Object.keys(navbarItemDef).filter(k => !defaultStore.state.menu.includes(k));
|
const menu = Object.keys(navbarItemDef).filter(k => !defaultStore.state.menu.includes(k));
|
||||||
const { canceled, result: item } = await os.select({
|
const { canceled, result: item } = await os.select({
|
||||||
|
@ -100,7 +90,7 @@ function removeItem(index: number) {
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
defaultStore.set('menu', items.value.map(x => x.type));
|
defaultStore.set('menu', items.value.map(x => x.type));
|
||||||
await reloadAsk();
|
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
|
@ -111,7 +101,7 @@ function reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(menuDisplay, async () => {
|
watch(menuDisplay, async () => {
|
||||||
await reloadAsk();
|
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
|
@ -98,7 +98,7 @@ import { defaultStore } from '@/store.js';
|
||||||
import { signout, signinRequired } from '@/account.js';
|
import { signout, signinRequired } from '@/account.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||||
import FormSection from '@/components/form/section.vue';
|
import FormSection from '@/components/form/section.vue';
|
||||||
|
|
||||||
const $i = signinRequired();
|
const $i = signinRequired();
|
||||||
|
@ -132,16 +132,6 @@ async function deleteAccount() {
|
||||||
await signout();
|
await signout();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reloadAsk() {
|
|
||||||
const { canceled } = await os.confirm({
|
|
||||||
type: 'info',
|
|
||||||
text: i18n.ts.reloadToApplySetting,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
unisonReload();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateRepliesAll(withReplies: boolean) {
|
async function updateRepliesAll(withReplies: boolean) {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
|
@ -155,7 +145,7 @@ async function updateRepliesAll(withReplies: boolean) {
|
||||||
watch([
|
watch([
|
||||||
enableCondensedLineForAcct,
|
enableCondensedLineForAcct,
|
||||||
], async () => {
|
], async () => {
|
||||||
await reloadAsk();
|
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
|
@ -88,19 +88,9 @@ import { uniqueBy } from '@/scripts/array.js';
|
||||||
import { fetchThemes, getThemes } from '@/theme-store.js';
|
import { fetchThemes, getThemes } from '@/theme-store.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
|
||||||
async function reloadAsk() {
|
|
||||||
const { canceled } = await os.confirm({
|
|
||||||
type: 'info',
|
|
||||||
text: i18n.ts.reloadToApplySetting,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
unisonReload();
|
|
||||||
}
|
|
||||||
|
|
||||||
const installedThemes = ref(getThemes());
|
const installedThemes = ref(getThemes());
|
||||||
const builtinThemes = getBuiltinThemesRef();
|
const builtinThemes = getBuiltinThemesRef();
|
||||||
|
|
||||||
|
@ -148,13 +138,13 @@ watch(syncDeviceDarkMode, () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(wallpaper, () => {
|
watch(wallpaper, async () => {
|
||||||
if (wallpaper.value == null) {
|
if (wallpaper.value == null) {
|
||||||
miLocalStorage.removeItem('wallpaper');
|
miLocalStorage.removeItem('wallpaper');
|
||||||
} else {
|
} else {
|
||||||
miLocalStorage.setItem('wallpaper', wallpaper.value);
|
miLocalStorage.setItem('wallpaper', wallpaper.value);
|
||||||
}
|
}
|
||||||
reloadAsk();
|
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
onActivated(() => {
|
onActivated(() => {
|
||||||
|
|
|
@ -21,14 +21,41 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<FormSection>
|
<FormSection>
|
||||||
<template #label>{{ i18n.ts._webhookSettings.trigger }}</template>
|
<template #label>{{ i18n.ts._webhookSettings.trigger }}</template>
|
||||||
|
|
||||||
<div class="_gaps_s">
|
<div class="_gaps">
|
||||||
<MkSwitch v-model="event_follow">{{ i18n.ts._webhookSettings._events.follow }}</MkSwitch>
|
<div class="_gaps_s">
|
||||||
<MkSwitch v-model="event_followed">{{ i18n.ts._webhookSettings._events.followed }}</MkSwitch>
|
<div :class="$style.switchBox">
|
||||||
<MkSwitch v-model="event_note">{{ i18n.ts._webhookSettings._events.note }}</MkSwitch>
|
<MkSwitch v-model="event_follow">{{ i18n.ts._webhookSettings._events.follow }}</MkSwitch>
|
||||||
<MkSwitch v-model="event_reply">{{ i18n.ts._webhookSettings._events.reply }}</MkSwitch>
|
<MkButton transparent :class="$style.testButton" :disabled="!(active && event_follow)" @click="test('follow')"><i class="ti ti-send"></i></MkButton>
|
||||||
<MkSwitch v-model="event_renote">{{ i18n.ts._webhookSettings._events.renote }}</MkSwitch>
|
</div>
|
||||||
<MkSwitch v-model="event_reaction">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch>
|
<div :class="$style.switchBox">
|
||||||
<MkSwitch v-model="event_mention">{{ i18n.ts._webhookSettings._events.mention }}</MkSwitch>
|
<MkSwitch v-model="event_followed">{{ i18n.ts._webhookSettings._events.followed }}</MkSwitch>
|
||||||
|
<MkButton transparent :class="$style.testButton" :disabled="!(active && event_followed)" @click="test('followed')"><i class="ti ti-send"></i></MkButton>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.switchBox">
|
||||||
|
<MkSwitch v-model="event_note">{{ i18n.ts._webhookSettings._events.note }}</MkSwitch>
|
||||||
|
<MkButton transparent :class="$style.testButton" :disabled="!(active && event_note)" @click="test('note')"><i class="ti ti-send"></i></MkButton>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.switchBox">
|
||||||
|
<MkSwitch v-model="event_reply">{{ i18n.ts._webhookSettings._events.reply }}</MkSwitch>
|
||||||
|
<MkButton transparent :class="$style.testButton" :disabled="!(active && event_reply)" @click="test('reply')"><i class="ti ti-send"></i></MkButton>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.switchBox">
|
||||||
|
<MkSwitch v-model="event_renote">{{ i18n.ts._webhookSettings._events.renote }}</MkSwitch>
|
||||||
|
<MkButton transparent :class="$style.testButton" :disabled="!(active && event_renote)" @click="test('renote')"><i class="ti ti-send"></i></MkButton>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.switchBox">
|
||||||
|
<MkSwitch v-model="event_reaction">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch>
|
||||||
|
<MkButton transparent :class="$style.testButton" :disabled="!(active && event_reaction)" @click="test('reaction')"><i class="ti ti-send"></i></MkButton>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.switchBox">
|
||||||
|
<MkSwitch v-model="event_mention">{{ i18n.ts._webhookSettings._events.mention }}</MkSwitch>
|
||||||
|
<MkButton transparent :class="$style.testButton" :disabled="!(active && event_mention)" @click="test('mention')"><i class="ti ti-send"></i></MkButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :class="$style.description">
|
||||||
|
{{ i18n.ts._webhookSettings.testRemarks }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
||||||
|
@ -43,6 +70,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import FormSection from '@/components/form/section.vue';
|
import FormSection from '@/components/form/section.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
|
@ -76,8 +104,8 @@ const event_renote = ref(webhook.on.includes('renote'));
|
||||||
const event_reaction = ref(webhook.on.includes('reaction'));
|
const event_reaction = ref(webhook.on.includes('reaction'));
|
||||||
const event_mention = ref(webhook.on.includes('mention'));
|
const event_mention = ref(webhook.on.includes('mention'));
|
||||||
|
|
||||||
async function save(): Promise<void> {
|
function save() {
|
||||||
const events = [];
|
const events: Misskey.entities.UserWebhook['on'] = [];
|
||||||
if (event_follow.value) events.push('follow');
|
if (event_follow.value) events.push('follow');
|
||||||
if (event_followed.value) events.push('followed');
|
if (event_followed.value) events.push('followed');
|
||||||
if (event_note.value) events.push('note');
|
if (event_note.value) events.push('note');
|
||||||
|
@ -110,8 +138,21 @@ async function del(): Promise<void> {
|
||||||
router.push('/settings/webhook');
|
router.push('/settings/webhook');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function test(type: Misskey.entities.UserWebhook['on'][number]): Promise<void> {
|
||||||
|
await os.apiWithDialog('i/webhooks/test', {
|
||||||
|
webhookId: props.webhookId,
|
||||||
|
type,
|
||||||
|
override: {
|
||||||
|
secret: secret.value,
|
||||||
|
url: url.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePageMetadata(() => ({
|
definePageMetadata(() => ({
|
||||||
|
@ -119,3 +160,30 @@ definePageMetadata(() => ({
|
||||||
icon: 'ti ti-webhook',
|
icon: 'ti ti-webhook',
|
||||||
}));
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style module lang="scss">
|
||||||
|
.switchBox {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: start;
|
||||||
|
|
||||||
|
.testButton {
|
||||||
|
$buttonSize: 28px;
|
||||||
|
padding: 0;
|
||||||
|
width: $buttonSize;
|
||||||
|
min-width: $buttonSize;
|
||||||
|
max-width: $buttonSize;
|
||||||
|
height: $buttonSize;
|
||||||
|
margin-left: auto;
|
||||||
|
line-height: inherit;
|
||||||
|
font-size: 90%;
|
||||||
|
border-radius: 9999px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: 0.85em;
|
||||||
|
padding: 8px 0 0 0;
|
||||||
|
color: var(--fgTransparentWeak);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
40
packages/frontend/src/scripts/reload-ask.ts
Normal file
40
packages/frontend/src/scripts/reload-ask.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import * as os from '@/os.js';
|
||||||
|
import { unisonReload } from '@/scripts/unison-reload.js';
|
||||||
|
|
||||||
|
let isReloadConfirming = false;
|
||||||
|
|
||||||
|
export async function reloadAsk(opts: {
|
||||||
|
unison?: boolean;
|
||||||
|
reason?: string;
|
||||||
|
}) {
|
||||||
|
if (isReloadConfirming) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isReloadConfirming = true;
|
||||||
|
|
||||||
|
const { canceled } = await os.confirm(opts.reason == null ? {
|
||||||
|
type: 'info',
|
||||||
|
text: i18n.ts.reloadConfirm,
|
||||||
|
} : {
|
||||||
|
type: 'info',
|
||||||
|
title: i18n.ts.reloadConfirm,
|
||||||
|
text: opts.reason,
|
||||||
|
}).finally(() => {
|
||||||
|
isReloadConfirming = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
|
if (opts.unison) {
|
||||||
|
unisonReload();
|
||||||
|
} else {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
}
|
|
@ -358,6 +358,9 @@ type AdminSystemWebhookShowRequest = operations['admin___system-webhook___show']
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type AdminSystemWebhookShowResponse = operations['admin___system-webhook___show']['responses']['200']['content']['application/json'];
|
type AdminSystemWebhookShowResponse = operations['admin___system-webhook___show']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type AdminSystemWebhookTestRequest = operations['admin___system-webhook___test']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type AdminSystemWebhookUpdateRequest = operations['admin___system-webhook___update']['requestBody']['content']['application/json'];
|
type AdminSystemWebhookUpdateRequest = operations['admin___system-webhook___update']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
@ -1308,6 +1311,7 @@ declare namespace entities {
|
||||||
AdminSystemWebhookShowResponse,
|
AdminSystemWebhookShowResponse,
|
||||||
AdminSystemWebhookUpdateRequest,
|
AdminSystemWebhookUpdateRequest,
|
||||||
AdminSystemWebhookUpdateResponse,
|
AdminSystemWebhookUpdateResponse,
|
||||||
|
AdminSystemWebhookTestRequest,
|
||||||
AnnouncementsRequest,
|
AnnouncementsRequest,
|
||||||
AnnouncementsResponse,
|
AnnouncementsResponse,
|
||||||
AnnouncementsShowRequest,
|
AnnouncementsShowRequest,
|
||||||
|
@ -1567,6 +1571,7 @@ declare namespace entities {
|
||||||
IWebhooksShowResponse,
|
IWebhooksShowResponse,
|
||||||
IWebhooksUpdateRequest,
|
IWebhooksUpdateRequest,
|
||||||
IWebhooksDeleteRequest,
|
IWebhooksDeleteRequest,
|
||||||
|
IWebhooksTestRequest,
|
||||||
InviteCreateResponse,
|
InviteCreateResponse,
|
||||||
InviteDeleteRequest,
|
InviteDeleteRequest,
|
||||||
InviteListRequest,
|
InviteListRequest,
|
||||||
|
@ -2369,6 +2374,9 @@ type IWebhooksShowRequest = operations['i___webhooks___show']['requestBody']['co
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type IWebhooksShowResponse = operations['i___webhooks___show']['responses']['200']['content']['application/json'];
|
type IWebhooksShowResponse = operations['i___webhooks___show']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type IWebhooksTestRequest = operations['i___webhooks___test']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type IWebhooksUpdateRequest = operations['i___webhooks___update']['requestBody']['content']['application/json'];
|
type IWebhooksUpdateRequest = operations['i___webhooks___update']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "misskey-js",
|
"name": "misskey-js",
|
||||||
"version": "2024.8.0",
|
"version": "2024.9.0-alpha.2",
|
||||||
"description": "Misskey SDK for JavaScript",
|
"description": "Misskey SDK for JavaScript",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
|
|
|
@ -960,6 +960,18 @@ declare module '../api.js' {
|
||||||
credential?: string | null,
|
credential?: string | null,
|
||||||
): Promise<SwitchCaseResponseType<E, P>>;
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No description provided.
|
||||||
|
*
|
||||||
|
* **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *read:admin:system-webhook*
|
||||||
|
*/
|
||||||
|
request<E extends 'admin/system-webhook/test', P extends Endpoints[E]['req']>(
|
||||||
|
endpoint: E,
|
||||||
|
params: P,
|
||||||
|
credential?: string | null,
|
||||||
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No description provided.
|
* No description provided.
|
||||||
*
|
*
|
||||||
|
@ -2819,6 +2831,18 @@ declare module '../api.js' {
|
||||||
credential?: string | null,
|
credential?: string | null,
|
||||||
): Promise<SwitchCaseResponseType<E, P>>;
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No description provided.
|
||||||
|
*
|
||||||
|
* **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *read:account*
|
||||||
|
*/
|
||||||
|
request<E extends 'i/webhooks/test', P extends Endpoints[E]['req']>(
|
||||||
|
endpoint: E,
|
||||||
|
params: P,
|
||||||
|
credential?: string | null,
|
||||||
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No description provided.
|
* No description provided.
|
||||||
*
|
*
|
||||||
|
|
|
@ -117,6 +117,7 @@ import type {
|
||||||
AdminSystemWebhookShowResponse,
|
AdminSystemWebhookShowResponse,
|
||||||
AdminSystemWebhookUpdateRequest,
|
AdminSystemWebhookUpdateRequest,
|
||||||
AdminSystemWebhookUpdateResponse,
|
AdminSystemWebhookUpdateResponse,
|
||||||
|
AdminSystemWebhookTestRequest,
|
||||||
AnnouncementsRequest,
|
AnnouncementsRequest,
|
||||||
AnnouncementsResponse,
|
AnnouncementsResponse,
|
||||||
AnnouncementsShowRequest,
|
AnnouncementsShowRequest,
|
||||||
|
@ -376,6 +377,7 @@ import type {
|
||||||
IWebhooksShowResponse,
|
IWebhooksShowResponse,
|
||||||
IWebhooksUpdateRequest,
|
IWebhooksUpdateRequest,
|
||||||
IWebhooksDeleteRequest,
|
IWebhooksDeleteRequest,
|
||||||
|
IWebhooksTestRequest,
|
||||||
InviteCreateResponse,
|
InviteCreateResponse,
|
||||||
InviteDeleteRequest,
|
InviteDeleteRequest,
|
||||||
InviteListRequest,
|
InviteListRequest,
|
||||||
|
@ -660,6 +662,7 @@ export type Endpoints = {
|
||||||
'admin/system-webhook/list': { req: AdminSystemWebhookListRequest; res: AdminSystemWebhookListResponse };
|
'admin/system-webhook/list': { req: AdminSystemWebhookListRequest; res: AdminSystemWebhookListResponse };
|
||||||
'admin/system-webhook/show': { req: AdminSystemWebhookShowRequest; res: AdminSystemWebhookShowResponse };
|
'admin/system-webhook/show': { req: AdminSystemWebhookShowRequest; res: AdminSystemWebhookShowResponse };
|
||||||
'admin/system-webhook/update': { req: AdminSystemWebhookUpdateRequest; res: AdminSystemWebhookUpdateResponse };
|
'admin/system-webhook/update': { req: AdminSystemWebhookUpdateRequest; res: AdminSystemWebhookUpdateResponse };
|
||||||
|
'admin/system-webhook/test': { req: AdminSystemWebhookTestRequest; res: EmptyResponse };
|
||||||
'announcements': { req: AnnouncementsRequest; res: AnnouncementsResponse };
|
'announcements': { req: AnnouncementsRequest; res: AnnouncementsResponse };
|
||||||
'announcements/show': { req: AnnouncementsShowRequest; res: AnnouncementsShowResponse };
|
'announcements/show': { req: AnnouncementsShowRequest; res: AnnouncementsShowResponse };
|
||||||
'antennas/create': { req: AntennasCreateRequest; res: AntennasCreateResponse };
|
'antennas/create': { req: AntennasCreateRequest; res: AntennasCreateResponse };
|
||||||
|
@ -826,6 +829,7 @@ export type Endpoints = {
|
||||||
'i/webhooks/show': { req: IWebhooksShowRequest; res: IWebhooksShowResponse };
|
'i/webhooks/show': { req: IWebhooksShowRequest; res: IWebhooksShowResponse };
|
||||||
'i/webhooks/update': { req: IWebhooksUpdateRequest; res: EmptyResponse };
|
'i/webhooks/update': { req: IWebhooksUpdateRequest; res: EmptyResponse };
|
||||||
'i/webhooks/delete': { req: IWebhooksDeleteRequest; res: EmptyResponse };
|
'i/webhooks/delete': { req: IWebhooksDeleteRequest; res: EmptyResponse };
|
||||||
|
'i/webhooks/test': { req: IWebhooksTestRequest; res: EmptyResponse };
|
||||||
'invite/create': { req: EmptyRequest; res: InviteCreateResponse };
|
'invite/create': { req: EmptyRequest; res: InviteCreateResponse };
|
||||||
'invite/delete': { req: InviteDeleteRequest; res: EmptyResponse };
|
'invite/delete': { req: InviteDeleteRequest; res: EmptyResponse };
|
||||||
'invite/list': { req: InviteListRequest; res: InviteListResponse };
|
'invite/list': { req: InviteListRequest; res: InviteListResponse };
|
||||||
|
|
|
@ -120,6 +120,7 @@ export type AdminSystemWebhookShowRequest = operations['admin___system-webhook__
|
||||||
export type AdminSystemWebhookShowResponse = operations['admin___system-webhook___show']['responses']['200']['content']['application/json'];
|
export type AdminSystemWebhookShowResponse = operations['admin___system-webhook___show']['responses']['200']['content']['application/json'];
|
||||||
export type AdminSystemWebhookUpdateRequest = operations['admin___system-webhook___update']['requestBody']['content']['application/json'];
|
export type AdminSystemWebhookUpdateRequest = operations['admin___system-webhook___update']['requestBody']['content']['application/json'];
|
||||||
export type AdminSystemWebhookUpdateResponse = operations['admin___system-webhook___update']['responses']['200']['content']['application/json'];
|
export type AdminSystemWebhookUpdateResponse = operations['admin___system-webhook___update']['responses']['200']['content']['application/json'];
|
||||||
|
export type AdminSystemWebhookTestRequest = operations['admin___system-webhook___test']['requestBody']['content']['application/json'];
|
||||||
export type AnnouncementsRequest = operations['announcements']['requestBody']['content']['application/json'];
|
export type AnnouncementsRequest = operations['announcements']['requestBody']['content']['application/json'];
|
||||||
export type AnnouncementsResponse = operations['announcements']['responses']['200']['content']['application/json'];
|
export type AnnouncementsResponse = operations['announcements']['responses']['200']['content']['application/json'];
|
||||||
export type AnnouncementsShowRequest = operations['announcements___show']['requestBody']['content']['application/json'];
|
export type AnnouncementsShowRequest = operations['announcements___show']['requestBody']['content']['application/json'];
|
||||||
|
@ -379,6 +380,7 @@ export type IWebhooksShowRequest = operations['i___webhooks___show']['requestBod
|
||||||
export type IWebhooksShowResponse = operations['i___webhooks___show']['responses']['200']['content']['application/json'];
|
export type IWebhooksShowResponse = operations['i___webhooks___show']['responses']['200']['content']['application/json'];
|
||||||
export type IWebhooksUpdateRequest = operations['i___webhooks___update']['requestBody']['content']['application/json'];
|
export type IWebhooksUpdateRequest = operations['i___webhooks___update']['requestBody']['content']['application/json'];
|
||||||
export type IWebhooksDeleteRequest = operations['i___webhooks___delete']['requestBody']['content']['application/json'];
|
export type IWebhooksDeleteRequest = operations['i___webhooks___delete']['requestBody']['content']['application/json'];
|
||||||
|
export type IWebhooksTestRequest = operations['i___webhooks___test']['requestBody']['content']['application/json'];
|
||||||
export type InviteCreateResponse = operations['invite___create']['responses']['200']['content']['application/json'];
|
export type InviteCreateResponse = operations['invite___create']['responses']['200']['content']['application/json'];
|
||||||
export type InviteDeleteRequest = operations['invite___delete']['requestBody']['content']['application/json'];
|
export type InviteDeleteRequest = operations['invite___delete']['requestBody']['content']['application/json'];
|
||||||
export type InviteListRequest = operations['invite___list']['requestBody']['content']['application/json'];
|
export type InviteListRequest = operations['invite___list']['requestBody']['content']['application/json'];
|
||||||
|
|
|
@ -797,6 +797,16 @@ export type paths = {
|
||||||
*/
|
*/
|
||||||
post: operations['admin___system-webhook___update'];
|
post: operations['admin___system-webhook___update'];
|
||||||
};
|
};
|
||||||
|
'/admin/system-webhook/test': {
|
||||||
|
/**
|
||||||
|
* admin/system-webhook/test
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *read:admin:system-webhook*
|
||||||
|
*/
|
||||||
|
post: operations['admin___system-webhook___test'];
|
||||||
|
};
|
||||||
'/announcements': {
|
'/announcements': {
|
||||||
/**
|
/**
|
||||||
* announcements
|
* announcements
|
||||||
|
@ -2436,6 +2446,16 @@ export type paths = {
|
||||||
*/
|
*/
|
||||||
post: operations['i___webhooks___delete'];
|
post: operations['i___webhooks___delete'];
|
||||||
};
|
};
|
||||||
|
'/i/webhooks/test': {
|
||||||
|
/**
|
||||||
|
* i/webhooks/test
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *read:account*
|
||||||
|
*/
|
||||||
|
post: operations['i___webhooks___test'];
|
||||||
|
};
|
||||||
'/invite/create': {
|
'/invite/create': {
|
||||||
/**
|
/**
|
||||||
* invite/create
|
* invite/create
|
||||||
|
@ -4802,6 +4822,11 @@ export type components = {
|
||||||
userEachUserListsLimit: number;
|
userEachUserListsLimit: number;
|
||||||
rateLimitFactor: number;
|
rateLimitFactor: number;
|
||||||
avatarDecorationLimit: number;
|
avatarDecorationLimit: number;
|
||||||
|
canImportAntennas: boolean;
|
||||||
|
canImportBlocking: boolean;
|
||||||
|
canImportFollowing: boolean;
|
||||||
|
canImportMuting: boolean;
|
||||||
|
canImportUserLists: boolean;
|
||||||
};
|
};
|
||||||
ReversiGameLite: {
|
ReversiGameLite: {
|
||||||
/** Format: id */
|
/** Format: id */
|
||||||
|
@ -5105,6 +5130,7 @@ export type operations = {
|
||||||
perRemoteUserUserTimelineCacheMax: number;
|
perRemoteUserUserTimelineCacheMax: number;
|
||||||
perUserHomeTimelineCacheMax: number;
|
perUserHomeTimelineCacheMax: number;
|
||||||
perUserListTimelineCacheMax: number;
|
perUserListTimelineCacheMax: number;
|
||||||
|
enableReactionsBuffering: boolean;
|
||||||
notesPerOneAd: number;
|
notesPerOneAd: number;
|
||||||
backgroundImageUrl: string | null;
|
backgroundImageUrl: string | null;
|
||||||
deeplAuthKey: string | null;
|
deeplAuthKey: string | null;
|
||||||
|
@ -9375,6 +9401,7 @@ export type operations = {
|
||||||
perRemoteUserUserTimelineCacheMax?: number;
|
perRemoteUserUserTimelineCacheMax?: number;
|
||||||
perUserHomeTimelineCacheMax?: number;
|
perUserHomeTimelineCacheMax?: number;
|
||||||
perUserListTimelineCacheMax?: number;
|
perUserListTimelineCacheMax?: number;
|
||||||
|
enableReactionsBuffering?: boolean;
|
||||||
notesPerOneAd?: number;
|
notesPerOneAd?: number;
|
||||||
silencedHosts?: string[] | null;
|
silencedHosts?: string[] | null;
|
||||||
mediaSilencedHosts?: string[] | null;
|
mediaSilencedHosts?: string[] | null;
|
||||||
|
@ -10327,6 +10354,71 @@ export type operations = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* admin/system-webhook/test
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *read:admin:system-webhook*
|
||||||
|
*/
|
||||||
|
'admin___system-webhook___test': {
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
/** Format: misskey:id */
|
||||||
|
webhookId: string;
|
||||||
|
/** @enum {string} */
|
||||||
|
type: 'abuseReport' | 'abuseReportResolved' | 'userCreated';
|
||||||
|
override?: {
|
||||||
|
url?: string;
|
||||||
|
secret?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK (without any results) */
|
||||||
|
204: {
|
||||||
|
content: never;
|
||||||
|
};
|
||||||
|
/** @description Client error */
|
||||||
|
400: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Authentication error */
|
||||||
|
401: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Forbidden error */
|
||||||
|
403: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description I'm Ai */
|
||||||
|
418: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description To many requests */
|
||||||
|
429: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Internal server error */
|
||||||
|
500: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* announcements
|
* announcements
|
||||||
* @description No description provided.
|
* @description No description provided.
|
||||||
|
@ -20146,6 +20238,71 @@ export type operations = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* i/webhooks/test
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *read:account*
|
||||||
|
*/
|
||||||
|
i___webhooks___test: {
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
/** Format: misskey:id */
|
||||||
|
webhookId: string;
|
||||||
|
/** @enum {string} */
|
||||||
|
type: 'mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction';
|
||||||
|
override?: {
|
||||||
|
url?: string;
|
||||||
|
secret?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK (without any results) */
|
||||||
|
204: {
|
||||||
|
content: never;
|
||||||
|
};
|
||||||
|
/** @description Client error */
|
||||||
|
400: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Authentication error */
|
||||||
|
401: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Forbidden error */
|
||||||
|
403: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description I'm Ai */
|
||||||
|
418: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description To many requests */
|
||||||
|
429: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Internal server error */
|
||||||
|
500: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* invite/create
|
* invite/create
|
||||||
* @description No description provided.
|
* @description No description provided.
|
||||||
|
|
255
pnpm-lock.yaml
255
pnpm-lock.yaml
|
@ -284,6 +284,9 @@ importers:
|
||||||
jsrsasign:
|
jsrsasign:
|
||||||
specifier: 11.1.0
|
specifier: 11.1.0
|
||||||
version: 11.1.0
|
version: 11.1.0
|
||||||
|
juice:
|
||||||
|
specifier: 11.0.0
|
||||||
|
version: 11.0.0
|
||||||
meilisearch:
|
meilisearch:
|
||||||
specifier: 0.41.0
|
specifier: 0.41.0
|
||||||
version: 0.41.0(encoding@0.1.13)
|
version: 0.41.0(encoding@0.1.13)
|
||||||
|
@ -1202,7 +1205,7 @@ importers:
|
||||||
version: 7.17.0(eslint@9.8.0)(typescript@5.5.4)
|
version: 7.17.0(eslint@9.8.0)(typescript@5.5.4)
|
||||||
'@vitest/coverage-v8':
|
'@vitest/coverage-v8':
|
||||||
specifier: 1.6.0
|
specifier: 1.6.0
|
||||||
version: 1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3))
|
version: 1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.77.8)(terser@5.31.3))
|
||||||
'@vue/runtime-core':
|
'@vue/runtime-core':
|
||||||
specifier: 3.4.37
|
specifier: 3.4.37
|
||||||
version: 3.4.37
|
version: 3.4.37
|
||||||
|
@ -6379,6 +6382,10 @@ packages:
|
||||||
cheerio-select@2.1.0:
|
cheerio-select@2.1.0:
|
||||||
resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
|
resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
|
||||||
|
|
||||||
|
cheerio@1.0.0:
|
||||||
|
resolution: {integrity: sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==}
|
||||||
|
engines: {node: '>=18.17'}
|
||||||
|
|
||||||
cheerio@1.0.0-rc.12:
|
cheerio@1.0.0-rc.12:
|
||||||
resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==}
|
resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
@ -6520,6 +6527,10 @@ packages:
|
||||||
resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
|
resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
commander@12.1.0:
|
||||||
|
resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
commander@2.20.3:
|
commander@2.20.3:
|
||||||
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
|
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
|
||||||
|
|
||||||
|
@ -6958,19 +6969,36 @@ packages:
|
||||||
dom-accessibility-api@0.6.3:
|
dom-accessibility-api@0.6.3:
|
||||||
resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
|
resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
|
||||||
|
|
||||||
|
dom-serializer@1.4.1:
|
||||||
|
resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
|
||||||
|
|
||||||
dom-serializer@2.0.0:
|
dom-serializer@2.0.0:
|
||||||
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
|
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
|
||||||
|
|
||||||
domelementtype@2.3.0:
|
domelementtype@2.3.0:
|
||||||
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
|
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
|
||||||
|
|
||||||
|
domhandler@3.3.0:
|
||||||
|
resolution: {integrity: sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==}
|
||||||
|
engines: {node: '>= 4'}
|
||||||
|
|
||||||
|
domhandler@4.3.1:
|
||||||
|
resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
|
||||||
|
engines: {node: '>= 4'}
|
||||||
|
|
||||||
domhandler@5.0.3:
|
domhandler@5.0.3:
|
||||||
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
|
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
|
|
||||||
|
domutils@2.8.0:
|
||||||
|
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
|
||||||
|
|
||||||
domutils@3.0.1:
|
domutils@3.0.1:
|
||||||
resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==}
|
resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==}
|
||||||
|
|
||||||
|
domutils@3.1.0:
|
||||||
|
resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==}
|
||||||
|
|
||||||
dotenv-expand@10.0.0:
|
dotenv-expand@10.0.0:
|
||||||
resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==}
|
resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
@ -7027,6 +7055,9 @@ packages:
|
||||||
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
|
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
encoding-sniffer@0.2.0:
|
||||||
|
resolution: {integrity: sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==}
|
||||||
|
|
||||||
encoding@0.1.13:
|
encoding@0.1.13:
|
||||||
resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
|
resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
|
||||||
|
|
||||||
|
@ -7130,6 +7161,10 @@ packages:
|
||||||
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
|
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
escape-goat@3.0.0:
|
||||||
|
resolution: {integrity: sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
escape-html@1.0.3:
|
escape-html@1.0.3:
|
||||||
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||||
|
|
||||||
|
@ -7921,9 +7956,15 @@ packages:
|
||||||
resolution: {integrity: sha512-eVcrzgbR4tim7c7soKQKtxa/kQM4TzjnlU83rcZ9bHU6t31ehfV7SktN6McWgwPWg+JYMA/O3qpGxBvFq1z2Jg==}
|
resolution: {integrity: sha512-eVcrzgbR4tim7c7soKQKtxa/kQM4TzjnlU83rcZ9bHU6t31ehfV7SktN6McWgwPWg+JYMA/O3qpGxBvFq1z2Jg==}
|
||||||
engines: {node: '>=0.10'}
|
engines: {node: '>=0.10'}
|
||||||
|
|
||||||
|
htmlparser2@5.0.1:
|
||||||
|
resolution: {integrity: sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==}
|
||||||
|
|
||||||
htmlparser2@8.0.1:
|
htmlparser2@8.0.1:
|
||||||
resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==}
|
resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==}
|
||||||
|
|
||||||
|
htmlparser2@9.1.0:
|
||||||
|
resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==}
|
||||||
|
|
||||||
http-cache-semantics@4.1.1:
|
http-cache-semantics@4.1.1:
|
||||||
resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
|
resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
|
||||||
|
|
||||||
|
@ -8673,6 +8714,11 @@ packages:
|
||||||
jstransformer@1.0.0:
|
jstransformer@1.0.0:
|
||||||
resolution: {integrity: sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==}
|
resolution: {integrity: sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==}
|
||||||
|
|
||||||
|
juice@11.0.0:
|
||||||
|
resolution: {integrity: sha512-sGF8hPz9/Wg+YXbaNDqc1Iuoaw+J/P9lBHNQKXAGc9pPNjCd4fyPai0Zxj7MRtdjMr0lcgk5PjEIkP2b8R9F3w==}
|
||||||
|
engines: {node: '>=18.17'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
just-extend@4.2.1:
|
just-extend@4.2.1:
|
||||||
resolution: {integrity: sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==}
|
resolution: {integrity: sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==}
|
||||||
|
|
||||||
|
@ -8959,6 +9005,9 @@ packages:
|
||||||
memoizerific@1.11.3:
|
memoizerific@1.11.3:
|
||||||
resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==}
|
resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==}
|
||||||
|
|
||||||
|
mensch@0.3.4:
|
||||||
|
resolution: {integrity: sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==}
|
||||||
|
|
||||||
meow@9.0.0:
|
meow@9.0.0:
|
||||||
resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==}
|
resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
@ -9085,6 +9134,11 @@ packages:
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
mime@2.6.0:
|
||||||
|
resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==}
|
||||||
|
engines: {node: '>=4.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
mime@3.0.0:
|
mime@3.0.0:
|
||||||
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
|
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
|
@ -9700,6 +9754,9 @@ packages:
|
||||||
parse5-htmlparser2-tree-adapter@7.0.0:
|
parse5-htmlparser2-tree-adapter@7.0.0:
|
||||||
resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==}
|
resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==}
|
||||||
|
|
||||||
|
parse5-parser-stream@7.1.2:
|
||||||
|
resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==}
|
||||||
|
|
||||||
parse5@5.1.1:
|
parse5@5.1.1:
|
||||||
resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==}
|
resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==}
|
||||||
|
|
||||||
|
@ -10901,6 +10958,9 @@ packages:
|
||||||
resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==}
|
resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
slick@1.12.2:
|
||||||
|
resolution: {integrity: sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==}
|
||||||
|
|
||||||
smart-buffer@4.2.0:
|
smart-buffer@4.2.0:
|
||||||
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
|
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
|
||||||
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
|
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
|
||||||
|
@ -11380,7 +11440,6 @@ packages:
|
||||||
|
|
||||||
ts-case-convert@2.0.2:
|
ts-case-convert@2.0.2:
|
||||||
resolution: {integrity: sha512-vdKfx1VAdpvEBOBv5OpVu5ZFqRg9HdTI4sYt6qqMeICBeNyXvitrarCnFWNDAki51IKwCyx+ZssY46Q9jH5otA==}
|
resolution: {integrity: sha512-vdKfx1VAdpvEBOBv5OpVu5ZFqRg9HdTI4sYt6qqMeICBeNyXvitrarCnFWNDAki51IKwCyx+ZssY46Q9jH5otA==}
|
||||||
bundledDependencies: []
|
|
||||||
|
|
||||||
ts-dedent@2.2.0:
|
ts-dedent@2.2.0:
|
||||||
resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
|
resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
|
||||||
|
@ -11596,6 +11655,10 @@ packages:
|
||||||
resolution: {integrity: sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==}
|
resolution: {integrity: sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==}
|
||||||
engines: {node: '>=14.0'}
|
engines: {node: '>=14.0'}
|
||||||
|
|
||||||
|
undici@6.19.8:
|
||||||
|
resolution: {integrity: sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==}
|
||||||
|
engines: {node: '>=18.17'}
|
||||||
|
|
||||||
unicode-canonical-property-names-ecmascript@2.0.0:
|
unicode-canonical-property-names-ecmascript@2.0.0:
|
||||||
resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==}
|
resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
@ -11732,6 +11795,10 @@ packages:
|
||||||
resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==}
|
resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==}
|
||||||
engines: {node: '>=10.12.0'}
|
engines: {node: '>=10.12.0'}
|
||||||
|
|
||||||
|
valid-data-url@3.0.1:
|
||||||
|
resolution: {integrity: sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
validate-npm-package-license@3.0.4:
|
validate-npm-package-license@3.0.4:
|
||||||
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
|
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
|
||||||
|
|
||||||
|
@ -11946,6 +12013,10 @@ packages:
|
||||||
engines: {node: '>= 16'}
|
engines: {node: '>= 16'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
web-resource-inliner@7.0.0:
|
||||||
|
resolution: {integrity: sha512-NlfnGF8MY9ZUwFjyq3vOUBx7KwF8bmE+ywR781SB0nWB6MoMxN4BA8gtgP1KGTZo/O/AyWJz7HZpR704eaj4mg==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
||||||
web-streams-polyfill@3.2.1:
|
web-streams-polyfill@3.2.1:
|
||||||
resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
|
resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
@ -17521,6 +17592,25 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.77.8)(terser@5.31.3))':
|
||||||
|
dependencies:
|
||||||
|
'@ampproject/remapping': 2.2.1
|
||||||
|
'@bcoe/v8-coverage': 0.2.3
|
||||||
|
debug: 4.3.5(supports-color@8.1.1)
|
||||||
|
istanbul-lib-coverage: 3.2.2
|
||||||
|
istanbul-lib-report: 3.0.1
|
||||||
|
istanbul-lib-source-maps: 5.0.4
|
||||||
|
istanbul-reports: 3.1.6
|
||||||
|
magic-string: 0.30.10
|
||||||
|
magicast: 0.3.4
|
||||||
|
picocolors: 1.0.1
|
||||||
|
std-env: 3.7.0
|
||||||
|
strip-literal: 2.1.0
|
||||||
|
test-exclude: 6.0.0
|
||||||
|
vitest: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.77.8)(terser@5.31.3)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@vitest/expect@1.6.0':
|
'@vitest/expect@1.6.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/spy': 1.6.0
|
'@vitest/spy': 1.6.0
|
||||||
|
@ -18496,7 +18586,21 @@ snapshots:
|
||||||
css-what: 6.1.0
|
css-what: 6.1.0
|
||||||
domelementtype: 2.3.0
|
domelementtype: 2.3.0
|
||||||
domhandler: 5.0.3
|
domhandler: 5.0.3
|
||||||
domutils: 3.0.1
|
domutils: 3.1.0
|
||||||
|
|
||||||
|
cheerio@1.0.0:
|
||||||
|
dependencies:
|
||||||
|
cheerio-select: 2.1.0
|
||||||
|
dom-serializer: 2.0.0
|
||||||
|
domhandler: 5.0.3
|
||||||
|
domutils: 3.1.0
|
||||||
|
encoding-sniffer: 0.2.0
|
||||||
|
htmlparser2: 9.1.0
|
||||||
|
parse5: 7.1.2
|
||||||
|
parse5-htmlparser2-tree-adapter: 7.0.0
|
||||||
|
parse5-parser-stream: 7.1.2
|
||||||
|
undici: 6.19.8
|
||||||
|
whatwg-mimetype: 4.0.0
|
||||||
|
|
||||||
cheerio@1.0.0-rc.12:
|
cheerio@1.0.0-rc.12:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -18638,6 +18742,8 @@ snapshots:
|
||||||
|
|
||||||
commander@10.0.1: {}
|
commander@10.0.1: {}
|
||||||
|
|
||||||
|
commander@12.1.0: {}
|
||||||
|
|
||||||
commander@2.20.3: {}
|
commander@2.20.3: {}
|
||||||
|
|
||||||
commander@6.2.1: {}
|
commander@6.2.1: {}
|
||||||
|
@ -18807,7 +18913,7 @@ snapshots:
|
||||||
boolbase: 1.0.0
|
boolbase: 1.0.0
|
||||||
css-what: 6.1.0
|
css-what: 6.1.0
|
||||||
domhandler: 5.0.3
|
domhandler: 5.0.3
|
||||||
domutils: 3.0.1
|
domutils: 3.1.0
|
||||||
nth-check: 2.1.1
|
nth-check: 2.1.1
|
||||||
|
|
||||||
css-tree@2.2.1:
|
css-tree@2.2.1:
|
||||||
|
@ -19135,6 +19241,12 @@ snapshots:
|
||||||
|
|
||||||
dom-accessibility-api@0.6.3: {}
|
dom-accessibility-api@0.6.3: {}
|
||||||
|
|
||||||
|
dom-serializer@1.4.1:
|
||||||
|
dependencies:
|
||||||
|
domelementtype: 2.3.0
|
||||||
|
domhandler: 4.3.1
|
||||||
|
entities: 2.2.0
|
||||||
|
|
||||||
dom-serializer@2.0.0:
|
dom-serializer@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
domelementtype: 2.3.0
|
domelementtype: 2.3.0
|
||||||
|
@ -19143,16 +19255,36 @@ snapshots:
|
||||||
|
|
||||||
domelementtype@2.3.0: {}
|
domelementtype@2.3.0: {}
|
||||||
|
|
||||||
|
domhandler@3.3.0:
|
||||||
|
dependencies:
|
||||||
|
domelementtype: 2.3.0
|
||||||
|
|
||||||
|
domhandler@4.3.1:
|
||||||
|
dependencies:
|
||||||
|
domelementtype: 2.3.0
|
||||||
|
|
||||||
domhandler@5.0.3:
|
domhandler@5.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
domelementtype: 2.3.0
|
domelementtype: 2.3.0
|
||||||
|
|
||||||
|
domutils@2.8.0:
|
||||||
|
dependencies:
|
||||||
|
dom-serializer: 1.4.1
|
||||||
|
domelementtype: 2.3.0
|
||||||
|
domhandler: 4.3.1
|
||||||
|
|
||||||
domutils@3.0.1:
|
domutils@3.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
dom-serializer: 2.0.0
|
dom-serializer: 2.0.0
|
||||||
domelementtype: 2.3.0
|
domelementtype: 2.3.0
|
||||||
domhandler: 5.0.3
|
domhandler: 5.0.3
|
||||||
|
|
||||||
|
domutils@3.1.0:
|
||||||
|
dependencies:
|
||||||
|
dom-serializer: 2.0.0
|
||||||
|
domelementtype: 2.3.0
|
||||||
|
domhandler: 5.0.3
|
||||||
|
|
||||||
dotenv-expand@10.0.0: {}
|
dotenv-expand@10.0.0: {}
|
||||||
|
|
||||||
dotenv@16.0.3: {}
|
dotenv@16.0.3: {}
|
||||||
|
@ -19197,6 +19329,11 @@ snapshots:
|
||||||
|
|
||||||
encodeurl@1.0.2: {}
|
encodeurl@1.0.2: {}
|
||||||
|
|
||||||
|
encoding-sniffer@0.2.0:
|
||||||
|
dependencies:
|
||||||
|
iconv-lite: 0.6.3
|
||||||
|
whatwg-encoding: 3.1.1
|
||||||
|
|
||||||
encoding@0.1.13:
|
encoding@0.1.13:
|
||||||
dependencies:
|
dependencies:
|
||||||
iconv-lite: 0.6.3
|
iconv-lite: 0.6.3
|
||||||
|
@ -19427,6 +19564,8 @@ snapshots:
|
||||||
|
|
||||||
escalade@3.1.1: {}
|
escalade@3.1.1: {}
|
||||||
|
|
||||||
|
escape-goat@3.0.0: {}
|
||||||
|
|
||||||
escape-html@1.0.3: {}
|
escape-html@1.0.3: {}
|
||||||
|
|
||||||
escape-regexp@0.0.1: {}
|
escape-regexp@0.0.1: {}
|
||||||
|
@ -20455,6 +20594,13 @@ snapshots:
|
||||||
|
|
||||||
htmlescape@1.1.1: {}
|
htmlescape@1.1.1: {}
|
||||||
|
|
||||||
|
htmlparser2@5.0.1:
|
||||||
|
dependencies:
|
||||||
|
domelementtype: 2.3.0
|
||||||
|
domhandler: 3.3.0
|
||||||
|
domutils: 2.8.0
|
||||||
|
entities: 2.2.0
|
||||||
|
|
||||||
htmlparser2@8.0.1:
|
htmlparser2@8.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
domelementtype: 2.3.0
|
domelementtype: 2.3.0
|
||||||
|
@ -20462,6 +20608,13 @@ snapshots:
|
||||||
domutils: 3.0.1
|
domutils: 3.0.1
|
||||||
entities: 4.5.0
|
entities: 4.5.0
|
||||||
|
|
||||||
|
htmlparser2@9.1.0:
|
||||||
|
dependencies:
|
||||||
|
domelementtype: 2.3.0
|
||||||
|
domhandler: 5.0.3
|
||||||
|
domutils: 3.1.0
|
||||||
|
entities: 4.5.0
|
||||||
|
|
||||||
http-cache-semantics@4.1.1: {}
|
http-cache-semantics@4.1.1: {}
|
||||||
|
|
||||||
http-errors@2.0.0:
|
http-errors@2.0.0:
|
||||||
|
@ -21318,6 +21471,35 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
jsdom@24.1.1:
|
||||||
|
dependencies:
|
||||||
|
cssstyle: 4.0.1
|
||||||
|
data-urls: 5.0.0
|
||||||
|
decimal.js: 10.4.3
|
||||||
|
form-data: 4.0.0
|
||||||
|
html-encoding-sniffer: 4.0.0
|
||||||
|
http-proxy-agent: 7.0.2
|
||||||
|
https-proxy-agent: 7.0.5
|
||||||
|
is-potential-custom-element-name: 1.0.1
|
||||||
|
nwsapi: 2.2.12
|
||||||
|
parse5: 7.1.2
|
||||||
|
rrweb-cssom: 0.7.1
|
||||||
|
saxes: 6.0.0
|
||||||
|
symbol-tree: 3.2.4
|
||||||
|
tough-cookie: 4.1.4
|
||||||
|
w3c-xmlserializer: 5.0.0
|
||||||
|
webidl-conversions: 7.0.0
|
||||||
|
whatwg-encoding: 3.1.1
|
||||||
|
whatwg-mimetype: 4.0.0
|
||||||
|
whatwg-url: 14.0.0
|
||||||
|
ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
|
||||||
|
xml-name-validator: 5.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- supports-color
|
||||||
|
- utf-8-validate
|
||||||
|
optional: true
|
||||||
|
|
||||||
jsdom@24.1.1(bufferutil@4.0.7)(utf-8-validate@6.0.3):
|
jsdom@24.1.1(bufferutil@4.0.7)(utf-8-validate@6.0.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
cssstyle: 4.0.1
|
cssstyle: 4.0.1
|
||||||
|
@ -21454,6 +21636,14 @@ snapshots:
|
||||||
is-promise: 2.2.2
|
is-promise: 2.2.2
|
||||||
promise: 7.3.1
|
promise: 7.3.1
|
||||||
|
|
||||||
|
juice@11.0.0:
|
||||||
|
dependencies:
|
||||||
|
cheerio: 1.0.0
|
||||||
|
commander: 12.1.0
|
||||||
|
mensch: 0.3.4
|
||||||
|
slick: 1.12.2
|
||||||
|
web-resource-inliner: 7.0.0
|
||||||
|
|
||||||
just-extend@4.2.1: {}
|
just-extend@4.2.1: {}
|
||||||
|
|
||||||
jwa@2.0.0:
|
jwa@2.0.0:
|
||||||
|
@ -21800,6 +21990,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
map-or-similar: 1.5.0
|
map-or-similar: 1.5.0
|
||||||
|
|
||||||
|
mensch@0.3.4: {}
|
||||||
|
|
||||||
meow@9.0.0:
|
meow@9.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/minimist': 1.2.2
|
'@types/minimist': 1.2.2
|
||||||
|
@ -22035,6 +22227,8 @@ snapshots:
|
||||||
|
|
||||||
mime@1.6.0: {}
|
mime@1.6.0: {}
|
||||||
|
|
||||||
|
mime@2.6.0: {}
|
||||||
|
|
||||||
mime@3.0.0: {}
|
mime@3.0.0: {}
|
||||||
|
|
||||||
mimic-fn@2.1.0: {}
|
mimic-fn@2.1.0: {}
|
||||||
|
@ -22679,6 +22873,10 @@ snapshots:
|
||||||
domhandler: 5.0.3
|
domhandler: 5.0.3
|
||||||
parse5: 7.1.2
|
parse5: 7.1.2
|
||||||
|
|
||||||
|
parse5-parser-stream@7.1.2:
|
||||||
|
dependencies:
|
||||||
|
parse5: 7.1.2
|
||||||
|
|
||||||
parse5@5.1.1: {}
|
parse5@5.1.1: {}
|
||||||
|
|
||||||
parse5@6.0.1: {}
|
parse5@6.0.1: {}
|
||||||
|
@ -23958,6 +24156,8 @@ snapshots:
|
||||||
astral-regex: 2.0.0
|
astral-regex: 2.0.0
|
||||||
is-fullwidth-code-point: 3.0.0
|
is-fullwidth-code-point: 3.0.0
|
||||||
|
|
||||||
|
slick@1.12.2: {}
|
||||||
|
|
||||||
smart-buffer@4.2.0: {}
|
smart-buffer@4.2.0: {}
|
||||||
|
|
||||||
socks-proxy-agent@8.0.2:
|
socks-proxy-agent@8.0.2:
|
||||||
|
@ -24631,6 +24831,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@fastify/busboy': 2.1.0
|
'@fastify/busboy': 2.1.0
|
||||||
|
|
||||||
|
undici@6.19.8: {}
|
||||||
|
|
||||||
unicode-canonical-property-names-ecmascript@2.0.0: {}
|
unicode-canonical-property-names-ecmascript@2.0.0: {}
|
||||||
|
|
||||||
unicode-match-property-ecmascript@2.0.0:
|
unicode-match-property-ecmascript@2.0.0:
|
||||||
|
@ -24772,6 +24974,8 @@ snapshots:
|
||||||
'@types/istanbul-lib-coverage': 2.0.4
|
'@types/istanbul-lib-coverage': 2.0.4
|
||||||
convert-source-map: 2.0.0
|
convert-source-map: 2.0.0
|
||||||
|
|
||||||
|
valid-data-url@3.0.1: {}
|
||||||
|
|
||||||
validate-npm-package-license@3.0.4:
|
validate-npm-package-license@3.0.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
spdx-correct: 3.1.1
|
spdx-correct: 3.1.1
|
||||||
|
@ -24868,6 +25072,41 @@ snapshots:
|
||||||
- supports-color
|
- supports-color
|
||||||
- terser
|
- terser
|
||||||
|
|
||||||
|
vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.77.8)(terser@5.31.3):
|
||||||
|
dependencies:
|
||||||
|
'@vitest/expect': 1.6.0
|
||||||
|
'@vitest/runner': 1.6.0
|
||||||
|
'@vitest/snapshot': 1.6.0
|
||||||
|
'@vitest/spy': 1.6.0
|
||||||
|
'@vitest/utils': 1.6.0
|
||||||
|
acorn-walk: 8.3.2
|
||||||
|
chai: 4.3.10
|
||||||
|
debug: 4.3.4(supports-color@5.5.0)
|
||||||
|
execa: 8.0.1
|
||||||
|
local-pkg: 0.5.0
|
||||||
|
magic-string: 0.30.10
|
||||||
|
pathe: 1.1.2
|
||||||
|
picocolors: 1.0.0
|
||||||
|
std-env: 3.7.0
|
||||||
|
strip-literal: 2.1.0
|
||||||
|
tinybench: 2.6.0
|
||||||
|
tinypool: 0.8.4
|
||||||
|
vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
|
||||||
|
vite-node: 1.6.0(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
|
||||||
|
why-is-node-running: 2.2.2
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 20.14.12
|
||||||
|
happy-dom: 10.0.3
|
||||||
|
jsdom: 24.1.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- less
|
||||||
|
- lightningcss
|
||||||
|
- sass
|
||||||
|
- stylus
|
||||||
|
- sugarss
|
||||||
|
- supports-color
|
||||||
|
- terser
|
||||||
|
|
||||||
void-elements@3.1.0: {}
|
void-elements@3.1.0: {}
|
||||||
|
|
||||||
vscode-jsonrpc@8.2.0: {}
|
vscode-jsonrpc@8.2.0: {}
|
||||||
|
@ -25019,6 +25258,14 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
web-resource-inliner@7.0.0:
|
||||||
|
dependencies:
|
||||||
|
ansi-colors: 4.1.3
|
||||||
|
escape-goat: 3.0.0
|
||||||
|
htmlparser2: 5.0.1
|
||||||
|
mime: 2.6.0
|
||||||
|
valid-data-url: 3.0.1
|
||||||
|
|
||||||
web-streams-polyfill@3.2.1: {}
|
web-streams-polyfill@3.2.1: {}
|
||||||
|
|
||||||
web-streams-polyfill@4.0.0:
|
web-streams-polyfill@4.0.0:
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue