Merge branch 'develop' into re-ed25519
This commit is contained in:
commit
97cdde1b32
|
@ -48,13 +48,15 @@ jobs:
|
||||||
"packages/backend/migration"
|
"packages/backend/migration"
|
||||||
"packages/backend/src"
|
"packages/backend/src"
|
||||||
"packages/backend/test"
|
"packages/backend/test"
|
||||||
"packages/frontend-shared/src"
|
"packages/frontend-shared/@types"
|
||||||
|
"packages/frontend-shared/js"
|
||||||
"packages/frontend/.storybook"
|
"packages/frontend/.storybook"
|
||||||
"packages/frontend/@types"
|
"packages/frontend/@types"
|
||||||
"packages/frontend/lib"
|
"packages/frontend/lib"
|
||||||
"packages/frontend/public"
|
"packages/frontend/public"
|
||||||
"packages/frontend/src"
|
"packages/frontend/src"
|
||||||
"packages/frontend/test"
|
"packages/frontend/test"
|
||||||
|
"packages/frontend-embed/@types"
|
||||||
"packages/frontend-embed/src"
|
"packages/frontend-embed/src"
|
||||||
"packages/misskey-bubble-game/src"
|
"packages/misskey-bubble-game/src"
|
||||||
"packages/misskey-reversi/src"
|
"packages/misskey-reversi/src"
|
||||||
|
|
|
@ -12,6 +12,8 @@ on:
|
||||||
- packages/frontend-embed/**
|
- packages/frontend-embed/**
|
||||||
- packages/sw/**
|
- packages/sw/**
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
|
- packages/misskey-bubble-game/**
|
||||||
|
- packages/misskey-reversi/**
|
||||||
- packages/shared/eslint.config.js
|
- packages/shared/eslint.config.js
|
||||||
- .github/workflows/lint.yml
|
- .github/workflows/lint.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
|
@ -22,6 +24,8 @@ on:
|
||||||
- packages/frontend-embed/**
|
- packages/frontend-embed/**
|
||||||
- packages/sw/**
|
- packages/sw/**
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
|
- packages/misskey-bubble-game/**
|
||||||
|
- packages/misskey-reversi/**
|
||||||
- packages/shared/eslint.config.js
|
- packages/shared/eslint.config.js
|
||||||
- .github/workflows/lint.yml
|
- .github/workflows/lint.yml
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -53,6 +57,8 @@ jobs:
|
||||||
- frontend-embed
|
- frontend-embed
|
||||||
- sw
|
- sw
|
||||||
- misskey-js
|
- misskey-js
|
||||||
|
- misskey-bubble-game
|
||||||
|
- misskey-reversi
|
||||||
env:
|
env:
|
||||||
eslint-cache-version: v1
|
eslint-cache-version: v1
|
||||||
eslint-cache-path: ${{ github.workspace }}/node_modules/.cache/eslint-${{ matrix.workspace }}
|
eslint-cache-path: ${{ github.workspace }}/node_modules/.cache/eslint-${{ matrix.workspace }}
|
||||||
|
|
|
@ -79,15 +79,13 @@ jobs:
|
||||||
if (( "$DIFF_BYTES" <= 1 )); then
|
if (( "$DIFF_BYTES" <= 1 )); then
|
||||||
echo '差分はありません。' >> ./output.md
|
echo '差分はありません。' >> ./output.md
|
||||||
else
|
else
|
||||||
cat <<- EOF >> ./output.md
|
echo '<details>' >> ./output.md
|
||||||
<details>
|
echo '<summary>差分はこちら</summary>' >> ./output.md
|
||||||
<summary>差分はこちら</summary>
|
echo >> ./output.md
|
||||||
|
echo '```diff' >> ./output.md
|
||||||
\`\`\`diff
|
cat ./api.json.diff >> ./output.md
|
||||||
$(cat ./api.json.diff)
|
echo '```' >> ./output.md
|
||||||
\`\`\`
|
echo '</details>' >> .output.md
|
||||||
</details>
|
|
||||||
EOF
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "$FOOTER" >> ./output.md
|
echo "$FOOTER" >> ./output.md
|
||||||
|
|
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
### General
|
### General
|
||||||
- Feat: UserWebhookとSystemWebhookのテスト送信機能を追加 (#14445)
|
- Feat: UserWebhookとSystemWebhookのテスト送信機能を追加 (#14445)
|
||||||
|
- Feat: モデレーターはユーザーにかかわらずファイルが添付されているノートを検索できるように
|
||||||
|
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/680)
|
||||||
- Enhance: ユーザーによるコンテンツインポートの可否をロールポリシーで制御できるように
|
- Enhance: ユーザーによるコンテンツインポートの可否をロールポリシーで制御できるように
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
|
@ -11,13 +13,18 @@
|
||||||
- Enhance: アイコンデコレーション管理画面にプレビューを追加
|
- Enhance: アイコンデコレーション管理画面にプレビューを追加
|
||||||
- Enhance: コントロールパネル内のファイル一覧でセンシティブなファイルを区別しやすく
|
- Enhance: コントロールパネル内のファイル一覧でセンシティブなファイルを区別しやすく
|
||||||
- Enhance: ScratchpadにUIインスペクターを追加
|
- Enhance: ScratchpadにUIインスペクターを追加
|
||||||
|
- Enhance: Play編集画面の項目の並びを少しリデザイン
|
||||||
- Fix: サーバーメトリクスが2つ以上あるとリロード直後の表示がおかしくなる問題を修正
|
- Fix: サーバーメトリクスが2つ以上あるとリロード直後の表示がおかしくなる問題を修正
|
||||||
|
- Fix: コントロールパネル内のAp requests内のチャートの表示がおかしかった問題を修正
|
||||||
- Fix: 月の違う同じ日はセパレータが表示されないのを修正
|
- Fix: 月の違う同じ日はセパレータが表示されないのを修正
|
||||||
|
- Fix: タッチ画面でレンジスライダーを操作するとツールチップが複数表示される問題を修正
|
||||||
|
(Cherry-picked from https://github.com/taiyme/misskey/pull/265)
|
||||||
- Fix: 縦横比が極端なカスタム絵文字を表示する際にレイアウトが崩れる箇所があるのを修正
|
- Fix: 縦横比が極端なカスタム絵文字を表示する際にレイアウトが崩れる箇所があるのを修正
|
||||||
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/725)
|
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/725)
|
||||||
- Fix: 設定変更時のリロード確認ダイアログが複数個表示されることがある問題を修正
|
- Fix: 設定変更時のリロード確認ダイアログが複数個表示されることがある問題を修正
|
||||||
- Fix: ファイルの詳細ページのファイルの説明で改行が正しく表示されない問題を修正
|
- Fix: ファイルの詳細ページのファイルの説明で改行が正しく表示されない問題を修正
|
||||||
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/bde6bb0bd2e8b0d027e724d2acdb8ae0585a8110)
|
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/bde6bb0bd2e8b0d027e724d2acdb8ae0585a8110)
|
||||||
|
- Fix: 一部画面のページネーションが動作しにくくなっていたのを修正 ( #12766 , #11449 )
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Feat: Misskey® Reactions Buffering Technology™ (RBT)により、リアクションの作成負荷を低減することが可能に
|
- Feat: Misskey® Reactions Buffering Technology™ (RBT)により、リアクションの作成負荷を低減することが可能に
|
||||||
|
@ -26,8 +33,11 @@
|
||||||
- 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: Continue importing from file if single emoji import fails
|
||||||
- Fix: `Retry-After`ヘッダーが送信されなかった問題を修正
|
- Fix: `Retry-After`ヘッダーが送信されなかった問題を修正
|
||||||
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/8a982c61c01909e7540ff1be9f019df07c3f0624)
|
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/8a982c61c01909e7540ff1be9f019df07c3f0624)
|
||||||
|
- Fix: サーバーサイドのDOM解析完了時にリソースを開放するように
|
||||||
|
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/634)
|
||||||
|
|
||||||
## 2024.8.0
|
## 2024.8.0
|
||||||
|
|
||||||
|
|
|
@ -572,3 +572,24 @@ marginはそのコンポーネントを使う側が設定する
|
||||||
|
|
||||||
### indexというファイル名を使うな
|
### indexというファイル名を使うな
|
||||||
ESMではディレクトリインポートは廃止されているのと、ディレクトリインポートせずともファイル名が index だと何故か一部のライブラリ?でディレクトリインポートだと見做されてエラーになる
|
ESMではディレクトリインポートは廃止されているのと、ディレクトリインポートせずともファイル名が index だと何故か一部のライブラリ?でディレクトリインポートだと見做されてエラーになる
|
||||||
|
|
||||||
|
## CSS Recipe
|
||||||
|
|
||||||
|
### Lighten CSS vars
|
||||||
|
|
||||||
|
``` css
|
||||||
|
color: hsl(from var(--accent) h s calc(l + 10));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Darken CSS vars
|
||||||
|
|
||||||
|
``` css
|
||||||
|
color: hsl(from var(--accent) h s calc(l - 10));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add alpha to CSS vars
|
||||||
|
|
||||||
|
``` css
|
||||||
|
color: color(from var(--accent) srgb r g b / 0.5);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="[$style.root]">
|
||||||
|
<div :inert="disabled" :class="[{ [$style.disabled]: disabled }]">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<div v-if="disabled" :class="[$style.cover]"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{
|
||||||
|
disabled?: boolean;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.root {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
cursor: not-allowed;
|
||||||
|
--color: color(from var(--error) srgb r g b / 0.25);
|
||||||
|
background-size: auto auto;
|
||||||
|
background-image: repeating-linear-gradient(135deg, transparent, transparent 10px, var(--color) 4px, var(--color) 14px);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1 @@
|
||||||
|
使われなくなったけど消すのは勿体ない(将来使えるかもしれない)コードを入れておくとこ
|
|
@ -5092,6 +5092,22 @@ export interface Locale extends ILocale {
|
||||||
* これ以上このクリップにノートを追加できません。
|
* これ以上このクリップにノートを追加できません。
|
||||||
*/
|
*/
|
||||||
"clipNoteLimitExceeded": string;
|
"clipNoteLimitExceeded": string;
|
||||||
|
/**
|
||||||
|
* パフォーマンス
|
||||||
|
*/
|
||||||
|
"performance": string;
|
||||||
|
/**
|
||||||
|
* 変更あり
|
||||||
|
*/
|
||||||
|
"modified": string;
|
||||||
|
/**
|
||||||
|
* 破棄
|
||||||
|
*/
|
||||||
|
"discard": string;
|
||||||
|
/**
|
||||||
|
* {n}件の変更があります
|
||||||
|
*/
|
||||||
|
"thereAreNChanges": ParameterizedString<"n">;
|
||||||
"_delivery": {
|
"_delivery": {
|
||||||
/**
|
/**
|
||||||
* 配信状態
|
* 配信状態
|
||||||
|
|
|
@ -1269,6 +1269,10 @@ fromX: "{x}から"
|
||||||
genEmbedCode: "埋め込みコードを生成"
|
genEmbedCode: "埋め込みコードを生成"
|
||||||
noteOfThisUser: "このユーザーのノート一覧"
|
noteOfThisUser: "このユーザーのノート一覧"
|
||||||
clipNoteLimitExceeded: "これ以上このクリップにノートを追加できません。"
|
clipNoteLimitExceeded: "これ以上このクリップにノートを追加できません。"
|
||||||
|
performance: "パフォーマンス"
|
||||||
|
modified: "変更あり"
|
||||||
|
discard: "破棄"
|
||||||
|
thereAreNChanges: "{n}件の変更があります"
|
||||||
|
|
||||||
_delivery:
|
_delivery:
|
||||||
status: "配信状態"
|
status: "配信状態"
|
||||||
|
|
16
package.json
16
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2024.9.0-alpha.2",
|
"version": "2024.9.0-alpha.9",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -56,11 +56,11 @@
|
||||||
"fast-glob": "3.3.2",
|
"fast-glob": "3.3.2",
|
||||||
"ignore-walk": "6.0.5",
|
"ignore-walk": "6.0.5",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"postcss": "8.4.40",
|
"postcss": "8.4.47",
|
||||||
"tar": "6.2.1",
|
"tar": "6.2.1",
|
||||||
"terser": "5.31.3",
|
"terser": "5.33.0",
|
||||||
"typescript": "5.5.4",
|
"typescript": "5.6.2",
|
||||||
"esbuild": "0.23.0",
|
"esbuild": "0.23.1",
|
||||||
"glob": "11.0.0"
|
"glob": "11.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -69,11 +69,11 @@
|
||||||
"@typescript-eslint/eslint-plugin": "7.17.0",
|
"@typescript-eslint/eslint-plugin": "7.17.0",
|
||||||
"@typescript-eslint/parser": "7.17.0",
|
"@typescript-eslint/parser": "7.17.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "13.13.1",
|
"cypress": "13.14.2",
|
||||||
"eslint": "9.8.0",
|
"eslint": "9.8.0",
|
||||||
"globals": "15.8.0",
|
"globals": "15.9.0",
|
||||||
"ncp": "2.0.0",
|
"ncp": "2.0.0",
|
||||||
"start-server-and-test": "2.0.4"
|
"start-server-and-test": "2.0.8"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@tensorflow/tfjs-core": "4.4.0"
|
"@tensorflow/tfjs-core": "4.4.0"
|
||||||
|
|
|
@ -67,25 +67,25 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "3.620.0",
|
"@aws-sdk/client-s3": "3.620.0",
|
||||||
"@aws-sdk/lib-storage": "3.620.0",
|
"@aws-sdk/lib-storage": "3.620.0",
|
||||||
"@bull-board/api": "5.21.1",
|
"@bull-board/api": "5.23.0",
|
||||||
"@bull-board/fastify": "5.21.1",
|
"@bull-board/fastify": "5.23.0",
|
||||||
"@bull-board/ui": "5.21.1",
|
"@bull-board/ui": "5.23.0",
|
||||||
"@discordapp/twemoji": "15.0.3",
|
"@discordapp/twemoji": "15.1.0",
|
||||||
"@fastify/accepts": "4.3.0",
|
"@fastify/accepts": "5.0.0",
|
||||||
"@fastify/cookie": "9.3.1",
|
"@fastify/cookie": "10.0.0",
|
||||||
"@fastify/cors": "9.0.1",
|
"@fastify/cors": "10.0.0",
|
||||||
"@fastify/express": "3.0.0",
|
"@fastify/express": "4.0.0",
|
||||||
"@fastify/http-proxy": "9.5.0",
|
"@fastify/http-proxy": "10.0.0",
|
||||||
"@fastify/multipart": "8.3.0",
|
"@fastify/multipart": "9.0.0",
|
||||||
"@fastify/static": "7.0.4",
|
"@fastify/static": "8.0.0",
|
||||||
"@fastify/view": "9.1.0",
|
"@fastify/view": "10.0.0",
|
||||||
"@misskey-dev/node-http-message-signatures": "0.0.10",
|
"@misskey-dev/node-http-message-signatures": "0.0.10",
|
||||||
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
||||||
"@misskey-dev/summaly": "5.1.0",
|
"@misskey-dev/summaly": "5.1.0",
|
||||||
"@napi-rs/canvas": "^0.1.53",
|
"@napi-rs/canvas": "0.1.56",
|
||||||
"@nestjs/common": "10.3.10",
|
"@nestjs/common": "10.4.3",
|
||||||
"@nestjs/core": "10.3.10",
|
"@nestjs/core": "10.4.3",
|
||||||
"@nestjs/testing": "10.3.10",
|
"@nestjs/testing": "10.4.3",
|
||||||
"@sentry/node": "8.20.0",
|
"@sentry/node": "8.20.0",
|
||||||
"@sentry/profiling-node": "8.20.0",
|
"@sentry/profiling-node": "8.20.0",
|
||||||
"@simplewebauthn/server": "10.0.1",
|
"@simplewebauthn/server": "10.0.1",
|
||||||
|
@ -101,7 +101,7 @@
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
"body-parser": "1.20.3",
|
"body-parser": "1.20.3",
|
||||||
"bullmq": "5.10.4",
|
"bullmq": "5.13.2",
|
||||||
"cacheable-lookup": "7.0.0",
|
"cacheable-lookup": "7.0.0",
|
||||||
"cbor": "9.0.2",
|
"cbor": "9.0.2",
|
||||||
"chalk": "5.3.0",
|
"chalk": "5.3.0",
|
||||||
|
@ -112,28 +112,28 @@
|
||||||
"content-disposition": "0.5.4",
|
"content-disposition": "0.5.4",
|
||||||
"date-fns": "2.30.0",
|
"date-fns": "2.30.0",
|
||||||
"deep-email-validator": "0.1.21",
|
"deep-email-validator": "0.1.21",
|
||||||
"fastify": "4.28.1",
|
"fastify": "5.0.0",
|
||||||
"fastify-raw-body": "4.3.0",
|
"fastify-raw-body": "5.0.0",
|
||||||
"feed": "4.2.2",
|
"feed": "4.2.2",
|
||||||
"file-type": "19.3.0",
|
"file-type": "19.5.0",
|
||||||
"fluent-ffmpeg": "2.1.3",
|
"fluent-ffmpeg": "2.1.3",
|
||||||
"form-data": "4.0.0",
|
"form-data": "4.0.0",
|
||||||
"got": "14.4.2",
|
"got": "14.4.2",
|
||||||
"happy-dom": "15.6.1",
|
"happy-dom": "15.7.4",
|
||||||
"hpagent": "1.2.0",
|
"hpagent": "1.2.0",
|
||||||
"htmlescape": "1.1.1",
|
"htmlescape": "1.1.1",
|
||||||
"http-link-header": "1.1.3",
|
"http-link-header": "1.1.3",
|
||||||
"ioredis": "5.4.1",
|
"ioredis": "5.4.1",
|
||||||
"ip-cidr": "4.0.1",
|
"ip-cidr": "4.0.2",
|
||||||
"ipaddr.js": "2.2.0",
|
"ipaddr.js": "2.2.0",
|
||||||
"is-svg": "5.0.1",
|
"is-svg": "5.1.0",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"jsdom": "24.1.1",
|
"jsdom": "24.1.1",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"jsonld": "8.3.2",
|
"jsonld": "8.3.2",
|
||||||
"jsrsasign": "11.1.0",
|
"jsrsasign": "11.1.0",
|
||||||
|
"meilisearch": "0.42.0",
|
||||||
"juice": "11.0.0",
|
"juice": "11.0.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",
|
||||||
"mime-types": "2.1.35",
|
"mime-types": "2.1.35",
|
||||||
|
@ -143,24 +143,24 @@
|
||||||
"nanoid": "5.0.7",
|
"nanoid": "5.0.7",
|
||||||
"nested-property": "4.0.0",
|
"nested-property": "4.0.0",
|
||||||
"node-fetch": "3.3.2",
|
"node-fetch": "3.3.2",
|
||||||
"nodemailer": "6.9.14",
|
"nodemailer": "6.9.15",
|
||||||
"nsfwjs": "2.4.2",
|
"nsfwjs": "2.4.2",
|
||||||
"oauth": "0.10.0",
|
"oauth": "0.10.0",
|
||||||
"oauth2orize": "1.12.0",
|
"oauth2orize": "1.12.0",
|
||||||
"oauth2orize-pkce": "0.1.2",
|
"oauth2orize-pkce": "0.1.2",
|
||||||
"os-utils": "0.0.14",
|
"os-utils": "0.0.14",
|
||||||
"otpauth": "9.3.1",
|
"otpauth": "9.3.2",
|
||||||
"parse5": "7.1.2",
|
"parse5": "7.1.2",
|
||||||
"pg": "8.12.0",
|
"pg": "8.13.0",
|
||||||
"pkce-challenge": "4.1.0",
|
"pkce-challenge": "4.1.0",
|
||||||
"probe-image-size": "7.2.3",
|
"probe-image-size": "7.2.3",
|
||||||
"promise-limit": "2.7.0",
|
"promise-limit": "2.7.0",
|
||||||
"pug": "3.0.3",
|
"pug": "3.0.3",
|
||||||
"punycode": "2.3.1",
|
"punycode": "2.3.1",
|
||||||
"qrcode": "1.5.3",
|
"qrcode": "1.5.4",
|
||||||
"random-seed": "0.3.0",
|
"random-seed": "0.3.0",
|
||||||
"ratelimiter": "3.4.1",
|
"ratelimiter": "3.4.1",
|
||||||
"re2": "1.21.3",
|
"re2": "1.21.4",
|
||||||
"redis-lock": "0.1.4",
|
"redis-lock": "0.1.4",
|
||||||
"reflect-metadata": "0.2.2",
|
"reflect-metadata": "0.2.2",
|
||||||
"rename": "1.0.4",
|
"rename": "1.0.4",
|
||||||
|
@ -168,17 +168,17 @@
|
||||||
"rxjs": "7.8.1",
|
"rxjs": "7.8.1",
|
||||||
"sanitize-html": "2.13.0",
|
"sanitize-html": "2.13.0",
|
||||||
"secure-json-parse": "2.7.0",
|
"secure-json-parse": "2.7.0",
|
||||||
"sharp": "0.33.4",
|
"sharp": "0.33.5",
|
||||||
"slacc": "0.0.10",
|
"slacc": "0.0.10",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"stringz": "2.1.0",
|
"stringz": "2.1.0",
|
||||||
"systeminformation": "5.22.11",
|
"systeminformation": "5.23.5",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tmp": "0.2.3",
|
"tmp": "0.2.3",
|
||||||
"tsc-alias": "1.8.10",
|
"tsc-alias": "1.8.10",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typeorm": "0.3.20",
|
"typeorm": "0.3.20",
|
||||||
"typescript": "5.5.4",
|
"typescript": "5.6.2",
|
||||||
"ulid": "2.3.0",
|
"ulid": "2.3.0",
|
||||||
"vary": "1.1.2",
|
"vary": "1.1.2",
|
||||||
"web-push": "3.6.7",
|
"web-push": "3.6.7",
|
||||||
|
@ -187,7 +187,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/globals": "29.7.0",
|
"@jest/globals": "29.7.0",
|
||||||
"@nestjs/platform-express": "10.3.10",
|
"@nestjs/platform-express": "10.4.3",
|
||||||
"@simplewebauthn/types": "10.0.0",
|
"@simplewebauthn/types": "10.0.0",
|
||||||
"@swc/jest": "0.2.36",
|
"@swc/jest": "0.2.36",
|
||||||
"@types/accepts": "1.3.7",
|
"@types/accepts": "1.3.7",
|
||||||
|
@ -196,10 +196,10 @@
|
||||||
"@types/body-parser": "1.19.5",
|
"@types/body-parser": "1.19.5",
|
||||||
"@types/color-convert": "2.0.3",
|
"@types/color-convert": "2.0.3",
|
||||||
"@types/content-disposition": "0.5.8",
|
"@types/content-disposition": "0.5.8",
|
||||||
"@types/fluent-ffmpeg": "2.1.24",
|
"@types/fluent-ffmpeg": "2.1.26",
|
||||||
"@types/htmlescape": "1.1.3",
|
"@types/htmlescape": "1.1.3",
|
||||||
"@types/http-link-header": "1.0.7",
|
"@types/http-link-header": "1.0.7",
|
||||||
"@types/jest": "29.5.12",
|
"@types/jest": "29.5.13",
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/jsdom": "21.1.7",
|
"@types/jsdom": "21.1.7",
|
||||||
"@types/jsonld": "1.5.15",
|
"@types/jsonld": "1.5.15",
|
||||||
|
@ -207,18 +207,18 @@
|
||||||
"@types/mime-types": "2.1.4",
|
"@types/mime-types": "2.1.4",
|
||||||
"@types/ms": "0.7.34",
|
"@types/ms": "0.7.34",
|
||||||
"@types/node": "20.14.12",
|
"@types/node": "20.14.12",
|
||||||
"@types/nodemailer": "6.4.15",
|
"@types/nodemailer": "6.4.16",
|
||||||
"@types/oauth": "0.9.5",
|
"@types/oauth": "0.9.5",
|
||||||
"@types/oauth2orize": "1.11.5",
|
"@types/oauth2orize": "1.11.5",
|
||||||
"@types/oauth2orize-pkce": "0.1.2",
|
"@types/oauth2orize-pkce": "0.1.2",
|
||||||
"@types/pg": "8.11.6",
|
"@types/pg": "8.11.10",
|
||||||
"@types/pug": "2.0.10",
|
"@types/pug": "2.0.10",
|
||||||
"@types/punycode": "2.1.4",
|
"@types/punycode": "2.1.4",
|
||||||
"@types/qrcode": "1.5.5",
|
"@types/qrcode": "1.5.5",
|
||||||
"@types/random-seed": "0.3.5",
|
"@types/random-seed": "0.3.5",
|
||||||
"@types/ratelimiter": "3.4.6",
|
"@types/ratelimiter": "3.4.6",
|
||||||
"@types/rename": "1.0.7",
|
"@types/rename": "1.0.7",
|
||||||
"@types/sanitize-html": "2.11.0",
|
"@types/sanitize-html": "2.13.0",
|
||||||
"@types/semver": "7.5.8",
|
"@types/semver": "7.5.8",
|
||||||
"@types/simple-oauth2": "5.0.7",
|
"@types/simple-oauth2": "5.0.7",
|
||||||
"@types/sinonjs__fake-timers": "8.1.5",
|
"@types/sinonjs__fake-timers": "8.1.5",
|
||||||
|
@ -226,17 +226,17 @@
|
||||||
"@types/tmp": "0.2.6",
|
"@types/tmp": "0.2.6",
|
||||||
"@types/vary": "1.1.3",
|
"@types/vary": "1.1.3",
|
||||||
"@types/web-push": "3.6.3",
|
"@types/web-push": "3.6.3",
|
||||||
"@types/ws": "8.5.11",
|
"@types/ws": "8.5.12",
|
||||||
"@typescript-eslint/eslint-plugin": "7.17.0",
|
"@typescript-eslint/eslint-plugin": "7.17.0",
|
||||||
"@typescript-eslint/parser": "7.17.0",
|
"@typescript-eslint/parser": "7.17.0",
|
||||||
"aws-sdk-client-mock": "4.0.1",
|
"aws-sdk-client-mock": "4.0.1",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint-plugin-import": "2.29.1",
|
"eslint-plugin-import": "2.30.0",
|
||||||
"execa": "9.3.0",
|
"execa": "9.4.0",
|
||||||
"fkill": "9.0.0",
|
"fkill": "9.0.0",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-mock": "29.7.0",
|
"jest-mock": "29.7.0",
|
||||||
"nodemon": "3.1.4",
|
"nodemon": "3.1.7",
|
||||||
"pid-port": "1.0.0",
|
"pid-port": "1.0.0",
|
||||||
"simple-oauth2": "5.1.0"
|
"simple-oauth2": "5.1.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@ import { createPostgresDataSource } from './postgres.js';
|
||||||
import { RepositoryModule } from './models/RepositoryModule.js';
|
import { RepositoryModule } from './models/RepositoryModule.js';
|
||||||
import { allSettled } from './misc/promise-tracker.js';
|
import { allSettled } from './misc/promise-tracker.js';
|
||||||
import type { Provider, OnApplicationShutdown } from '@nestjs/common';
|
import type { Provider, OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
import { MiMeta } from '@/models/Meta.js';
|
||||||
|
import { GlobalEvents } from './core/GlobalEventService.js';
|
||||||
|
|
||||||
const $config: Provider = {
|
const $config: Provider = {
|
||||||
provide: DI.config,
|
provide: DI.config,
|
||||||
|
@ -86,11 +88,68 @@ const $redisForReactions: Provider = {
|
||||||
inject: [DI.config],
|
inject: [DI.config],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const $meta: Provider = {
|
||||||
|
provide: DI.meta,
|
||||||
|
useFactory: async (db: DataSource, redisForSub: Redis.Redis) => {
|
||||||
|
const meta = await db.transaction(async transactionalEntityManager => {
|
||||||
|
// 過去のバグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する
|
||||||
|
const metas = await transactionalEntityManager.find(MiMeta, {
|
||||||
|
order: {
|
||||||
|
id: 'DESC',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const meta = metas[0];
|
||||||
|
|
||||||
|
if (meta) {
|
||||||
|
return meta;
|
||||||
|
} else {
|
||||||
|
// metaが空のときfetchMetaが同時に呼ばれるとここが同時に呼ばれてしまうことがあるのでフェイルセーフなupsertを使う
|
||||||
|
const saved = await transactionalEntityManager
|
||||||
|
.upsert(
|
||||||
|
MiMeta,
|
||||||
|
{
|
||||||
|
id: 'x',
|
||||||
|
},
|
||||||
|
['id'],
|
||||||
|
)
|
||||||
|
.then((x) => transactionalEntityManager.findOneByOrFail(MiMeta, x.identifiers[0]));
|
||||||
|
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function onMessage(_: string, data: string): Promise<void> {
|
||||||
|
const obj = JSON.parse(data);
|
||||||
|
|
||||||
|
if (obj.channel === 'internal') {
|
||||||
|
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
|
||||||
|
switch (type) {
|
||||||
|
case 'metaUpdated': {
|
||||||
|
for (const key in body.after) {
|
||||||
|
(meta as any)[key] = (body.after as any)[key];
|
||||||
|
}
|
||||||
|
meta.proxyAccount = null; // joinなカラムは通常取ってこないので
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
redisForSub.on('message', onMessage);
|
||||||
|
|
||||||
|
return meta;
|
||||||
|
},
|
||||||
|
inject: [DI.db, DI.redisForSub],
|
||||||
|
};
|
||||||
|
|
||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
imports: [RepositoryModule],
|
imports: [RepositoryModule],
|
||||||
providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions],
|
providers: [$config, $db, $meta, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions],
|
||||||
exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions, RepositoryModule],
|
exports: [$config, $db, $meta, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions, RepositoryModule],
|
||||||
})
|
})
|
||||||
export class GlobalModule implements OnApplicationShutdown {
|
export class GlobalModule implements OnApplicationShutdown {
|
||||||
constructor(
|
constructor(
|
||||||
|
|
|
@ -14,10 +14,10 @@ import type {
|
||||||
AbuseReportNotificationRecipientRepository,
|
AbuseReportNotificationRecipientRepository,
|
||||||
MiAbuseReportNotificationRecipient,
|
MiAbuseReportNotificationRecipient,
|
||||||
MiAbuseUserReport,
|
MiAbuseUserReport,
|
||||||
|
MiMeta,
|
||||||
MiUser,
|
MiUser,
|
||||||
} from '@/models/_.js';
|
} from '@/models/_.js';
|
||||||
import { EmailService } from '@/core/EmailService.js';
|
import { EmailService } from '@/core/EmailService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { RecipientMethod } from '@/models/AbuseReportNotificationRecipient.js';
|
import { RecipientMethod } from '@/models/AbuseReportNotificationRecipient.js';
|
||||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
@ -27,15 +27,19 @@ import { IdService } from './IdService.js';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AbuseReportNotificationService implements OnApplicationShutdown {
|
export class AbuseReportNotificationService implements OnApplicationShutdown {
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.abuseReportNotificationRecipientRepository)
|
@Inject(DI.abuseReportNotificationRecipientRepository)
|
||||||
private abuseReportNotificationRecipientRepository: AbuseReportNotificationRecipientRepository,
|
private abuseReportNotificationRecipientRepository: AbuseReportNotificationRecipientRepository,
|
||||||
|
|
||||||
@Inject(DI.redisForSub)
|
@Inject(DI.redisForSub)
|
||||||
private redisForSub: Redis.Redis,
|
private redisForSub: Redis.Redis,
|
||||||
|
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
private systemWebhookService: SystemWebhookService,
|
private systemWebhookService: SystemWebhookService,
|
||||||
private emailService: EmailService,
|
private emailService: EmailService,
|
||||||
private metaService: MetaService,
|
|
||||||
private moderationLogService: ModerationLogService,
|
private moderationLogService: ModerationLogService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
) {
|
) {
|
||||||
|
@ -93,10 +97,8 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
|
||||||
.filter(x => x != null),
|
.filter(x => x != null),
|
||||||
);
|
);
|
||||||
|
|
||||||
// 送信先の鮮度を保つため、毎回取得する
|
|
||||||
const meta = await this.metaService.fetch(true);
|
|
||||||
recipientEMailAddresses.push(
|
recipientEMailAddresses.push(
|
||||||
...(meta.email ? [meta.email] : []),
|
...(this.meta.email ? [this.meta.email] : []),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (recipientEMailAddresses.length <= 0) {
|
if (recipientEMailAddresses.length <= 0) {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { IsNull, In, MoreThan, Not } from 'typeorm';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js';
|
import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js';
|
||||||
import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListMembershipsRepository, UsersRepository } from '@/models/_.js';
|
import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MiMeta, MutingsRepository, UserListMembershipsRepository, UsersRepository } from '@/models/_.js';
|
||||||
import type { RelationshipJobData, ThinUser } from '@/queue/types.js';
|
import type { RelationshipJobData, ThinUser } from '@/queue/types.js';
|
||||||
|
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
@ -22,13 +22,15 @@ import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { ProxyAccountService } from '@/core/ProxyAccountService.js';
|
import { ProxyAccountService } from '@/core/ProxyAccountService.js';
|
||||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import InstanceChart from '@/core/chart/charts/instance.js';
|
import InstanceChart from '@/core/chart/charts/instance.js';
|
||||||
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
|
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AccountMoveService {
|
export class AccountMoveService {
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@ -57,7 +59,6 @@ export class AccountMoveService {
|
||||||
private perUserFollowingChart: PerUserFollowingChart,
|
private perUserFollowingChart: PerUserFollowingChart,
|
||||||
private federatedInstanceService: FederatedInstanceService,
|
private federatedInstanceService: FederatedInstanceService,
|
||||||
private instanceChart: InstanceChart,
|
private instanceChart: InstanceChart,
|
||||||
private metaService: MetaService,
|
|
||||||
private relayService: RelayService,
|
private relayService: RelayService,
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
) {
|
) {
|
||||||
|
@ -276,7 +277,7 @@ export class AccountMoveService {
|
||||||
if (this.userEntityService.isRemoteUser(oldAccount)) {
|
if (this.userEntityService.isRemoteUser(oldAccount)) {
|
||||||
this.federatedInstanceService.fetch(oldAccount.host).then(async i => {
|
this.federatedInstanceService.fetch(oldAccount.host).then(async i => {
|
||||||
this.instancesRepository.decrement({ id: i.id }, 'followersCount', localFollowerIds.length);
|
this.instancesRepository.decrement({ id: i.id }, 'followersCount', localFollowerIds.length);
|
||||||
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
|
if (this.meta.enableChartsForFederatedInstances) {
|
||||||
this.instanceChart.updateFollowers(i.host, false);
|
this.instanceChart.updateFollowers(i.host, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,11 +11,10 @@ import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
|
||||||
import { IsNull } from 'typeorm';
|
import { IsNull } from 'typeorm';
|
||||||
import { DeleteObjectCommandInput, PutObjectCommandInput, NoSuchKey } from '@aws-sdk/client-s3';
|
import { DeleteObjectCommandInput, PutObjectCommandInput, NoSuchKey } from '@aws-sdk/client-s3';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/_.js';
|
import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository, MiMeta } from '@/models/_.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import Logger from '@/logger.js';
|
import Logger from '@/logger.js';
|
||||||
import type { MiRemoteUser, MiUser } from '@/models/User.js';
|
import type { MiRemoteUser, MiUser } from '@/models/User.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { MiDriveFile } from '@/models/DriveFile.js';
|
import { MiDriveFile } from '@/models/DriveFile.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
|
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
|
||||||
|
@ -99,6 +98,9 @@ export class DriveService {
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@ -115,7 +117,6 @@ export class DriveService {
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private driveFileEntityService: DriveFileEntityService,
|
private driveFileEntityService: DriveFileEntityService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private metaService: MetaService,
|
|
||||||
private downloadService: DownloadService,
|
private downloadService: DownloadService,
|
||||||
private internalStorageService: InternalStorageService,
|
private internalStorageService: InternalStorageService,
|
||||||
private s3Service: S3Service,
|
private s3Service: S3Service,
|
||||||
|
@ -149,9 +150,7 @@ export class DriveService {
|
||||||
// thunbnail, webpublic を必要なら生成
|
// thunbnail, webpublic を必要なら生成
|
||||||
const alts = await this.generateAlts(path, type, !file.uri);
|
const alts = await this.generateAlts(path, type, !file.uri);
|
||||||
|
|
||||||
const meta = await this.metaService.fetch();
|
if (this.meta.useObjectStorage) {
|
||||||
|
|
||||||
if (meta.useObjectStorage) {
|
|
||||||
//#region ObjectStorage params
|
//#region ObjectStorage params
|
||||||
let [ext] = (name.match(/\.([a-zA-Z0-9_-]+)$/) ?? ['']);
|
let [ext] = (name.match(/\.([a-zA-Z0-9_-]+)$/) ?? ['']);
|
||||||
|
|
||||||
|
@ -170,11 +169,11 @@ export class DriveService {
|
||||||
ext = '';
|
ext = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseUrl = meta.objectStorageBaseUrl
|
const baseUrl = this.meta.objectStorageBaseUrl
|
||||||
?? `${ meta.objectStorageUseSSL ? 'https' : 'http' }://${ meta.objectStorageEndpoint }${ meta.objectStoragePort ? `:${meta.objectStoragePort}` : '' }/${ meta.objectStorageBucket }`;
|
?? `${ this.meta.objectStorageUseSSL ? 'https' : 'http' }://${ this.meta.objectStorageEndpoint }${ this.meta.objectStoragePort ? `:${this.meta.objectStoragePort}` : '' }/${ this.meta.objectStorageBucket }`;
|
||||||
|
|
||||||
// for original
|
// for original
|
||||||
const key = `${meta.objectStoragePrefix}/${randomUUID()}${ext}`;
|
const key = `${this.meta.objectStoragePrefix}/${randomUUID()}${ext}`;
|
||||||
const url = `${ baseUrl }/${ key }`;
|
const url = `${ baseUrl }/${ key }`;
|
||||||
|
|
||||||
// for alts
|
// for alts
|
||||||
|
@ -191,7 +190,7 @@ export class DriveService {
|
||||||
];
|
];
|
||||||
|
|
||||||
if (alts.webpublic) {
|
if (alts.webpublic) {
|
||||||
webpublicKey = `${meta.objectStoragePrefix}/webpublic-${randomUUID()}.${alts.webpublic.ext}`;
|
webpublicKey = `${this.meta.objectStoragePrefix}/webpublic-${randomUUID()}.${alts.webpublic.ext}`;
|
||||||
webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
|
webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
|
||||||
|
|
||||||
this.registerLogger.info(`uploading webpublic: ${webpublicKey}`);
|
this.registerLogger.info(`uploading webpublic: ${webpublicKey}`);
|
||||||
|
@ -199,7 +198,7 @@ export class DriveService {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alts.thumbnail) {
|
if (alts.thumbnail) {
|
||||||
thumbnailKey = `${meta.objectStoragePrefix}/thumbnail-${randomUUID()}.${alts.thumbnail.ext}`;
|
thumbnailKey = `${this.meta.objectStoragePrefix}/thumbnail-${randomUUID()}.${alts.thumbnail.ext}`;
|
||||||
thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`;
|
thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`;
|
||||||
|
|
||||||
this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`);
|
this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`);
|
||||||
|
@ -376,10 +375,8 @@ export class DriveService {
|
||||||
if (type === 'image/apng') type = 'image/png';
|
if (type === 'image/apng') type = 'image/png';
|
||||||
if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream';
|
if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream';
|
||||||
|
|
||||||
const meta = await this.metaService.fetch();
|
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
Bucket: meta.objectStorageBucket,
|
Bucket: this.meta.objectStorageBucket,
|
||||||
Key: key,
|
Key: key,
|
||||||
Body: stream,
|
Body: stream,
|
||||||
ContentType: type,
|
ContentType: type,
|
||||||
|
@ -392,9 +389,9 @@ export class DriveService {
|
||||||
// 許可されているファイル形式でしか拡張子をつけない
|
// 許可されているファイル形式でしか拡張子をつけない
|
||||||
ext ? correctFilename(filename, ext) : filename,
|
ext ? correctFilename(filename, ext) : filename,
|
||||||
);
|
);
|
||||||
if (meta.objectStorageSetPublicRead) params.ACL = 'public-read';
|
if (this.meta.objectStorageSetPublicRead) params.ACL = 'public-read';
|
||||||
|
|
||||||
await this.s3Service.upload(meta, params)
|
await this.s3Service.upload(this.meta, params)
|
||||||
.then(
|
.then(
|
||||||
result => {
|
result => {
|
||||||
if ('Bucket' in result) { // CompleteMultipartUploadCommandOutput
|
if ('Bucket' in result) { // CompleteMultipartUploadCommandOutput
|
||||||
|
@ -460,32 +457,31 @@ export class DriveService {
|
||||||
ext = null,
|
ext = null,
|
||||||
}: AddFileArgs): Promise<MiDriveFile> {
|
}: AddFileArgs): Promise<MiDriveFile> {
|
||||||
let skipNsfwCheck = false;
|
let skipNsfwCheck = false;
|
||||||
const instance = await this.metaService.fetch();
|
|
||||||
const userRoleNSFW = user && (await this.roleService.getUserPolicies(user.id)).alwaysMarkNsfw;
|
const userRoleNSFW = user && (await this.roleService.getUserPolicies(user.id)).alwaysMarkNsfw;
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
skipNsfwCheck = true;
|
skipNsfwCheck = true;
|
||||||
} else if (userRoleNSFW) {
|
} else if (userRoleNSFW) {
|
||||||
skipNsfwCheck = true;
|
skipNsfwCheck = true;
|
||||||
}
|
}
|
||||||
if (instance.sensitiveMediaDetection === 'none') skipNsfwCheck = true;
|
if (this.meta.sensitiveMediaDetection === 'none') skipNsfwCheck = true;
|
||||||
if (user && instance.sensitiveMediaDetection === 'local' && this.userEntityService.isRemoteUser(user)) skipNsfwCheck = true;
|
if (user && this.meta.sensitiveMediaDetection === 'local' && this.userEntityService.isRemoteUser(user)) skipNsfwCheck = true;
|
||||||
if (user && instance.sensitiveMediaDetection === 'remote' && this.userEntityService.isLocalUser(user)) skipNsfwCheck = true;
|
if (user && this.meta.sensitiveMediaDetection === 'remote' && this.userEntityService.isLocalUser(user)) skipNsfwCheck = true;
|
||||||
|
|
||||||
const info = await this.fileInfoService.getFileInfo(path, {
|
const info = await this.fileInfoService.getFileInfo(path, {
|
||||||
skipSensitiveDetection: skipNsfwCheck,
|
skipSensitiveDetection: skipNsfwCheck,
|
||||||
sensitiveThreshold: // 感度が高いほどしきい値は低くすることになる
|
sensitiveThreshold: // 感度が高いほどしきい値は低くすることになる
|
||||||
instance.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 0.1 :
|
this.meta.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 0.1 :
|
||||||
instance.sensitiveMediaDetectionSensitivity === 'high' ? 0.3 :
|
this.meta.sensitiveMediaDetectionSensitivity === 'high' ? 0.3 :
|
||||||
instance.sensitiveMediaDetectionSensitivity === 'low' ? 0.7 :
|
this.meta.sensitiveMediaDetectionSensitivity === 'low' ? 0.7 :
|
||||||
instance.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0.9 :
|
this.meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0.9 :
|
||||||
0.5,
|
0.5,
|
||||||
sensitiveThresholdForPorn: 0.75,
|
sensitiveThresholdForPorn: 0.75,
|
||||||
enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos,
|
enableSensitiveMediaDetectionForVideos: this.meta.enableSensitiveMediaDetectionForVideos,
|
||||||
});
|
});
|
||||||
this.registerLogger.info(`${JSON.stringify(info)}`);
|
this.registerLogger.info(`${JSON.stringify(info)}`);
|
||||||
|
|
||||||
// 現状 false positive が多すぎて実用に耐えない
|
// 現状 false positive が多すぎて実用に耐えない
|
||||||
//if (info.porn && instance.disallowUploadWhenPredictedAsPorn) {
|
//if (info.porn && this.meta.disallowUploadWhenPredictedAsPorn) {
|
||||||
// throw new IdentifiableError('282f77bf-5816-4f72-9264-aa14d8261a21', 'Detected as porn.');
|
// throw new IdentifiableError('282f77bf-5816-4f72-9264-aa14d8261a21', 'Detected as porn.');
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
@ -589,9 +585,9 @@ export class DriveService {
|
||||||
sensitive ?? false
|
sensitive ?? false
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
if (user && this.utilityService.isMediaSilencedHost(instance.mediaSilencedHosts, user.host)) file.isSensitive = true;
|
if (user && this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, user.host)) file.isSensitive = true;
|
||||||
if (info.sensitive && profile!.autoSensitive) file.isSensitive = true;
|
if (info.sensitive && profile!.autoSensitive) file.isSensitive = true;
|
||||||
if (info.sensitive && instance.setSensitiveFlagAutomatically) file.isSensitive = true;
|
if (info.sensitive && this.meta.setSensitiveFlagAutomatically) file.isSensitive = true;
|
||||||
if (userRoleNSFW) file.isSensitive = true;
|
if (userRoleNSFW) file.isSensitive = true;
|
||||||
|
|
||||||
if (url !== null) {
|
if (url !== null) {
|
||||||
|
@ -652,7 +648,7 @@ export class DriveService {
|
||||||
// ローカルユーザーのみ
|
// ローカルユーザーのみ
|
||||||
this.perUserDriveChart.update(file, true);
|
this.perUserDriveChart.update(file, true);
|
||||||
} else {
|
} else {
|
||||||
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
|
if (this.meta.enableChartsForFederatedInstances) {
|
||||||
this.instanceChart.updateDrive(file, true);
|
this.instanceChart.updateDrive(file, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -798,7 +794,7 @@ export class DriveService {
|
||||||
// ローカルユーザーのみ
|
// ローカルユーザーのみ
|
||||||
this.perUserDriveChart.update(file, false);
|
this.perUserDriveChart.update(file, false);
|
||||||
} else {
|
} else {
|
||||||
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
|
if (this.meta.enableChartsForFederatedInstances) {
|
||||||
this.instanceChart.updateDrive(file, false);
|
this.instanceChart.updateDrive(file, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -820,14 +816,13 @@ export class DriveService {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async deleteObjectStorageFile(key: string) {
|
public async deleteObjectStorageFile(key: string) {
|
||||||
const meta = await this.metaService.fetch();
|
|
||||||
try {
|
try {
|
||||||
const param = {
|
const param = {
|
||||||
Bucket: meta.objectStorageBucket,
|
Bucket: this.meta.objectStorageBucket,
|
||||||
Key: key,
|
Key: key,
|
||||||
} as DeleteObjectCommandInput;
|
} as DeleteObjectCommandInput;
|
||||||
|
|
||||||
await this.s3Service.delete(meta, param);
|
await this.s3Service.delete(this.meta, param);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err.name === 'NoSuchKey') {
|
if (err.name === 'NoSuchKey') {
|
||||||
this.deleteLogger.warn(`The object storage had no such key to delete: ${key}. Skipping this.`, err as Error);
|
this.deleteLogger.warn(`The object storage had no such key to delete: ${key}. Skipping this.`, err as Error);
|
||||||
|
|
|
@ -8,16 +8,14 @@ import * as nodemailer from 'nodemailer';
|
||||||
import juice from 'juice';
|
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 { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import type { UserProfilesRepository } from '@/models/_.js';
|
import type { MiMeta, UserProfilesRepository } from '@/models/_.js';
|
||||||
import { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class EmailService {
|
export class EmailService {
|
||||||
|
@ -27,38 +25,37 @@ export class EmailService {
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.userProfilesRepository)
|
@Inject(DI.userProfilesRepository)
|
||||||
private userProfilesRepository: UserProfilesRepository,
|
private userProfilesRepository: UserProfilesRepository,
|
||||||
|
|
||||||
private metaService: MetaService,
|
|
||||||
private loggerService: LoggerService,
|
private loggerService: LoggerService,
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
private queueService: QueueService,
|
|
||||||
) {
|
) {
|
||||||
this.logger = this.loggerService.getLogger('email');
|
this.logger = this.loggerService.getLogger('email');
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async sendEmail(to: string, subject: string, html: string, text: string) {
|
public async sendEmail(to: string, subject: string, html: string, text: string) {
|
||||||
const meta = await this.metaService.fetch(true);
|
if (!this.meta.enableEmail) return;
|
||||||
|
|
||||||
if (!meta.enableEmail) return;
|
|
||||||
|
|
||||||
const iconUrl = `${this.config.url}/static-assets/mi-white.png`;
|
const iconUrl = `${this.config.url}/static-assets/mi-white.png`;
|
||||||
const emailSettingUrl = `${this.config.url}/settings/email`;
|
const emailSettingUrl = `${this.config.url}/settings/email`;
|
||||||
|
|
||||||
const enableAuth = meta.smtpUser != null && meta.smtpUser !== '';
|
const enableAuth = this.meta.smtpUser != null && this.meta.smtpUser !== '';
|
||||||
|
|
||||||
const transporter = nodemailer.createTransport({
|
const transporter = nodemailer.createTransport({
|
||||||
host: meta.smtpHost,
|
host: this.meta.smtpHost,
|
||||||
port: meta.smtpPort,
|
port: this.meta.smtpPort,
|
||||||
secure: meta.smtpSecure,
|
secure: this.meta.smtpSecure,
|
||||||
ignoreTLS: !enableAuth,
|
ignoreTLS: !enableAuth,
|
||||||
proxy: this.config.proxySmtp,
|
proxy: this.config.proxySmtp,
|
||||||
auth: enableAuth ? {
|
auth: enableAuth ? {
|
||||||
user: meta.smtpUser,
|
user: this.meta.smtpUser,
|
||||||
pass: meta.smtpPass,
|
pass: this.meta.smtpPass,
|
||||||
} : undefined,
|
} : undefined,
|
||||||
} as any);
|
} as any);
|
||||||
|
|
||||||
|
@ -127,7 +124,7 @@ export class EmailService {
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
<header>
|
<header>
|
||||||
<img src="${ meta.logoImageUrl ?? meta.iconUrl ?? iconUrl }"/>
|
<img src="${ this.meta.logoImageUrl ?? this.meta.iconUrl ?? iconUrl }"/>
|
||||||
</header>
|
</header>
|
||||||
<article>
|
<article>
|
||||||
<h1>${ subject }</h1>
|
<h1>${ subject }</h1>
|
||||||
|
@ -148,7 +145,7 @@ export class EmailService {
|
||||||
try {
|
try {
|
||||||
// TODO: htmlサニタイズ
|
// TODO: htmlサニタイズ
|
||||||
const info = await transporter.sendMail({
|
const info = await transporter.sendMail({
|
||||||
from: meta.email!,
|
from: this.meta.email!,
|
||||||
to: to,
|
to: to,
|
||||||
subject: subject,
|
subject: subject,
|
||||||
text: text,
|
text: text,
|
||||||
|
@ -167,8 +164,6 @@ export class EmailService {
|
||||||
available: boolean;
|
available: boolean;
|
||||||
reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned' | 'network' | 'blacklist';
|
reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned' | 'network' | 'blacklist';
|
||||||
}> {
|
}> {
|
||||||
const meta = await this.metaService.fetch();
|
|
||||||
|
|
||||||
const exist = await this.userProfilesRepository.countBy({
|
const exist = await this.userProfilesRepository.countBy({
|
||||||
emailVerified: true,
|
emailVerified: true,
|
||||||
email: emailAddress,
|
email: emailAddress,
|
||||||
|
@ -186,11 +181,11 @@ export class EmailService {
|
||||||
reason?: string | null,
|
reason?: string | null,
|
||||||
} = { valid: true, reason: null };
|
} = { valid: true, reason: null };
|
||||||
|
|
||||||
if (meta.enableActiveEmailValidation) {
|
if (this.meta.enableActiveEmailValidation) {
|
||||||
if (meta.enableVerifymailApi && meta.verifymailAuthKey != null) {
|
if (this.meta.enableVerifymailApi && this.meta.verifymailAuthKey != null) {
|
||||||
validated = await this.verifyMail(emailAddress, meta.verifymailAuthKey);
|
validated = await this.verifyMail(emailAddress, this.meta.verifymailAuthKey);
|
||||||
} else if (meta.enableTruemailApi && meta.truemailInstance && meta.truemailAuthKey != null) {
|
} else if (this.meta.enableTruemailApi && this.meta.truemailInstance && this.meta.truemailAuthKey != null) {
|
||||||
validated = await this.trueMail(meta.truemailInstance, emailAddress, meta.truemailAuthKey);
|
validated = await this.trueMail(this.meta.truemailInstance, emailAddress, this.meta.truemailAuthKey);
|
||||||
} else {
|
} else {
|
||||||
validated = await validateEmail({
|
validated = await validateEmail({
|
||||||
email: emailAddress,
|
email: emailAddress,
|
||||||
|
@ -220,7 +215,7 @@ export class EmailService {
|
||||||
}
|
}
|
||||||
|
|
||||||
const emailDomain: string = emailAddress.split('@')[1];
|
const emailDomain: string = emailAddress.split('@')[1];
|
||||||
const isBanned = this.utilityService.isBlockedHost(meta.bannedEmailDomains, emailDomain);
|
const isBanned = this.utilityService.isBlockedHost(this.meta.bannedEmailDomains, emailDomain);
|
||||||
|
|
||||||
if (isBanned) {
|
if (isBanned) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -241,7 +241,7 @@ export interface InternalEventTypes {
|
||||||
avatarDecorationCreated: MiAvatarDecoration;
|
avatarDecorationCreated: MiAvatarDecoration;
|
||||||
avatarDecorationDeleted: MiAvatarDecoration;
|
avatarDecorationDeleted: MiAvatarDecoration;
|
||||||
avatarDecorationUpdated: MiAvatarDecoration;
|
avatarDecorationUpdated: MiAvatarDecoration;
|
||||||
metaUpdated: MiMeta;
|
metaUpdated: { before?: MiMeta; after: MiMeta; };
|
||||||
followChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
|
followChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
|
||||||
unfollowChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
|
unfollowChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
|
||||||
updateUserProfile: MiUserProfile;
|
updateUserProfile: MiUserProfile;
|
||||||
|
|
|
@ -10,16 +10,18 @@ import type { MiUser } from '@/models/User.js';
|
||||||
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import type { MiHashtag } from '@/models/Hashtag.js';
|
import type { MiHashtag } from '@/models/Hashtag.js';
|
||||||
import type { HashtagsRepository } from '@/models/_.js';
|
import type { HashtagsRepository, MiMeta } from '@/models/_.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { FeaturedService } from '@/core/FeaturedService.js';
|
import { FeaturedService } from '@/core/FeaturedService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class HashtagService {
|
export class HashtagService {
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.redis)
|
@Inject(DI.redis)
|
||||||
private redisClient: Redis.Redis, // TODO: 専用のRedisサーバーを設定できるようにする
|
private redisClient: Redis.Redis, // TODO: 専用のRedisサーバーを設定できるようにする
|
||||||
|
|
||||||
|
@ -29,7 +31,6 @@ export class HashtagService {
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private featuredService: FeaturedService,
|
private featuredService: FeaturedService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private metaService: MetaService,
|
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
@ -160,10 +161,9 @@ export class HashtagService {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async updateHashtagsRanking(hashtag: string, userId: MiUser['id']): Promise<void> {
|
public async updateHashtagsRanking(hashtag: string, userId: MiUser['id']): Promise<void> {
|
||||||
const instance = await this.metaService.fetch();
|
const hiddenTags = this.meta.hiddenTags.map(t => normalizeForSearch(t));
|
||||||
const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
|
|
||||||
if (hiddenTags.includes(hashtag)) return;
|
if (hiddenTags.includes(hashtag)) return;
|
||||||
if (this.utilityService.isKeyWordIncluded(hashtag, instance.sensitiveWords)) return;
|
if (this.utilityService.isKeyWordIncluded(hashtag, this.meta.sensitiveWords)) return;
|
||||||
|
|
||||||
// YYYYMMDDHHmm (10分間隔)
|
// YYYYMMDDHHmm (10分間隔)
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
|
@ -52,7 +52,7 @@ export class MetaService implements OnApplicationShutdown {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'metaUpdated': {
|
case 'metaUpdated': {
|
||||||
this.cache = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
|
this.cache = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
|
||||||
...body,
|
...(body.after),
|
||||||
proxyAccount: null, // joinなカラムは通常取ってこないので
|
proxyAccount: null, // joinなカラムは通常取ってこないので
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
@ -141,7 +141,7 @@ export class MetaService implements OnApplicationShutdown {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.globalEventService.publishInternalEvent('metaUpdated', updated);
|
this.globalEventService.publishInternalEvent('metaUpdated', { before, after: updated });
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
|
@ -239,7 +239,7 @@ export class MfmService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { window } = new Window();
|
const { happyDOM, window } = new Window();
|
||||||
|
|
||||||
const doc = window.document;
|
const doc = window.document;
|
||||||
|
|
||||||
|
@ -457,6 +457,10 @@ export class MfmService {
|
||||||
|
|
||||||
appendChildren(nodes, body);
|
appendChildren(nodes, body);
|
||||||
|
|
||||||
return new XMLSerializer().serializeToString(body);
|
const serialized = new XMLSerializer().serializeToString(body);
|
||||||
|
|
||||||
|
happyDOM.close().catch(err => {});
|
||||||
|
|
||||||
|
return serialized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,12 @@ import * as mfm from 'mfm-js';
|
||||||
import { In, DataSource, IsNull, LessThan } from 'typeorm';
|
import { In, DataSource, IsNull, LessThan } from 'typeorm';
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||||
import RE2 from 're2';
|
|
||||||
import { extractMentions } from '@/misc/extract-mentions.js';
|
import { extractMentions } from '@/misc/extract-mentions.js';
|
||||||
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
|
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
|
||||||
import { extractHashtags } from '@/misc/extract-hashtags.js';
|
import { extractHashtags } from '@/misc/extract-hashtags.js';
|
||||||
import type { IMentionedRemoteUsers } from '@/models/Note.js';
|
import type { IMentionedRemoteUsers } from '@/models/Note.js';
|
||||||
import { MiNote } from '@/models/Note.js';
|
import { MiNote } from '@/models/Note.js';
|
||||||
import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MiMeta, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||||
import type { MiApp } from '@/models/App.js';
|
import type { MiApp } from '@/models/App.js';
|
||||||
import { concat } from '@/misc/prelude/array.js';
|
import { concat } from '@/misc/prelude/array.js';
|
||||||
|
@ -23,11 +22,8 @@ import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js';
|
||||||
import type { IPoll } from '@/models/Poll.js';
|
import type { IPoll } from '@/models/Poll.js';
|
||||||
import { MiPoll } from '@/models/Poll.js';
|
import { MiPoll } from '@/models/Poll.js';
|
||||||
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
|
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
|
||||||
import { checkWordMute } from '@/misc/check-word-mute.js';
|
|
||||||
import type { MiChannel } from '@/models/Channel.js';
|
import type { MiChannel } from '@/models/Channel.js';
|
||||||
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||||
import { MemorySingleCache } from '@/misc/cache.js';
|
|
||||||
import type { MiUserProfile } from '@/models/UserProfile.js';
|
|
||||||
import { RelayService } from '@/core/RelayService.js';
|
import { RelayService } from '@/core/RelayService.js';
|
||||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
@ -51,7 +47,6 @@ import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { SearchService } from '@/core/SearchService.js';
|
import { SearchService } from '@/core/SearchService.js';
|
||||||
import { FeaturedService } from '@/core/FeaturedService.js';
|
import { FeaturedService } from '@/core/FeaturedService.js';
|
||||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||||
|
@ -156,6 +151,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.db)
|
@Inject(DI.db)
|
||||||
private db: DataSource,
|
private db: DataSource,
|
||||||
|
|
||||||
|
@ -210,7 +208,6 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
private apDeliverManagerService: ApDeliverManagerService,
|
private apDeliverManagerService: ApDeliverManagerService,
|
||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
private metaService: MetaService,
|
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
private notesChart: NotesChart,
|
private notesChart: NotesChart,
|
||||||
private perUserNotesChart: PerUserNotesChart,
|
private perUserNotesChart: PerUserNotesChart,
|
||||||
|
@ -251,10 +248,8 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
if (data.channel != null) data.visibleUsers = [];
|
if (data.channel != null) data.visibleUsers = [];
|
||||||
if (data.channel != null) data.localOnly = true;
|
if (data.channel != null) data.localOnly = true;
|
||||||
|
|
||||||
const meta = await this.metaService.fetch();
|
|
||||||
|
|
||||||
if (data.visibility === 'public' && data.channel == null) {
|
if (data.visibility === 'public' && data.channel == null) {
|
||||||
const sensitiveWords = meta.sensitiveWords;
|
const sensitiveWords = this.meta.sensitiveWords;
|
||||||
if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
|
if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
|
||||||
data.visibility = 'home';
|
data.visibility = 'home';
|
||||||
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
|
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
|
||||||
|
@ -262,17 +257,17 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasProhibitedWords = await this.checkProhibitedWordsContain({
|
const hasProhibitedWords = this.checkProhibitedWordsContain({
|
||||||
cw: data.cw,
|
cw: data.cw,
|
||||||
text: data.text,
|
text: data.text,
|
||||||
pollChoices: data.poll?.choices,
|
pollChoices: data.poll?.choices,
|
||||||
}, meta.prohibitedWords);
|
}, this.meta.prohibitedWords);
|
||||||
|
|
||||||
if (hasProhibitedWords) {
|
if (hasProhibitedWords) {
|
||||||
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
|
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
|
||||||
}
|
}
|
||||||
|
|
||||||
const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host);
|
const inSilencedInstance = this.utilityService.isSilencedHost(this.meta.silencedHosts, user.host);
|
||||||
|
|
||||||
if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
|
if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
|
||||||
data.visibility = 'home';
|
data.visibility = 'home';
|
||||||
|
@ -365,7 +360,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the host is media-silenced, custom emojis are not allowed
|
// if the host is media-silenced, custom emojis are not allowed
|
||||||
if (this.utilityService.isMediaSilencedHost(meta.mediaSilencedHosts, user.host)) emojis = [];
|
if (this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, user.host)) emojis = [];
|
||||||
|
|
||||||
tags = tags.filter(tag => Array.from(tag).length <= 128).splice(0, 32);
|
tags = tags.filter(tag => Array.from(tag).length <= 128).splice(0, 32);
|
||||||
|
|
||||||
|
@ -506,10 +501,8 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
host: MiUser['host'];
|
host: MiUser['host'];
|
||||||
isBot: MiUser['isBot'];
|
isBot: MiUser['isBot'];
|
||||||
}, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) {
|
}, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) {
|
||||||
const meta = await this.metaService.fetch();
|
|
||||||
|
|
||||||
this.notesChart.update(note, true);
|
this.notesChart.update(note, true);
|
||||||
if (note.visibility !== 'specified' && (meta.enableChartsForRemoteUser || (user.host == null))) {
|
if (note.visibility !== 'specified' && (this.meta.enableChartsForRemoteUser || (user.host == null))) {
|
||||||
this.perUserNotesChart.update(user, note, true);
|
this.perUserNotesChart.update(user, note, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -517,7 +510,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
if (this.userEntityService.isRemoteUser(user)) {
|
if (this.userEntityService.isRemoteUser(user)) {
|
||||||
this.federatedInstanceService.fetch(user.host).then(async i => {
|
this.federatedInstanceService.fetch(user.host).then(async i => {
|
||||||
this.instancesRepository.increment({ id: i.id }, 'notesCount', 1);
|
this.instancesRepository.increment({ id: i.id }, 'notesCount', 1);
|
||||||
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
|
if (this.meta.enableChartsForFederatedInstances) {
|
||||||
this.instanceChart.updateNote(i.host, note, true);
|
this.instanceChart.updateNote(i.host, note, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -853,15 +846,14 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) {
|
private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) {
|
||||||
const meta = await this.metaService.fetch();
|
if (!this.meta.enableFanoutTimeline) return;
|
||||||
if (!meta.enableFanoutTimeline) return;
|
|
||||||
|
|
||||||
const r = this.redisForTimelines.pipeline();
|
const r = this.redisForTimelines.pipeline();
|
||||||
|
|
||||||
if (note.channelId) {
|
if (note.channelId) {
|
||||||
this.fanoutTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r);
|
this.fanoutTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r);
|
||||||
|
|
||||||
this.fanoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
this.fanoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax : this.meta.perRemoteUserUserTimelineCacheMax, r);
|
||||||
|
|
||||||
const channelFollowings = await this.channelFollowingsRepository.find({
|
const channelFollowings = await this.channelFollowingsRepository.find({
|
||||||
where: {
|
where: {
|
||||||
|
@ -871,9 +863,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const channelFollowing of channelFollowings) {
|
for (const channelFollowing of channelFollowings) {
|
||||||
this.fanoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
|
this.fanoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax, r);
|
||||||
if (note.fileIds.length > 0) {
|
if (note.fileIds.length > 0) {
|
||||||
this.fanoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
|
this.fanoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax / 2, r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -911,9 +903,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
if (!following.withReplies) continue;
|
if (!following.withReplies) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fanoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
|
this.fanoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax, r);
|
||||||
if (note.fileIds.length > 0) {
|
if (note.fileIds.length > 0) {
|
||||||
this.fanoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
|
this.fanoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax / 2, r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -930,25 +922,25 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
if (!userListMembership.withReplies) continue;
|
if (!userListMembership.withReplies) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fanoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r);
|
this.fanoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, this.meta.perUserListTimelineCacheMax, r);
|
||||||
if (note.fileIds.length > 0) {
|
if (note.fileIds.length > 0) {
|
||||||
this.fanoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r);
|
this.fanoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, this.meta.perUserListTimelineCacheMax / 2, r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自分自身のHTL
|
// 自分自身のHTL
|
||||||
if (note.userHost == null) {
|
if (note.userHost == null) {
|
||||||
if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) {
|
if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) {
|
||||||
this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r);
|
this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, this.meta.perUserHomeTimelineCacheMax, r);
|
||||||
if (note.fileIds.length > 0) {
|
if (note.fileIds.length > 0) {
|
||||||
this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
|
this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, this.meta.perUserHomeTimelineCacheMax / 2, r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自分自身以外への返信
|
// 自分自身以外への返信
|
||||||
if (isReply(note)) {
|
if (isReply(note)) {
|
||||||
this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax : this.meta.perRemoteUserUserTimelineCacheMax, r);
|
||||||
|
|
||||||
if (note.visibility === 'public' && note.userHost == null) {
|
if (note.visibility === 'public' && note.userHost == null) {
|
||||||
this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
|
this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
|
||||||
|
@ -957,9 +949,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax : this.meta.perRemoteUserUserTimelineCacheMax, r);
|
||||||
if (note.fileIds.length > 0) {
|
if (note.fileIds.length > 0) {
|
||||||
this.fanoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r);
|
this.fanoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax / 2 : this.meta.perRemoteUserUserTimelineCacheMax / 2, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (note.visibility === 'public' && note.userHost == null) {
|
if (note.visibility === 'public' && note.userHost == null) {
|
||||||
|
@ -1018,9 +1010,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async checkProhibitedWordsContain(content: Parameters<UtilityService['concatNoteContentsForKeyWordCheck']>[0], prohibitedWords?: string[]) {
|
public checkProhibitedWordsContain(content: Parameters<UtilityService['concatNoteContentsForKeyWordCheck']>[0], prohibitedWords?: string[]) {
|
||||||
if (prohibitedWords == null) {
|
if (prohibitedWords == null) {
|
||||||
prohibitedWords = (await this.metaService.fetch()).prohibitedWords;
|
prohibitedWords = this.meta.prohibitedWords;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { Brackets, In } from 'typeorm';
|
||||||
import { Injectable, Inject } from '@nestjs/common';
|
import { Injectable, Inject } from '@nestjs/common';
|
||||||
import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js';
|
import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js';
|
||||||
import type { MiNote, IMentionedRemoteUsers } from '@/models/Note.js';
|
import type { MiNote, IMentionedRemoteUsers } from '@/models/Note.js';
|
||||||
import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/_.js';
|
import type { InstancesRepository, MiMeta, NotesRepository, UsersRepository } from '@/models/_.js';
|
||||||
import { RelayService } from '@/core/RelayService.js';
|
import { RelayService } from '@/core/RelayService.js';
|
||||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
@ -19,9 +19,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
|
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { SearchService } from '@/core/SearchService.js';
|
import { SearchService } from '@/core/SearchService.js';
|
||||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||||
|
@ -32,6 +30,9 @@ export class NoteDeleteService {
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@ -42,13 +43,11 @@ export class NoteDeleteService {
|
||||||
private instancesRepository: InstancesRepository,
|
private instancesRepository: InstancesRepository,
|
||||||
|
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private noteEntityService: NoteEntityService,
|
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private relayService: RelayService,
|
private relayService: RelayService,
|
||||||
private federatedInstanceService: FederatedInstanceService,
|
private federatedInstanceService: FederatedInstanceService,
|
||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
private apDeliverManagerService: ApDeliverManagerService,
|
private apDeliverManagerService: ApDeliverManagerService,
|
||||||
private metaService: MetaService,
|
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
private moderationLogService: ModerationLogService,
|
private moderationLogService: ModerationLogService,
|
||||||
private notesChart: NotesChart,
|
private notesChart: NotesChart,
|
||||||
|
@ -102,17 +101,15 @@ export class NoteDeleteService {
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
const meta = await this.metaService.fetch();
|
|
||||||
|
|
||||||
this.notesChart.update(note, false);
|
this.notesChart.update(note, false);
|
||||||
if (meta.enableChartsForRemoteUser || (user.host == null)) {
|
if (this.meta.enableChartsForRemoteUser || (user.host == null)) {
|
||||||
this.perUserNotesChart.update(user, note, false);
|
this.perUserNotesChart.update(user, note, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.userEntityService.isRemoteUser(user)) {
|
if (this.userEntityService.isRemoteUser(user)) {
|
||||||
this.federatedInstanceService.fetch(user.host).then(async i => {
|
this.federatedInstanceService.fetch(user.host).then(async i => {
|
||||||
this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1);
|
this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1);
|
||||||
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
|
if (this.meta.enableChartsForFederatedInstances) {
|
||||||
this.instanceChart.updateNote(i.host, note, false);
|
this.instanceChart.updateNote(i.host, note, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,26 +4,25 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { UsersRepository } from '@/models/_.js';
|
import type { MiMeta, UsersRepository } from '@/models/_.js';
|
||||||
import type { MiLocalUser } from '@/models/User.js';
|
import type { MiLocalUser } from '@/models/User.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ProxyAccountService {
|
export class ProxyAccountService {
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
private metaService: MetaService,
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async fetch(): Promise<MiLocalUser | null> {
|
public async fetch(): Promise<MiLocalUser | null> {
|
||||||
const meta = await this.metaService.fetch();
|
if (this.meta.proxyAccountId == null) return null;
|
||||||
if (meta.proxyAccountId == null) return null;
|
return await this.usersRepository.findOneByOrFail({ id: this.meta.proxyAccountId }) as MiLocalUser;
|
||||||
return await this.usersRepository.findOneByOrFail({ id: meta.proxyAccountId }) as MiLocalUser;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,7 @@ import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
import { getNoteSummary } from '@/misc/get-note-summary.js';
|
import { getNoteSummary } from '@/misc/get-note-summary.js';
|
||||||
import type { MiSwSubscription, SwSubscriptionsRepository } from '@/models/_.js';
|
import type { MiMeta, MiSwSubscription, SwSubscriptionsRepository } from '@/models/_.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { RedisKVCache } from '@/misc/cache.js';
|
import { RedisKVCache } from '@/misc/cache.js';
|
||||||
|
|
||||||
|
@ -54,13 +53,14 @@ export class PushNotificationService implements OnApplicationShutdown {
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.redis)
|
@Inject(DI.redis)
|
||||||
private redisClient: Redis.Redis,
|
private redisClient: Redis.Redis,
|
||||||
|
|
||||||
@Inject(DI.swSubscriptionsRepository)
|
@Inject(DI.swSubscriptionsRepository)
|
||||||
private swSubscriptionsRepository: SwSubscriptionsRepository,
|
private swSubscriptionsRepository: SwSubscriptionsRepository,
|
||||||
|
|
||||||
private metaService: MetaService,
|
|
||||||
) {
|
) {
|
||||||
this.subscriptionsCache = new RedisKVCache<MiSwSubscription[]>(this.redisClient, 'userSwSubscriptions', {
|
this.subscriptionsCache = new RedisKVCache<MiSwSubscription[]>(this.redisClient, 'userSwSubscriptions', {
|
||||||
lifetime: 1000 * 60 * 60 * 1, // 1h
|
lifetime: 1000 * 60 * 60 * 1, // 1h
|
||||||
|
@ -73,14 +73,12 @@ export class PushNotificationService implements OnApplicationShutdown {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async pushNotification<T extends keyof PushNotificationsTypes>(userId: string, type: T, body: PushNotificationsTypes[T]) {
|
public async pushNotification<T extends keyof PushNotificationsTypes>(userId: string, type: T, body: PushNotificationsTypes[T]) {
|
||||||
const meta = await this.metaService.fetch();
|
if (!this.meta.enableServiceWorker || this.meta.swPublicKey == null || this.meta.swPrivateKey == null) return;
|
||||||
|
|
||||||
if (!meta.enableServiceWorker || meta.swPublicKey == null || meta.swPrivateKey == null) return;
|
|
||||||
|
|
||||||
// アプリケーションの連絡先と、サーバーサイドの鍵ペアの情報を登録
|
// アプリケーションの連絡先と、サーバーサイドの鍵ペアの情報を登録
|
||||||
push.setVapidDetails(this.config.url,
|
push.setVapidDetails(this.config.url,
|
||||||
meta.swPublicKey,
|
this.meta.swPublicKey,
|
||||||
meta.swPrivateKey);
|
this.meta.swPrivateKey);
|
||||||
|
|
||||||
const subscriptions = await this.subscriptionsCache.fetch(userId);
|
const subscriptions = await this.subscriptionsCache.fetch(userId);
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
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, MiMeta } from '@/models/_.js';
|
||||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
import type { MiRemoteUser, MiUser } from '@/models/User.js';
|
import type { MiRemoteUser, MiUser } from '@/models/User.js';
|
||||||
import type { MiNote } from '@/models/Note.js';
|
import type { MiNote } from '@/models/Note.js';
|
||||||
|
@ -20,7 +20,6 @@ import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerServ
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||||
|
@ -71,6 +70,9 @@ const decodeCustomEmojiRegexp = /^:([\w+-]+)(?:@([\w.-]+))?:$/;
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ReactionService {
|
export class ReactionService {
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@ -84,7 +86,6 @@ export class ReactionService {
|
||||||
private emojisRepository: EmojisRepository,
|
private emojisRepository: EmojisRepository,
|
||||||
|
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private metaService: MetaService,
|
|
||||||
private customEmojiService: CustomEmojiService,
|
private customEmojiService: CustomEmojiService,
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
|
@ -103,8 +104,6 @@ export class ReactionService {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async create(user: { id: MiUser['id']; host: MiUser['host']; isBot: MiUser['isBot'] }, note: MiNote, _reaction?: string | null) {
|
public async create(user: { id: MiUser['id']; host: MiUser['host']; isBot: MiUser['isBot'] }, note: MiNote, _reaction?: string | null) {
|
||||||
const meta = await this.metaService.fetch();
|
|
||||||
|
|
||||||
// Check blocking
|
// Check blocking
|
||||||
if (note.userId !== user.id) {
|
if (note.userId !== user.id) {
|
||||||
const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id);
|
const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id);
|
||||||
|
@ -150,7 +149,7 @@ export class ReactionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// for media silenced host, custom emoji reactions are not allowed
|
// for media silenced host, custom emoji reactions are not allowed
|
||||||
if (reacterHost != null && this.utilityService.isMediaSilencedHost(meta.mediaSilencedHosts, reacterHost)) {
|
if (reacterHost != null && this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, reacterHost)) {
|
||||||
reaction = FALLBACK;
|
reaction = FALLBACK;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -195,7 +194,7 @@ export class ReactionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment reactions count
|
// Increment reactions count
|
||||||
if (meta.enableReactionsBuffering) {
|
if (this.meta.enableReactionsBuffering) {
|
||||||
await this.reactionsBufferingService.create(note.id, user.id, reaction, note.reactionAndUserPairCache);
|
await this.reactionsBufferingService.create(note.id, user.id, reaction, note.reactionAndUserPairCache);
|
||||||
} else {
|
} else {
|
||||||
const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`;
|
const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`;
|
||||||
|
@ -228,7 +227,7 @@ export class ReactionService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meta.enableChartsForRemoteUser || (user.host == null)) {
|
if (this.meta.enableChartsForRemoteUser || (user.host == null)) {
|
||||||
this.perUserReactionsChart.update(user, note);
|
this.perUserReactionsChart.update(user, note);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,10 +304,8 @@ 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
|
||||||
if (meta.enableReactionsBuffering) {
|
if (this.meta.enableReactionsBuffering) {
|
||||||
await this.reactionsBufferingService.delete(note.id, user.id, exist.reaction);
|
await this.reactionsBufferingService.delete(note.id, user.id, exist.reaction);
|
||||||
} else {
|
} else {
|
||||||
const sql = `jsonb_set("reactions", '{${exist.reaction}}', (COALESCE("reactions"->>'${exist.reaction}', '0')::int - 1)::text::jsonb)`;
|
const sql = `jsonb_set("reactions", '{${exist.reaction}}', (COALESCE("reactions"->>'${exist.reaction}', '0')::int - 1)::text::jsonb)`;
|
||||||
|
@ -341,8 +338,21 @@ export class ReactionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文字列タイプのレガシーな形式のリアクションを現在の形式に変換しつつ、
|
* - 文字列タイプのレガシーな形式のリアクションを現在の形式に変換する
|
||||||
* データベース上には存在する「0個のリアクションがついている」という情報を削除する。
|
* - ローカルのリアクションのホストを `@.` にする(`decodeReaction()`の効果)
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
|
public convertLegacyReaction(reaction: string): string {
|
||||||
|
reaction = this.decodeReaction(reaction).reaction;
|
||||||
|
if (Object.keys(legacies).includes(reaction)) return legacies[reaction];
|
||||||
|
return reaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 廃止
|
||||||
|
/**
|
||||||
|
* - 文字列タイプのレガシーな形式のリアクションを現在の形式に変換する
|
||||||
|
* - ローカルのリアクションのホストを `@.` にする(`decodeReaction()`の効果)
|
||||||
|
* - データベース上には存在する「0個のリアクションがついている」という情報を削除する
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public convertLegacyReactions(reactions: MiNote['reactions']): MiNote['reactions'] {
|
public convertLegacyReactions(reactions: MiNote['reactions']): MiNote['reactions'] {
|
||||||
|
@ -355,10 +365,7 @@ export class ReactionService {
|
||||||
return count > 0;
|
return count > 0;
|
||||||
})
|
})
|
||||||
.map(([reaction, count]) => {
|
.map(([reaction, count]) => {
|
||||||
// unchecked indexed access
|
const key = this.convertLegacyReaction(reaction);
|
||||||
const convertedReaction = legacies[reaction] as string | undefined;
|
|
||||||
|
|
||||||
const key = this.decodeReaction(convertedReaction ?? reaction).reaction;
|
|
||||||
|
|
||||||
return [key, count] as const;
|
return [key, count] as const;
|
||||||
})
|
})
|
||||||
|
@ -413,11 +420,4 @@ export class ReactionService {
|
||||||
host: undefined,
|
host: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public convertLegacyReaction(reaction: string): string {
|
|
||||||
reaction = this.decodeReaction(reaction).reaction;
|
|
||||||
if (Object.keys(legacies).includes(reaction)) return legacies[reaction];
|
|
||||||
return reaction;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,22 +11,48 @@ import { bindThis } from '@/decorators.js';
|
||||||
import type { MiUser, NotesRepository } from '@/models/_.js';
|
import type { MiUser, NotesRepository } from '@/models/_.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.js';
|
import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.js';
|
||||||
|
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||||
|
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
|
||||||
const REDIS_DELTA_PREFIX = 'reactionsBufferDeltas';
|
const REDIS_DELTA_PREFIX = 'reactionsBufferDeltas';
|
||||||
const REDIS_PAIR_PREFIX = 'reactionsBufferPairs';
|
const REDIS_PAIR_PREFIX = 'reactionsBufferPairs';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ReactionsBufferingService {
|
export class ReactionsBufferingService implements OnApplicationShutdown {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.redisForSub)
|
||||||
|
private redisForSub: Redis.Redis,
|
||||||
|
|
||||||
@Inject(DI.redisForReactions)
|
@Inject(DI.redisForReactions)
|
||||||
private redisForReactions: Redis.Redis, // TODO: 専用のRedisインスタンスにする
|
private redisForReactions: Redis.Redis, // TODO: 専用のRedisインスタンスにする
|
||||||
|
|
||||||
@Inject(DI.notesRepository)
|
@Inject(DI.notesRepository)
|
||||||
private notesRepository: NotesRepository,
|
private notesRepository: NotesRepository,
|
||||||
) {
|
) {
|
||||||
|
this.redisForSub.on('message', this.onMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private async onMessage(_: string, data: string) {
|
||||||
|
const obj = JSON.parse(data);
|
||||||
|
|
||||||
|
if (obj.channel === 'internal') {
|
||||||
|
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
|
||||||
|
switch (type) {
|
||||||
|
case 'metaUpdated': {
|
||||||
|
// リアクションバッファリングが有効→無効になったら即bake
|
||||||
|
if (body.before != null && body.before.enableReactionsBuffering && !body.after.enableReactionsBuffering) {
|
||||||
|
this.bake();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -159,4 +185,27 @@ export class ReactionsBufferingService {
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public mergeReactions(src: MiNote['reactions'], delta: Record<string, number>): MiNote['reactions'] {
|
||||||
|
const reactions = { ...src };
|
||||||
|
for (const [name, count] of Object.entries(delta)) {
|
||||||
|
if (reactions[name] != null) {
|
||||||
|
reactions[name] += count;
|
||||||
|
} else {
|
||||||
|
reactions[name] = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public dispose(): void {
|
||||||
|
this.redisForSub.off('message', this.onMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public onApplicationShutdown(signal?: string | undefined): void {
|
||||||
|
this.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import * as Redis from 'ioredis';
|
||||||
import { In } from 'typeorm';
|
import { In } from 'typeorm';
|
||||||
import { ModuleRef } from '@nestjs/core';
|
import { ModuleRef } from '@nestjs/core';
|
||||||
import type {
|
import type {
|
||||||
|
MiMeta,
|
||||||
MiRole,
|
MiRole,
|
||||||
MiRoleAssignment,
|
MiRoleAssignment,
|
||||||
RoleAssignmentsRepository,
|
RoleAssignmentsRepository,
|
||||||
|
@ -18,7 +19,6 @@ import { MemoryKVCache, MemorySingleCache } from '@/misc/cache.js';
|
||||||
import type { MiUser } from '@/models/User.js';
|
import type { MiUser } from '@/models/User.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import type { RoleCondFormulaValue } from '@/models/Role.js';
|
import type { RoleCondFormulaValue } from '@/models/Role.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
@ -111,8 +111,8 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
constructor(
|
constructor(
|
||||||
private moduleRef: ModuleRef,
|
private moduleRef: ModuleRef,
|
||||||
|
|
||||||
@Inject(DI.redis)
|
@Inject(DI.meta)
|
||||||
private redisClient: Redis.Redis,
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.redisForTimelines)
|
@Inject(DI.redisForTimelines)
|
||||||
private redisForTimelines: Redis.Redis,
|
private redisForTimelines: Redis.Redis,
|
||||||
|
@ -129,7 +129,6 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
@Inject(DI.roleAssignmentsRepository)
|
@Inject(DI.roleAssignmentsRepository)
|
||||||
private roleAssignmentsRepository: RoleAssignmentsRepository,
|
private roleAssignmentsRepository: RoleAssignmentsRepository,
|
||||||
|
|
||||||
private metaService: MetaService,
|
|
||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
|
@ -349,8 +348,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getUserPolicies(userId: MiUser['id'] | null): Promise<RolePolicies> {
|
public async getUserPolicies(userId: MiUser['id'] | null): Promise<RolePolicies> {
|
||||||
const meta = await this.metaService.fetch();
|
const basePolicies = { ...DEFAULT_POLICIES, ...this.meta.policies };
|
||||||
const basePolicies = { ...DEFAULT_POLICIES, ...meta.policies };
|
|
||||||
|
|
||||||
if (userId == null) return basePolicies;
|
if (userId == null) return basePolicies;
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
import { DataSource, IsNull } from 'typeorm';
|
import { DataSource, IsNull } from 'typeorm';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { UsedUsernamesRepository, UsersRepository } from '@/models/_.js';
|
import type { MiMeta, UsedUsernamesRepository, UsersRepository } from '@/models/_.js';
|
||||||
import { MiUser } from '@/models/User.js';
|
import { MiUser } from '@/models/User.js';
|
||||||
import { MiUserProfile } from '@/models/UserProfile.js';
|
import { MiUserProfile } from '@/models/UserProfile.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
@ -19,7 +19,6 @@ import { InstanceActorService } from '@/core/InstanceActorService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import UsersChart from '@/core/chart/charts/users.js';
|
import UsersChart from '@/core/chart/charts/users.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { genRSAAndEd25519KeyPair } from '@/misc/gen-key-pair.js';
|
import { genRSAAndEd25519KeyPair } from '@/misc/gen-key-pair.js';
|
||||||
import { UserService } from '@/core/UserService.js';
|
import { UserService } from '@/core/UserService.js';
|
||||||
|
|
||||||
|
@ -29,6 +28,9 @@ export class SignupService {
|
||||||
@Inject(DI.db)
|
@Inject(DI.db)
|
||||||
private db: DataSource,
|
private db: DataSource,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@ -39,7 +41,6 @@ export class SignupService {
|
||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private metaService: MetaService,
|
|
||||||
private instanceActorService: InstanceActorService,
|
private instanceActorService: InstanceActorService,
|
||||||
private usersChart: UsersChart,
|
private usersChart: UsersChart,
|
||||||
) {
|
) {
|
||||||
|
@ -88,8 +89,7 @@ export class SignupService {
|
||||||
const isTheFirstUser = !await this.instanceActorService.realLocalUsersPresent();
|
const isTheFirstUser = !await this.instanceActorService.realLocalUsersPresent();
|
||||||
|
|
||||||
if (!opts.ignorePreservedUsernames && !isTheFirstUser) {
|
if (!opts.ignorePreservedUsernames && !isTheFirstUser) {
|
||||||
const instance = await this.metaService.fetch(true);
|
const isPreserved = this.meta.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
|
||||||
const isPreserved = instance.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
|
|
||||||
if (isPreserved) {
|
if (isPreserved) {
|
||||||
throw new Error('USED_USERNAME');
|
throw new Error('USED_USERNAME');
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,23 +13,20 @@ import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
|
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
|
||||||
import InstanceChart from '@/core/chart/charts/instance.js';
|
import InstanceChart from '@/core/chart/charts/instance.js';
|
||||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||||
import { UserWebhookService } from '@/core/UserWebhookService.js';
|
import { UserWebhookService } from '@/core/UserWebhookService.js';
|
||||||
import { NotificationService } from '@/core/NotificationService.js';
|
import { NotificationService } from '@/core/NotificationService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { FollowingsRepository, FollowRequestsRepository, InstancesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
import type { FollowingsRepository, FollowRequestsRepository, InstancesRepository, MiMeta, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { AccountMoveService } from '@/core/AccountMoveService.js';
|
import { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
|
||||||
import type { ThinUser } from '@/queue/types.js';
|
import type { ThinUser } from '@/queue/types.js';
|
||||||
import Logger from '../logger.js';
|
import Logger from '../logger.js';
|
||||||
|
|
||||||
|
@ -58,6 +55,9 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@ -79,13 +79,11 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private metaService: MetaService,
|
|
||||||
private notificationService: NotificationService,
|
private notificationService: NotificationService,
|
||||||
private federatedInstanceService: FederatedInstanceService,
|
private federatedInstanceService: FederatedInstanceService,
|
||||||
private webhookService: UserWebhookService,
|
private webhookService: UserWebhookService,
|
||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
private accountMoveService: AccountMoveService,
|
private accountMoveService: AccountMoveService,
|
||||||
private fanoutTimelineService: FanoutTimelineService,
|
|
||||||
private perUserFollowingChart: PerUserFollowingChart,
|
private perUserFollowingChart: PerUserFollowingChart,
|
||||||
private instanceChart: InstanceChart,
|
private instanceChart: InstanceChart,
|
||||||
) {
|
) {
|
||||||
|
@ -172,7 +170,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
followee.isLocked ||
|
followee.isLocked ||
|
||||||
(followeeProfile.carefulBot && follower.isBot) ||
|
(followeeProfile.carefulBot && follower.isBot) ||
|
||||||
(this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee) && process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING !== 'true') ||
|
(this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee) && process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING !== 'true') ||
|
||||||
(this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower) && this.utilityService.isSilencedHost((await this.metaService.fetch()).silencedHosts, follower.host))
|
(this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower) && this.utilityService.isSilencedHost(this.meta.silencedHosts, follower.host))
|
||||||
) {
|
) {
|
||||||
let autoAccept = false;
|
let autoAccept = false;
|
||||||
|
|
||||||
|
@ -307,14 +305,14 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
|
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
|
||||||
this.federatedInstanceService.fetch(follower.host).then(async i => {
|
this.federatedInstanceService.fetch(follower.host).then(async i => {
|
||||||
this.instancesRepository.increment({ id: i.id }, 'followingCount', 1);
|
this.instancesRepository.increment({ id: i.id }, 'followingCount', 1);
|
||||||
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
|
if (this.meta.enableChartsForFederatedInstances) {
|
||||||
this.instanceChart.updateFollowing(i.host, true);
|
this.instanceChart.updateFollowing(i.host, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
|
} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
|
||||||
this.federatedInstanceService.fetch(followee.host).then(async i => {
|
this.federatedInstanceService.fetch(followee.host).then(async i => {
|
||||||
this.instancesRepository.increment({ id: i.id }, 'followersCount', 1);
|
this.instancesRepository.increment({ id: i.id }, 'followersCount', 1);
|
||||||
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
|
if (this.meta.enableChartsForFederatedInstances) {
|
||||||
this.instanceChart.updateFollowers(i.host, true);
|
this.instanceChart.updateFollowers(i.host, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -439,14 +437,14 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
|
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
|
||||||
this.federatedInstanceService.fetch(follower.host).then(async i => {
|
this.federatedInstanceService.fetch(follower.host).then(async i => {
|
||||||
this.instancesRepository.decrement({ id: i.id }, 'followingCount', 1);
|
this.instancesRepository.decrement({ id: i.id }, 'followingCount', 1);
|
||||||
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
|
if (this.meta.enableChartsForFederatedInstances) {
|
||||||
this.instanceChart.updateFollowing(i.host, false);
|
this.instanceChart.updateFollowing(i.host, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
|
} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
|
||||||
this.federatedInstanceService.fetch(followee.host).then(async i => {
|
this.federatedInstanceService.fetch(followee.host).then(async i => {
|
||||||
this.instancesRepository.decrement({ id: i.id }, 'followersCount', 1);
|
this.instancesRepository.decrement({ id: i.id }, 'followersCount', 1);
|
||||||
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
|
if (this.meta.enableChartsForFederatedInstances) {
|
||||||
this.instanceChart.updateFollowers(i.host, false);
|
this.instanceChart.updateFollowers(i.host, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,10 +12,9 @@ import {
|
||||||
} from '@simplewebauthn/server';
|
} from '@simplewebauthn/server';
|
||||||
import { AttestationFormat, isoCBOR, isoUint8Array } from '@simplewebauthn/server/helpers';
|
import { AttestationFormat, isoCBOR, isoUint8Array } from '@simplewebauthn/server/helpers';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { UserSecurityKeysRepository } from '@/models/_.js';
|
import type { MiMeta, UserSecurityKeysRepository } from '@/models/_.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { MiUser } from '@/models/_.js';
|
import { MiUser } from '@/models/_.js';
|
||||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
import type {
|
import type {
|
||||||
|
@ -23,7 +22,6 @@ import type {
|
||||||
AuthenticatorTransportFuture,
|
AuthenticatorTransportFuture,
|
||||||
CredentialDeviceType,
|
CredentialDeviceType,
|
||||||
PublicKeyCredentialCreationOptionsJSON,
|
PublicKeyCredentialCreationOptionsJSON,
|
||||||
PublicKeyCredentialDescriptorFuture,
|
|
||||||
PublicKeyCredentialRequestOptionsJSON,
|
PublicKeyCredentialRequestOptionsJSON,
|
||||||
RegistrationResponseJSON,
|
RegistrationResponseJSON,
|
||||||
} from '@simplewebauthn/types';
|
} from '@simplewebauthn/types';
|
||||||
|
@ -31,33 +29,33 @@ import type {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WebAuthnService {
|
export class WebAuthnService {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.redis)
|
|
||||||
private redisClient: Redis.Redis,
|
|
||||||
|
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
|
@Inject(DI.redis)
|
||||||
|
private redisClient: Redis.Redis,
|
||||||
|
|
||||||
@Inject(DI.userSecurityKeysRepository)
|
@Inject(DI.userSecurityKeysRepository)
|
||||||
private userSecurityKeysRepository: UserSecurityKeysRepository,
|
private userSecurityKeysRepository: UserSecurityKeysRepository,
|
||||||
|
|
||||||
private metaService: MetaService,
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getRelyingParty(): Promise<{ origin: string; rpId: string; rpName: string; rpIcon?: string; }> {
|
public getRelyingParty(): { origin: string; rpId: string; rpName: string; rpIcon?: string; } {
|
||||||
const instance = await this.metaService.fetch();
|
|
||||||
return {
|
return {
|
||||||
origin: this.config.url,
|
origin: this.config.url,
|
||||||
rpId: this.config.hostname,
|
rpId: this.config.hostname,
|
||||||
rpName: instance.name ?? this.config.host,
|
rpName: this.meta.name ?? this.config.host,
|
||||||
rpIcon: instance.iconUrl ?? undefined,
|
rpIcon: this.meta.iconUrl ?? undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async initiateRegistration(userId: MiUser['id'], userName: string, userDisplayName?: string): Promise<PublicKeyCredentialCreationOptionsJSON> {
|
public async initiateRegistration(userId: MiUser['id'], userName: string, userDisplayName?: string): Promise<PublicKeyCredentialCreationOptionsJSON> {
|
||||||
const relyingParty = await this.getRelyingParty();
|
const relyingParty = this.getRelyingParty();
|
||||||
const keys = await this.userSecurityKeysRepository.findBy({
|
const keys = await this.userSecurityKeysRepository.findBy({
|
||||||
userId: userId,
|
userId: userId,
|
||||||
});
|
});
|
||||||
|
@ -104,7 +102,7 @@ export class WebAuthnService {
|
||||||
|
|
||||||
await this.redisClient.del(`webauthn:challenge:${userId}`);
|
await this.redisClient.del(`webauthn:challenge:${userId}`);
|
||||||
|
|
||||||
const relyingParty = await this.getRelyingParty();
|
const relyingParty = this.getRelyingParty();
|
||||||
|
|
||||||
let verification;
|
let verification;
|
||||||
try {
|
try {
|
||||||
|
@ -143,7 +141,7 @@ export class WebAuthnService {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async initiateAuthentication(userId: MiUser['id']): Promise<PublicKeyCredentialRequestOptionsJSON> {
|
public async initiateAuthentication(userId: MiUser['id']): Promise<PublicKeyCredentialRequestOptionsJSON> {
|
||||||
const relyingParty = await this.getRelyingParty();
|
const relyingParty = this.getRelyingParty();
|
||||||
const keys = await this.userSecurityKeysRepository.findBy({
|
const keys = await this.userSecurityKeysRepository.findBy({
|
||||||
userId: userId,
|
userId: userId,
|
||||||
});
|
});
|
||||||
|
@ -209,7 +207,7 @@ export class WebAuthnService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const relyingParty = await this.getRelyingParty();
|
const relyingParty = this.getRelyingParty();
|
||||||
|
|
||||||
let verification;
|
let verification;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -17,14 +17,13 @@ import { NoteCreateService } from '@/core/NoteCreateService.js';
|
||||||
import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js';
|
import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js';
|
||||||
import { AppLockService } from '@/core/AppLockService.js';
|
import { AppLockService } from '@/core/AppLockService.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { StatusError } from '@/misc/status-error.js';
|
import { StatusError } from '@/misc/status-error.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/_.js';
|
import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import type { MiRemoteUser } from '@/models/User.js';
|
import type { MiRemoteUser } from '@/models/User.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
|
@ -48,6 +47,9 @@ export class ApInboxService {
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@ -64,7 +66,6 @@ export class ApInboxService {
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private metaService: MetaService,
|
|
||||||
private abuseReportService: AbuseReportService,
|
private abuseReportService: AbuseReportService,
|
||||||
private userFollowingService: UserFollowingService,
|
private userFollowingService: UserFollowingService,
|
||||||
private apAudienceService: ApAudienceService,
|
private apAudienceService: ApAudienceService,
|
||||||
|
@ -283,8 +284,7 @@ export class ApInboxService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// アナウンス先をブロックしてたら中断
|
// アナウンス先をブロックしてたら中断
|
||||||
const meta = await this.metaService.fetch();
|
if (this.utilityService.isBlockedHost(this.meta.blockedHosts, this.utilityService.extractDbHost(uri))) return;
|
||||||
if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) return;
|
|
||||||
|
|
||||||
const unlock = await this.appLockService.getApLock(uri);
|
const unlock = await this.appLockService.getApLock(uri);
|
||||||
|
|
||||||
|
|
|
@ -162,7 +162,7 @@ export class ApRequestService {
|
||||||
|
|
||||||
if ((contentType ?? '').split(';')[0].trimEnd().toLowerCase() === 'text/html' && _followAlternate === true) {
|
if ((contentType ?? '').split(';')[0].trimEnd().toLowerCase() === 'text/html' && _followAlternate === true) {
|
||||||
const html = await res.text();
|
const html = await res.text();
|
||||||
const window = new Window({
|
const { window, happyDOM } = new Window({
|
||||||
settings: {
|
settings: {
|
||||||
disableJavaScriptEvaluation: true,
|
disableJavaScriptEvaluation: true,
|
||||||
disableJavaScriptFileLoading: true,
|
disableJavaScriptFileLoading: true,
|
||||||
|
@ -196,7 +196,7 @@ export class ApRequestService {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// something went wrong parsing the HTML, ignore the whole thing
|
// something went wrong parsing the HTML, ignore the whole thing
|
||||||
} finally {
|
} finally {
|
||||||
window.close();
|
happyDOM.close().catch(err => {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
|
@ -7,9 +7,8 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { IsNull, Not } from 'typeorm';
|
import { IsNull, Not } from 'typeorm';
|
||||||
import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
|
import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
|
||||||
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
||||||
import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository } from '@/models/_.js';
|
import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
|
@ -30,6 +29,7 @@ export class Resolver {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
private meta: MiMeta,
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
private notesRepository: NotesRepository,
|
private notesRepository: NotesRepository,
|
||||||
private pollsRepository: PollsRepository,
|
private pollsRepository: PollsRepository,
|
||||||
|
@ -37,7 +37,6 @@ export class Resolver {
|
||||||
private followRequestsRepository: FollowRequestsRepository,
|
private followRequestsRepository: FollowRequestsRepository,
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private instanceActorService: InstanceActorService,
|
private instanceActorService: InstanceActorService,
|
||||||
private metaService: MetaService,
|
|
||||||
private apRequestService: ApRequestService,
|
private apRequestService: ApRequestService,
|
||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
|
@ -96,8 +95,7 @@ export class Resolver {
|
||||||
return await this.resolveLocal(value);
|
return await this.resolveLocal(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const meta = await this.metaService.fetch();
|
if (this.utilityService.isBlockedHost(this.meta.blockedHosts, host)) {
|
||||||
if (this.utilityService.isBlockedHost(meta.blockedHosts, host)) {
|
|
||||||
throw new Error('Instance is blocked');
|
throw new Error('Instance is blocked');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,6 +180,9 @@ export class ApResolverService {
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@ -199,7 +200,6 @@ export class ApResolverService {
|
||||||
|
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private instanceActorService: InstanceActorService,
|
private instanceActorService: InstanceActorService,
|
||||||
private metaService: MetaService,
|
|
||||||
private apRequestService: ApRequestService,
|
private apRequestService: ApRequestService,
|
||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
|
@ -213,6 +213,7 @@ export class ApResolverService {
|
||||||
public createResolver(): Resolver {
|
public createResolver(): Resolver {
|
||||||
return new Resolver(
|
return new Resolver(
|
||||||
this.config,
|
this.config,
|
||||||
|
this.meta,
|
||||||
this.usersRepository,
|
this.usersRepository,
|
||||||
this.notesRepository,
|
this.notesRepository,
|
||||||
this.pollsRepository,
|
this.pollsRepository,
|
||||||
|
@ -220,7 +221,6 @@ export class ApResolverService {
|
||||||
this.followRequestsRepository,
|
this.followRequestsRepository,
|
||||||
this.utilityService,
|
this.utilityService,
|
||||||
this.instanceActorService,
|
this.instanceActorService,
|
||||||
this.metaService,
|
|
||||||
this.apRequestService,
|
this.apRequestService,
|
||||||
this.httpRequestService,
|
this.httpRequestService,
|
||||||
this.apRendererService,
|
this.apRendererService,
|
||||||
|
|
|
@ -5,10 +5,9 @@
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { DriveFilesRepository } from '@/models/_.js';
|
import type { DriveFilesRepository, MiMeta } from '@/models/_.js';
|
||||||
import type { MiRemoteUser } from '@/models/User.js';
|
import type { MiRemoteUser } from '@/models/User.js';
|
||||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { truncate } from '@/misc/truncate.js';
|
import { truncate } from '@/misc/truncate.js';
|
||||||
import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js';
|
import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js';
|
||||||
import { DriveService } from '@/core/DriveService.js';
|
import { DriveService } from '@/core/DriveService.js';
|
||||||
|
@ -24,10 +23,12 @@ export class ApImageService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.driveFilesRepository)
|
@Inject(DI.driveFilesRepository)
|
||||||
private driveFilesRepository: DriveFilesRepository,
|
private driveFilesRepository: DriveFilesRepository,
|
||||||
|
|
||||||
private metaService: MetaService,
|
|
||||||
private apResolverService: ApResolverService,
|
private apResolverService: ApResolverService,
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private apLoggerService: ApLoggerService,
|
private apLoggerService: ApLoggerService,
|
||||||
|
@ -63,12 +64,10 @@ export class ApImageService {
|
||||||
|
|
||||||
this.logger.info(`Creating the Image: ${image.url}`);
|
this.logger.info(`Creating the Image: ${image.url}`);
|
||||||
|
|
||||||
const instance = await this.metaService.fetch();
|
|
||||||
|
|
||||||
// Cache if remote file cache is on AND either
|
// Cache if remote file cache is on AND either
|
||||||
// 1. remote sensitive file is also on
|
// 1. remote sensitive file is also on
|
||||||
// 2. or the image is not sensitive
|
// 2. or the image is not sensitive
|
||||||
const shouldBeCached = instance.cacheRemoteFiles && (instance.cacheRemoteSensitiveFiles || !image.sensitive);
|
const shouldBeCached = this.meta.cacheRemoteFiles && (this.meta.cacheRemoteSensitiveFiles || !image.sensitive);
|
||||||
|
|
||||||
const file = await this.driveService.uploadFromUrl({
|
const file = await this.driveService.uploadFromUrl({
|
||||||
url: image.url,
|
url: image.url,
|
||||||
|
|
|
@ -6,13 +6,12 @@
|
||||||
import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||||
import { In } from 'typeorm';
|
import { In } from 'typeorm';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { PollsRepository, EmojisRepository } from '@/models/_.js';
|
import type { PollsRepository, EmojisRepository, MiMeta } from '@/models/_.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import type { MiRemoteUser } from '@/models/User.js';
|
import type { MiRemoteUser } from '@/models/User.js';
|
||||||
import type { MiNote } from '@/models/Note.js';
|
import type { MiNote } from '@/models/Note.js';
|
||||||
import { toArray, toSingle, unique } from '@/misc/prelude/array.js';
|
import { toArray, toSingle, unique } from '@/misc/prelude/array.js';
|
||||||
import type { MiEmoji } from '@/models/Emoji.js';
|
import type { MiEmoji } from '@/models/Emoji.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { AppLockService } from '@/core/AppLockService.js';
|
import { AppLockService } from '@/core/AppLockService.js';
|
||||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||||
import { NoteCreateService } from '@/core/NoteCreateService.js';
|
import { NoteCreateService } from '@/core/NoteCreateService.js';
|
||||||
|
@ -46,6 +45,9 @@ export class ApNoteService {
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.pollsRepository)
|
@Inject(DI.pollsRepository)
|
||||||
private pollsRepository: PollsRepository,
|
private pollsRepository: PollsRepository,
|
||||||
|
|
||||||
|
@ -65,7 +67,6 @@ export class ApNoteService {
|
||||||
private apMentionService: ApMentionService,
|
private apMentionService: ApMentionService,
|
||||||
private apImageService: ApImageService,
|
private apImageService: ApImageService,
|
||||||
private apQuestionService: ApQuestionService,
|
private apQuestionService: ApQuestionService,
|
||||||
private metaService: MetaService,
|
|
||||||
private appLockService: AppLockService,
|
private appLockService: AppLockService,
|
||||||
private pollService: PollService,
|
private pollService: PollService,
|
||||||
private noteCreateService: NoteCreateService,
|
private noteCreateService: NoteCreateService,
|
||||||
|
@ -182,7 +183,7 @@ export class ApNoteService {
|
||||||
/**
|
/**
|
||||||
* 禁止ワードチェック
|
* 禁止ワードチェック
|
||||||
*/
|
*/
|
||||||
const hasProhibitedWords = await this.noteCreateService.checkProhibitedWordsContain({ cw, text, pollChoices: poll?.choices });
|
const hasProhibitedWords = this.noteCreateService.checkProhibitedWordsContain({ cw, text, pollChoices: poll?.choices });
|
||||||
if (hasProhibitedWords) {
|
if (hasProhibitedWords) {
|
||||||
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
|
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
|
||||||
}
|
}
|
||||||
|
@ -336,8 +337,7 @@ export class ApNoteService {
|
||||||
const uri = getApId(value);
|
const uri = getApId(value);
|
||||||
|
|
||||||
// ブロックしていたら中断
|
// ブロックしていたら中断
|
||||||
const meta = await this.metaService.fetch();
|
if (this.utilityService.isBlockedHost(this.meta.blockedHosts, this.utilityService.extractDbHost(uri))) {
|
||||||
if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) {
|
|
||||||
throw new StatusError('blocked host', 451);
|
throw new StatusError('blocked host', 451);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import promiseLimit from 'promise-limit';
|
||||||
import { DataSource, In, Not } from 'typeorm';
|
import { DataSource, In, Not } from 'typeorm';
|
||||||
import { ModuleRef } from '@nestjs/core';
|
import { ModuleRef } from '@nestjs/core';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/_.js';
|
import type { FollowingsRepository, InstancesRepository, MiMeta, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/_.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
|
import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
|
||||||
import { MiUser } from '@/models/User.js';
|
import { MiUser } from '@/models/User.js';
|
||||||
|
@ -36,7 +36,6 @@ import type { UtilityService } from '@/core/UtilityService.js';
|
||||||
import type { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import type { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||||
import type { AccountMoveService } from '@/core/AccountMoveService.js';
|
import type { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||||
import { checkHttps } from '@/misc/check-https.js';
|
import { checkHttps } from '@/misc/check-https.js';
|
||||||
|
@ -64,7 +63,6 @@ export class ApPersonService implements OnModuleInit {
|
||||||
private driveFileEntityService: DriveFileEntityService;
|
private driveFileEntityService: DriveFileEntityService;
|
||||||
private idService: IdService;
|
private idService: IdService;
|
||||||
private globalEventService: GlobalEventService;
|
private globalEventService: GlobalEventService;
|
||||||
private metaService: MetaService;
|
|
||||||
private federatedInstanceService: FederatedInstanceService;
|
private federatedInstanceService: FederatedInstanceService;
|
||||||
private fetchInstanceMetadataService: FetchInstanceMetadataService;
|
private fetchInstanceMetadataService: FetchInstanceMetadataService;
|
||||||
private cacheService: CacheService;
|
private cacheService: CacheService;
|
||||||
|
@ -86,6 +84,9 @@ export class ApPersonService implements OnModuleInit {
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.db)
|
@Inject(DI.db)
|
||||||
private db: DataSource,
|
private db: DataSource,
|
||||||
|
|
||||||
|
@ -114,7 +115,6 @@ export class ApPersonService implements OnModuleInit {
|
||||||
this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService');
|
this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService');
|
||||||
this.idService = this.moduleRef.get('IdService');
|
this.idService = this.moduleRef.get('IdService');
|
||||||
this.globalEventService = this.moduleRef.get('GlobalEventService');
|
this.globalEventService = this.moduleRef.get('GlobalEventService');
|
||||||
this.metaService = this.moduleRef.get('MetaService');
|
|
||||||
this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService');
|
this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService');
|
||||||
this.fetchInstanceMetadataService = this.moduleRef.get('FetchInstanceMetadataService');
|
this.fetchInstanceMetadataService = this.moduleRef.get('FetchInstanceMetadataService');
|
||||||
this.cacheService = this.moduleRef.get('CacheService');
|
this.cacheService = this.moduleRef.get('CacheService');
|
||||||
|
@ -465,10 +465,10 @@ export class ApPersonService implements OnModuleInit {
|
||||||
this.cacheService.uriPersonCache.set(user.uri, user);
|
this.cacheService.uriPersonCache.set(user.uri, user);
|
||||||
|
|
||||||
// Register host
|
// Register host
|
||||||
this.federatedInstanceService.fetch(host).then(async i => {
|
this.federatedInstanceService.fetch(host).then(i => {
|
||||||
this.instancesRepository.increment({ id: i.id }, 'usersCount', 1);
|
this.instancesRepository.increment({ id: i.id }, 'usersCount', 1);
|
||||||
this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
|
this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
|
||||||
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
|
if (this.meta.enableChartsForFederatedInstances) {
|
||||||
this.instanceChart.newUser(i.host);
|
this.instanceChart.newUser(i.host);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,10 +5,9 @@
|
||||||
|
|
||||||
import { Injectable, Inject } from '@nestjs/common';
|
import { Injectable, Inject } from '@nestjs/common';
|
||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
import type { FollowingsRepository, InstancesRepository } from '@/models/_.js';
|
import type { FollowingsRepository, InstancesRepository, MiMeta } from '@/models/_.js';
|
||||||
import { AppLockService } from '@/core/AppLockService.js';
|
import { AppLockService } from '@/core/AppLockService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import Chart from '../core.js';
|
import Chart from '../core.js';
|
||||||
import { ChartLoggerService } from '../ChartLoggerService.js';
|
import { ChartLoggerService } from '../ChartLoggerService.js';
|
||||||
|
@ -24,13 +23,15 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
|
||||||
@Inject(DI.db)
|
@Inject(DI.db)
|
||||||
private db: DataSource,
|
private db: DataSource,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.followingsRepository)
|
@Inject(DI.followingsRepository)
|
||||||
private followingsRepository: FollowingsRepository,
|
private followingsRepository: FollowingsRepository,
|
||||||
|
|
||||||
@Inject(DI.instancesRepository)
|
@Inject(DI.instancesRepository)
|
||||||
private instancesRepository: InstancesRepository,
|
private instancesRepository: InstancesRepository,
|
||||||
|
|
||||||
private metaService: MetaService,
|
|
||||||
private appLockService: AppLockService,
|
private appLockService: AppLockService,
|
||||||
private chartLoggerService: ChartLoggerService,
|
private chartLoggerService: ChartLoggerService,
|
||||||
) {
|
) {
|
||||||
|
@ -43,8 +44,6 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
|
protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
|
||||||
const meta = await this.metaService.fetch();
|
|
||||||
|
|
||||||
const suspendedInstancesQuery = this.instancesRepository.createQueryBuilder('instance')
|
const suspendedInstancesQuery = this.instancesRepository.createQueryBuilder('instance')
|
||||||
.select('instance.host')
|
.select('instance.host')
|
||||||
.where('instance.suspensionState != \'none\'');
|
.where('instance.suspensionState != \'none\'');
|
||||||
|
@ -65,21 +64,21 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
|
||||||
this.followingsRepository.createQueryBuilder('following')
|
this.followingsRepository.createQueryBuilder('following')
|
||||||
.select('COUNT(DISTINCT following.followeeHost)')
|
.select('COUNT(DISTINCT following.followeeHost)')
|
||||||
.where('following.followeeHost IS NOT NULL')
|
.where('following.followeeHost IS NOT NULL')
|
||||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
.andWhere(this.meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: this.meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||||
.andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
|
.andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
|
||||||
.getRawOne()
|
.getRawOne()
|
||||||
.then(x => parseInt(x.count, 10)),
|
.then(x => parseInt(x.count, 10)),
|
||||||
this.followingsRepository.createQueryBuilder('following')
|
this.followingsRepository.createQueryBuilder('following')
|
||||||
.select('COUNT(DISTINCT following.followerHost)')
|
.select('COUNT(DISTINCT following.followerHost)')
|
||||||
.where('following.followerHost IS NOT NULL')
|
.where('following.followerHost IS NOT NULL')
|
||||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followerHost NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
.andWhere(this.meta.blockedHosts.length === 0 ? '1=1' : 'following.followerHost NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: this.meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||||
.andWhere(`following.followerHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
|
.andWhere(`following.followerHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
|
||||||
.getRawOne()
|
.getRawOne()
|
||||||
.then(x => parseInt(x.count, 10)),
|
.then(x => parseInt(x.count, 10)),
|
||||||
this.followingsRepository.createQueryBuilder('following')
|
this.followingsRepository.createQueryBuilder('following')
|
||||||
.select('COUNT(DISTINCT following.followeeHost)')
|
.select('COUNT(DISTINCT following.followeeHost)')
|
||||||
.where('following.followeeHost IS NOT NULL')
|
.where('following.followeeHost IS NOT NULL')
|
||||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
.andWhere(this.meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: this.meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||||
.andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
|
.andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
|
||||||
.andWhere(`following.followeeHost IN (${ pubsubSubQuery.getQuery() })`)
|
.andWhere(`following.followeeHost IN (${ pubsubSubQuery.getQuery() })`)
|
||||||
.setParameters(pubsubSubQuery.getParameters())
|
.setParameters(pubsubSubQuery.getParameters())
|
||||||
|
@ -88,7 +87,7 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
|
||||||
this.instancesRepository.createQueryBuilder('instance')
|
this.instancesRepository.createQueryBuilder('instance')
|
||||||
.select('COUNT(instance.id)')
|
.select('COUNT(instance.id)')
|
||||||
.where(`instance.host IN (${ subInstancesQuery.getQuery() })`)
|
.where(`instance.host IN (${ subInstancesQuery.getQuery() })`)
|
||||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
.andWhere(this.meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: this.meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||||
.andWhere('instance.suspensionState = \'none\'')
|
.andWhere('instance.suspensionState = \'none\'')
|
||||||
.andWhere('instance.isNotResponding = false')
|
.andWhere('instance.isNotResponding = false')
|
||||||
.getRawOne()
|
.getRawOne()
|
||||||
|
@ -96,7 +95,7 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
|
||||||
this.instancesRepository.createQueryBuilder('instance')
|
this.instancesRepository.createQueryBuilder('instance')
|
||||||
.select('COUNT(instance.id)')
|
.select('COUNT(instance.id)')
|
||||||
.where(`instance.host IN (${ pubInstancesQuery.getQuery() })`)
|
.where(`instance.host IN (${ pubInstancesQuery.getQuery() })`)
|
||||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
.andWhere(this.meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: this.meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||||
.andWhere('instance.suspensionState = \'none\'')
|
.andWhere('instance.suspensionState = \'none\'')
|
||||||
.andWhere('instance.isNotResponding = false')
|
.andWhere('instance.isNotResponding = false')
|
||||||
.getRawOne()
|
.getRawOne()
|
||||||
|
|
|
@ -3,19 +3,22 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
import type { MiInstance } from '@/models/Instance.js';
|
import type { MiInstance } from '@/models/Instance.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { MiUser } from '@/models/User.js';
|
import { MiUser } from '@/models/User.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { MiMeta } from '@/models/_.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InstanceEntityService {
|
export class InstanceEntityService {
|
||||||
constructor(
|
constructor(
|
||||||
private metaService: MetaService,
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
|
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
|
@ -27,7 +30,6 @@ export class InstanceEntityService {
|
||||||
instance: MiInstance,
|
instance: MiInstance,
|
||||||
me?: { id: MiUser['id']; } | null | undefined,
|
me?: { id: MiUser['id']; } | null | undefined,
|
||||||
): Promise<Packed<'FederationInstance'>> {
|
): Promise<Packed<'FederationInstance'>> {
|
||||||
const meta = await this.metaService.fetch();
|
|
||||||
const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false;
|
const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -41,7 +43,7 @@ export class InstanceEntityService {
|
||||||
isNotResponding: instance.isNotResponding,
|
isNotResponding: instance.isNotResponding,
|
||||||
isSuspended: instance.suspensionState !== 'none',
|
isSuspended: instance.suspensionState !== 'none',
|
||||||
suspensionState: instance.suspensionState,
|
suspensionState: instance.suspensionState,
|
||||||
isBlocked: this.utilityService.isBlockedHost(meta.blockedHosts, instance.host),
|
isBlocked: this.utilityService.isBlockedHost(this.meta.blockedHosts, instance.host),
|
||||||
softwareName: instance.softwareName,
|
softwareName: instance.softwareName,
|
||||||
softwareVersion: instance.softwareVersion,
|
softwareVersion: instance.softwareVersion,
|
||||||
openRegistrations: instance.openRegistrations,
|
openRegistrations: instance.openRegistrations,
|
||||||
|
@ -49,8 +51,8 @@ export class InstanceEntityService {
|
||||||
description: instance.description,
|
description: instance.description,
|
||||||
maintainerName: instance.maintainerName,
|
maintainerName: instance.maintainerName,
|
||||||
maintainerEmail: instance.maintainerEmail,
|
maintainerEmail: instance.maintainerEmail,
|
||||||
isSilenced: this.utilityService.isSilencedHost(meta.silencedHosts, instance.host),
|
isSilenced: this.utilityService.isSilencedHost(this.meta.silencedHosts, instance.host),
|
||||||
isMediaSilenced: this.utilityService.isMediaSilencedHost(meta.mediaSilencedHosts, instance.host),
|
isMediaSilenced: this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, instance.host),
|
||||||
iconUrl: instance.iconUrl,
|
iconUrl: instance.iconUrl,
|
||||||
faviconUrl: instance.faviconUrl,
|
faviconUrl: instance.faviconUrl,
|
||||||
themeColor: instance.themeColor,
|
themeColor: instance.themeColor,
|
||||||
|
|
|
@ -10,7 +10,6 @@ import type { Packed } from '@/misc/json-schema.js';
|
||||||
import type { MiMeta } from '@/models/Meta.js';
|
import type { MiMeta } from '@/models/Meta.js';
|
||||||
import type { AdsRepository } from '@/models/_.js';
|
import type { AdsRepository } from '@/models/_.js';
|
||||||
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
||||||
|
@ -24,11 +23,13 @@ export class MetaEntityService {
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.adsRepository)
|
@Inject(DI.adsRepository)
|
||||||
private adsRepository: AdsRepository,
|
private adsRepository: AdsRepository,
|
||||||
|
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private metaService: MetaService,
|
|
||||||
private instanceActorService: InstanceActorService,
|
private instanceActorService: InstanceActorService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ export class MetaEntityService {
|
||||||
let instance = meta;
|
let instance = meta;
|
||||||
|
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
instance = await this.metaService.fetch();
|
instance = this.meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ads = await this.adsRepository.createQueryBuilder('ads')
|
const ads = await this.adsRepository.createQueryBuilder('ads')
|
||||||
|
@ -140,7 +141,7 @@ export class MetaEntityService {
|
||||||
let instance = meta;
|
let instance = meta;
|
||||||
|
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
instance = await this.metaService.fetch();
|
instance = this.meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
const packed = await this.pack(instance);
|
const packed = await this.pack(instance);
|
||||||
|
|
|
@ -11,30 +11,17 @@ 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 { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository } from '@/models/_.js';
|
import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository, MiMeta } 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 { 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;
|
||||||
|
@ -43,12 +30,14 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
private reactionService: ReactionService;
|
private reactionService: ReactionService;
|
||||||
private reactionsBufferingService: ReactionsBufferingService;
|
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(
|
||||||
private moduleRef: ModuleRef,
|
private moduleRef: ModuleRef,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@ -76,7 +65,6 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
//private reactionService: ReactionService,
|
//private reactionService: ReactionService,
|
||||||
//private reactionsBufferingService: ReactionsBufferingService,
|
//private reactionsBufferingService: ReactionsBufferingService,
|
||||||
//private idService: IdService,
|
//private idService: IdService,
|
||||||
//private metaService: MetaService,
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +75,6 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
this.reactionService = this.moduleRef.get('ReactionService');
|
this.reactionService = this.moduleRef.get('ReactionService');
|
||||||
this.reactionsBufferingService = this.moduleRef.get('ReactionsBufferingService');
|
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
|
||||||
|
@ -307,7 +294,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;
|
bufferedReactions: 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'>>
|
||||||
|
@ -324,21 +311,14 @@ 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 bufferedReactions = opts._hint_?.bufferedReactions != null
|
||||||
|
? (opts._hint_.bufferedReactions.get(note.id) ?? { deltas: {}, pairs: [] })
|
||||||
const bufferdReactions = opts._hint_?.bufferdReactions != null
|
: this.meta.enableReactionsBuffering
|
||||||
? (opts._hint_.bufferdReactions.get(note.id) ?? { deltas: {}, pairs: [] })
|
|
||||||
: meta.enableReactionsBuffering
|
|
||||||
? await this.reactionsBufferingService.get(note.id)
|
? await this.reactionsBufferingService.get(note.id)
|
||||||
: { deltas: {}, pairs: [] };
|
: { deltas: {}, pairs: [] };
|
||||||
const reactions = mergeReactions(note.reactions, bufferdReactions.deltas ?? {});
|
const reactions = this.reactionService.convertLegacyReactions(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions.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('/')));
|
const reactionAndUserPairCache = note.reactionAndUserPairCache.concat(bufferedReactions.pairs.map(x => x.join('/')));
|
||||||
|
|
||||||
let text = note.text;
|
let text = note.text;
|
||||||
|
|
||||||
|
@ -441,9 +421,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
) {
|
) {
|
||||||
if (notes.length === 0) return [];
|
if (notes.length === 0) return [];
|
||||||
|
|
||||||
const meta = await this.metaService.fetch();
|
const bufferedReactions = this.meta.enableReactionsBuffering ? await this.reactionsBufferingService.getMany(notes.map(x => x.id)) : null;
|
||||||
|
|
||||||
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>();
|
||||||
|
@ -455,11 +433,11 @@ 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(mergeReactions(note.renote.reactions, bufferdReactions?.get(note.renote.id)?.deltas ?? {})).reduce((a, b) => a + b, 0);
|
const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.renote.reactions, bufferedReactions?.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 + (bufferdReactions?.get(note.renote.id)?.pairs.length ?? 0)) {
|
} else if (reactionsCount <= note.renote.reactionAndUserPairCache.length + (bufferedReactions?.get(note.renote.id)?.pairs.length ?? 0)) {
|
||||||
const pairInBuffer = bufferdReactions?.get(note.renote.id)?.pairs.find(p => p[0] === meId);
|
const pairInBuffer = bufferedReactions?.get(note.renote.id)?.pairs.find(p => p[0] === meId);
|
||||||
if (pairInBuffer) {
|
if (pairInBuffer) {
|
||||||
myReactionsMap.set(note.renote.id, pairInBuffer[1]);
|
myReactionsMap.set(note.renote.id, pairInBuffer[1]);
|
||||||
} else {
|
} else {
|
||||||
|
@ -471,11 +449,11 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (note.id < oldId) {
|
if (note.id < oldId) {
|
||||||
const reactionsCount = Object.values(mergeReactions(note.reactions, bufferdReactions?.get(note.id)?.deltas ?? {})).reduce((a, b) => a + b, 0);
|
const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions?.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 + (bufferdReactions?.get(note.id)?.pairs.length ?? 0)) {
|
} else if (reactionsCount <= note.reactionAndUserPairCache.length + (bufferedReactions?.get(note.id)?.pairs.length ?? 0)) {
|
||||||
const pairInBuffer = bufferdReactions?.get(note.id)?.pairs.find(p => p[0] === meId);
|
const pairInBuffer = bufferedReactions?.get(note.id)?.pairs.find(p => p[0] === meId);
|
||||||
if (pairInBuffer) {
|
if (pairInBuffer) {
|
||||||
myReactionsMap.set(note.id, pairInBuffer[1]);
|
myReactionsMap.set(note.id, pairInBuffer[1]);
|
||||||
} else {
|
} else {
|
||||||
|
@ -516,7 +494,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,
|
bufferedReactions,
|
||||||
myReactions: myReactionsMap,
|
myReactions: myReactionsMap,
|
||||||
packedFiles,
|
packedFiles,
|
||||||
packedUsers,
|
packedUsers,
|
||||||
|
|
|
@ -3,13 +3,14 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import si from 'systeminformation';
|
import si from 'systeminformation';
|
||||||
import Xev from 'xev';
|
import Xev from 'xev';
|
||||||
import * as osUtils from 'os-utils';
|
import * as osUtils from 'os-utils';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
import { MiMeta } from '@/models/_.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
|
||||||
const ev = new Xev();
|
const ev = new Xev();
|
||||||
|
|
||||||
|
@ -23,7 +24,8 @@ export class ServerStatsService implements OnApplicationShutdown {
|
||||||
private intervalId: NodeJS.Timeout | null = null;
|
private intervalId: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private metaService: MetaService,
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +34,7 @@ export class ServerStatsService implements OnApplicationShutdown {
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public async start(): Promise<void> {
|
public async start(): Promise<void> {
|
||||||
if (!(await this.metaService.fetch(true)).enableServerMachineStats) return;
|
if (!this.meta.enableServerMachineStats) return;
|
||||||
|
|
||||||
const log = [] as any[];
|
const log = [] as any[];
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
export const DI = {
|
export const DI = {
|
||||||
config: Symbol('config'),
|
config: Symbol('config'),
|
||||||
db: Symbol('db'),
|
db: Symbol('db'),
|
||||||
|
meta: Symbol('meta'),
|
||||||
meilisearch: Symbol('meilisearch'),
|
meilisearch: Symbol('meilisearch'),
|
||||||
redis: Symbol('redis'),
|
redis: Symbol('redis'),
|
||||||
redisForPub: Symbol('redisForPub'),
|
redisForPub: Symbol('redisForPub'),
|
||||||
|
|
|
@ -8,7 +8,7 @@ import type { onRequestHookHandler } from 'fastify';
|
||||||
export const handleRequestRedirectToOmitSearch: onRequestHookHandler = (request, reply, done) => {
|
export const handleRequestRedirectToOmitSearch: onRequestHookHandler = (request, reply, done) => {
|
||||||
const index = request.url.indexOf('?');
|
const index = request.url.indexOf('?');
|
||||||
if (~index) {
|
if (~index) {
|
||||||
reply.redirect(301, request.url.slice(0, index));
|
reply.redirect(request.url.slice(0, index), 301);
|
||||||
}
|
}
|
||||||
done();
|
done();
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,17 +7,20 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
|
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type * as Bull from 'bullmq';
|
import type * as Bull from 'bullmq';
|
||||||
|
import { MiMeta } from '@/models/_.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BakeBufferedReactionsProcessorService {
|
export class BakeBufferedReactionsProcessorService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
private reactionsBufferingService: ReactionsBufferingService,
|
private reactionsBufferingService: ReactionsBufferingService,
|
||||||
private metaService: MetaService,
|
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.logger = this.queueLoggerService.logger.createSubLogger('bake-buffered-reactions');
|
this.logger = this.queueLoggerService.logger.createSubLogger('bake-buffered-reactions');
|
||||||
|
@ -25,8 +28,7 @@ export class BakeBufferedReactionsProcessorService {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async process(): Promise<void> {
|
public async process(): Promise<void> {
|
||||||
const meta = await this.metaService.fetch();
|
if (!this.meta.enableReactionsBuffering) {
|
||||||
if (!meta.enableReactionsBuffering) {
|
|
||||||
this.logger.info('Reactions buffering is disabled. Skipping...');
|
this.logger.info('Reactions buffering is disabled. Skipping...');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,8 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||||
import * as Bull from 'bullmq';
|
import * as Bull from 'bullmq';
|
||||||
import { Not } from 'typeorm';
|
import { Not } from 'typeorm';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { InstancesRepository } from '@/models/_.js';
|
import type { InstancesRepository, MiMeta } from '@/models/_.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { ApRequestService } from '@/core/activitypub/ApRequestService.js';
|
import { ApRequestService } from '@/core/activitypub/ApRequestService.js';
|
||||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||||
import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js';
|
import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js';
|
||||||
|
@ -31,10 +30,12 @@ export class DeliverProcessorService {
|
||||||
private latest: string | null;
|
private latest: string | null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.instancesRepository)
|
@Inject(DI.instancesRepository)
|
||||||
private instancesRepository: InstancesRepository,
|
private instancesRepository: InstancesRepository,
|
||||||
|
|
||||||
private metaService: MetaService,
|
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private federatedInstanceService: FederatedInstanceService,
|
private federatedInstanceService: FederatedInstanceService,
|
||||||
private fetchInstanceMetadataService: FetchInstanceMetadataService,
|
private fetchInstanceMetadataService: FetchInstanceMetadataService,
|
||||||
|
@ -53,8 +54,7 @@ export class DeliverProcessorService {
|
||||||
const { host } = new URL(job.data.to);
|
const { host } = new URL(job.data.to);
|
||||||
|
|
||||||
// ブロックしてたら中断
|
// ブロックしてたら中断
|
||||||
const meta = await this.metaService.fetch();
|
if (this.utilityService.isBlockedHost(this.meta.blockedHosts, this.utilityService.toPuny(host))) {
|
||||||
if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.toPuny(host))) {
|
|
||||||
return 'skip (blocked)';
|
return 'skip (blocked)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ export class DeliverProcessorService {
|
||||||
this.apRequestChart.deliverSucc();
|
this.apRequestChart.deliverSucc();
|
||||||
this.federationChart.deliverd(server.host, true);
|
this.federationChart.deliverd(server.host, true);
|
||||||
|
|
||||||
if (meta.enableChartsForFederatedInstances) {
|
if (this.meta.enableChartsForFederatedInstances) {
|
||||||
this.instanceChart.requestSent(server.host, true);
|
this.instanceChart.requestSent(server.host, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ export class DeliverProcessorService {
|
||||||
this.apRequestChart.deliverFail();
|
this.apRequestChart.deliverFail();
|
||||||
this.federationChart.deliverd(i.host, false);
|
this.federationChart.deliverd(i.host, false);
|
||||||
|
|
||||||
if (meta.enableChartsForFederatedInstances) {
|
if (this.meta.enableChartsForFederatedInstances) {
|
||||||
this.instanceChart.requestSent(i.host, false);
|
this.instanceChart.requestSent(i.host, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -87,23 +87,30 @@ export class ImportCustomEmojisProcessorService {
|
||||||
await this.emojisRepository.delete({
|
await this.emojisRepository.delete({
|
||||||
name: emojiInfo.name,
|
name: emojiInfo.name,
|
||||||
});
|
});
|
||||||
const driveFile = await this.driveService.addFile({
|
try {
|
||||||
user: null,
|
const driveFile = await this.driveService.addFile({
|
||||||
path: emojiPath,
|
user: null,
|
||||||
name: record.fileName,
|
path: emojiPath,
|
||||||
force: true,
|
name: record.fileName,
|
||||||
});
|
force: true,
|
||||||
await this.customEmojiService.add({
|
});
|
||||||
name: emojiInfo.name,
|
await this.customEmojiService.add({
|
||||||
category: emojiInfo.category,
|
name: emojiInfo.name,
|
||||||
host: null,
|
category: emojiInfo.category,
|
||||||
aliases: emojiInfo.aliases,
|
host: null,
|
||||||
driveFile,
|
aliases: emojiInfo.aliases,
|
||||||
license: emojiInfo.license,
|
driveFile,
|
||||||
isSensitive: emojiInfo.isSensitive,
|
license: emojiInfo.license,
|
||||||
localOnly: emojiInfo.localOnly,
|
isSensitive: emojiInfo.isSensitive,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
|
localOnly: emojiInfo.localOnly,
|
||||||
});
|
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error || typeof e === 'string') {
|
||||||
|
this.logger.error(`couldn't import ${emojiPath} for ${emojiInfo.name}: ${e}`);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup();
|
cleanup();
|
||||||
|
|
|
@ -4,11 +4,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { URL } from 'node:url';
|
import { URL } from 'node:url';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import * as Bull from 'bullmq';
|
import * as Bull from 'bullmq';
|
||||||
import { verifyDraftSignature } from '@misskey-dev/node-http-message-signatures';
|
import { verifyDraftSignature } from '@misskey-dev/node-http-message-signatures';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||||
import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js';
|
import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js';
|
||||||
import InstanceChart from '@/core/chart/charts/instance.js';
|
import InstanceChart from '@/core/chart/charts/instance.js';
|
||||||
|
@ -26,14 +25,18 @@ import { bindThis } from '@/decorators.js';
|
||||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type { InboxJobData } from '../types.js';
|
import type { InboxJobData } from '../types.js';
|
||||||
|
import { MiMeta } from '@/models/Meta.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InboxProcessorService {
|
export class InboxProcessorService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private metaService: MetaService,
|
|
||||||
private apInboxService: ApInboxService,
|
private apInboxService: ApInboxService,
|
||||||
private federatedInstanceService: FederatedInstanceService,
|
private federatedInstanceService: FederatedInstanceService,
|
||||||
private fetchInstanceMetadataService: FetchInstanceMetadataService,
|
private fetchInstanceMetadataService: FetchInstanceMetadataService,
|
||||||
|
@ -68,8 +71,7 @@ export class InboxProcessorService {
|
||||||
const host = this.utilityService.toPuny(new URL(actorUri).hostname);
|
const host = this.utilityService.toPuny(new URL(actorUri).hostname);
|
||||||
|
|
||||||
// ブロックしてたら中断
|
// ブロックしてたら中断
|
||||||
const meta = await this.metaService.fetch();
|
if (this.utilityService.isBlockedHost(this.meta.blockedHosts, host)) {
|
||||||
if (this.utilityService.isBlockedHost(meta.blockedHosts, host)) {
|
|
||||||
return `Blocked request: ${host}`;
|
return `Blocked request: ${host}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +157,7 @@ export class InboxProcessorService {
|
||||||
|
|
||||||
// ブロックしてたら中断
|
// ブロックしてたら中断
|
||||||
const ldHost = this.utilityService.extractDbHost(authUser.user.uri);
|
const ldHost = this.utilityService.extractDbHost(authUser.user.uri);
|
||||||
if (this.utilityService.isBlockedHost(meta.blockedHosts, ldHost)) {
|
if (this.utilityService.isBlockedHost(this.meta.blockedHosts, ldHost)) {
|
||||||
throw new Bull.UnrecoverableError(`Blocked request: ${ldHost}`);
|
throw new Bull.UnrecoverableError(`Blocked request: ${ldHost}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,7 +213,7 @@ export class InboxProcessorService {
|
||||||
this.apRequestChart.inbox();
|
this.apRequestChart.inbox();
|
||||||
this.federationChart.inbox(i.host);
|
this.federationChart.inbox(i.host);
|
||||||
|
|
||||||
if (meta.enableChartsForFederatedInstances) {
|
if (this.meta.enableChartsForFederatedInstances) {
|
||||||
this.instanceChart.requestReceived(i.host);
|
this.instanceChart.requestReceived(i.host);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -82,7 +82,7 @@ export class FileServerService {
|
||||||
.catch(err => this.errorHandler(request, reply, err));
|
.catch(err => this.errorHandler(request, reply, err));
|
||||||
});
|
});
|
||||||
fastify.get<{ Params: { key: string; } }>('/files/:key/*', async (request, reply) => {
|
fastify.get<{ Params: { key: string; } }>('/files/:key/*', async (request, reply) => {
|
||||||
return await reply.redirect(301, `${this.config.url}/files/${request.params.key}`);
|
return await reply.redirect(`${this.config.url}/files/${request.params.key}`, 301);
|
||||||
});
|
});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -147,12 +147,12 @@ export class FileServerService {
|
||||||
url.searchParams.set('static', '1');
|
url.searchParams.set('static', '1');
|
||||||
|
|
||||||
file.cleanup();
|
file.cleanup();
|
||||||
return await reply.redirect(301, url.toString());
|
return await reply.redirect(url.toString(), 301);
|
||||||
} else if (file.mime.startsWith('video/')) {
|
} else if (file.mime.startsWith('video/')) {
|
||||||
const externalThumbnail = this.videoProcessingService.getExternalVideoThumbnailUrl(file.url);
|
const externalThumbnail = this.videoProcessingService.getExternalVideoThumbnailUrl(file.url);
|
||||||
if (externalThumbnail) {
|
if (externalThumbnail) {
|
||||||
file.cleanup();
|
file.cleanup();
|
||||||
return await reply.redirect(301, externalThumbnail);
|
return await reply.redirect(externalThumbnail, 301);
|
||||||
}
|
}
|
||||||
|
|
||||||
image = await this.videoProcessingService.generateVideoThumbnail(file.path);
|
image = await this.videoProcessingService.generateVideoThumbnail(file.path);
|
||||||
|
@ -167,7 +167,7 @@ export class FileServerService {
|
||||||
url.searchParams.set('url', file.url);
|
url.searchParams.set('url', file.url);
|
||||||
|
|
||||||
file.cleanup();
|
file.cleanup();
|
||||||
return await reply.redirect(301, url.toString());
|
return await reply.redirect(url.toString(), 301);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,8 +314,8 @@ export class FileServerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
return await reply.redirect(
|
return await reply.redirect(
|
||||||
301,
|
|
||||||
url.toString(),
|
url.toString(),
|
||||||
|
301,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import fastifyRawBody from 'fastify-raw-body';
|
||||||
import { IsNull } from 'typeorm';
|
import { IsNull } from 'typeorm';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import type { EmojisRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
import type { EmojisRepository, MiMeta, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import * as Acct from '@/misc/acct.js';
|
import * as Acct from '@/misc/acct.js';
|
||||||
|
@ -21,7 +21,6 @@ import { genIdenticon } from '@/misc/gen-identicon.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { ActivityPubServerService } from './ActivityPubServerService.js';
|
import { ActivityPubServerService } from './ActivityPubServerService.js';
|
||||||
import { NodeinfoServerService } from './NodeinfoServerService.js';
|
import { NodeinfoServerService } from './NodeinfoServerService.js';
|
||||||
import { ApiServerService } from './api/ApiServerService.js';
|
import { ApiServerService } from './api/ApiServerService.js';
|
||||||
|
@ -44,6 +43,9 @@ export class ServerService implements OnApplicationShutdown {
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@ -53,7 +55,6 @@ export class ServerService implements OnApplicationShutdown {
|
||||||
@Inject(DI.emojisRepository)
|
@Inject(DI.emojisRepository)
|
||||||
private emojisRepository: EmojisRepository,
|
private emojisRepository: EmojisRepository,
|
||||||
|
|
||||||
private metaService: MetaService,
|
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private apiServerService: ApiServerService,
|
private apiServerService: ApiServerService,
|
||||||
private openApiServerService: OpenApiServerService,
|
private openApiServerService: OpenApiServerService,
|
||||||
|
@ -165,8 +166,8 @@ export class ServerService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
|
|
||||||
return await reply.redirect(
|
return await reply.redirect(
|
||||||
301,
|
|
||||||
url.toString(),
|
url.toString(),
|
||||||
|
301,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -193,7 +194,7 @@ export class ServerService implements OnApplicationShutdown {
|
||||||
reply.header('Content-Type', 'image/png');
|
reply.header('Content-Type', 'image/png');
|
||||||
reply.header('Cache-Control', 'public, max-age=86400');
|
reply.header('Cache-Control', 'public, max-age=86400');
|
||||||
|
|
||||||
if ((await this.metaService.fetch()).enableIdenticonGeneration) {
|
if (this.meta.enableIdenticonGeneration) {
|
||||||
return await genIdenticon(request.params.x);
|
return await genIdenticon(request.params.x);
|
||||||
} else {
|
} else {
|
||||||
return reply.redirect('/static-assets/avatar.png');
|
return reply.redirect('/static-assets/avatar.png');
|
||||||
|
|
|
@ -13,8 +13,7 @@ import { getIpHash } from '@/misc/get-ip-hash.js';
|
||||||
import type { MiLocalUser, MiUser } from '@/models/User.js';
|
import type { MiLocalUser, MiUser } from '@/models/User.js';
|
||||||
import type { MiAccessToken } from '@/models/AccessToken.js';
|
import type { MiAccessToken } from '@/models/AccessToken.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import type { UserIpsRepository } from '@/models/_.js';
|
import type { MiMeta, UserIpsRepository } from '@/models/_.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { createTemp } from '@/misc/create-temp.js';
|
import { createTemp } from '@/misc/create-temp.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
@ -40,13 +39,15 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||||
private userIpHistoriesClearIntervalId: NodeJS.Timeout;
|
private userIpHistoriesClearIntervalId: NodeJS.Timeout;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
@Inject(DI.userIpsRepository)
|
@Inject(DI.userIpsRepository)
|
||||||
private userIpsRepository: UserIpsRepository,
|
private userIpsRepository: UserIpsRepository,
|
||||||
|
|
||||||
private metaService: MetaService,
|
|
||||||
private authenticateService: AuthenticateService,
|
private authenticateService: AuthenticateService,
|
||||||
private rateLimiterService: RateLimiterService,
|
private rateLimiterService: RateLimiterService,
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
|
@ -265,9 +266,8 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async logIp(request: FastifyRequest, user: MiLocalUser) {
|
private logIp(request: FastifyRequest, user: MiLocalUser) {
|
||||||
const meta = await this.metaService.fetch();
|
if (!this.meta.enableIpLogging) return;
|
||||||
if (!meta.enableIpLogging) return;
|
|
||||||
const ip = request.ip;
|
const ip = request.ip;
|
||||||
const ips = this.userIpHistories.get(user.id);
|
const ips = this.userIpHistories.get(user.id);
|
||||||
if (ips == null || !ips.has(ip)) {
|
if (ips == null || !ips.has(ip)) {
|
||||||
|
|
|
@ -7,9 +7,8 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
import { IsNull } from 'typeorm';
|
import { IsNull } from 'typeorm';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository, MiRegistrationTicket } from '@/models/_.js';
|
import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository, MiRegistrationTicket, MiMeta } from '@/models/_.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { CaptchaService } from '@/core/CaptchaService.js';
|
import { CaptchaService } from '@/core/CaptchaService.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { SignupService } from '@/core/SignupService.js';
|
import { SignupService } from '@/core/SignupService.js';
|
||||||
|
@ -28,6 +27,9 @@ export class SignupApiService {
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@ -45,7 +47,6 @@ export class SignupApiService {
|
||||||
|
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private metaService: MetaService,
|
|
||||||
private captchaService: CaptchaService,
|
private captchaService: CaptchaService,
|
||||||
private signupService: SignupService,
|
private signupService: SignupService,
|
||||||
private signinService: SigninService,
|
private signinService: SigninService,
|
||||||
|
@ -72,31 +73,29 @@ export class SignupApiService {
|
||||||
) {
|
) {
|
||||||
const body = request.body;
|
const body = request.body;
|
||||||
|
|
||||||
const instance = await this.metaService.fetch(true);
|
|
||||||
|
|
||||||
// Verify *Captcha
|
// Verify *Captcha
|
||||||
// ただしテスト時はこの機構は障害となるため無効にする
|
// ただしテスト時はこの機構は障害となるため無効にする
|
||||||
if (process.env.NODE_ENV !== 'test') {
|
if (process.env.NODE_ENV !== 'test') {
|
||||||
if (instance.enableHcaptcha && instance.hcaptchaSecretKey) {
|
if (this.meta.enableHcaptcha && this.meta.hcaptchaSecretKey) {
|
||||||
await this.captchaService.verifyHcaptcha(instance.hcaptchaSecretKey, body['hcaptcha-response']).catch(err => {
|
await this.captchaService.verifyHcaptcha(this.meta.hcaptchaSecretKey, body['hcaptcha-response']).catch(err => {
|
||||||
throw new FastifyReplyError(400, err);
|
throw new FastifyReplyError(400, err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instance.enableMcaptcha && instance.mcaptchaSecretKey && instance.mcaptchaSitekey && instance.mcaptchaInstanceUrl) {
|
if (this.meta.enableMcaptcha && this.meta.mcaptchaSecretKey && this.meta.mcaptchaSitekey && this.meta.mcaptchaInstanceUrl) {
|
||||||
await this.captchaService.verifyMcaptcha(instance.mcaptchaSecretKey, instance.mcaptchaSitekey, instance.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => {
|
await this.captchaService.verifyMcaptcha(this.meta.mcaptchaSecretKey, this.meta.mcaptchaSitekey, this.meta.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => {
|
||||||
throw new FastifyReplyError(400, err);
|
throw new FastifyReplyError(400, err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instance.enableRecaptcha && instance.recaptchaSecretKey) {
|
if (this.meta.enableRecaptcha && this.meta.recaptchaSecretKey) {
|
||||||
await this.captchaService.verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => {
|
await this.captchaService.verifyRecaptcha(this.meta.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => {
|
||||||
throw new FastifyReplyError(400, err);
|
throw new FastifyReplyError(400, err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instance.enableTurnstile && instance.turnstileSecretKey) {
|
if (this.meta.enableTurnstile && this.meta.turnstileSecretKey) {
|
||||||
await this.captchaService.verifyTurnstile(instance.turnstileSecretKey, body['turnstile-response']).catch(err => {
|
await this.captchaService.verifyTurnstile(this.meta.turnstileSecretKey, body['turnstile-response']).catch(err => {
|
||||||
throw new FastifyReplyError(400, err);
|
throw new FastifyReplyError(400, err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -108,7 +107,7 @@ export class SignupApiService {
|
||||||
const invitationCode = body['invitationCode'];
|
const invitationCode = body['invitationCode'];
|
||||||
const emailAddress = body['emailAddress'];
|
const emailAddress = body['emailAddress'];
|
||||||
|
|
||||||
if (instance.emailRequiredForSignup) {
|
if (this.meta.emailRequiredForSignup) {
|
||||||
if (emailAddress == null || typeof emailAddress !== 'string') {
|
if (emailAddress == null || typeof emailAddress !== 'string') {
|
||||||
reply.code(400);
|
reply.code(400);
|
||||||
return;
|
return;
|
||||||
|
@ -123,7 +122,7 @@ export class SignupApiService {
|
||||||
|
|
||||||
let ticket: MiRegistrationTicket | null = null;
|
let ticket: MiRegistrationTicket | null = null;
|
||||||
|
|
||||||
if (instance.disableRegistration) {
|
if (this.meta.disableRegistration) {
|
||||||
if (invitationCode == null || typeof invitationCode !== 'string') {
|
if (invitationCode == null || typeof invitationCode !== 'string') {
|
||||||
reply.code(400);
|
reply.code(400);
|
||||||
return;
|
return;
|
||||||
|
@ -144,7 +143,7 @@ export class SignupApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// メアド認証が有効の場合
|
// メアド認証が有効の場合
|
||||||
if (instance.emailRequiredForSignup) {
|
if (this.meta.emailRequiredForSignup) {
|
||||||
// メアド認証済みならエラー
|
// メアド認証済みならエラー
|
||||||
if (ticket.usedBy) {
|
if (ticket.usedBy) {
|
||||||
reply.code(400);
|
reply.code(400);
|
||||||
|
@ -162,7 +161,7 @@ export class SignupApiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instance.emailRequiredForSignup) {
|
if (this.meta.emailRequiredForSignup) {
|
||||||
if (await this.usersRepository.exists({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
|
if (await this.usersRepository.exists({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
|
||||||
throw new FastifyReplyError(400, 'DUPLICATED_USERNAME');
|
throw new FastifyReplyError(400, 'DUPLICATED_USERNAME');
|
||||||
}
|
}
|
||||||
|
@ -172,7 +171,7 @@ export class SignupApiService {
|
||||||
throw new FastifyReplyError(400, 'USED_USERNAME');
|
throw new FastifyReplyError(400, 'USED_USERNAME');
|
||||||
}
|
}
|
||||||
|
|
||||||
const isPreserved = instance.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
|
const isPreserved = this.meta.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
|
||||||
if (isPreserved) {
|
if (isPreserved) {
|
||||||
throw new FastifyReplyError(400, 'DENIED_USERNAME');
|
throw new FastifyReplyError(400, 'DENIED_USERNAME');
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import type { MiNote } from '@/models/Note.js';
|
import type { MiNote } from '@/models/Note.js';
|
||||||
|
@ -12,7 +12,6 @@ import { isActor, isPost, getApId } from '@/core/activitypub/type.js';
|
||||||
import type { SchemaType } from '@/misc/json-schema.js';
|
import type { SchemaType } from '@/misc/json-schema.js';
|
||||||
import { ApResolverService } from '@/core/activitypub/ApResolverService.js';
|
import { ApResolverService } from '@/core/activitypub/ApResolverService.js';
|
||||||
import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
|
import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
||||||
import { ApNoteService } from '@/core/activitypub/models/ApNoteService.js';
|
import { ApNoteService } from '@/core/activitypub/models/ApNoteService.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
@ -20,6 +19,8 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
import { MiMeta } from '@/models/_.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['federation'],
|
tags: ['federation'],
|
||||||
|
@ -88,10 +89,12 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
private metaService: MetaService,
|
|
||||||
private apResolverService: ApResolverService,
|
private apResolverService: ApResolverService,
|
||||||
private apDbResolverService: ApDbResolverService,
|
private apDbResolverService: ApDbResolverService,
|
||||||
private apPersonService: ApPersonService,
|
private apPersonService: ApPersonService,
|
||||||
|
@ -112,9 +115,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
private async fetchAny(uri: string, me: MiLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
|
private async fetchAny(uri: string, me: MiLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
|
||||||
// ブロックしてたら中断
|
// ブロックしてたら中断
|
||||||
const fetchedMeta = await this.metaService.fetch();
|
if (this.utilityService.isBlockedHost(this.serverSettings.blockedHosts, this.utilityService.extractDbHost(uri))) return null;
|
||||||
if (this.utilityService.isBlockedHost(fetchedMeta.blockedHosts, this.utilityService.extractDbHost(uri))) return null;
|
|
||||||
|
|
||||||
let local = await this.mergePack(me, ...await Promise.all([
|
let local = await this.mergePack(me, ...await Promise.all([
|
||||||
this.apDbResolverService.getUserFromApId(uri),
|
this.apDbResolverService.getUserFromApId(uri),
|
||||||
|
|
|
@ -5,14 +5,12 @@
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import type { ChannelsRepository, NotesRepository } from '@/models/_.js';
|
import type { ChannelsRepository, MiMeta, NotesRepository } from '@/models/_.js';
|
||||||
import { QueryService } from '@/core/QueryService.js';
|
import { QueryService } from '@/core/QueryService.js';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
||||||
import { MiLocalUser } from '@/models/User.js';
|
import { MiLocalUser } from '@/models/User.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
@ -58,6 +56,9 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.notesRepository)
|
@Inject(DI.notesRepository)
|
||||||
private notesRepository: NotesRepository,
|
private notesRepository: NotesRepository,
|
||||||
|
|
||||||
|
@ -68,16 +69,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
private queryService: QueryService,
|
private queryService: QueryService,
|
||||||
private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
|
private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
|
||||||
private cacheService: CacheService,
|
|
||||||
private activeUsersChart: ActiveUsersChart,
|
private activeUsersChart: ActiveUsersChart,
|
||||||
private metaService: MetaService,
|
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||||
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
|
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
|
||||||
|
|
||||||
const serverSettings = await this.metaService.fetch();
|
|
||||||
|
|
||||||
const channel = await this.channelsRepository.findOneBy({
|
const channel = await this.channelsRepository.findOneBy({
|
||||||
id: ps.channelId,
|
id: ps.channelId,
|
||||||
});
|
});
|
||||||
|
@ -88,7 +85,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
if (me) this.activeUsersChart.read(me);
|
if (me) this.activeUsersChart.read(me);
|
||||||
|
|
||||||
if (!serverSettings.enableFanoutTimeline) {
|
if (!this.serverSettings.enableFanoutTimeline) {
|
||||||
return await this.noteEntityService.packMany(await this.getFromDb({ untilId, sinceId, limit: ps.limit, channelId: channel.id }, me), me);
|
return await this.noteEntityService.packMany(await this.getFromDb({ untilId, sinceId, limit: ps.limit, channelId: channel.id }, me), me);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
|
||||||
|
@ -41,14 +40,10 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
private metaService: MetaService,
|
|
||||||
private driveFileEntityService: DriveFileEntityService,
|
private driveFileEntityService: DriveFileEntityService,
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const instance = await this.metaService.fetch(true);
|
|
||||||
|
|
||||||
// Calculate drive usage
|
|
||||||
const usage = await this.driveFileEntityService.calcDriveUsageOf(me.id);
|
const usage = await this.driveFileEntityService.calcDriveUsageOf(me.id);
|
||||||
|
|
||||||
const policies = await this.roleService.getUserPolicies(me.id);
|
const policies = await this.roleService.getUserPolicies(me.id);
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { QueryService } from '@/core/QueryService.js';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { ApiError } from '../../../error.js';
|
import { ApiError } from '../../../error.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['drive', 'notes'],
|
tags: ['drive', 'notes'],
|
||||||
|
@ -61,12 +62,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
private queryService: QueryService,
|
private queryService: QueryService,
|
||||||
|
private roleService: RoleService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
// Fetch file
|
// Fetch file
|
||||||
const file = await this.driveFilesRepository.findOneBy({
|
const file = await this.driveFilesRepository.findOneBy({
|
||||||
id: ps.fileId,
|
id: ps.fileId,
|
||||||
userId: me.id,
|
userId: await this.roleService.isModerator(me) ? undefined : me.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
|
|
|
@ -4,14 +4,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js';
|
import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js';
|
||||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { DriveService } from '@/core/DriveService.js';
|
import { DriveService } from '@/core/DriveService.js';
|
||||||
import { ApiError } from '../../../error.js';
|
import { ApiError } from '../../../error.js';
|
||||||
|
import { MiMeta } from '@/models/_.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['drive'],
|
tags: ['drive'],
|
||||||
|
@ -73,8 +74,10 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
private driveFileEntityService: DriveFileEntityService,
|
private driveFileEntityService: DriveFileEntityService,
|
||||||
private metaService: MetaService,
|
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me, _, file, cleanup, ip, headers) => {
|
super(meta, paramDef, async (ps, me, _, file, cleanup, ip, headers) => {
|
||||||
|
@ -91,8 +94,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const instance = await this.metaService.fetch();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create file
|
// Create file
|
||||||
const driveFile = await this.driveService.addFile({
|
const driveFile = await this.driveService.addFile({
|
||||||
|
@ -103,8 +104,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
folderId: ps.folderId,
|
folderId: ps.folderId,
|
||||||
force: ps.force,
|
force: ps.force,
|
||||||
sensitive: ps.isSensitive,
|
sensitive: ps.isSensitive,
|
||||||
requestIp: instance.enableIpLogging ? ip : null,
|
requestIp: this.serverSettings.enableIpLogging ? ip : null,
|
||||||
requestHeaders: instance.enableIpLogging ? headers : null,
|
requestHeaders: this.serverSettings.enableIpLogging ? headers : null,
|
||||||
});
|
});
|
||||||
return await this.driveFileEntityService.pack(driveFile, { self: true });
|
return await this.driveFileEntityService.pack(driveFile, { self: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import type { UserProfilesRepository } from '@/models/_.js';
|
import type { MiMeta, UserProfilesRepository } from '@/models/_.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { EmailService } from '@/core/EmailService.js';
|
import { EmailService } from '@/core/EmailService.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
|
@ -15,7 +15,6 @@ import { DI } from '@/di-symbols.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js';
|
import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js';
|
||||||
import { UserAuthService } from '@/core/UserAuthService.js';
|
import { UserAuthService } from '@/core/UserAuthService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -70,10 +69,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.userProfilesRepository)
|
@Inject(DI.userProfilesRepository)
|
||||||
private userProfilesRepository: UserProfilesRepository,
|
private userProfilesRepository: UserProfilesRepository,
|
||||||
|
|
||||||
private metaService: MetaService,
|
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private emailService: EmailService,
|
private emailService: EmailService,
|
||||||
private userAuthService: UserAuthService,
|
private userAuthService: UserAuthService,
|
||||||
|
@ -105,7 +106,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
if (!res.available) {
|
if (!res.available) {
|
||||||
throw new ApiError(meta.errors.unavailable);
|
throw new ApiError(meta.errors.unavailable);
|
||||||
}
|
}
|
||||||
} else if ((await this.metaService.fetch()).emailRequiredForSignup) {
|
} else if (this.serverSettings.emailRequiredForSignup) {
|
||||||
throw new ApiError(meta.errors.emailRequired);
|
throw new ApiError(meta.errors.emailRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,6 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { NoteCreateService } from '@/core/NoteCreateService.js';
|
import { NoteCreateService } from '@/core/NoteCreateService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
|
||||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import { Brackets } from 'typeorm';
|
import { Brackets } from 'typeorm';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { NotesRepository, ChannelFollowingsRepository } from '@/models/_.js';
|
import type { NotesRepository, ChannelFollowingsRepository, MiMeta } from '@/models/_.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
|
@ -16,7 +16,6 @@ import { CacheService } from '@/core/CacheService.js';
|
||||||
import { FanoutTimelineName } from '@/core/FanoutTimelineService.js';
|
import { FanoutTimelineName } from '@/core/FanoutTimelineService.js';
|
||||||
import { QueryService } from '@/core/QueryService.js';
|
import { QueryService } from '@/core/QueryService.js';
|
||||||
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { MiLocalUser } from '@/models/User.js';
|
import { MiLocalUser } from '@/models/User.js';
|
||||||
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
@ -74,6 +73,9 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.notesRepository)
|
@Inject(DI.notesRepository)
|
||||||
private notesRepository: NotesRepository,
|
private notesRepository: NotesRepository,
|
||||||
|
|
||||||
|
@ -87,7 +89,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
private queryService: QueryService,
|
private queryService: QueryService,
|
||||||
private userFollowingService: UserFollowingService,
|
private userFollowingService: UserFollowingService,
|
||||||
private metaService: MetaService,
|
|
||||||
private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
|
private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
@ -101,9 +102,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
if (ps.withReplies && ps.withFiles) throw new ApiError(meta.errors.bothWithRepliesAndWithFiles);
|
if (ps.withReplies && ps.withFiles) throw new ApiError(meta.errors.bothWithRepliesAndWithFiles);
|
||||||
|
|
||||||
const serverSettings = await this.metaService.fetch();
|
if (!this.serverSettings.enableFanoutTimeline) {
|
||||||
|
|
||||||
if (!serverSettings.enableFanoutTimeline) {
|
|
||||||
const timeline = await this.getFromDb({
|
const timeline = await this.getFromDb({
|
||||||
untilId,
|
untilId,
|
||||||
sinceId,
|
sinceId,
|
||||||
|
@ -156,7 +155,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
allowPartial: ps.allowPartial,
|
allowPartial: ps.allowPartial,
|
||||||
me,
|
me,
|
||||||
redisTimelines: timelineConfig,
|
redisTimelines: timelineConfig,
|
||||||
useDbFallback: serverSettings.enableFanoutTimelineDbFallback,
|
useDbFallback: this.serverSettings.enableFanoutTimelineDbFallback,
|
||||||
alwaysIncludeMyNotes: true,
|
alwaysIncludeMyNotes: true,
|
||||||
excludePureRenotes: !ps.withRenotes,
|
excludePureRenotes: !ps.withRenotes,
|
||||||
noteFilter: note => {
|
noteFilter: note => {
|
||||||
|
|
|
@ -5,16 +5,14 @@
|
||||||
|
|
||||||
import { Brackets } from 'typeorm';
|
import { Brackets } from 'typeorm';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { NotesRepository } from '@/models/_.js';
|
import type { MiMeta, NotesRepository } from '@/models/_.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
|
||||||
import { QueryService } from '@/core/QueryService.js';
|
import { QueryService } from '@/core/QueryService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { MiLocalUser } from '@/models/User.js';
|
import { MiLocalUser } from '@/models/User.js';
|
||||||
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
@ -66,6 +64,9 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.notesRepository)
|
@Inject(DI.notesRepository)
|
||||||
private notesRepository: NotesRepository,
|
private notesRepository: NotesRepository,
|
||||||
|
|
||||||
|
@ -73,10 +74,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
private activeUsersChart: ActiveUsersChart,
|
private activeUsersChart: ActiveUsersChart,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private cacheService: CacheService,
|
|
||||||
private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
|
private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
|
||||||
private queryService: QueryService,
|
private queryService: QueryService,
|
||||||
private metaService: MetaService,
|
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||||
|
@ -89,9 +88,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
if (ps.withReplies && ps.withFiles) throw new ApiError(meta.errors.bothWithRepliesAndWithFiles);
|
if (ps.withReplies && ps.withFiles) throw new ApiError(meta.errors.bothWithRepliesAndWithFiles);
|
||||||
|
|
||||||
const serverSettings = await this.metaService.fetch();
|
if (!this.serverSettings.enableFanoutTimeline) {
|
||||||
|
|
||||||
if (!serverSettings.enableFanoutTimeline) {
|
|
||||||
const timeline = await this.getFromDb({
|
const timeline = await this.getFromDb({
|
||||||
untilId,
|
untilId,
|
||||||
sinceId,
|
sinceId,
|
||||||
|
@ -115,7 +112,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
limit: ps.limit,
|
limit: ps.limit,
|
||||||
allowPartial: ps.allowPartial,
|
allowPartial: ps.allowPartial,
|
||||||
me,
|
me,
|
||||||
useDbFallback: serverSettings.enableFanoutTimelineDbFallback,
|
useDbFallback: this.serverSettings.enableFanoutTimelineDbFallback,
|
||||||
redisTimelines:
|
redisTimelines:
|
||||||
ps.withFiles ? ['localTimelineWithFiles']
|
ps.withFiles ? ['localTimelineWithFiles']
|
||||||
: ps.withReplies ? ['localTimeline', 'localTimelineWithReplies']
|
: ps.withReplies ? ['localTimeline', 'localTimelineWithReplies']
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import { Brackets } from 'typeorm';
|
import { Brackets } from 'typeorm';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { NotesRepository, ChannelFollowingsRepository } from '@/models/_.js';
|
import type { NotesRepository, ChannelFollowingsRepository, MiMeta } from '@/models/_.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { QueryService } from '@/core/QueryService.js';
|
import { QueryService } from '@/core/QueryService.js';
|
||||||
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
||||||
|
@ -15,7 +15,6 @@ import { IdService } from '@/core/IdService.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
||||||
import { MiLocalUser } from '@/models/User.js';
|
import { MiLocalUser } from '@/models/User.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -56,6 +55,9 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.notesRepository)
|
@Inject(DI.notesRepository)
|
||||||
private notesRepository: NotesRepository,
|
private notesRepository: NotesRepository,
|
||||||
|
|
||||||
|
@ -69,15 +71,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
|
private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
|
||||||
private userFollowingService: UserFollowingService,
|
private userFollowingService: UserFollowingService,
|
||||||
private queryService: QueryService,
|
private queryService: QueryService,
|
||||||
private metaService: MetaService,
|
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||||
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
|
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
|
||||||
|
|
||||||
const serverSettings = await this.metaService.fetch();
|
if (!this.serverSettings.enableFanoutTimeline) {
|
||||||
|
|
||||||
if (!serverSettings.enableFanoutTimeline) {
|
|
||||||
const timeline = await this.getFromDb({
|
const timeline = await this.getFromDb({
|
||||||
untilId,
|
untilId,
|
||||||
sinceId,
|
sinceId,
|
||||||
|
@ -108,7 +107,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
limit: ps.limit,
|
limit: ps.limit,
|
||||||
allowPartial: ps.allowPartial,
|
allowPartial: ps.allowPartial,
|
||||||
me,
|
me,
|
||||||
useDbFallback: serverSettings.enableFanoutTimelineDbFallback,
|
useDbFallback: this.serverSettings.enableFanoutTimelineDbFallback,
|
||||||
redisTimelines: ps.withFiles ? [`homeTimelineWithFiles:${me.id}`] : [`homeTimeline:${me.id}`],
|
redisTimelines: ps.withFiles ? [`homeTimelineWithFiles:${me.id}`] : [`homeTimeline:${me.id}`],
|
||||||
alwaysIncludeMyNotes: true,
|
alwaysIncludeMyNotes: true,
|
||||||
excludePureRenotes: !ps.withRenotes,
|
excludePureRenotes: !ps.withRenotes,
|
||||||
|
|
|
@ -4,14 +4,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { URLSearchParams } from 'node:url';
|
import { URLSearchParams } from 'node:url';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||||
import { GetterService } from '@/server/api/GetterService.js';
|
import { GetterService } from '@/server/api/GetterService.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
import { MiMeta } from '@/models/_.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
@ -59,9 +60,11 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
private getterService: GetterService,
|
private getterService: GetterService,
|
||||||
private metaService: MetaService,
|
|
||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
) {
|
) {
|
||||||
|
@ -84,9 +87,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const instance = await this.metaService.fetch();
|
if (this.serverSettings.deeplAuthKey == null) {
|
||||||
|
|
||||||
if (instance.deeplAuthKey == null) {
|
|
||||||
throw new ApiError(meta.errors.unavailable);
|
throw new ApiError(meta.errors.unavailable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,11 +95,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
if (targetLang.includes('-')) targetLang = targetLang.split('-')[0];
|
if (targetLang.includes('-')) targetLang = targetLang.split('-')[0];
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append('auth_key', instance.deeplAuthKey);
|
params.append('auth_key', this.serverSettings.deeplAuthKey);
|
||||||
params.append('text', note.text);
|
params.append('text', note.text);
|
||||||
params.append('target_lang', targetLang);
|
params.append('target_lang', targetLang);
|
||||||
|
|
||||||
const endpoint = instance.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate';
|
const endpoint = this.serverSettings.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate';
|
||||||
|
|
||||||
const res = await this.httpRequestService.send(endpoint, {
|
const res = await this.httpRequestService.send(endpoint, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
|
@ -5,16 +5,14 @@
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Brackets } from 'typeorm';
|
import { Brackets } from 'typeorm';
|
||||||
import type { MiUserList, NotesRepository, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js';
|
import type { MiMeta, MiUserList, NotesRepository, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { QueryService } from '@/core/QueryService.js';
|
import { QueryService } from '@/core/QueryService.js';
|
||||||
import { MiLocalUser } from '@/models/User.js';
|
import { MiLocalUser } from '@/models/User.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
|
@ -69,6 +67,9 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.notesRepository)
|
@Inject(DI.notesRepository)
|
||||||
private notesRepository: NotesRepository,
|
private notesRepository: NotesRepository,
|
||||||
|
|
||||||
|
@ -80,11 +81,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
private activeUsersChart: ActiveUsersChart,
|
private activeUsersChart: ActiveUsersChart,
|
||||||
private cacheService: CacheService,
|
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
|
private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
|
||||||
private queryService: QueryService,
|
private queryService: QueryService,
|
||||||
private metaService: MetaService,
|
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||||
|
@ -99,9 +98,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
throw new ApiError(meta.errors.noSuchList);
|
throw new ApiError(meta.errors.noSuchList);
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverSettings = await this.metaService.fetch();
|
if (!this.serverSettings.enableFanoutTimeline) {
|
||||||
|
|
||||||
if (!serverSettings.enableFanoutTimeline) {
|
|
||||||
const timeline = await this.getFromDb(list, {
|
const timeline = await this.getFromDb(list, {
|
||||||
untilId,
|
untilId,
|
||||||
sinceId,
|
sinceId,
|
||||||
|
@ -124,7 +121,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
limit: ps.limit,
|
limit: ps.limit,
|
||||||
allowPartial: ps.allowPartial,
|
allowPartial: ps.allowPartial,
|
||||||
me,
|
me,
|
||||||
useDbFallback: serverSettings.enableFanoutTimelineDbFallback,
|
useDbFallback: this.serverSettings.enableFanoutTimelineDbFallback,
|
||||||
redisTimelines: ps.withFiles ? [`userListTimelineWithFiles:${list.id}`] : [`userListTimeline:${list.id}`],
|
redisTimelines: ps.withFiles ? [`userListTimelineWithFiles:${list.id}`] : [`userListTimeline:${list.id}`],
|
||||||
alwaysIncludeMyNotes: true,
|
alwaysIncludeMyNotes: true,
|
||||||
excludePureRenotes: !ps.withRenotes,
|
excludePureRenotes: !ps.withRenotes,
|
||||||
|
|
|
@ -5,11 +5,10 @@
|
||||||
|
|
||||||
import { IsNull } from 'typeorm';
|
import { IsNull } from 'typeorm';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { UsersRepository } from '@/models/_.js';
|
import type { MiMeta, UsersRepository } from '@/models/_.js';
|
||||||
import * as Acct from '@/misc/acct.js';
|
import * as Acct from '@/misc/acct.js';
|
||||||
import type { MiUser } from '@/models/User.js';
|
import type { MiUser } from '@/models/User.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
|
||||||
|
@ -38,16 +37,16 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
private metaService: MetaService,
|
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const meta = await this.metaService.fetch();
|
const users = await Promise.all(this.serverSettings.pinnedUsers.map(acct => Acct.parse(acct)).map(acct => this.usersRepository.findOneBy({
|
||||||
|
|
||||||
const users = await Promise.all(meta.pinnedUsers.map(acct => Acct.parse(acct)).map(acct => this.usersRepository.findOneBy({
|
|
||||||
usernameLower: acct.username.toLowerCase(),
|
usernameLower: acct.username.toLowerCase(),
|
||||||
host: acct.host ?? IsNull(),
|
host: acct.host ?? IsNull(),
|
||||||
})));
|
})));
|
||||||
|
|
|
@ -5,9 +5,10 @@
|
||||||
|
|
||||||
import * as os from 'node:os';
|
import * as os from 'node:os';
|
||||||
import si from 'systeminformation';
|
import si from 'systeminformation';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MiMeta } from '@/models/_.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
@ -73,10 +74,11 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
private metaService: MetaService,
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async () => {
|
super(meta, paramDef, async () => {
|
||||||
if (!(await this.metaService.fetch()).enableServerMachineStats) return {
|
if (!this.serverSettings.enableServerMachineStats) return {
|
||||||
machine: '?',
|
machine: '?',
|
||||||
cpu: {
|
cpu: {
|
||||||
model: '?',
|
model: '?',
|
||||||
|
|
|
@ -5,9 +5,8 @@
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import type { SwSubscriptionsRepository } from '@/models/_.js';
|
import type { MiMeta, SwSubscriptionsRepository } from '@/models/_.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { PushNotificationService } from '@/core/PushNotificationService.js';
|
import { PushNotificationService } from '@/core/PushNotificationService.js';
|
||||||
|
|
||||||
|
@ -62,11 +61,13 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.swSubscriptionsRepository)
|
@Inject(DI.swSubscriptionsRepository)
|
||||||
private swSubscriptionsRepository: SwSubscriptionsRepository,
|
private swSubscriptionsRepository: SwSubscriptionsRepository,
|
||||||
|
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private metaService: MetaService,
|
|
||||||
private pushNotificationService: PushNotificationService,
|
private pushNotificationService: PushNotificationService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
@ -78,12 +79,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
publickey: ps.publickey,
|
publickey: ps.publickey,
|
||||||
});
|
});
|
||||||
|
|
||||||
const instance = await this.metaService.fetch(true);
|
|
||||||
|
|
||||||
if (exist != null) {
|
if (exist != null) {
|
||||||
return {
|
return {
|
||||||
state: 'already-subscribed' as const,
|
state: 'already-subscribed' as const,
|
||||||
key: instance.swPublicKey,
|
key: this.serverSettings.swPublicKey,
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
endpoint: exist.endpoint,
|
endpoint: exist.endpoint,
|
||||||
sendReadMessage: exist.sendReadMessage,
|
sendReadMessage: exist.sendReadMessage,
|
||||||
|
@ -103,7 +102,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state: 'subscribed' as const,
|
state: 'subscribed' as const,
|
||||||
key: instance.swPublicKey,
|
key: this.serverSettings.swPublicKey,
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
endpoint: ps.endpoint,
|
endpoint: ps.endpoint,
|
||||||
sendReadMessage: ps.sendReadMessage,
|
sendReadMessage: ps.sendReadMessage,
|
||||||
|
|
|
@ -5,11 +5,10 @@
|
||||||
|
|
||||||
import { IsNull } from 'typeorm';
|
import { IsNull } from 'typeorm';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { UsedUsernamesRepository, UsersRepository } from '@/models/_.js';
|
import type { MiMeta, UsedUsernamesRepository, UsersRepository } from '@/models/_.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { localUsernameSchema } from '@/models/User.js';
|
import { localUsernameSchema } from '@/models/User.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['users'],
|
tags: ['users'],
|
||||||
|
@ -39,13 +38,14 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
@Inject(DI.usedUsernamesRepository)
|
@Inject(DI.usedUsernamesRepository)
|
||||||
private usedUsernamesRepository: UsedUsernamesRepository,
|
private usedUsernamesRepository: UsedUsernamesRepository,
|
||||||
|
|
||||||
private metaService: MetaService,
|
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const exist = await this.usersRepository.countBy({
|
const exist = await this.usersRepository.countBy({
|
||||||
|
@ -55,8 +55,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
const exist2 = await this.usedUsernamesRepository.countBy({ username: ps.username.toLowerCase() });
|
const exist2 = await this.usedUsernamesRepository.countBy({ username: ps.username.toLowerCase() });
|
||||||
|
|
||||||
const meta = await this.metaService.fetch();
|
const isPreserved = this.serverSettings.preservedUsernames.map(x => x.toLowerCase()).includes(ps.username.toLowerCase());
|
||||||
const isPreserved = meta.preservedUsernames.map(x => x.toLowerCase()).includes(ps.username.toLowerCase());
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
available: exist === 0 && exist2 === 0 && !isPreserved,
|
available: exist === 0 && exist2 === 0 && !isPreserved,
|
||||||
|
|
|
@ -5,14 +5,13 @@
|
||||||
|
|
||||||
import { Brackets } from 'typeorm';
|
import { Brackets } from 'typeorm';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { NotesRepository } from '@/models/_.js';
|
import type { MiMeta, NotesRepository } from '@/models/_.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { QueryService } from '@/core/QueryService.js';
|
import { QueryService } from '@/core/QueryService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { MiLocalUser } from '@/models/User.js';
|
import { MiLocalUser } from '@/models/User.js';
|
||||||
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
||||||
import { FanoutTimelineName } from '@/core/FanoutTimelineService.js';
|
import { FanoutTimelineName } from '@/core/FanoutTimelineService.js';
|
||||||
|
@ -67,6 +66,9 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.notesRepository)
|
@Inject(DI.notesRepository)
|
||||||
private notesRepository: NotesRepository,
|
private notesRepository: NotesRepository,
|
||||||
|
|
||||||
|
@ -75,15 +77,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
|
private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
|
||||||
private metaService: MetaService,
|
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||||
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
|
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
|
||||||
const isSelf = me && (me.id === ps.userId);
|
const isSelf = me && (me.id === ps.userId);
|
||||||
|
|
||||||
const serverSettings = await this.metaService.fetch();
|
|
||||||
|
|
||||||
if (ps.withReplies && ps.withFiles) throw new ApiError(meta.errors.bothWithRepliesAndWithFiles);
|
if (ps.withReplies && ps.withFiles) throw new ApiError(meta.errors.bothWithRepliesAndWithFiles);
|
||||||
|
|
||||||
// early return if me is blocked by requesting user
|
// early return if me is blocked by requesting user
|
||||||
|
@ -94,7 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!serverSettings.enableFanoutTimeline) {
|
if (!this.serverSettings.enableFanoutTimeline) {
|
||||||
const timeline = await this.getFromDb({
|
const timeline = await this.getFromDb({
|
||||||
untilId,
|
untilId,
|
||||||
sinceId,
|
sinceId,
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { fileURLToPath } from 'node:url';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { createBullBoard } from '@bull-board/api';
|
import { createBullBoard } from '@bull-board/api';
|
||||||
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter.js';
|
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter.js';
|
||||||
import { FastifyAdapter } from '@bull-board/fastify';
|
import { FastifyAdapter as BullBoardFastifyAdapter } from '@bull-board/fastify';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
import pug from 'pug';
|
import pug from 'pug';
|
||||||
|
@ -24,7 +24,6 @@ import type { Config } from '@/config.js';
|
||||||
import { getNoteSummary } from '@/misc/get-note-summary.js';
|
import { getNoteSummary } from '@/misc/get-note-summary.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import * as Acct from '@/misc/acct.js';
|
import * as Acct from '@/misc/acct.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import type {
|
import type {
|
||||||
DbQueue,
|
DbQueue,
|
||||||
DeliverQueue,
|
DeliverQueue,
|
||||||
|
@ -73,6 +72,9 @@ export class ClientServerService {
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@ -109,7 +111,6 @@ export class ClientServerService {
|
||||||
private clipEntityService: ClipEntityService,
|
private clipEntityService: ClipEntityService,
|
||||||
private channelEntityService: ChannelEntityService,
|
private channelEntityService: ChannelEntityService,
|
||||||
private reversiGameEntityService: ReversiGameEntityService,
|
private reversiGameEntityService: ReversiGameEntityService,
|
||||||
private metaService: MetaService,
|
|
||||||
private urlPreviewService: UrlPreviewService,
|
private urlPreviewService: UrlPreviewService,
|
||||||
private feedService: FeedService,
|
private feedService: FeedService,
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
|
@ -129,32 +130,30 @@ export class ClientServerService {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async manifestHandler(reply: FastifyReply) {
|
private async manifestHandler(reply: FastifyReply) {
|
||||||
const instance = await this.metaService.fetch(true);
|
|
||||||
|
|
||||||
let manifest = {
|
let manifest = {
|
||||||
// 空文字列の場合右辺を使いたいため
|
// 空文字列の場合右辺を使いたいため
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||||
'short_name': instance.shortName || instance.name || this.config.host,
|
'short_name': this.meta.shortName || this.meta.name || this.config.host,
|
||||||
// 空文字列の場合右辺を使いたいため
|
// 空文字列の場合右辺を使いたいため
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||||
'name': instance.name || this.config.host,
|
'name': this.meta.name || this.config.host,
|
||||||
'start_url': '/',
|
'start_url': '/',
|
||||||
'display': 'standalone',
|
'display': 'standalone',
|
||||||
'background_color': '#313a42',
|
'background_color': '#313a42',
|
||||||
// 空文字列の場合右辺を使いたいため
|
// 空文字列の場合右辺を使いたいため
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||||
'theme_color': instance.themeColor || '#86b300',
|
'theme_color': this.meta.themeColor || '#86b300',
|
||||||
'icons': [{
|
'icons': [{
|
||||||
// 空文字列の場合右辺を使いたいため
|
// 空文字列の場合右辺を使いたいため
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||||
'src': instance.app192IconUrl || '/static-assets/icons/192.png',
|
'src': this.meta.app192IconUrl || '/static-assets/icons/192.png',
|
||||||
'sizes': '192x192',
|
'sizes': '192x192',
|
||||||
'type': 'image/png',
|
'type': 'image/png',
|
||||||
'purpose': 'maskable',
|
'purpose': 'maskable',
|
||||||
}, {
|
}, {
|
||||||
// 空文字列の場合右辺を使いたいため
|
// 空文字列の場合右辺を使いたいため
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||||
'src': instance.app512IconUrl || '/static-assets/icons/512.png',
|
'src': this.meta.app512IconUrl || '/static-assets/icons/512.png',
|
||||||
'sizes': '512x512',
|
'sizes': '512x512',
|
||||||
'type': 'image/png',
|
'type': 'image/png',
|
||||||
'purpose': 'maskable',
|
'purpose': 'maskable',
|
||||||
|
@ -178,7 +177,7 @@ export class ClientServerService {
|
||||||
|
|
||||||
manifest = {
|
manifest = {
|
||||||
...manifest,
|
...manifest,
|
||||||
...JSON.parse(instance.manifestJsonOverride === '' ? '{}' : instance.manifestJsonOverride),
|
...JSON.parse(this.meta.manifestJsonOverride === '' ? '{}' : this.meta.manifestJsonOverride),
|
||||||
};
|
};
|
||||||
|
|
||||||
reply.header('Cache-Control', 'max-age=300');
|
reply.header('Cache-Control', 'max-age=300');
|
||||||
|
@ -240,7 +239,7 @@ export class ClientServerService {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const serverAdapter = new FastifyAdapter();
|
const bullBoardServerAdapter = new BullBoardFastifyAdapter();
|
||||||
|
|
||||||
createBullBoard({
|
createBullBoard({
|
||||||
queues: [
|
queues: [
|
||||||
|
@ -253,11 +252,11 @@ export class ClientServerService {
|
||||||
this.userWebhookDeliverQueue,
|
this.userWebhookDeliverQueue,
|
||||||
this.systemWebhookDeliverQueue,
|
this.systemWebhookDeliverQueue,
|
||||||
].map(q => new BullMQAdapter(q)),
|
].map(q => new BullMQAdapter(q)),
|
||||||
serverAdapter,
|
serverAdapter: bullBoardServerAdapter,
|
||||||
});
|
});
|
||||||
|
|
||||||
serverAdapter.setBasePath(bullBoardPath);
|
bullBoardServerAdapter.setBasePath(bullBoardPath);
|
||||||
(fastify.register as any)(serverAdapter.registerPlugin(), { prefix: bullBoardPath });
|
//(fastify.register as any)(bullBoardServerAdapter.registerPlugin(), { prefix: bullBoardPath });
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
fastify.register(fastifyView, {
|
fastify.register(fastifyView, {
|
||||||
|
@ -453,9 +452,7 @@ export class ClientServerService {
|
||||||
|
|
||||||
// OpenSearch XML
|
// OpenSearch XML
|
||||||
fastify.get('/opensearch.xml', async (request, reply) => {
|
fastify.get('/opensearch.xml', async (request, reply) => {
|
||||||
const meta = await this.metaService.fetch();
|
const name = this.meta.name ?? 'Misskey';
|
||||||
|
|
||||||
const name = meta.name ?? 'Misskey';
|
|
||||||
let content = '';
|
let content = '';
|
||||||
content += '<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">';
|
content += '<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">';
|
||||||
content += `<ShortName>${name}</ShortName>`;
|
content += `<ShortName>${name}</ShortName>`;
|
||||||
|
@ -472,14 +469,13 @@ export class ClientServerService {
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
const renderBase = async (reply: FastifyReply, data: { [key: string]: any } = {}) => {
|
const renderBase = async (reply: FastifyReply, data: { [key: string]: any } = {}) => {
|
||||||
const meta = await this.metaService.fetch();
|
|
||||||
reply.header('Cache-Control', 'public, max-age=30');
|
reply.header('Cache-Control', 'public, max-age=30');
|
||||||
return await reply.view('base', {
|
return await reply.view('base', {
|
||||||
img: meta.bannerUrl,
|
img: this.meta.bannerUrl,
|
||||||
url: this.config.url,
|
url: this.config.url,
|
||||||
title: meta.name ?? 'Misskey',
|
title: this.meta.name ?? 'Misskey',
|
||||||
desc: meta.description,
|
desc: this.meta.description,
|
||||||
...await this.generateCommonPugData(meta),
|
...await this.generateCommonPugData(this.meta),
|
||||||
...data,
|
...data,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -557,7 +553,6 @@ export class ClientServerService {
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||||
const meta = await this.metaService.fetch();
|
|
||||||
const me = profile.fields
|
const me = profile.fields
|
||||||
? profile.fields
|
? profile.fields
|
||||||
.filter(filed => filed.value != null && filed.value.match(/^https?:/))
|
.filter(filed => filed.value != null && filed.value.match(/^https?:/))
|
||||||
|
@ -573,7 +568,7 @@ export class ClientServerService {
|
||||||
user, profile, me,
|
user, profile, me,
|
||||||
avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
|
avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
|
||||||
sub: request.params.sub,
|
sub: request.params.sub,
|
||||||
...await this.generateCommonPugData(meta),
|
...await this.generateCommonPugData(this.meta),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// リモートユーザーなので
|
// リモートユーザーなので
|
||||||
|
@ -611,7 +606,6 @@ export class ClientServerService {
|
||||||
if (note) {
|
if (note) {
|
||||||
const _note = await this.noteEntityService.pack(note);
|
const _note = await this.noteEntityService.pack(note);
|
||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId });
|
||||||
const meta = await this.metaService.fetch();
|
|
||||||
reply.header('Cache-Control', 'public, max-age=15');
|
reply.header('Cache-Control', 'public, max-age=15');
|
||||||
if (profile.preventAiLearning) {
|
if (profile.preventAiLearning) {
|
||||||
reply.header('X-Robots-Tag', 'noimageai');
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
|
@ -623,7 +617,7 @@ export class ClientServerService {
|
||||||
avatarUrl: _note.user.avatarUrl,
|
avatarUrl: _note.user.avatarUrl,
|
||||||
// TODO: Let locale changeable by instance setting
|
// TODO: Let locale changeable by instance setting
|
||||||
summary: getNoteSummary(_note),
|
summary: getNoteSummary(_note),
|
||||||
...await this.generateCommonPugData(meta),
|
...await this.generateCommonPugData(this.meta),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return await renderBase(reply);
|
return await renderBase(reply);
|
||||||
|
@ -648,7 +642,6 @@ export class ClientServerService {
|
||||||
if (page) {
|
if (page) {
|
||||||
const _page = await this.pageEntityService.pack(page);
|
const _page = await this.pageEntityService.pack(page);
|
||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: page.userId });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: page.userId });
|
||||||
const meta = await this.metaService.fetch();
|
|
||||||
if (['public'].includes(page.visibility)) {
|
if (['public'].includes(page.visibility)) {
|
||||||
reply.header('Cache-Control', 'public, max-age=15');
|
reply.header('Cache-Control', 'public, max-age=15');
|
||||||
} else {
|
} else {
|
||||||
|
@ -662,7 +655,7 @@ export class ClientServerService {
|
||||||
page: _page,
|
page: _page,
|
||||||
profile,
|
profile,
|
||||||
avatarUrl: _page.user.avatarUrl,
|
avatarUrl: _page.user.avatarUrl,
|
||||||
...await this.generateCommonPugData(meta),
|
...await this.generateCommonPugData(this.meta),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return await renderBase(reply);
|
return await renderBase(reply);
|
||||||
|
@ -678,7 +671,6 @@ export class ClientServerService {
|
||||||
if (flash) {
|
if (flash) {
|
||||||
const _flash = await this.flashEntityService.pack(flash);
|
const _flash = await this.flashEntityService.pack(flash);
|
||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: flash.userId });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: flash.userId });
|
||||||
const meta = await this.metaService.fetch();
|
|
||||||
reply.header('Cache-Control', 'public, max-age=15');
|
reply.header('Cache-Control', 'public, max-age=15');
|
||||||
if (profile.preventAiLearning) {
|
if (profile.preventAiLearning) {
|
||||||
reply.header('X-Robots-Tag', 'noimageai');
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
|
@ -688,7 +680,7 @@ export class ClientServerService {
|
||||||
flash: _flash,
|
flash: _flash,
|
||||||
profile,
|
profile,
|
||||||
avatarUrl: _flash.user.avatarUrl,
|
avatarUrl: _flash.user.avatarUrl,
|
||||||
...await this.generateCommonPugData(meta),
|
...await this.generateCommonPugData(this.meta),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return await renderBase(reply);
|
return await renderBase(reply);
|
||||||
|
@ -704,7 +696,6 @@ export class ClientServerService {
|
||||||
if (clip && clip.isPublic) {
|
if (clip && clip.isPublic) {
|
||||||
const _clip = await this.clipEntityService.pack(clip);
|
const _clip = await this.clipEntityService.pack(clip);
|
||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: clip.userId });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: clip.userId });
|
||||||
const meta = await this.metaService.fetch();
|
|
||||||
reply.header('Cache-Control', 'public, max-age=15');
|
reply.header('Cache-Control', 'public, max-age=15');
|
||||||
if (profile.preventAiLearning) {
|
if (profile.preventAiLearning) {
|
||||||
reply.header('X-Robots-Tag', 'noimageai');
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
|
@ -714,7 +705,7 @@ export class ClientServerService {
|
||||||
clip: _clip,
|
clip: _clip,
|
||||||
profile,
|
profile,
|
||||||
avatarUrl: _clip.user.avatarUrl,
|
avatarUrl: _clip.user.avatarUrl,
|
||||||
...await this.generateCommonPugData(meta),
|
...await this.generateCommonPugData(this.meta),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return await renderBase(reply);
|
return await renderBase(reply);
|
||||||
|
@ -728,7 +719,6 @@ export class ClientServerService {
|
||||||
if (post) {
|
if (post) {
|
||||||
const _post = await this.galleryPostEntityService.pack(post);
|
const _post = await this.galleryPostEntityService.pack(post);
|
||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: post.userId });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: post.userId });
|
||||||
const meta = await this.metaService.fetch();
|
|
||||||
reply.header('Cache-Control', 'public, max-age=15');
|
reply.header('Cache-Control', 'public, max-age=15');
|
||||||
if (profile.preventAiLearning) {
|
if (profile.preventAiLearning) {
|
||||||
reply.header('X-Robots-Tag', 'noimageai');
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
|
@ -738,7 +728,7 @@ export class ClientServerService {
|
||||||
post: _post,
|
post: _post,
|
||||||
profile,
|
profile,
|
||||||
avatarUrl: _post.user.avatarUrl,
|
avatarUrl: _post.user.avatarUrl,
|
||||||
...await this.generateCommonPugData(meta),
|
...await this.generateCommonPugData(this.meta),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return await renderBase(reply);
|
return await renderBase(reply);
|
||||||
|
@ -753,11 +743,10 @@ export class ClientServerService {
|
||||||
|
|
||||||
if (channel) {
|
if (channel) {
|
||||||
const _channel = await this.channelEntityService.pack(channel);
|
const _channel = await this.channelEntityService.pack(channel);
|
||||||
const meta = await this.metaService.fetch();
|
|
||||||
reply.header('Cache-Control', 'public, max-age=15');
|
reply.header('Cache-Control', 'public, max-age=15');
|
||||||
return await reply.view('channel', {
|
return await reply.view('channel', {
|
||||||
channel: _channel,
|
channel: _channel,
|
||||||
...await this.generateCommonPugData(meta),
|
...await this.generateCommonPugData(this.meta),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return await renderBase(reply);
|
return await renderBase(reply);
|
||||||
|
@ -772,11 +761,10 @@ export class ClientServerService {
|
||||||
|
|
||||||
if (game) {
|
if (game) {
|
||||||
const _game = await this.reversiGameEntityService.packDetail(game);
|
const _game = await this.reversiGameEntityService.packDetail(game);
|
||||||
const meta = await this.metaService.fetch();
|
|
||||||
reply.header('Cache-Control', 'public, max-age=3600');
|
reply.header('Cache-Control', 'public, max-age=3600');
|
||||||
return await reply.view('reversi-game', {
|
return await reply.view('reversi-game', {
|
||||||
game: _game,
|
game: _game,
|
||||||
...await this.generateCommonPugData(meta),
|
...await this.generateCommonPugData(this.meta),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return await renderBase(reply);
|
return await renderBase(reply);
|
||||||
|
@ -797,27 +785,89 @@ export class ClientServerService {
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region embed pages
|
//#region embed pages
|
||||||
fastify.get('/embed/*', async (request, reply) => {
|
fastify.get<{ Params: { user: string; } }>('/embed/user-timeline/:user', async (request, reply) => {
|
||||||
const meta = await this.metaService.fetch();
|
reply.removeHeader('X-Frame-Options');
|
||||||
|
|
||||||
|
const user = await this.usersRepository.findOneBy({
|
||||||
|
id: request.params.user,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user == null) return;
|
||||||
|
if (user.host != null) return;
|
||||||
|
|
||||||
|
const _user = await this.userEntityService.pack(user);
|
||||||
|
|
||||||
|
reply.header('Cache-Control', 'public, max-age=3600');
|
||||||
|
return await reply.view('base-embed', {
|
||||||
|
title: this.meta.name ?? 'Misskey',
|
||||||
|
...await this.generateCommonPugData(this.meta),
|
||||||
|
embedCtx: htmlSafeJsonStringify({
|
||||||
|
user: _user,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
fastify.get<{ Params: { note: string; } }>('/embed/notes/:note', async (request, reply) => {
|
||||||
|
reply.removeHeader('X-Frame-Options');
|
||||||
|
|
||||||
|
const note = await this.notesRepository.findOneBy({
|
||||||
|
id: request.params.note,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (note == null) return;
|
||||||
|
if (note.visibility !== 'public') return;
|
||||||
|
if (note.userHost != null) return;
|
||||||
|
|
||||||
|
const _note = await this.noteEntityService.pack(note, null, { detail: true });
|
||||||
|
|
||||||
|
reply.header('Cache-Control', 'public, max-age=3600');
|
||||||
|
return await reply.view('base-embed', {
|
||||||
|
title: this.meta.name ?? 'Misskey',
|
||||||
|
...await this.generateCommonPugData(this.meta),
|
||||||
|
embedCtx: htmlSafeJsonStringify({
|
||||||
|
note: _note,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
fastify.get<{ Params: { clip: string; } }>('/embed/clips/:clip', async (request, reply) => {
|
||||||
|
reply.removeHeader('X-Frame-Options');
|
||||||
|
|
||||||
|
const clip = await this.clipsRepository.findOneBy({
|
||||||
|
id: request.params.clip,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (clip == null) return;
|
||||||
|
|
||||||
|
const _clip = await this.clipEntityService.pack(clip);
|
||||||
|
|
||||||
|
reply.header('Cache-Control', 'public, max-age=3600');
|
||||||
|
return await reply.view('base-embed', {
|
||||||
|
title: this.meta.name ?? 'Misskey',
|
||||||
|
...await this.generateCommonPugData(this.meta),
|
||||||
|
embedCtx: htmlSafeJsonStringify({
|
||||||
|
clip: _clip,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
fastify.get('/embed/*', async (request, reply) => {
|
||||||
reply.removeHeader('X-Frame-Options');
|
reply.removeHeader('X-Frame-Options');
|
||||||
|
|
||||||
reply.header('Cache-Control', 'public, max-age=3600');
|
reply.header('Cache-Control', 'public, max-age=3600');
|
||||||
return await reply.view('base-embed', {
|
return await reply.view('base-embed', {
|
||||||
title: meta.name ?? 'Misskey',
|
title: this.meta.name ?? 'Misskey',
|
||||||
...await this.generateCommonPugData(meta),
|
...await this.generateCommonPugData(this.meta),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.get('/_info_card_', async (request, reply) => {
|
fastify.get('/_info_card_', async (request, reply) => {
|
||||||
const meta = await this.metaService.fetch(true);
|
|
||||||
|
|
||||||
reply.removeHeader('X-Frame-Options');
|
reply.removeHeader('X-Frame-Options');
|
||||||
|
|
||||||
return await reply.view('info-card', {
|
return await reply.view('info-card', {
|
||||||
version: this.config.version,
|
version: this.config.version,
|
||||||
host: this.config.host,
|
host: this.config.host,
|
||||||
meta: meta,
|
meta: this.meta,
|
||||||
originalUsersCount: await this.usersRepository.countBy({ host: IsNull() }),
|
originalUsersCount: await this.usersRepository.countBy({ host: IsNull() }),
|
||||||
originalNotesCount: await this.notesRepository.countBy({ userHost: IsNull() }),
|
originalNotesCount: await this.notesRepository.countBy({ userHost: IsNull() }),
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { summaly } from '@misskey-dev/summaly';
|
||||||
import { SummalyResult } from '@misskey-dev/summaly/built/summary.js';
|
import { SummalyResult } from '@misskey-dev/summaly/built/summary.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import { query } from '@/misc/prelude/url.js';
|
import { query } from '@/misc/prelude/url.js';
|
||||||
|
@ -26,7 +25,9 @@ export class UrlPreviewService {
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
private metaService: MetaService,
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
private loggerService: LoggerService,
|
private loggerService: LoggerService,
|
||||||
) {
|
) {
|
||||||
|
@ -62,9 +63,7 @@ export class UrlPreviewService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const meta = await this.metaService.fetch();
|
if (!this.meta.urlPreviewEnabled) {
|
||||||
|
|
||||||
if (!meta.urlPreviewEnabled) {
|
|
||||||
reply.code(403);
|
reply.code(403);
|
||||||
return {
|
return {
|
||||||
error: new ApiError({
|
error: new ApiError({
|
||||||
|
@ -75,14 +74,14 @@ export class UrlPreviewService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.info(meta.urlPreviewSummaryProxyUrl
|
this.logger.info(this.meta.urlPreviewSummaryProxyUrl
|
||||||
? `(Proxy) Getting preview of ${url}@${lang} ...`
|
? `(Proxy) Getting preview of ${url}@${lang} ...`
|
||||||
: `Getting preview of ${url}@${lang} ...`);
|
: `Getting preview of ${url}@${lang} ...`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const summary = meta.urlPreviewSummaryProxyUrl
|
const summary = this.meta.urlPreviewSummaryProxyUrl
|
||||||
? await this.fetchSummaryFromProxy(url, meta, lang)
|
? await this.fetchSummaryFromProxy(url, this.meta, lang)
|
||||||
: await this.fetchSummary(url, meta, lang);
|
: await this.fetchSummary(url, this.meta, lang);
|
||||||
|
|
||||||
this.logger.succ(`Got preview of ${url}: ${summary.title}`);
|
this.logger.succ(`Got preview of ${url}: ${summary.title}`);
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,9 @@ html(class='embed')
|
||||||
script(type='application/json' id='misskey_meta' data-generated-at=now)
|
script(type='application/json' id='misskey_meta' data-generated-at=now)
|
||||||
!= metaJson
|
!= metaJson
|
||||||
|
|
||||||
|
script(type='application/json' id='misskey_embedCtx' data-generated-at=now)
|
||||||
|
!= embedCtx
|
||||||
|
|
||||||
script
|
script
|
||||||
include ../boot.embed.js
|
include ../boot.embed.js
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,16 @@ import { MainModule } from '@/MainModule.js';
|
||||||
import { ServerService } from '@/server/ServerService.js';
|
import { ServerService } from '@/server/ServerService.js';
|
||||||
import { loadConfig } from '@/config.js';
|
import { loadConfig } from '@/config.js';
|
||||||
import { NestLogger } from '@/NestLogger.js';
|
import { NestLogger } from '@/NestLogger.js';
|
||||||
|
import { INestApplicationContext } from '@nestjs/common';
|
||||||
|
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
const originEnv = JSON.stringify(process.env);
|
const originEnv = JSON.stringify(process.env);
|
||||||
|
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
|
let app: INestApplicationContext;
|
||||||
|
let serverService: ServerService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* テスト用のサーバインスタンスを起動する
|
* テスト用のサーバインスタンスを起動する
|
||||||
*/
|
*/
|
||||||
|
@ -20,10 +24,10 @@ async function launch() {
|
||||||
|
|
||||||
console.log('starting application...');
|
console.log('starting application...');
|
||||||
|
|
||||||
const app = await NestFactory.createApplicationContext(MainModule, {
|
app = await NestFactory.createApplicationContext(MainModule, {
|
||||||
logger: new NestLogger(),
|
logger: new NestLogger(),
|
||||||
});
|
});
|
||||||
const serverService = app.get(ServerService);
|
serverService = app.get(ServerService);
|
||||||
await serverService.launch();
|
await serverService.launch();
|
||||||
|
|
||||||
await startControllerEndpoints();
|
await startControllerEndpoints();
|
||||||
|
@ -71,6 +75,20 @@ async function startControllerEndpoints(port = config.port + 1000) {
|
||||||
|
|
||||||
fastify.post<{ Body: { key?: string, value?: string } }>('/env-reset', async (req, res) => {
|
fastify.post<{ Body: { key?: string, value?: string } }>('/env-reset', async (req, res) => {
|
||||||
process.env = JSON.parse(originEnv);
|
process.env = JSON.parse(originEnv);
|
||||||
|
|
||||||
|
await serverService.dispose();
|
||||||
|
await app.close();
|
||||||
|
|
||||||
|
await killTestServer();
|
||||||
|
|
||||||
|
console.log('starting application...');
|
||||||
|
|
||||||
|
app = await NestFactory.createApplicationContext(MainModule, {
|
||||||
|
logger: new NestLogger(),
|
||||||
|
});
|
||||||
|
serverService = app.get(ServerService);
|
||||||
|
await serverService.launch();
|
||||||
|
|
||||||
res.code(200).send({ success: true });
|
res.code(200).send({ success: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -180,6 +180,7 @@ describe('Webリソース', () => {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* queueは一時的に無効化されている
|
||||||
describe.each([{ path: '/queue' }])('$path', ({ path }) => {
|
describe.each([{ path: '/queue' }])('$path', ({ path }) => {
|
||||||
test('はログインしないとGETできない。', async () => await notOk({
|
test('はログインしないとGETできない。', async () => await notOk({
|
||||||
path,
|
path,
|
||||||
|
@ -197,6 +198,7 @@ describe('Webリソース', () => {
|
||||||
cookie: cookie(alice),
|
cookie: cookie(alice),
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
describe.each([{ path: '/streaming' }])('$path', ({ path }) => {
|
describe.each([{ path: '/streaming' }])('$path', ({ path }) => {
|
||||||
test('はGETできない。', async () => await notOk({
|
test('はGETできない。', async () => await notOk({
|
||||||
|
|
|
@ -6,8 +6,6 @@
|
||||||
import { initTestDb, sendEnvResetRequest } from './utils.js';
|
import { initTestDb, sendEnvResetRequest } from './utils.js';
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await Promise.all([
|
await initTestDb(false);
|
||||||
initTestDb(false),
|
await sendEnvResetRequest();
|
||||||
sendEnvResetRequest(),
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,6 +18,7 @@ import type { FederatedInstanceService } from '@/core/FederatedInstanceService.j
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import type {
|
import type {
|
||||||
FollowRequestsRepository,
|
FollowRequestsRepository,
|
||||||
|
MiMeta,
|
||||||
NoteReactionsRepository,
|
NoteReactionsRepository,
|
||||||
NotesRepository,
|
NotesRepository,
|
||||||
PollsRepository,
|
PollsRepository,
|
||||||
|
@ -36,6 +37,7 @@ export class MockResolver extends Resolver {
|
||||||
constructor(loggerService: LoggerService) {
|
constructor(loggerService: LoggerService) {
|
||||||
super(
|
super(
|
||||||
{} as Config,
|
{} as Config,
|
||||||
|
{} as MiMeta,
|
||||||
{} as UsersRepository,
|
{} as UsersRepository,
|
||||||
{} as NotesRepository,
|
{} as NotesRepository,
|
||||||
{} as PollsRepository,
|
{} as PollsRepository,
|
||||||
|
@ -43,7 +45,6 @@ export class MockResolver extends Resolver {
|
||||||
{} as FollowRequestsRepository,
|
{} as FollowRequestsRepository,
|
||||||
{} as UtilityService,
|
{} as UtilityService,
|
||||||
{} as InstanceActorService,
|
{} as InstanceActorService,
|
||||||
{} as MetaService,
|
|
||||||
{} as ApRequestService,
|
{} as ApRequestService,
|
||||||
{} as HttpRequestService,
|
{} as HttpRequestService,
|
||||||
{} as ApRendererService,
|
{} as ApRendererService,
|
||||||
|
|
|
@ -13,6 +13,7 @@ import * as lolex from '@sinonjs/fake-timers';
|
||||||
import { GlobalModule } from '@/GlobalModule.js';
|
import { GlobalModule } from '@/GlobalModule.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import {
|
import {
|
||||||
|
MiMeta,
|
||||||
MiRole,
|
MiRole,
|
||||||
MiRoleAssignment,
|
MiRoleAssignment,
|
||||||
MiUser,
|
MiUser,
|
||||||
|
@ -41,7 +42,7 @@ describe('RoleService', () => {
|
||||||
let usersRepository: UsersRepository;
|
let usersRepository: UsersRepository;
|
||||||
let rolesRepository: RolesRepository;
|
let rolesRepository: RolesRepository;
|
||||||
let roleAssignmentsRepository: RoleAssignmentsRepository;
|
let roleAssignmentsRepository: RoleAssignmentsRepository;
|
||||||
let metaService: jest.Mocked<MetaService>;
|
let meta: jest.Mocked<MiMeta>;
|
||||||
let notificationService: jest.Mocked<NotificationService>;
|
let notificationService: jest.Mocked<NotificationService>;
|
||||||
let clock: lolex.InstalledClock;
|
let clock: lolex.InstalledClock;
|
||||||
|
|
||||||
|
@ -142,7 +143,7 @@ describe('RoleService', () => {
|
||||||
rolesRepository = app.get<RolesRepository>(DI.rolesRepository);
|
rolesRepository = app.get<RolesRepository>(DI.rolesRepository);
|
||||||
roleAssignmentsRepository = app.get<RoleAssignmentsRepository>(DI.roleAssignmentsRepository);
|
roleAssignmentsRepository = app.get<RoleAssignmentsRepository>(DI.roleAssignmentsRepository);
|
||||||
|
|
||||||
metaService = app.get<MetaService>(MetaService) as jest.Mocked<MetaService>;
|
meta = app.get<MiMeta>(DI.meta) as jest.Mocked<MiMeta>;
|
||||||
notificationService = app.get<NotificationService>(NotificationService) as jest.Mocked<NotificationService>;
|
notificationService = app.get<NotificationService>(NotificationService) as jest.Mocked<NotificationService>;
|
||||||
|
|
||||||
await roleService.onModuleInit();
|
await roleService.onModuleInit();
|
||||||
|
@ -164,11 +165,9 @@ describe('RoleService', () => {
|
||||||
describe('getUserPolicies', () => {
|
describe('getUserPolicies', () => {
|
||||||
test('instance default policies', async () => {
|
test('instance default policies', async () => {
|
||||||
const user = await createUser();
|
const user = await createUser();
|
||||||
metaService.fetch.mockResolvedValue({
|
meta.policies = {
|
||||||
policies: {
|
canManageCustomEmojis: false,
|
||||||
canManageCustomEmojis: false,
|
};
|
||||||
},
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
const result = await roleService.getUserPolicies(user.id);
|
const result = await roleService.getUserPolicies(user.id);
|
||||||
|
|
||||||
|
@ -177,11 +176,9 @@ describe('RoleService', () => {
|
||||||
|
|
||||||
test('instance default policies 2', async () => {
|
test('instance default policies 2', async () => {
|
||||||
const user = await createUser();
|
const user = await createUser();
|
||||||
metaService.fetch.mockResolvedValue({
|
meta.policies = {
|
||||||
policies: {
|
canManageCustomEmojis: true,
|
||||||
canManageCustomEmojis: true,
|
};
|
||||||
},
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
const result = await roleService.getUserPolicies(user.id);
|
const result = await roleService.getUserPolicies(user.id);
|
||||||
|
|
||||||
|
@ -201,11 +198,9 @@ describe('RoleService', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await roleService.assign(user.id, role.id);
|
await roleService.assign(user.id, role.id);
|
||||||
metaService.fetch.mockResolvedValue({
|
meta.policies = {
|
||||||
policies: {
|
canManageCustomEmojis: false,
|
||||||
canManageCustomEmojis: false,
|
};
|
||||||
},
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
const result = await roleService.getUserPolicies(user.id);
|
const result = await roleService.getUserPolicies(user.id);
|
||||||
|
|
||||||
|
@ -236,11 +231,9 @@ describe('RoleService', () => {
|
||||||
});
|
});
|
||||||
await roleService.assign(user.id, role1.id);
|
await roleService.assign(user.id, role1.id);
|
||||||
await roleService.assign(user.id, role2.id);
|
await roleService.assign(user.id, role2.id);
|
||||||
metaService.fetch.mockResolvedValue({
|
meta.policies = {
|
||||||
policies: {
|
driveCapacityMb: 50,
|
||||||
driveCapacityMb: 50,
|
};
|
||||||
},
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
const result = await roleService.getUserPolicies(user.id);
|
const result = await roleService.getUserPolicies(user.id);
|
||||||
|
|
||||||
|
@ -260,11 +253,9 @@ describe('RoleService', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await roleService.assign(user.id, role.id, new Date(Date.now() + (1000 * 60 * 60 * 24)));
|
await roleService.assign(user.id, role.id, new Date(Date.now() + (1000 * 60 * 60 * 24)));
|
||||||
metaService.fetch.mockResolvedValue({
|
meta.policies = {
|
||||||
policies: {
|
canManageCustomEmojis: false,
|
||||||
canManageCustomEmojis: false,
|
};
|
||||||
},
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
const result = await roleService.getUserPolicies(user.id);
|
const result = await roleService.getUserPolicies(user.id);
|
||||||
expect(result.canManageCustomEmojis).toBe(true);
|
expect(result.canManageCustomEmojis).toBe(true);
|
||||||
|
|
|
@ -24,7 +24,6 @@ import { MiMeta, MiNote, UserProfilesRepository } from '@/models/_.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||||
import { DownloadService } from '@/core/DownloadService.js';
|
import { DownloadService } from '@/core/DownloadService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import type { MiRemoteUser } from '@/models/User.js';
|
import type { MiRemoteUser } from '@/models/User.js';
|
||||||
import { genAidx } from '@/misc/id/aidx.js';
|
import { genAidx } from '@/misc/id/aidx.js';
|
||||||
import { MockResolver } from '../misc/mock-resolver.js';
|
import { MockResolver } from '../misc/mock-resolver.js';
|
||||||
|
@ -107,7 +106,14 @@ describe('ActivityPub', () => {
|
||||||
sensitiveWords: [] as string[],
|
sensitiveWords: [] as string[],
|
||||||
prohibitedWords: [] as string[],
|
prohibitedWords: [] as string[],
|
||||||
} as MiMeta;
|
} as MiMeta;
|
||||||
let meta = metaInitial;
|
const meta = { ...metaInitial };
|
||||||
|
|
||||||
|
function updateMeta(newMeta: Partial<MiMeta>): void {
|
||||||
|
for (const key in meta) {
|
||||||
|
delete (meta as any)[key];
|
||||||
|
}
|
||||||
|
Object.assign(meta, newMeta);
|
||||||
|
}
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const app = await Test.createTestingModule({
|
const app = await Test.createTestingModule({
|
||||||
|
@ -120,11 +126,8 @@ describe('ActivityPub', () => {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.overrideProvider(MetaService).useValue({
|
.overrideProvider(DI.meta).useFactory({ factory: () => meta })
|
||||||
async fetch(): Promise<MiMeta> {
|
.compile();
|
||||||
return meta;
|
|
||||||
},
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
await app.init();
|
await app.init();
|
||||||
app.enableShutdownHooks();
|
app.enableShutdownHooks();
|
||||||
|
@ -367,7 +370,7 @@ describe('ActivityPub', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('cacheRemoteFiles=false disables caching', async () => {
|
test('cacheRemoteFiles=false disables caching', async () => {
|
||||||
meta = { ...metaInitial, cacheRemoteFiles: false };
|
updateMeta({ ...metaInitial, cacheRemoteFiles: false });
|
||||||
|
|
||||||
const imageObject: IApDocument = {
|
const imageObject: IApDocument = {
|
||||||
type: 'Document',
|
type: 'Document',
|
||||||
|
@ -396,7 +399,7 @@ describe('ActivityPub', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('cacheRemoteSensitiveFiles=false only affects sensitive files', async () => {
|
test('cacheRemoteSensitiveFiles=false only affects sensitive files', async () => {
|
||||||
meta = { ...metaInitial, cacheRemoteSensitiveFiles: false };
|
updateMeta({ ...metaInitial, cacheRemoteSensitiveFiles: false });
|
||||||
|
|
||||||
const imageObject: IApDocument = {
|
const imageObject: IApDocument = {
|
||||||
type: 'Document',
|
type: 'Document',
|
||||||
|
|
|
@ -11,16 +11,16 @@
|
||||||
"lint": "pnpm typecheck && pnpm eslint"
|
"lint": "pnpm typecheck && pnpm eslint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordapp/twemoji": "15.0.3",
|
"@discordapp/twemoji": "15.1.0",
|
||||||
"@github/webauthn-json": "2.1.1",
|
"@github/webauthn-json": "2.1.1",
|
||||||
"@rollup/plugin-json": "6.1.0",
|
"@rollup/plugin-json": "6.1.0",
|
||||||
"@rollup/plugin-replace": "5.0.7",
|
"@rollup/plugin-replace": "5.0.7",
|
||||||
"@rollup/pluginutils": "5.1.0",
|
"@rollup/pluginutils": "5.1.0",
|
||||||
"@tabler/icons-webfont": "3.3.0",
|
"@tabler/icons-webfont": "3.3.0",
|
||||||
"@twemoji/parser": "15.1.1",
|
"@twemoji/parser": "15.1.1",
|
||||||
"@vitejs/plugin-vue": "5.1.0",
|
"@vitejs/plugin-vue": "5.1.4",
|
||||||
"@vue/compiler-sfc": "3.4.37",
|
"@vue/compiler-sfc": "3.5.7",
|
||||||
"astring": "1.8.6",
|
"astring": "1.9.0",
|
||||||
"buraha": "0.0.1",
|
"buraha": "0.0.1",
|
||||||
"compare-versions": "6.1.1",
|
"compare-versions": "6.1.1",
|
||||||
"date-fns": "2.30.0",
|
"date-fns": "2.30.0",
|
||||||
|
@ -33,53 +33,53 @@
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
"frontend-shared": "workspace:*",
|
"frontend-shared": "workspace:*",
|
||||||
"punycode": "2.3.1",
|
"punycode": "2.3.1",
|
||||||
"rollup": "4.19.1",
|
"rollup": "4.22.2",
|
||||||
"sanitize-html": "2.13.0",
|
"sanitize-html": "2.13.0",
|
||||||
"sass": "1.77.8",
|
"sass": "1.79.3",
|
||||||
"shiki": "1.12.0",
|
"shiki": "1.12.0",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"throttle-debounce": "5.0.2",
|
"throttle-debounce": "5.0.2",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tsc-alias": "1.8.10",
|
"tsc-alias": "1.8.10",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typescript": "5.5.4",
|
"typescript": "5.6.2",
|
||||||
"uuid": "10.0.0",
|
"uuid": "10.0.0",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"vite": "5.3.5",
|
"vite": "5.4.7",
|
||||||
"vue": "3.4.37"
|
"vue": "3.5.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/summaly": "5.1.0",
|
"@misskey-dev/summaly": "5.1.0",
|
||||||
"@testing-library/vue": "8.1.0",
|
"@testing-library/vue": "8.1.0",
|
||||||
"@types/escape-regexp": "0.0.3",
|
"@types/escape-regexp": "0.0.3",
|
||||||
"@types/estree": "1.0.5",
|
"@types/estree": "1.0.6",
|
||||||
"@types/micromatch": "4.0.9",
|
"@types/micromatch": "4.0.9",
|
||||||
"@types/node": "20.14.12",
|
"@types/node": "20.14.12",
|
||||||
"@types/punycode": "2.1.4",
|
"@types/punycode": "2.1.4",
|
||||||
"@types/sanitize-html": "2.11.0",
|
"@types/sanitize-html": "2.13.0",
|
||||||
"@types/throttle-debounce": "5.0.2",
|
"@types/throttle-debounce": "5.0.2",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"@types/uuid": "10.0.0",
|
"@types/uuid": "10.0.0",
|
||||||
"@types/ws": "8.5.11",
|
"@types/ws": "8.5.12",
|
||||||
"@typescript-eslint/eslint-plugin": "7.17.0",
|
"@typescript-eslint/eslint-plugin": "7.17.0",
|
||||||
"@typescript-eslint/parser": "7.17.0",
|
"@typescript-eslint/parser": "7.17.0",
|
||||||
"@vitest/coverage-v8": "1.6.0",
|
"@vitest/coverage-v8": "1.6.0",
|
||||||
"@vue/runtime-core": "3.4.37",
|
"@vue/runtime-core": "3.5.7",
|
||||||
"acorn": "8.12.1",
|
"acorn": "8.12.1",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint-plugin-import": "2.29.1",
|
"eslint-plugin-import": "2.30.0",
|
||||||
"eslint-plugin-vue": "9.27.0",
|
"eslint-plugin-vue": "9.28.0",
|
||||||
"fast-glob": "3.3.2",
|
"fast-glob": "3.3.2",
|
||||||
"happy-dom": "10.0.3",
|
"happy-dom": "10.0.3",
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"micromatch": "4.0.7",
|
"micromatch": "4.0.8",
|
||||||
"msw": "2.3.4",
|
"msw": "2.3.4",
|
||||||
"nodemon": "3.1.4",
|
"nodemon": "3.1.7",
|
||||||
"prettier": "3.3.3",
|
"prettier": "3.3.3",
|
||||||
"start-server-and-test": "2.0.4",
|
"start-server-and-test": "2.0.8",
|
||||||
"vite-plugin-turbosnap": "1.0.3",
|
"vite-plugin-turbosnap": "1.0.3",
|
||||||
"vue-component-type-helpers": "2.0.29",
|
"vue-component-type-helpers": "2.1.6",
|
||||||
"vue-eslint-parser": "9.4.3",
|
"vue-eslint-parser": "9.4.3",
|
||||||
"vue-tsc": "2.0.29"
|
"vue-tsc": "2.1.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,16 +20,19 @@ import { serverMetadata } from '@/server-metadata.js';
|
||||||
import { url } from '@@/js/config.js';
|
import { url } from '@@/js/config.js';
|
||||||
import { parseEmbedParams } from '@@/js/embed-page.js';
|
import { parseEmbedParams } from '@@/js/embed-page.js';
|
||||||
import { postMessageToParentWindow, setIframeId } from '@/post-message.js';
|
import { postMessageToParentWindow, setIframeId } from '@/post-message.js';
|
||||||
|
import { serverContext } from '@/server-context.js';
|
||||||
|
|
||||||
import type { Theme } from '@/theme.js';
|
import type { Theme } from '@/theme.js';
|
||||||
|
|
||||||
console.log('Misskey Embed');
|
console.log('Misskey Embed');
|
||||||
|
|
||||||
|
//#region Embedパラメータの取得・パース
|
||||||
const params = new URLSearchParams(location.search);
|
const params = new URLSearchParams(location.search);
|
||||||
const embedParams = parseEmbedParams(params);
|
const embedParams = parseEmbedParams(params);
|
||||||
|
|
||||||
if (_DEV_) console.log(embedParams);
|
if (_DEV_) console.log(embedParams);
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region テーマ
|
||||||
function parseThemeOrNull(theme: string | null): Theme | null {
|
function parseThemeOrNull(theme: string | null): Theme | null {
|
||||||
if (theme == null) return null;
|
if (theme == null) return null;
|
||||||
try {
|
try {
|
||||||
|
@ -65,6 +68,7 @@ if (embedParams.colorMode === 'dark') {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
// サイズの制限
|
// サイズの制限
|
||||||
document.documentElement.style.maxWidth = '500px';
|
document.documentElement.style.maxWidth = '500px';
|
||||||
|
@ -89,6 +93,10 @@ const app = createApp(
|
||||||
|
|
||||||
app.provide(DI.mediaProxy, new MediaProxy(serverMetadata, url));
|
app.provide(DI.mediaProxy, new MediaProxy(serverMetadata, url));
|
||||||
|
|
||||||
|
app.provide(DI.serverMetadata, serverMetadata);
|
||||||
|
|
||||||
|
app.provide(DI.serverContext, serverContext);
|
||||||
|
|
||||||
app.provide(DI.embedParams, embedParams);
|
app.provide(DI.embedParams, embedParams);
|
||||||
|
|
||||||
// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210
|
// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210
|
||||||
|
|
|
@ -142,8 +142,8 @@ import EmAcct from '@/components/EmAcct.vue';
|
||||||
import { userPage } from '@/utils.js';
|
import { userPage } from '@/utils.js';
|
||||||
import { notePage } from '@/utils.js';
|
import { notePage } from '@/utils.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { DI } from '@/di.js';
|
||||||
import { shouldCollapsed } from '@@/js/collapsed.js';
|
import { shouldCollapsed } from '@@/js/collapsed.js';
|
||||||
import { serverMetadata } from '@/server-metadata.js';
|
|
||||||
import { url } from '@@/js/config.js';
|
import { url } from '@@/js/config.js';
|
||||||
import EmMfm from '@/components/EmMfm.js';
|
import EmMfm from '@/components/EmMfm.js';
|
||||||
|
|
||||||
|
@ -151,6 +151,8 @@ const props = defineProps<{
|
||||||
note: Misskey.entities.Note;
|
note: Misskey.entities.Note;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const serverMetadata = inject(DI.serverMetadata)!;
|
||||||
|
|
||||||
const inChannel = inject('inChannel', null);
|
const inChannel = inject('inChannel', null);
|
||||||
|
|
||||||
const note = ref(props.note);
|
const note = ref(props.note);
|
||||||
|
|
|
@ -20,12 +20,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { shallowRef } from 'vue';
|
import { useTemplateRef } from 'vue';
|
||||||
import EmNote from '@/components/EmNote.vue';
|
import EmNote from '@/components/EmNote.vue';
|
||||||
import EmPagination, { Paging } from '@/components/EmPagination.vue';
|
import EmPagination, { Paging } from '@/components/EmPagination.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
withDefaults(defineProps<{
|
||||||
pagination: Paging;
|
pagination: Paging;
|
||||||
noGap?: boolean;
|
noGap?: boolean;
|
||||||
disableAutoLoad?: boolean;
|
disableAutoLoad?: boolean;
|
||||||
|
@ -34,7 +34,7 @@ const props = withDefaults(defineProps<{
|
||||||
ad: true,
|
ad: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const pagingComponent = shallowRef<InstanceType<typeof EmPagination>>();
|
const pagingComponent = useTemplateRef('pagingComponent');
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
pagingComponent,
|
pagingComponent,
|
||||||
|
|
|
@ -7,9 +7,11 @@ import type { InjectionKey } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { MediaProxy } from '@@/js/media-proxy.js';
|
import { MediaProxy } from '@@/js/media-proxy.js';
|
||||||
import type { ParsedEmbedParams } from '@@/js/embed-page.js';
|
import type { ParsedEmbedParams } from '@@/js/embed-page.js';
|
||||||
|
import type { ServerContext } from '@/server-context.js';
|
||||||
|
|
||||||
export const DI = {
|
export const DI = {
|
||||||
serverMetadata: Symbol() as InjectionKey<Misskey.entities.MetaDetailed>,
|
serverMetadata: Symbol() as InjectionKey<Misskey.entities.MetaDetailed>,
|
||||||
embedParams: Symbol() as InjectionKey<ParsedEmbedParams>,
|
embedParams: Symbol() as InjectionKey<ParsedEmbedParams>,
|
||||||
|
serverContext: Symbol() as InjectionKey<ServerContext>,
|
||||||
mediaProxy: Symbol() as InjectionKey<MediaProxy>,
|
mediaProxy: Symbol() as InjectionKey<MediaProxy>,
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,8 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<EmLoading v-if="loading"/>
|
<EmTimelineContainer v-if="clip" :showHeader="embedParams.header">
|
||||||
<EmTimelineContainer v-else-if="clip" :showHeader="embedParams.header">
|
|
||||||
<template #header>
|
<template #header>
|
||||||
<div :class="$style.clipHeader">
|
<div :class="$style.clipHeader">
|
||||||
<div :class="$style.headerClipIconRoot">
|
<div :class="$style.headerClipIconRoot">
|
||||||
|
@ -39,20 +38,19 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, shallowRef, inject } from 'vue';
|
import { ref, computed, inject, useTemplateRef } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { scrollToTop } from '@@/js/scroll.js';
|
import { scrollToTop } from '@@/js/scroll.js';
|
||||||
|
import { url, instanceName } from '@@/js/config.js';
|
||||||
|
import { isLink } from '@@/js/is-link.js';
|
||||||
|
import { defaultEmbedParams } from '@@/js/embed-page.js';
|
||||||
import type { Paging } from '@/components/EmPagination.vue';
|
import type { Paging } from '@/components/EmPagination.vue';
|
||||||
import EmLoading from '@/components/EmLoading.vue';
|
|
||||||
import EmNotes from '@/components/EmNotes.vue';
|
import EmNotes from '@/components/EmNotes.vue';
|
||||||
import XNotFound from '@/pages/not-found.vue';
|
import XNotFound from '@/pages/not-found.vue';
|
||||||
import EmTimelineContainer from '@/components/EmTimelineContainer.vue';
|
import EmTimelineContainer from '@/components/EmTimelineContainer.vue';
|
||||||
import { misskeyApi } from '@/misskey-api.js';
|
import { misskeyApi } from '@/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { serverMetadata } from '@/server-metadata.js';
|
import { assertServerContext } from '@/server-context.js';
|
||||||
import { url, instanceName } from '@@/js/config.js';
|
|
||||||
import { isLink } from '@@/js/is-link.js';
|
|
||||||
import { defaultEmbedParams } from '@@/js/embed-page.js';
|
|
||||||
import { DI } from '@/di.js';
|
import { DI } from '@/di.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -61,16 +59,30 @@ const props = defineProps<{
|
||||||
|
|
||||||
const embedParams = inject(DI.embedParams, defaultEmbedParams);
|
const embedParams = inject(DI.embedParams, defaultEmbedParams);
|
||||||
|
|
||||||
const clip = ref<Misskey.entities.Clip | null>(null);
|
const serverMetadata = inject(DI.serverMetadata)!;
|
||||||
|
|
||||||
|
const serverContext = inject(DI.serverContext)!;
|
||||||
|
|
||||||
|
const clip = ref<Misskey.entities.Clip | null>();
|
||||||
|
|
||||||
|
if (assertServerContext(serverContext, 'clip')) {
|
||||||
|
clip.value = serverContext.clip;
|
||||||
|
} else {
|
||||||
|
clip.value = await misskeyApi('clips/show', {
|
||||||
|
clipId: props.clipId,
|
||||||
|
}).catch(() => {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const pagination = computed(() => ({
|
const pagination = computed(() => ({
|
||||||
endpoint: 'clips/notes',
|
endpoint: 'clips/notes',
|
||||||
params: {
|
params: {
|
||||||
clipId: props.clipId,
|
clipId: props.clipId,
|
||||||
},
|
},
|
||||||
} as Paging));
|
} as Paging));
|
||||||
const loading = ref(true);
|
|
||||||
|
|
||||||
const notesEl = shallowRef<InstanceType<typeof EmNotes> | null>(null);
|
const notesEl = useTemplateRef('notesEl');
|
||||||
|
|
||||||
function top(ev: MouseEvent) {
|
function top(ev: MouseEvent) {
|
||||||
const target = ev.target as HTMLElement | null;
|
const target = ev.target as HTMLElement | null;
|
||||||
|
@ -80,16 +92,6 @@ function top(ev: MouseEvent) {
|
||||||
scrollToTop(notesEl.value.$el as HTMLElement, { behavior: 'smooth' });
|
scrollToTop(notesEl.value.$el as HTMLElement, { behavior: 'smooth' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
misskeyApi('clips/show', {
|
|
||||||
clipId: props.clipId,
|
|
||||||
}).then(res => {
|
|
||||||
clip.value = res;
|
|
||||||
loading.value = false;
|
|
||||||
}).catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -5,40 +5,37 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="$style.noteEmbedRoot">
|
<div :class="$style.noteEmbedRoot">
|
||||||
<EmLoading v-if="loading"/>
|
<EmNoteDetailed v-if="note" :note="note"/>
|
||||||
<EmNoteDetailed v-else-if="note" :note="note"/>
|
|
||||||
<XNotFound v-else/>
|
<XNotFound v-else/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { inject, ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import EmNoteDetailed from '@/components/EmNoteDetailed.vue';
|
import EmNoteDetailed from '@/components/EmNoteDetailed.vue';
|
||||||
import EmLoading from '@/components/EmLoading.vue';
|
|
||||||
import XNotFound from '@/pages/not-found.vue';
|
import XNotFound from '@/pages/not-found.vue';
|
||||||
|
import { DI } from '@/di.js';
|
||||||
import { misskeyApi } from '@/misskey-api.js';
|
import { misskeyApi } from '@/misskey-api.js';
|
||||||
|
import { assertServerContext } from '@/server-context';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
noteId: string;
|
noteId: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const note = ref<Misskey.entities.Note | null>(null);
|
const serverContext = inject(DI.serverContext)!;
|
||||||
const loading = ref(true);
|
|
||||||
|
|
||||||
// TODO: クライアント側でAPIを叩くのは二度手間なので予めHTMLに埋め込んでおく
|
const note = ref<Misskey.entities.Note | null>(null);
|
||||||
misskeyApi('notes/show', {
|
|
||||||
noteId: props.noteId,
|
if (assertServerContext(serverContext, 'note')) {
|
||||||
}).then(res => {
|
note.value = serverContext.note;
|
||||||
// リモートのノートは埋め込ませない
|
} else {
|
||||||
if (res.url == null && res.uri == null) {
|
note.value = await misskeyApi('notes/show', {
|
||||||
note.value = res;
|
noteId: props.noteId,
|
||||||
}
|
}).catch(() => {
|
||||||
loading.value = false;
|
return null;
|
||||||
}).catch(err => {
|
});
|
||||||
console.error(err);
|
}
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -38,14 +38,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, shallowRef, inject } from 'vue';
|
import { computed, inject, useTemplateRef } from 'vue';
|
||||||
import { scrollToTop } from '@@/js/scroll.js';
|
import { scrollToTop } from '@@/js/scroll.js';
|
||||||
import type { Paging } from '@/components/EmPagination.vue';
|
import type { Paging } from '@/components/EmPagination.vue';
|
||||||
import EmNotes from '@/components/EmNotes.vue';
|
import EmNotes from '@/components/EmNotes.vue';
|
||||||
import XNotFound from '@/pages/not-found.vue';
|
import XNotFound from '@/pages/not-found.vue';
|
||||||
import EmTimelineContainer from '@/components/EmTimelineContainer.vue';
|
import EmTimelineContainer from '@/components/EmTimelineContainer.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { serverMetadata } from '@/server-metadata.js';
|
|
||||||
import { url, instanceName } from '@@/js/config.js';
|
import { url, instanceName } from '@@/js/config.js';
|
||||||
import { isLink } from '@@/js/is-link.js';
|
import { isLink } from '@@/js/is-link.js';
|
||||||
import { DI } from '@/di.js';
|
import { DI } from '@/di.js';
|
||||||
|
@ -55,6 +54,8 @@ const props = defineProps<{
|
||||||
tag: string;
|
tag: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const serverMetadata = inject(DI.serverMetadata)!;
|
||||||
|
|
||||||
const embedParams = inject(DI.embedParams, defaultEmbedParams);
|
const embedParams = inject(DI.embedParams, defaultEmbedParams);
|
||||||
|
|
||||||
const pagination = computed(() => ({
|
const pagination = computed(() => ({
|
||||||
|
@ -64,7 +65,7 @@ const pagination = computed(() => ({
|
||||||
},
|
},
|
||||||
} as Paging));
|
} as Paging));
|
||||||
|
|
||||||
const notesEl = shallowRef<InstanceType<typeof EmNotes> | null>(null);
|
const notesEl = useTemplateRef('notesEl');
|
||||||
|
|
||||||
function top(ev: MouseEvent) {
|
function top(ev: MouseEvent) {
|
||||||
const target = ev.target as HTMLElement | null;
|
const target = ev.target as HTMLElement | null;
|
||||||
|
|
|
@ -5,8 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<EmLoading v-if="loading"/>
|
<EmTimelineContainer v-if="user && !prohibited" :showHeader="embedParams.header">
|
||||||
<EmTimelineContainer v-else-if="user" :showHeader="embedParams.header">
|
|
||||||
<template #header>
|
<template #header>
|
||||||
<div :class="$style.userHeader">
|
<div :class="$style.userHeader">
|
||||||
<a :href="`/@${user.username}`" target="_blank" rel="noopener noreferrer" :class="$style.avatarLink">
|
<a :href="`/@${user.username}`" target="_blank" rel="noopener noreferrer" :class="$style.avatarLink">
|
||||||
|
@ -46,21 +45,20 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, shallowRef, inject } from 'vue';
|
import { ref, computed, inject, useTemplateRef } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
|
import { url, instanceName } from '@@/js/config.js';
|
||||||
|
import { defaultEmbedParams } from '@@/js/embed-page.js';
|
||||||
import type { Paging } from '@/components/EmPagination.vue';
|
import type { Paging } from '@/components/EmPagination.vue';
|
||||||
import EmNotes from '@/components/EmNotes.vue';
|
import EmNotes from '@/components/EmNotes.vue';
|
||||||
import EmAvatar from '@/components/EmAvatar.vue';
|
import EmAvatar from '@/components/EmAvatar.vue';
|
||||||
import EmLoading from '@/components/EmLoading.vue';
|
|
||||||
import EmUserName from '@/components/EmUserName.vue';
|
import EmUserName from '@/components/EmUserName.vue';
|
||||||
import I18n from '@/components/I18n.vue';
|
import I18n from '@/components/I18n.vue';
|
||||||
import XNotFound from '@/pages/not-found.vue';
|
import XNotFound from '@/pages/not-found.vue';
|
||||||
import EmTimelineContainer from '@/components/EmTimelineContainer.vue';
|
import EmTimelineContainer from '@/components/EmTimelineContainer.vue';
|
||||||
import { misskeyApi } from '@/misskey-api.js';
|
import { misskeyApi } from '@/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { serverMetadata } from '@/server-metadata.js';
|
import { assertServerContext } from '@/server-context.js';
|
||||||
import { url, instanceName } from '@@/js/config.js';
|
|
||||||
import { defaultEmbedParams } from '@@/js/embed-page.js';
|
|
||||||
import { DI } from '@/di.js';
|
import { DI } from '@/di.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -69,26 +67,37 @@ const props = defineProps<{
|
||||||
|
|
||||||
const embedParams = inject(DI.embedParams, defaultEmbedParams);
|
const embedParams = inject(DI.embedParams, defaultEmbedParams);
|
||||||
|
|
||||||
const user = ref<Misskey.entities.UserLite | null>(null);
|
const serverMetadata = inject(DI.serverMetadata)!;
|
||||||
|
|
||||||
|
const serverContext = inject(DI.serverContext)!;
|
||||||
|
|
||||||
|
const user = ref<Misskey.entities.UserLite | null>();
|
||||||
|
|
||||||
|
const prohibited = ref(false);
|
||||||
|
|
||||||
|
if (assertServerContext(serverContext, 'user')) {
|
||||||
|
user.value = serverContext.user;
|
||||||
|
} else {
|
||||||
|
user.value = await misskeyApi('users/show', {
|
||||||
|
userId: props.userId,
|
||||||
|
}).catch(() => {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.value?.host != null) {
|
||||||
|
// リモートサーバーのユーザーは弾く
|
||||||
|
prohibited.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
const pagination = computed(() => ({
|
const pagination = computed(() => ({
|
||||||
endpoint: 'users/notes',
|
endpoint: 'users/notes',
|
||||||
params: {
|
params: {
|
||||||
userId: user.value?.id,
|
userId: user.value?.id,
|
||||||
},
|
},
|
||||||
} as Paging));
|
} as Paging));
|
||||||
const loading = ref(true);
|
|
||||||
|
|
||||||
const notesEl = shallowRef<InstanceType<typeof EmNotes> | null>(null);
|
const notesEl = useTemplateRef('notesEl');
|
||||||
|
|
||||||
misskeyApi('users/show', {
|
|
||||||
userId: props.userId,
|
|
||||||
}).then(res => {
|
|
||||||
user.value = res;
|
|
||||||
loading.value = false;
|
|
||||||
}).catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
|
||||||
|
const providedContextEl = document.getElementById('misskey_embedCtx');
|
||||||
|
|
||||||
|
export type ServerContext = {
|
||||||
|
clip?: Misskey.entities.Clip;
|
||||||
|
note?: Misskey.entities.Note;
|
||||||
|
user?: Misskey.entities.UserLite;
|
||||||
|
} | null;
|
||||||
|
|
||||||
|
// NOTE: devモードのときしか embedCtx が null になることは無い
|
||||||
|
export const serverContext: ServerContext = (providedContextEl && providedContextEl.textContent) ? JSON.parse(providedContextEl.textContent) : null;
|
||||||
|
|
||||||
|
export function assertServerContext<K extends keyof NonNullable<ServerContext>>(ctx: ServerContext, entity: K): ctx is Required<Pick<NonNullable<ServerContext>, K>> {
|
||||||
|
if (ctx == null) return false;
|
||||||
|
return entity in ctx;
|
||||||
|
}
|
|
@ -18,11 +18,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div
|
<div
|
||||||
:class="$style.routerViewContainer"
|
:class="$style.routerViewContainer"
|
||||||
>
|
>
|
||||||
<EmNotePage v-if="page === 'notes'" :noteId="contentId"/>
|
<Suspense :timeout="0">
|
||||||
<EmUserTimelinePage v-else-if="page === 'user-timeline'" :userId="contentId"/>
|
<EmNotePage v-if="page === 'notes'" :noteId="contentId"/>
|
||||||
<EmClipPage v-else-if="page === 'clips'" :clipId="contentId"/>
|
<EmUserTimelinePage v-else-if="page === 'user-timeline'" :userId="contentId"/>
|
||||||
<EmTagPage v-else-if="page === 'tags'" :tag="contentId"/>
|
<EmClipPage v-else-if="page === 'clips'" :clipId="contentId"/>
|
||||||
<XNotFound v-else/>
|
<EmTagPage v-else-if="page === 'tags'" :tag="contentId"/>
|
||||||
|
<XNotFound v-else/>
|
||||||
|
<template #fallback>
|
||||||
|
<EmLoading/>
|
||||||
|
</template>
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -37,6 +42,7 @@ import EmUserTimelinePage from '@/pages/user-timeline.vue';
|
||||||
import EmClipPage from '@/pages/clip.vue';
|
import EmClipPage from '@/pages/clip.vue';
|
||||||
import EmTagPage from '@/pages/tag.vue';
|
import EmTagPage from '@/pages/tag.vue';
|
||||||
import XNotFound from '@/pages/not-found.vue';
|
import XNotFound from '@/pages/not-found.vue';
|
||||||
|
import EmLoading from '@/components/EmLoading.vue';
|
||||||
|
|
||||||
const page = location.pathname.split('/')[2];
|
const page = location.pathname.split('/')[2];
|
||||||
const contentId = location.pathname.split('/')[3];
|
const contentId = location.pathname.split('/')[3];
|
||||||
|
|
|
@ -36,19 +36,27 @@ export function getScrollPosition(el: HTMLElement | null): number {
|
||||||
return container == null ? window.scrollY : container.scrollTop;
|
return container == null ? window.scrollY : container.scrollTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance = 1, once = false) {
|
export function onScrollTop(el: HTMLElement, cb: (topVisible: boolean) => unknown, tolerance = 1, once = false) {
|
||||||
// とりあえず評価してみる
|
// とりあえず評価してみる
|
||||||
if (el.isConnected && isTopVisible(el)) {
|
const firstTopVisible = isTopVisible(el);
|
||||||
cb();
|
if (el.isConnected && firstTopVisible) {
|
||||||
|
cb(firstTopVisible);
|
||||||
if (once) return null;
|
if (once) return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = getScrollContainer(el) ?? window;
|
const container = getScrollContainer(el) ?? window;
|
||||||
|
|
||||||
|
// 以下のケースにおいて、cbが何度も呼び出されてしまって具合が悪いので1回呼んだら以降は無視するようにする
|
||||||
|
// - スクロールイベントは1回のスクロールで複数回発生することがある
|
||||||
|
// - toleranceの範囲内に収まる程度の微量なスクロールが発生した
|
||||||
|
let prevTopVisible = firstTopVisible;
|
||||||
const onScroll = () => {
|
const onScroll = () => {
|
||||||
if (!document.body.contains(el)) return;
|
if (!document.body.contains(el)) return;
|
||||||
if (isTopVisible(el, tolerance)) {
|
|
||||||
cb();
|
const topVisible = isTopVisible(el, tolerance);
|
||||||
|
if (topVisible !== prevTopVisible) {
|
||||||
|
prevTopVisible = topVisible;
|
||||||
|
cb(topVisible);
|
||||||
if (once) removeListener();
|
if (once) removeListener();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -126,6 +134,7 @@ export function scrollToBottom(
|
||||||
|
|
||||||
export function isTopVisible(el: HTMLElement, tolerance = 1): boolean {
|
export function isTopVisible(el: HTMLElement, tolerance = 1): boolean {
|
||||||
const scrollTop = getScrollPosition(el);
|
const scrollTop = getScrollPosition(el);
|
||||||
|
if (_DEV_) console.log(scrollTop, tolerance, scrollTop <= tolerance);
|
||||||
return scrollTop <= tolerance;
|
return scrollTop <= tolerance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,11 +53,13 @@
|
||||||
infoFg: '#fff',
|
infoFg: '#fff',
|
||||||
infoWarnBg: '#42321c',
|
infoWarnBg: '#42321c',
|
||||||
infoWarnFg: '#ffbd3e',
|
infoWarnFg: '#ffbd3e',
|
||||||
switchBg: 'rgba(255, 255, 255, 0.15)',
|
folderHeaderBg: 'rgba(255, 255, 255, 0.05)',
|
||||||
buttonBg: 'rgba(255, 255, 255, 0.05)',
|
folderHeaderHoverBg: 'rgba(255, 255, 255, 0.1)',
|
||||||
buttonHoverBg: 'rgba(255, 255, 255, 0.1)',
|
buttonBg: ':lighten<5<@panel',
|
||||||
|
buttonHoverBg: ':lighten<10<@panel',
|
||||||
buttonGradateA: '@accent',
|
buttonGradateA: '@accent',
|
||||||
buttonGradateB: ':hue<20<@accent',
|
buttonGradateB: ':hue<20<@accent',
|
||||||
|
switchBg: 'rgba(255, 255, 255, 0.15)',
|
||||||
switchOffBg: 'rgba(255, 255, 255, 0.1)',
|
switchOffBg: 'rgba(255, 255, 255, 0.1)',
|
||||||
switchOffFg: ':alpha<0.8<@fg',
|
switchOffFg: ':alpha<0.8<@fg',
|
||||||
switchOnBg: '@accentedBg',
|
switchOnBg: '@accentedBg',
|
||||||
|
|
|
@ -53,11 +53,13 @@
|
||||||
infoFg: '#72818a',
|
infoFg: '#72818a',
|
||||||
infoWarnBg: '#fff0db',
|
infoWarnBg: '#fff0db',
|
||||||
infoWarnFg: '#8f6e31',
|
infoWarnFg: '#8f6e31',
|
||||||
switchBg: 'rgba(0, 0, 0, 0.15)',
|
folderHeaderBg: 'rgba(0, 0, 0, 0.05)',
|
||||||
buttonBg: 'rgba(0, 0, 0, 0.05)',
|
folderHeaderHoverBg: 'rgba(0, 0, 0, 0.1)',
|
||||||
buttonHoverBg: 'rgba(0, 0, 0, 0.1)',
|
buttonBg: ':darken<5<@panel',
|
||||||
|
buttonHoverBg: ':darken<10<@panel',
|
||||||
buttonGradateA: '@accent',
|
buttonGradateA: '@accent',
|
||||||
buttonGradateB: ':hue<20<@accent',
|
buttonGradateB: ':hue<20<@accent',
|
||||||
|
switchBg: 'rgba(0, 0, 0, 0.15)',
|
||||||
switchOffBg: 'rgba(0, 0, 0, 0.1)',
|
switchOffBg: 'rgba(0, 0, 0, 0.1)',
|
||||||
switchOffFg: '@panel',
|
switchOffFg: '@panel',
|
||||||
switchOnBg: '@accent',
|
switchOnBg: '@accent',
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
mention: '#ffd152',
|
mention: '#ffd152',
|
||||||
modalBg: 'rgba(0, 0, 0, 0.5)',
|
modalBg: 'rgba(0, 0, 0, 0.5)',
|
||||||
success: '#86b300',
|
success: '#86b300',
|
||||||
buttonBg: 'rgba(255, 255, 255, 0.05)',
|
|
||||||
acrylicBg: ':alpha<0.5<@bg',
|
acrylicBg: ':alpha<0.5<@bg',
|
||||||
indicator: '@accent',
|
indicator: '@accent',
|
||||||
mentionMe: '#fb5d38',
|
mentionMe: '#fb5d38',
|
||||||
|
@ -42,7 +41,6 @@
|
||||||
acrylicPanel: ':alpha<0.5<@panel',
|
acrylicPanel: ':alpha<0.5<@panel',
|
||||||
navIndicator: '@accent',
|
navIndicator: '@accent',
|
||||||
accentLighten: ':lighten<10<@accent',
|
accentLighten: ':lighten<10<@accent',
|
||||||
buttonHoverBg: 'rgba(255, 255, 255, 0.1)',
|
|
||||||
buttonGradateA: '@accent',
|
buttonGradateA: '@accent',
|
||||||
buttonGradateB: ':hue<-20<@accent',
|
buttonGradateB: ':hue<-20<@accent',
|
||||||
driveFolderBg: ':alpha<0.3<@accent',
|
driveFolderBg: ':alpha<0.3<@accent',
|
||||||
|
|
|
@ -38,7 +38,6 @@
|
||||||
mention: '@accent',
|
mention: '@accent',
|
||||||
modalBg: 'rgba(0, 0, 0, 0.5)',
|
modalBg: 'rgba(0, 0, 0, 0.5)',
|
||||||
success: '#86b300',
|
success: '#86b300',
|
||||||
buttonBg: 'rgba(255, 255, 255, 0.05)',
|
|
||||||
switchBg: 'rgba(255, 255, 255, 0.15)',
|
switchBg: 'rgba(255, 255, 255, 0.15)',
|
||||||
acrylicBg: ':alpha<0.5<@bg',
|
acrylicBg: ':alpha<0.5<@bg',
|
||||||
indicator: '@accent',
|
indicator: '@accent',
|
||||||
|
@ -61,7 +60,6 @@
|
||||||
acrylicPanel: ':alpha<0.5<@panel',
|
acrylicPanel: ':alpha<0.5<@panel',
|
||||||
navIndicator: '@indicator',
|
navIndicator: '@indicator',
|
||||||
accentLighten: ':lighten<10<@accent',
|
accentLighten: ':lighten<10<@accent',
|
||||||
buttonHoverBg: 'rgba(255, 255, 255, 0.1)',
|
|
||||||
driveFolderBg: ':alpha<0.3<@accent',
|
driveFolderBg: ':alpha<0.3<@accent',
|
||||||
fgHighlighted: ':lighten<3<@fg',
|
fgHighlighted: ':lighten<3<@fg',
|
||||||
fgTransparent: ':alpha<0.5<@fg',
|
fgTransparent: ':alpha<0.5<@fg',
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
mention: '@accent',
|
mention: '@accent',
|
||||||
modalBg: 'rgba(0, 0, 0, 0.3)',
|
modalBg: 'rgba(0, 0, 0, 0.3)',
|
||||||
success: '#86b300',
|
success: '#86b300',
|
||||||
buttonBg: 'rgba(0, 0, 0, 0.05)',
|
|
||||||
acrylicBg: ':alpha<0.5<@bg',
|
acrylicBg: ':alpha<0.5<@bg',
|
||||||
indicator: '@accent',
|
indicator: '@accent',
|
||||||
mentionMe: '@mention',
|
mentionMe: '@mention',
|
||||||
|
@ -45,7 +44,6 @@
|
||||||
acrylicPanel: ':alpha<0.5<@panel',
|
acrylicPanel: ':alpha<0.5<@panel',
|
||||||
navIndicator: '@accent',
|
navIndicator: '@accent',
|
||||||
accentLighten: ':lighten<10<@accent',
|
accentLighten: ':lighten<10<@accent',
|
||||||
buttonHoverBg: 'rgba(0, 0, 0, 0.1)',
|
|
||||||
driveFolderBg: ':alpha<0.3<@accent',
|
driveFolderBg: ':alpha<0.3<@accent',
|
||||||
fgHighlighted: ':darken<3<@fg',
|
fgHighlighted: ':darken<3<@fg',
|
||||||
fgTransparent: ':alpha<0.5<@fg',
|
fgTransparent: ':alpha<0.5<@fg',
|
||||||
|
|
|
@ -405,8 +405,9 @@ function toStories(component: string): Promise<string> {
|
||||||
glob('src/components/MkUserSetupDialog.*.vue'),
|
glob('src/components/MkUserSetupDialog.*.vue'),
|
||||||
glob('src/components/MkInstanceCardMini.vue'),
|
glob('src/components/MkInstanceCardMini.vue'),
|
||||||
glob('src/components/MkInviteCode.vue'),
|
glob('src/components/MkInviteCode.vue'),
|
||||||
glob('src/pages/search.vue'),
|
glob('src/pages/admin/overview.ap-requests.vue'),
|
||||||
glob('src/pages/user/home.vue'),
|
glob('src/pages/user/home.vue'),
|
||||||
|
glob('src/pages/search.vue'),
|
||||||
]);
|
]);
|
||||||
const components = globs.flat();
|
const components = globs.flat();
|
||||||
await Promise.all(components.map(async (component) => {
|
await Promise.all(components.map(async (component) => {
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
"lint": "pnpm typecheck && pnpm eslint"
|
"lint": "pnpm typecheck && pnpm eslint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordapp/twemoji": "15.0.3",
|
"@discordapp/twemoji": "15.1.0",
|
||||||
"@github/webauthn-json": "2.1.1",
|
"@github/webauthn-json": "2.1.1",
|
||||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||||
"@misskey-dev/browser-image-resizer": "2024.1.0",
|
"@misskey-dev/browser-image-resizer": "2024.1.0",
|
||||||
|
@ -27,21 +27,21 @@
|
||||||
"@syuilo/aiscript": "0.19.0",
|
"@syuilo/aiscript": "0.19.0",
|
||||||
"@tabler/icons-webfont": "3.3.0",
|
"@tabler/icons-webfont": "3.3.0",
|
||||||
"@twemoji/parser": "15.1.1",
|
"@twemoji/parser": "15.1.1",
|
||||||
"@vitejs/plugin-vue": "5.1.0",
|
"@vitejs/plugin-vue": "5.1.4",
|
||||||
"@vue/compiler-sfc": "3.4.37",
|
"@vue/compiler-sfc": "3.5.7",
|
||||||
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.11",
|
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.11",
|
||||||
"astring": "1.8.6",
|
"astring": "1.9.0",
|
||||||
"broadcast-channel": "7.0.0",
|
"broadcast-channel": "7.0.0",
|
||||||
"buraha": "0.0.1",
|
"buraha": "0.0.1",
|
||||||
"canvas-confetti": "1.9.3",
|
"canvas-confetti": "1.9.3",
|
||||||
"chart.js": "4.4.3",
|
"chart.js": "4.4.4",
|
||||||
"chartjs-adapter-date-fns": "3.0.0",
|
"chartjs-adapter-date-fns": "3.0.0",
|
||||||
"chartjs-chart-matrix": "2.0.1",
|
"chartjs-chart-matrix": "2.0.1",
|
||||||
"chartjs-plugin-gradient": "0.6.1",
|
"chartjs-plugin-gradient": "0.6.1",
|
||||||
"chartjs-plugin-zoom": "2.0.1",
|
"chartjs-plugin-zoom": "2.0.1",
|
||||||
"chromatic": "11.5.6",
|
"chromatic": "11.10.2",
|
||||||
"compare-versions": "6.1.1",
|
"compare-versions": "6.1.1",
|
||||||
"cropperjs": "2.0.0-rc.1",
|
"cropperjs": "2.0.0-rc.2",
|
||||||
"date-fns": "2.30.0",
|
"date-fns": "2.30.0",
|
||||||
"escape-regexp": "0.0.1",
|
"escape-regexp": "0.0.1",
|
||||||
"estree-walker": "3.0.3",
|
"estree-walker": "3.0.3",
|
||||||
|
@ -58,85 +58,85 @@
|
||||||
"frontend-shared": "workspace:*",
|
"frontend-shared": "workspace:*",
|
||||||
"photoswipe": "5.4.4",
|
"photoswipe": "5.4.4",
|
||||||
"punycode": "2.3.1",
|
"punycode": "2.3.1",
|
||||||
"rollup": "4.19.1",
|
"rollup": "4.22.2",
|
||||||
"sanitize-html": "2.13.0",
|
"sanitize-html": "2.13.0",
|
||||||
"sass": "1.77.8",
|
"sass": "1.79.3",
|
||||||
"shiki": "1.12.0",
|
"shiki": "1.12.0",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
"three": "0.167.0",
|
"three": "0.168.0",
|
||||||
"throttle-debounce": "5.0.2",
|
"throttle-debounce": "5.0.2",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tsc-alias": "1.8.10",
|
"tsc-alias": "1.8.10",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typescript": "5.5.4",
|
"typescript": "5.6.2",
|
||||||
"uuid": "10.0.0",
|
"uuid": "10.0.0",
|
||||||
"v-code-diff": "1.12.0",
|
"v-code-diff": "1.13.1",
|
||||||
"vite": "5.3.5",
|
"vite": "5.4.7",
|
||||||
"vue": "3.4.37",
|
"vue": "3.5.7",
|
||||||
"vuedraggable": "next"
|
"vuedraggable": "next"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/summaly": "5.1.0",
|
"@misskey-dev/summaly": "5.1.0",
|
||||||
"@storybook/addon-actions": "8.2.6",
|
"@storybook/addon-actions": "8.3.2",
|
||||||
"@storybook/addon-essentials": "8.2.6",
|
"@storybook/addon-essentials": "8.3.2",
|
||||||
"@storybook/addon-interactions": "8.2.6",
|
"@storybook/addon-interactions": "8.3.2",
|
||||||
"@storybook/addon-links": "8.2.6",
|
"@storybook/addon-links": "8.3.2",
|
||||||
"@storybook/addon-mdx-gfm": "8.2.6",
|
"@storybook/addon-mdx-gfm": "8.3.2",
|
||||||
"@storybook/addon-storysource": "8.2.6",
|
"@storybook/addon-storysource": "8.3.2",
|
||||||
"@storybook/blocks": "8.2.6",
|
"@storybook/blocks": "8.3.2",
|
||||||
"@storybook/components": "8.2.6",
|
"@storybook/components": "8.3.2",
|
||||||
"@storybook/core-events": "8.2.6",
|
"@storybook/core-events": "8.3.2",
|
||||||
"@storybook/manager-api": "8.2.6",
|
"@storybook/manager-api": "8.3.2",
|
||||||
"@storybook/preview-api": "8.2.6",
|
"@storybook/preview-api": "8.3.2",
|
||||||
"@storybook/react": "8.2.6",
|
"@storybook/react": "8.3.2",
|
||||||
"@storybook/react-vite": "8.2.6",
|
"@storybook/react-vite": "8.3.2",
|
||||||
"@storybook/test": "8.2.6",
|
"@storybook/test": "8.3.2",
|
||||||
"@storybook/theming": "8.2.6",
|
"@storybook/theming": "8.3.2",
|
||||||
"@storybook/types": "8.2.6",
|
"@storybook/types": "8.3.2",
|
||||||
"@storybook/vue3": "8.2.6",
|
"@storybook/vue3": "8.3.2",
|
||||||
"@storybook/vue3-vite": "8.1.11",
|
"@storybook/vue3-vite": "8.3.2",
|
||||||
"@testing-library/vue": "8.1.0",
|
"@testing-library/vue": "8.1.0",
|
||||||
"@types/escape-regexp": "0.0.3",
|
"@types/escape-regexp": "0.0.3",
|
||||||
"@types/estree": "1.0.5",
|
"@types/estree": "1.0.6",
|
||||||
"@types/matter-js": "0.19.7",
|
"@types/matter-js": "0.19.7",
|
||||||
"@types/micromatch": "4.0.9",
|
"@types/micromatch": "4.0.9",
|
||||||
"@types/node": "20.14.12",
|
"@types/node": "20.14.12",
|
||||||
"@types/punycode": "2.1.4",
|
"@types/punycode": "2.1.4",
|
||||||
"@types/sanitize-html": "2.11.0",
|
"@types/sanitize-html": "2.13.0",
|
||||||
"@types/seedrandom": "3.0.8",
|
"@types/seedrandom": "3.0.8",
|
||||||
"@types/throttle-debounce": "5.0.2",
|
"@types/throttle-debounce": "5.0.2",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"@types/uuid": "10.0.0",
|
"@types/uuid": "10.0.0",
|
||||||
"@types/ws": "8.5.11",
|
"@types/ws": "8.5.12",
|
||||||
"@typescript-eslint/eslint-plugin": "7.17.0",
|
"@typescript-eslint/eslint-plugin": "7.17.0",
|
||||||
"@typescript-eslint/parser": "7.17.0",
|
"@typescript-eslint/parser": "7.17.0",
|
||||||
"@vitest/coverage-v8": "1.6.0",
|
"@vitest/coverage-v8": "1.6.0",
|
||||||
"@vue/runtime-core": "3.4.37",
|
"@vue/runtime-core": "3.5.7",
|
||||||
"acorn": "8.12.1",
|
"acorn": "8.12.1",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "13.13.1",
|
"cypress": "13.14.2",
|
||||||
"eslint-plugin-import": "2.29.1",
|
"eslint-plugin-import": "2.30.0",
|
||||||
"eslint-plugin-vue": "9.27.0",
|
"eslint-plugin-vue": "9.28.0",
|
||||||
"fast-glob": "3.3.2",
|
"fast-glob": "3.3.2",
|
||||||
"happy-dom": "10.0.3",
|
"happy-dom": "10.0.3",
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"micromatch": "4.0.7",
|
"micromatch": "4.0.8",
|
||||||
"msw": "2.3.4",
|
"msw": "2.4.9",
|
||||||
"msw-storybook-addon": "2.0.3",
|
"msw-storybook-addon": "2.0.3",
|
||||||
"nodemon": "3.1.4",
|
"nodemon": "3.1.7",
|
||||||
"prettier": "3.3.3",
|
"prettier": "3.3.3",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"seedrandom": "3.0.5",
|
"seedrandom": "3.0.5",
|
||||||
"start-server-and-test": "2.0.4",
|
"start-server-and-test": "2.0.8",
|
||||||
"storybook": "8.2.6",
|
"storybook": "8.3.2",
|
||||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
||||||
"vite-plugin-turbosnap": "1.0.3",
|
"vite-plugin-turbosnap": "1.0.3",
|
||||||
"vitest": "1.6.0",
|
"vitest": "1.6.0",
|
||||||
"vitest-fetch-mock": "0.2.2",
|
"vitest-fetch-mock": "0.2.2",
|
||||||
"vue-component-type-helpers": "2.0.29",
|
"vue-component-type-helpers": "2.1.6",
|
||||||
"vue-eslint-parser": "9.4.3",
|
"vue-eslint-parser": "9.4.3",
|
||||||
"vue-tsc": "2.0.29"
|
"vue-tsc": "2.1.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import * as Misskey from 'misskey-js';
|
||||||
import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js';
|
import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { MenuButton } from '@/types/menu.js';
|
import type { MenuItem, MenuButton } from '@/types/menu.js';
|
||||||
import { del, get, set } from '@/scripts/idb-proxy.js';
|
import { del, get, set } from '@/scripts/idb-proxy.js';
|
||||||
import { apiUrl } from '@@/js/config.js';
|
import { apiUrl } from '@@/js/config.js';
|
||||||
import { waiting, popup, popupMenu, success, alert } from '@/os.js';
|
import { waiting, popup, popupMenu, success, alert } from '@/os.js';
|
||||||
|
@ -288,14 +288,26 @@ export async function openAccountMenu(opts: {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const menuItems: MenuItem[] = [];
|
||||||
|
|
||||||
if (opts.withExtraOperation) {
|
if (opts.withExtraOperation) {
|
||||||
popupMenu([...[{
|
menuItems.push({
|
||||||
type: 'link' as const,
|
type: 'link',
|
||||||
text: i18n.ts.profile,
|
text: i18n.ts.profile,
|
||||||
to: `/@${ $i.username }`,
|
to: `/@${$i.username}`,
|
||||||
avatar: $i,
|
avatar: $i,
|
||||||
}, { type: 'divider' as const }, ...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises, {
|
}, {
|
||||||
type: 'parent' as const,
|
type: 'divider',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (opts.includeCurrentAccount) {
|
||||||
|
menuItems.push(createItem($i));
|
||||||
|
}
|
||||||
|
|
||||||
|
menuItems.push(...accountItemPromises);
|
||||||
|
|
||||||
|
menuItems.push({
|
||||||
|
type: 'parent',
|
||||||
icon: 'ti ti-plus',
|
icon: 'ti ti-plus',
|
||||||
text: i18n.ts.addAccount,
|
text: i18n.ts.addAccount,
|
||||||
children: [{
|
children: [{
|
||||||
|
@ -306,18 +318,22 @@ export async function openAccountMenu(opts: {
|
||||||
action: () => { createAccount(); },
|
action: () => { createAccount(); },
|
||||||
}],
|
}],
|
||||||
}, {
|
}, {
|
||||||
type: 'link' as const,
|
type: 'link',
|
||||||
icon: 'ti ti-users',
|
icon: 'ti ti-users',
|
||||||
text: i18n.ts.manageAccounts,
|
text: i18n.ts.manageAccounts,
|
||||||
to: '/settings/accounts',
|
to: '/settings/accounts',
|
||||||
}]], ev.currentTarget ?? ev.target, {
|
|
||||||
align: 'left',
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
popupMenu([...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises], ev.currentTarget ?? ev.target, {
|
if (opts.includeCurrentAccount) {
|
||||||
align: 'left',
|
menuItems.push(createItem($i));
|
||||||
});
|
}
|
||||||
|
|
||||||
|
menuItems.push(...accountItemPromises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
popupMenu(menuItems, ev.currentTarget ?? ev.target, {
|
||||||
|
align: 'left',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_DEV_) {
|
if (_DEV_) {
|
||||||
|
|
|
@ -246,7 +246,7 @@ function onMousedown(evt: MouseEvent): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
opacity: 0.7;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
|
|
|
@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, onBeforeUnmount, shallowRef, ref } from 'vue';
|
import { onMounted, onBeforeUnmount, shallowRef, ref } from 'vue';
|
||||||
import MkMenu from './MkMenu.vue';
|
import MkMenu from './MkMenu.vue';
|
||||||
import { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
import contains from '@/scripts/contains.js';
|
import contains from '@/scripts/contains.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
|
|
@ -42,7 +42,7 @@ import { i18n } from '@/i18n.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { claimAchievement } from '@/scripts/achievements.js';
|
import { claimAchievement } from '@/scripts/achievements.js';
|
||||||
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
|
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
|
||||||
import { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
folder: Misskey.entities.DriveFolder;
|
folder: Misskey.entities.DriveFolder;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue