Compare commits
61 Commits
2024.5.0-b
...
fix/invali
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d02198417b | ||
![]() |
9366515565 | ||
![]() |
623eb410bb | ||
![]() |
b233444ed6 | ||
![]() |
2b8056a852 | ||
![]() |
ecf7945fe8 | ||
![]() |
cc1ee0106f | ||
![]() |
6078081c33 | ||
![]() |
a59aa20be8 | ||
![]() |
61eec93f4e | ||
![]() |
27d1b7e615 | ||
![]() |
316d192bc0 | ||
![]() |
2eaa3e256f | ||
![]() |
46164f879b | ||
![]() |
374c8791d7 | ||
![]() |
e8f523f00a | ||
![]() |
030082f756 | ||
![]() |
dc55adbaf7 | ||
![]() |
90ba1ca1f9 | ||
![]() |
514a65e453 | ||
![]() |
a3468fd05b | ||
![]() |
97be1a53ad | ||
![]() |
1e007b63aa | ||
![]() |
a0c596b030 | ||
![]() |
eaa85f5aa3 | ||
![]() |
dfeaa1145b | ||
![]() |
0082747237 | ||
![]() |
5b8f8e7087 | ||
![]() |
be11fd7508 | ||
![]() |
ac4a001e9f | ||
![]() |
24d4124ffc | ||
![]() |
eaadd643eb | ||
![]() |
cf670e8a3d | ||
![]() |
e57ce4fa0f | ||
![]() |
44cafbb9f2 | ||
![]() |
f75e46752e | ||
![]() |
244adef70e | ||
![]() |
e2eb7e8ca9 | ||
![]() |
80f3cb96b0 | ||
![]() |
89b27d8587 | ||
![]() |
1bb1a32986 | ||
![]() |
de9e391e34 | ||
![]() |
934f9f80bd | ||
![]() |
4704dfe061 | ||
![]() |
d3b969306c | ||
![]() |
4579be0f54 | ||
![]() |
d7982e471c | ||
![]() |
a7a8dc4dbb | ||
![]() |
cf2256cf41 | ||
![]() |
28e0e20879 | ||
![]() |
e50107792c | ||
![]() |
579b88adc7 | ||
![]() |
6af9492ea5 | ||
![]() |
d013e4516d | ||
![]() |
805a11aadb | ||
![]() |
1b81ca4563 | ||
![]() |
1df8ea824e | ||
![]() |
3ffbf6296f | ||
![]() |
e0b47999fa | ||
![]() |
83a9aa4533 | ||
![]() |
611e303bab |
@@ -136,6 +136,21 @@ redis:
|
|||||||
|
|
||||||
id: 'aidx'
|
id: 'aidx'
|
||||||
|
|
||||||
|
# ┌────────────────┐
|
||||||
|
#───┘ Error tracking └──────────────────────────────────────────
|
||||||
|
|
||||||
|
# Sentry is available for error tracking.
|
||||||
|
# See the Sentry documentation for more details on options.
|
||||||
|
|
||||||
|
#sentryForBackend:
|
||||||
|
# enableNodeProfiling: true
|
||||||
|
# options:
|
||||||
|
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
||||||
|
|
||||||
|
#sentryForFrontend:
|
||||||
|
# options:
|
||||||
|
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
||||||
|
|
||||||
# ┌─────────────────────┐
|
# ┌─────────────────────┐
|
||||||
#───┘ Other configuration └─────────────────────────────────────
|
#───┘ Other configuration └─────────────────────────────────────
|
||||||
|
|
||||||
|
@@ -205,6 +205,21 @@ redis:
|
|||||||
|
|
||||||
id: 'aidx'
|
id: 'aidx'
|
||||||
|
|
||||||
|
# ┌────────────────┐
|
||||||
|
#───┘ Error tracking └──────────────────────────────────────────
|
||||||
|
|
||||||
|
# Sentry is available for error tracking.
|
||||||
|
# See the Sentry documentation for more details on options.
|
||||||
|
|
||||||
|
#sentryForBackend:
|
||||||
|
# enableNodeProfiling: true
|
||||||
|
# options:
|
||||||
|
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
||||||
|
|
||||||
|
#sentryForFrontend:
|
||||||
|
# options:
|
||||||
|
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
||||||
|
|
||||||
# ┌─────────────────────┐
|
# ┌─────────────────────┐
|
||||||
#───┘ Other configuration └─────────────────────────────────────
|
#───┘ Other configuration └─────────────────────────────────────
|
||||||
|
|
||||||
|
@@ -132,6 +132,21 @@ redis:
|
|||||||
|
|
||||||
id: 'aidx'
|
id: 'aidx'
|
||||||
|
|
||||||
|
# ┌────────────────┐
|
||||||
|
#───┘ Error tracking └──────────────────────────────────────────
|
||||||
|
|
||||||
|
# Sentry is available for error tracking.
|
||||||
|
# See the Sentry documentation for more details on options.
|
||||||
|
|
||||||
|
#sentryForBackend:
|
||||||
|
# enableNodeProfiling: true
|
||||||
|
# options:
|
||||||
|
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
||||||
|
|
||||||
|
#sentryForFrontend:
|
||||||
|
# options:
|
||||||
|
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
||||||
|
|
||||||
# ┌─────────────────────┐
|
# ┌─────────────────────┐
|
||||||
#───┘ Other configuration └─────────────────────────────────────
|
#───┘ Other configuration └─────────────────────────────────────
|
||||||
|
|
||||||
|
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
@@ -1,4 +0,0 @@
|
|||||||
# These are supported funding model platforms
|
|
||||||
|
|
||||||
github: [misskey-dev]
|
|
||||||
patreon: syuilo
|
|
7
.github/workflows/release-edit-with-push.yml
vendored
7
.github/workflows/release-edit-with-push.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
# headがrelease/かつopenのPRを1つ取得
|
# headがrelease/かつopenのPRを1つ取得
|
||||||
- name: Get PR
|
- name: Get PR
|
||||||
run: |
|
run: |
|
||||||
echo "pr_number=$(gh pr list --limit 1 --head "${{ github.ref_name }}" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT
|
echo "pr_number=$(gh pr list --limit 1 --head "$GITHUB_REF_NAME" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT
|
||||||
id: get_pr
|
id: get_pr
|
||||||
- name: Get target version
|
- name: Get target version
|
||||||
uses: misskey-dev/release-manager-actions/.github/actions/get-target-version@v1
|
uses: misskey-dev/release-manager-actions/.github/actions/get-target-version@v1
|
||||||
@@ -37,4 +37,7 @@ jobs:
|
|||||||
# PRのnotesを更新
|
# PRのnotesを更新
|
||||||
- name: Update PR
|
- name: Update PR
|
||||||
run: |
|
run: |
|
||||||
gh pr edit ${{ steps.get_pr.outputs.pr_number }} --body "${{ steps.changelog.outputs.changelog }}"
|
gh pr edit "$PR_NUMBER" --body "$CHANGELOG"
|
||||||
|
env:
|
||||||
|
CHANGELOG: ${{ steps.changelog.outputs.changelog }}
|
||||||
|
PR_NUMBER: ${{ steps.get_pr.outputs.pr_number }}
|
||||||
|
4
.github/workflows/release-with-ready.yml
vendored
4
.github/workflows/release-with-ready.yml
vendored
@@ -22,9 +22,11 @@ jobs:
|
|||||||
# PR情報を取得
|
# PR情報を取得
|
||||||
- name: Get PR
|
- name: Get PR
|
||||||
run: |
|
run: |
|
||||||
pr_json=$(gh pr view ${{ github.event.pull_request.number }} --json isDraft,headRefName)
|
pr_json=$(gh pr view "$PR_NUMBER" --json isDraft,headRefName)
|
||||||
echo "ref=$(echo $pr_json | jq -r '.headRefName')" >> $GITHUB_OUTPUT
|
echo "ref=$(echo $pr_json | jq -r '.headRefName')" >> $GITHUB_OUTPUT
|
||||||
id: get_pr
|
id: get_pr
|
||||||
|
env:
|
||||||
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
release:
|
release:
|
||||||
uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v1
|
uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v1
|
||||||
needs: check
|
needs: check
|
||||||
|
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,10 +1,24 @@
|
|||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### General
|
||||||
|
-
|
||||||
|
|
||||||
|
### Client
|
||||||
|
-
|
||||||
|
|
||||||
|
### Server
|
||||||
|
-
|
||||||
|
|
||||||
|
|
||||||
## 2024.5.0
|
## 2024.5.0
|
||||||
|
|
||||||
### Note
|
### Note
|
||||||
- コントロールパネル内にあるサマリープロキシの設定個所がセキュリティから全般へ変更となります。
|
- コントロールパネル内にあるサマリープロキシの設定個所がセキュリティから全般へ変更となります。
|
||||||
- 悪意のある第三者がリモートユーザーになりすましたアクティビティを受け取れてしまう問題を修正しました。詳しくは[GitHub security advisory](https://github.com/misskey-dev/misskey/security/advisories/GHSA-2vxv-pv3m-3wvj)をご覧ください。
|
- 悪意のある第三者がリモートユーザーになりすましたアクティビティを受け取れてしまう問題を修正しました。詳しくは[GitHub security advisory](https://github.com/misskey-dev/misskey/security/advisories/GHSA-2vxv-pv3m-3wvj)をご覧ください。
|
||||||
|
- 管理者向け権限 `read:admin:show-users` は `read:admin:show-user` に統合されました。必要に応じてAPIトークンを再発行してください。
|
||||||
|
|
||||||
### General
|
### General
|
||||||
|
- Feat: エラートラッキングにSentryを使用できるようになりました
|
||||||
- Enhance: URLプレビューの有効化・無効化を設定できるように #13569
|
- Enhance: URLプレビューの有効化・無効化を設定できるように #13569
|
||||||
- Enhance: アンテナでBotによるノートを除外できるように
|
- Enhance: アンテナでBotによるノートを除外できるように
|
||||||
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/545)
|
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/545)
|
||||||
@@ -15,12 +29,18 @@
|
|||||||
- サスペンド済みユーザーか
|
- サスペンド済みユーザーか
|
||||||
- 鍵アカウントユーザーか
|
- 鍵アカウントユーザーか
|
||||||
- 「アカウントを見つけやすくする」が有効なユーザーか
|
- 「アカウントを見つけやすくする」が有効なユーザーか
|
||||||
|
- Enhance: Goneを出さずに終了したサーバーへの配信停止を自動的に行うように
|
||||||
|
- もしそのようなサーバーからから配信が届いた場合には自動的に配信を再開します
|
||||||
|
- Enhance: 配信停止の理由を表示するように
|
||||||
|
- Enhance: サーバーのお問い合わせ先URLを設定できるようになりました
|
||||||
- Fix: Play作成時に設定した公開範囲が機能していない問題を修正
|
- Fix: Play作成時に設定した公開範囲が機能していない問題を修正
|
||||||
- Fix: 正規化されていない状態のhashtagが連合されてきたhtmlに含まれているとhashtagが正しくhashtagに復元されない問題を修正
|
- Fix: 正規化されていない状態のhashtagが連合されてきたhtmlに含まれているとhashtagが正しくhashtagに復元されない問題を修正
|
||||||
- Fix: みつけるのアンケート欄にてチャンネルのアンケートが含まれてしまう問題を修正
|
- Fix: みつけるのアンケート欄にてチャンネルのアンケートが含まれてしまう問題を修正
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Feat: アップロードするファイルの名前をランダム文字列にできるように
|
- Feat: アップロードするファイルの名前をランダム文字列にできるように
|
||||||
|
- Feat: 個別のお知らせにリンクで飛べるように
|
||||||
|
(Based on https://github.com/MisskeyIO/misskey/pull/639)
|
||||||
- Enhance: 自分のノートの添付ファイルから直接ファイルの詳細ページに飛べるように
|
- Enhance: 自分のノートの添付ファイルから直接ファイルの詳細ページに飛べるように
|
||||||
- Enhance: 広告がMisskeyと同一ドメインの場合はRouterで遷移するように
|
- Enhance: 広告がMisskeyと同一ドメインの場合はRouterで遷移するように
|
||||||
- Enhance: リアクション・いいねの総数を表示するように
|
- Enhance: リアクション・いいねの総数を表示するように
|
||||||
@@ -43,6 +63,9 @@
|
|||||||
- Enhance: AiScriptを0.18.0にバージョンアップ
|
- Enhance: AiScriptを0.18.0にバージョンアップ
|
||||||
- Enhance: 通常のノートでも、お気に入りに登録したチャンネルにリノートできるように
|
- Enhance: 通常のノートでも、お気に入りに登録したチャンネルにリノートできるように
|
||||||
- Enhance: 長いテキストをペーストした際にテキストファイルとして添付するかどうかを選択できるように
|
- Enhance: 長いテキストをペーストした際にテキストファイルとして添付するかどうかを選択できるように
|
||||||
|
- Enhance: 新着ノートをサウンドで通知する機能をdeck UIに追加しました
|
||||||
|
- Enhance: コントロールパネルのクイックアクションからファイルを照会できるように
|
||||||
|
- Enhance: コントロールパネルのクイックアクションから通常の照会を行えるように
|
||||||
- Fix: 一部のページ内リンクが正しく動作しない問題を修正
|
- Fix: 一部のページ内リンクが正しく動作しない問題を修正
|
||||||
- Fix: 周年の実績が閏年を考慮しない問題を修正
|
- Fix: 周年の実績が閏年を考慮しない問題を修正
|
||||||
- Fix: ローカルURLのプレビューポップアップが左上に表示される
|
- Fix: ローカルURLのプレビューポップアップが左上に表示される
|
||||||
@@ -63,6 +86,7 @@
|
|||||||
- Fix: 通知をグループ化している際に、人数が正常に表示されないことがある問題を修正
|
- Fix: 通知をグループ化している際に、人数が正常に表示されないことがある問題を修正
|
||||||
- Fix: 連合なしの状態の読み書きができない問題を修正
|
- Fix: 連合なしの状態の読み書きができない問題を修正
|
||||||
- Fix: `/share` で日本語等を含むurlがurlエンコードされない問題を修正
|
- Fix: `/share` で日本語等を含むurlがurlエンコードされない問題を修正
|
||||||
|
- Fix: ファイルを5つ以上添付してもテキストがないとノートが折りたたまれない問題を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Enhance: エンドポイント`antennas/update`の必須項目を`antennaId`のみに
|
- Enhance: エンドポイント`antennas/update`の必須項目を`antennaId`のみに
|
||||||
@@ -88,6 +112,8 @@
|
|||||||
- Fix: `/i/notifications`に `includeTypes`か`excludeTypes`を指定しているとき、通知が存在するのに空配列を返すことがある問題を修正
|
- Fix: `/i/notifications`に `includeTypes`か`excludeTypes`を指定しているとき、通知が存在するのに空配列を返すことがある問題を修正
|
||||||
- Fix: 複数idを指定する`users/show`が関係ないユーザを返すことがある問題を修正
|
- Fix: 複数idを指定する`users/show`が関係ないユーザを返すことがある問題を修正
|
||||||
- Fix: `/tags` と `/user-tags` が検索エンジンにインデックスされないように
|
- Fix: `/tags` と `/user-tags` が検索エンジンにインデックスされないように
|
||||||
|
- Fix: もともとセンシティブではないと連合されていたファイルがセンシティブとして連合された場合にセンシティブとしてそのファイルを扱うように
|
||||||
|
- センシティブとして連合したファイルは非センシティブとして連合されてもセンシティブとして扱われます
|
||||||
|
|
||||||
## 2024.3.1
|
## 2024.3.1
|
||||||
|
|
||||||
|
@@ -28,6 +28,10 @@
|
|||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
||||||
|
<a href="https://sentry.io/"><img src="https://github.com/misskey-dev/misskey/assets/4439005/98576556-222f-467a-94be-e98dbda1d852" height="30" alt="Sentry" /></a>
|
||||||
|
|
||||||
|
Thanks to [Sentry](https://sentry.io/) for providing the error tracking platform that helps us catch unexpected errors.
|
||||||
|
|
||||||
<a href="https://www.chromatic.com/"><img src="https://user-images.githubusercontent.com/321738/84662277-e3db4f80-af1b-11ea-88f5-91d67a5e59f6.png" height="30" alt="Chromatic" /></a>
|
<a href="https://www.chromatic.com/"><img src="https://user-images.githubusercontent.com/321738/84662277-e3db4f80-af1b-11ea-88f5-91d67a5e59f6.png" height="30" alt="Chromatic" /></a>
|
||||||
|
|
||||||
Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions.
|
Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions.
|
||||||
|
@@ -152,6 +152,22 @@ redis:
|
|||||||
# ID SETTINGS AFTER THAT!
|
# ID SETTINGS AFTER THAT!
|
||||||
|
|
||||||
id: "aidx"
|
id: "aidx"
|
||||||
|
|
||||||
|
# ┌────────────────┐
|
||||||
|
#───┘ Error tracking └──────────────────────────────────────────
|
||||||
|
|
||||||
|
# Sentry is available for error tracking.
|
||||||
|
# See the Sentry documentation for more details on options.
|
||||||
|
|
||||||
|
#sentryForBackend:
|
||||||
|
# enableNodeProfiling: true
|
||||||
|
# options:
|
||||||
|
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
||||||
|
|
||||||
|
#sentryForFrontend:
|
||||||
|
# options:
|
||||||
|
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
|
||||||
|
|
||||||
# ┌─────────────────────┐
|
# ┌─────────────────────┐
|
||||||
#───┘ Other configuration └─────────────────────────────────────
|
#───┘ Other configuration └─────────────────────────────────────
|
||||||
|
|
||||||
|
@@ -4,4 +4,4 @@
|
|||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
PORT=$(grep '^port:' /misskey/.config/default.yml | awk 'NR==1{print $2; exit}')
|
PORT=$(grep '^port:' /misskey/.config/default.yml | awk 'NR==1{print $2; exit}')
|
||||||
curl -s -S -o /dev/null "http://localhost:${PORT}"
|
curl -Sfso/dev/null "http://localhost:${PORT}/healthz"
|
||||||
|
@@ -1016,6 +1016,8 @@ sourceCode: "الشفرة المصدرية"
|
|||||||
flip: "اقلب"
|
flip: "اقلب"
|
||||||
lastNDays: "آخر {n} أيام"
|
lastNDays: "آخر {n} أيام"
|
||||||
surrender: "ألغِ"
|
surrender: "ألغِ"
|
||||||
|
_delivery:
|
||||||
|
stop: "مُعلّق"
|
||||||
_initialAccountSetting:
|
_initialAccountSetting:
|
||||||
accountCreated: "نجح إنشاء حسابك!"
|
accountCreated: "نجح إنشاء حسابك!"
|
||||||
letsStartAccountSetup: "إذا كنت جديدًا لنعدّ حسابك الشخصي."
|
letsStartAccountSetup: "إذا كنت جديدًا لنعدّ حسابك الشخصي."
|
||||||
|
@@ -857,6 +857,10 @@ replies: "জবাব"
|
|||||||
renotes: "রিনোট"
|
renotes: "রিনোট"
|
||||||
sourceCode: "সোর্স কোড"
|
sourceCode: "সোর্স কোড"
|
||||||
flip: "উল্টান"
|
flip: "উল্টান"
|
||||||
|
_delivery:
|
||||||
|
stop: "স্থগিত করা হয়েছে"
|
||||||
|
_type:
|
||||||
|
none: "প্রকাশ করা হচ্ছে"
|
||||||
_role:
|
_role:
|
||||||
priority: "অগ্রাধিকার"
|
priority: "অগ্রাধিকার"
|
||||||
_priority:
|
_priority:
|
||||||
|
@@ -1224,6 +1224,10 @@ gameRetry: "Torna a provar"
|
|||||||
notUsePleaseLeaveBlank: "Si no voleu usar-ho, deixeu-ho en blanc"
|
notUsePleaseLeaveBlank: "Si no voleu usar-ho, deixeu-ho en blanc"
|
||||||
useTotp: "Usa una contrasenya d'un sol ús"
|
useTotp: "Usa una contrasenya d'un sol ús"
|
||||||
useBackupCode: "Usa un codi de recuperació"
|
useBackupCode: "Usa un codi de recuperació"
|
||||||
|
_delivery:
|
||||||
|
stop: "Suspés"
|
||||||
|
_type:
|
||||||
|
none: "S'està publicant"
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "Com es juga"
|
howToPlay: "Com es juga"
|
||||||
_howToPlay:
|
_howToPlay:
|
||||||
@@ -2001,7 +2005,6 @@ _permissions:
|
|||||||
"read:admin:server-info": "Veure informació del servidor"
|
"read:admin:server-info": "Veure informació del servidor"
|
||||||
"read:admin:show-moderation-log": "Veure registre de moderació "
|
"read:admin:show-moderation-log": "Veure registre de moderació "
|
||||||
"read:admin:show-user": "Veure informació privada de l'usuari "
|
"read:admin:show-user": "Veure informació privada de l'usuari "
|
||||||
"read:admin:show-users": "Veure informació privada de l'usuari "
|
|
||||||
"write:admin:suspend-user": "Suspendre usuari"
|
"write:admin:suspend-user": "Suspendre usuari"
|
||||||
"write:admin:unset-user-avatar": "Esborrar avatar d'usuari "
|
"write:admin:unset-user-avatar": "Esborrar avatar d'usuari "
|
||||||
"write:admin:unset-user-banner": "Esborrar bàner de l'usuari "
|
"write:admin:unset-user-banner": "Esborrar bàner de l'usuari "
|
||||||
|
@@ -1099,6 +1099,10 @@ sourceCode: "Zdrojový kód"
|
|||||||
flip: "Otočit"
|
flip: "Otočit"
|
||||||
lastNDays: "Posledních {n} dnů"
|
lastNDays: "Posledních {n} dnů"
|
||||||
surrender: "Zrušit"
|
surrender: "Zrušit"
|
||||||
|
_delivery:
|
||||||
|
stop: "Suspendováno"
|
||||||
|
_type:
|
||||||
|
none: "Publikuji"
|
||||||
_initialAccountSetting:
|
_initialAccountSetting:
|
||||||
accountCreated: "Váš účet byl úspěšně vytvořen!"
|
accountCreated: "Váš účet byl úspěšně vytvořen!"
|
||||||
letsStartAccountSetup: "Pro začátek si nastavte svůj profil."
|
letsStartAccountSetup: "Pro začátek si nastavte svůj profil."
|
||||||
|
@@ -1,2 +1,4 @@
|
|||||||
---
|
---
|
||||||
_lang_: "Dansk"
|
_lang_: "Dansk"
|
||||||
|
headlineMisskey: ""
|
||||||
|
introMisskey: "ようこそ!Misskeyは、オープンソースの分散型マイクロブログサービスです。\n「ノート」を作成して、いま起こっていることを共有したり、あなたについて皆に発信しよう📡\n「リアクション」機能で、皆のノートに素早く反応を追加することもできます👍\n新しい世界を探検しよう🚀"
|
||||||
|
@@ -1185,6 +1185,10 @@ addMfmFunction: "MFM hinzufügen"
|
|||||||
sfx: "Soundeffekte"
|
sfx: "Soundeffekte"
|
||||||
lastNDays: "Letzten {n} Tage"
|
lastNDays: "Letzten {n} Tage"
|
||||||
surrender: "Abbrechen"
|
surrender: "Abbrechen"
|
||||||
|
_delivery:
|
||||||
|
stop: "Gesperrt"
|
||||||
|
_type:
|
||||||
|
none: "Wird veröffentlicht"
|
||||||
_announcement:
|
_announcement:
|
||||||
forExistingUsers: "Nur für existierende Nutzer"
|
forExistingUsers: "Nur für existierende Nutzer"
|
||||||
forExistingUsersDescription: "Ist diese Option aktiviert, wird diese Ankündigung nur Nutzern angezeigt, die zum Zeitpunkt der Ankündigung bereits registriert sind. Ist sie deaktiviert, wird sie auch Nutzern, die sich nach dessen Veröffentlichung registrieren, angezeigt."
|
forExistingUsersDescription: "Ist diese Option aktiviert, wird diese Ankündigung nur Nutzern angezeigt, die zum Zeitpunkt der Ankündigung bereits registriert sind. Ist sie deaktiviert, wird sie auch Nutzern, die sich nach dessen Veröffentlichung registrieren, angezeigt."
|
||||||
|
@@ -108,11 +108,14 @@ enterEmoji: "Enter an emoji"
|
|||||||
renote: "Renote"
|
renote: "Renote"
|
||||||
unrenote: "Remove renote"
|
unrenote: "Remove renote"
|
||||||
renoted: "Renoted."
|
renoted: "Renoted."
|
||||||
|
renotedToX: "Renote from {name} users。"
|
||||||
cantRenote: "This post can't be renoted."
|
cantRenote: "This post can't be renoted."
|
||||||
cantReRenote: "A renote can't be renoted."
|
cantReRenote: "A renote can't be renoted."
|
||||||
quote: "Quote"
|
quote: "Quote"
|
||||||
inChannelRenote: "Channel-only Renote"
|
inChannelRenote: "Channel-only Renote"
|
||||||
inChannelQuote: "Channel-only Quote"
|
inChannelQuote: "Channel-only Quote"
|
||||||
|
renoteToChannel: "Renote to channel"
|
||||||
|
renoteToOtherChannel: "Renote to other channel"
|
||||||
pinnedNote: "Pinned note"
|
pinnedNote: "Pinned note"
|
||||||
pinned: "Pin to profile"
|
pinned: "Pin to profile"
|
||||||
you: "You"
|
you: "You"
|
||||||
@@ -468,6 +471,7 @@ retype: "Enter again"
|
|||||||
noteOf: "Note by {user}"
|
noteOf: "Note by {user}"
|
||||||
quoteAttached: "Quote"
|
quoteAttached: "Quote"
|
||||||
quoteQuestion: "Append as quote?"
|
quoteQuestion: "Append as quote?"
|
||||||
|
attachAsFileQuestion: "The text in clipboard is long. Would you want to attach it as text file?"
|
||||||
noMessagesYet: "No messages yet"
|
noMessagesYet: "No messages yet"
|
||||||
newMessageExists: "There are new messages"
|
newMessageExists: "There are new messages"
|
||||||
onlyOneFileCanBeAttached: "You can only attach one file to a message"
|
onlyOneFileCanBeAttached: "You can only attach one file to a message"
|
||||||
@@ -1235,6 +1239,15 @@ keepOriginalFilenameDescription: "If you turn off this setting, files names will
|
|||||||
noDescription: "There is not the explanation"
|
noDescription: "There is not the explanation"
|
||||||
alwaysConfirmFollow: "Always confirm when following"
|
alwaysConfirmFollow: "Always confirm when following"
|
||||||
inquiry: "Contact"
|
inquiry: "Contact"
|
||||||
|
_delivery:
|
||||||
|
status: "Delivery status"
|
||||||
|
stop: "Suspended"
|
||||||
|
resume: "Delivery resume"
|
||||||
|
_type:
|
||||||
|
none: "Publishing"
|
||||||
|
manuallySuspended: "Manually suspended"
|
||||||
|
goneSuspended: "Server is suspended due to server deletion"
|
||||||
|
autoSuspendedForNotResponding: "Server is suspended due to no responding"
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "How to play"
|
howToPlay: "How to play"
|
||||||
hold: "Hold"
|
hold: "Hold"
|
||||||
@@ -2032,7 +2045,6 @@ _permissions:
|
|||||||
"read:admin:server-info": "View server info"
|
"read:admin:server-info": "View server info"
|
||||||
"read:admin:show-moderation-log": "View moderation log"
|
"read:admin:show-moderation-log": "View moderation log"
|
||||||
"read:admin:show-user": "View private user info"
|
"read:admin:show-user": "View private user info"
|
||||||
"read:admin:show-users": "View private user info"
|
|
||||||
"write:admin:suspend-user": "Suspend user"
|
"write:admin:suspend-user": "Suspend user"
|
||||||
"write:admin:unset-user-avatar": "Remove user avatar"
|
"write:admin:unset-user-avatar": "Remove user avatar"
|
||||||
"write:admin:unset-user-banner": "Remove user banner"
|
"write:admin:unset-user-banner": "Remove user banner"
|
||||||
|
@@ -1233,6 +1233,10 @@ useNativeUIForVideoAudioPlayer: "Usar la interfaz del navegador cuando se reprod
|
|||||||
keepOriginalFilename: "Mantener el nombre original del archivo"
|
keepOriginalFilename: "Mantener el nombre original del archivo"
|
||||||
noDescription: "No hay descripción"
|
noDescription: "No hay descripción"
|
||||||
alwaysConfirmFollow: "Confirmar siempre cuando se sigue a alguien"
|
alwaysConfirmFollow: "Confirmar siempre cuando se sigue a alguien"
|
||||||
|
_delivery:
|
||||||
|
stop: "Suspendido"
|
||||||
|
_type:
|
||||||
|
none: "Publicando"
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "Cómo jugar"
|
howToPlay: "Cómo jugar"
|
||||||
hold: "Mantener"
|
hold: "Mantener"
|
||||||
@@ -2029,7 +2033,6 @@ _permissions:
|
|||||||
"read:admin:server-info": "Ver información del servidor"
|
"read:admin:server-info": "Ver información del servidor"
|
||||||
"read:admin:show-moderation-log": "Ver log de moderación"
|
"read:admin:show-moderation-log": "Ver log de moderación"
|
||||||
"read:admin:show-user": "Ver información privada de usuario"
|
"read:admin:show-user": "Ver información privada de usuario"
|
||||||
"read:admin:show-users": "Ver información privada de usuario"
|
|
||||||
"write:admin:suspend-user": "Suspender cuentas de usuario"
|
"write:admin:suspend-user": "Suspender cuentas de usuario"
|
||||||
"write:admin:unset-user-avatar": "Quitar avatares de usuario"
|
"write:admin:unset-user-avatar": "Quitar avatares de usuario"
|
||||||
"write:admin:unset-user-banner": "Quitar banner de usuarios"
|
"write:admin:unset-user-banner": "Quitar banner de usuarios"
|
||||||
|
@@ -1224,6 +1224,10 @@ enableHorizontalSwipe: "Glisser pour changer d'onglet"
|
|||||||
loading: "Chargement en cours"
|
loading: "Chargement en cours"
|
||||||
surrender: "Annuler"
|
surrender: "Annuler"
|
||||||
gameRetry: "Réessayer"
|
gameRetry: "Réessayer"
|
||||||
|
_delivery:
|
||||||
|
stop: "Suspendu·e"
|
||||||
|
_type:
|
||||||
|
none: "Publié"
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "Comment jouer"
|
howToPlay: "Comment jouer"
|
||||||
hold: "Réserver"
|
hold: "Réserver"
|
||||||
|
@@ -108,11 +108,14 @@ enterEmoji: "Masukkan emoji"
|
|||||||
renote: "Renote"
|
renote: "Renote"
|
||||||
unrenote: "Hapus renote"
|
unrenote: "Hapus renote"
|
||||||
renoted: "Telah direnote"
|
renoted: "Telah direnote"
|
||||||
|
renotedToX: "{name} telah merenote"
|
||||||
cantRenote: "Postingan ini tidak dapat direnote"
|
cantRenote: "Postingan ini tidak dapat direnote"
|
||||||
cantReRenote: "Renote tidak dapat direnote"
|
cantReRenote: "Renote tidak dapat direnote"
|
||||||
quote: "Kutip"
|
quote: "Kutip"
|
||||||
inChannelRenote: "Hanya renote dalam kanal"
|
inChannelRenote: "Hanya renote dalam kanal"
|
||||||
inChannelQuote: "Hanya kutip dalam kanal"
|
inChannelQuote: "Hanya kutip dalam kanal"
|
||||||
|
renoteToChannel: "Renote ke kanal"
|
||||||
|
renoteToOtherChannel: "Renote ke kanal lainnya"
|
||||||
pinnedNote: "Catatan yang disematkan"
|
pinnedNote: "Catatan yang disematkan"
|
||||||
pinned: "Sematkan ke profil"
|
pinned: "Sematkan ke profil"
|
||||||
you: "Kamu"
|
you: "Kamu"
|
||||||
@@ -468,6 +471,7 @@ retype: "Masukkan ulang"
|
|||||||
noteOf: "Catatan milik {user}"
|
noteOf: "Catatan milik {user}"
|
||||||
quoteAttached: "Dikutip"
|
quoteAttached: "Dikutip"
|
||||||
quoteQuestion: "Apakah kamu ingin menambahkan kutipan?"
|
quoteQuestion: "Apakah kamu ingin menambahkan kutipan?"
|
||||||
|
attachAsFileQuestion: "Teks dalam papan klip terlalu panjang. Apakah kamu ingin melampirkannya sebagai berkas teks?"
|
||||||
noMessagesYet: "Tidak ada pesan"
|
noMessagesYet: "Tidak ada pesan"
|
||||||
newMessageExists: "Kamu mendapatkan pesan baru"
|
newMessageExists: "Kamu mendapatkan pesan baru"
|
||||||
onlyOneFileCanBeAttached: "Kamu hanya dapat melampirkan satu berkas ke dalam pesan"
|
onlyOneFileCanBeAttached: "Kamu hanya dapat melampirkan satu berkas ke dalam pesan"
|
||||||
@@ -1235,6 +1239,15 @@ keepOriginalFilenameDescription: "Apabila pengaturan ini dimatikan, nama berkas
|
|||||||
noDescription: "Tidak ada deskripsi"
|
noDescription: "Tidak ada deskripsi"
|
||||||
alwaysConfirmFollow: "Selalu konfirmasi ketika mengikuti"
|
alwaysConfirmFollow: "Selalu konfirmasi ketika mengikuti"
|
||||||
inquiry: "Hubungi kami"
|
inquiry: "Hubungi kami"
|
||||||
|
_delivery:
|
||||||
|
status: "Status pengiriman"
|
||||||
|
stop: "Ditangguhkan"
|
||||||
|
resume: "Lanjutkan pengiriman"
|
||||||
|
_type:
|
||||||
|
none: "Sedang menyiarkan langsung"
|
||||||
|
manuallySuspended: "Ditangguhkan manual"
|
||||||
|
goneSuspended: "Sedang ditangguhkan untuk penghapusan peladen"
|
||||||
|
autoSuspendedForNotResponding: "Sedang ditangguhkan karena peladen tidak menjawab"
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "Cara bermain"
|
howToPlay: "Cara bermain"
|
||||||
hold: "Tahan"
|
hold: "Tahan"
|
||||||
@@ -2032,7 +2045,6 @@ _permissions:
|
|||||||
"read:admin:server-info": "Lihat informasi peladen"
|
"read:admin:server-info": "Lihat informasi peladen"
|
||||||
"read:admin:show-moderation-log": "Lihat log moderasi"
|
"read:admin:show-moderation-log": "Lihat log moderasi"
|
||||||
"read:admin:show-user": "Lihat informasi pengguna privat"
|
"read:admin:show-user": "Lihat informasi pengguna privat"
|
||||||
"read:admin:show-users": "Lihat informasi pengguna privat"
|
|
||||||
"write:admin:suspend-user": "Tangguhkan pengguna"
|
"write:admin:suspend-user": "Tangguhkan pengguna"
|
||||||
"write:admin:unset-user-avatar": "Hapus avatar pengguna"
|
"write:admin:unset-user-avatar": "Hapus avatar pengguna"
|
||||||
"write:admin:unset-user-banner": "Hapus banner pengguna"
|
"write:admin:unset-user-banner": "Hapus banner pengguna"
|
||||||
|
62
locales/index.d.ts
vendored
62
locales/index.d.ts
vendored
@@ -1280,6 +1280,10 @@ export interface Locale extends ILocale {
|
|||||||
* フォルダーを選択
|
* フォルダーを選択
|
||||||
*/
|
*/
|
||||||
"selectFolders": string;
|
"selectFolders": string;
|
||||||
|
/**
|
||||||
|
* ファイルが選択されていません
|
||||||
|
*/
|
||||||
|
"fileNotSelected": string;
|
||||||
/**
|
/**
|
||||||
* ファイル名を変更
|
* ファイル名を変更
|
||||||
*/
|
*/
|
||||||
@@ -3360,6 +3364,10 @@ export interface Locale extends ILocale {
|
|||||||
* 管理者情報が設定されていません。
|
* 管理者情報が設定されていません。
|
||||||
*/
|
*/
|
||||||
"noMaintainerInformationWarning": string;
|
"noMaintainerInformationWarning": string;
|
||||||
|
/**
|
||||||
|
* 問い合わせ先URLが設定されていません。
|
||||||
|
*/
|
||||||
|
"noInquiryUrlWarning": string;
|
||||||
/**
|
/**
|
||||||
* Botプロテクションが設定されていません。
|
* Botプロテクションが設定されていません。
|
||||||
*/
|
*/
|
||||||
@@ -4113,9 +4121,13 @@ export interface Locale extends ILocale {
|
|||||||
*/
|
*/
|
||||||
"thisPostMayBeAnnoyingIgnore": string;
|
"thisPostMayBeAnnoyingIgnore": string;
|
||||||
/**
|
/**
|
||||||
* 見たことのあるリノートを省略して表示
|
* リノートのスマート省略
|
||||||
*/
|
*/
|
||||||
"collapseRenotes": string;
|
"collapseRenotes": string;
|
||||||
|
/**
|
||||||
|
* リアクションやリノートをしたことがあるノートをたたんで表示します。
|
||||||
|
*/
|
||||||
|
"collapseRenotesDescription": string;
|
||||||
/**
|
/**
|
||||||
* サーバー内部エラー
|
* サーバー内部エラー
|
||||||
*/
|
*/
|
||||||
@@ -4972,6 +4984,38 @@ export interface Locale extends ILocale {
|
|||||||
* お問い合わせ
|
* お問い合わせ
|
||||||
*/
|
*/
|
||||||
"inquiry": string;
|
"inquiry": string;
|
||||||
|
"_delivery": {
|
||||||
|
/**
|
||||||
|
* 配信状態
|
||||||
|
*/
|
||||||
|
"status": string;
|
||||||
|
/**
|
||||||
|
* 配信停止
|
||||||
|
*/
|
||||||
|
"stop": string;
|
||||||
|
/**
|
||||||
|
* 配信再開
|
||||||
|
*/
|
||||||
|
"resume": string;
|
||||||
|
"_type": {
|
||||||
|
/**
|
||||||
|
* 配信中
|
||||||
|
*/
|
||||||
|
"none": string;
|
||||||
|
/**
|
||||||
|
* 手動停止中
|
||||||
|
*/
|
||||||
|
"manuallySuspended": string;
|
||||||
|
/**
|
||||||
|
* サーバー削除のため停止中
|
||||||
|
*/
|
||||||
|
"goneSuspended": string;
|
||||||
|
/**
|
||||||
|
* サーバー応答なしのため停止中
|
||||||
|
*/
|
||||||
|
"autoSuspendedForNotResponding": string;
|
||||||
|
};
|
||||||
|
};
|
||||||
"_bubbleGame": {
|
"_bubbleGame": {
|
||||||
/**
|
/**
|
||||||
* 遊び方
|
* 遊び方
|
||||||
@@ -5431,6 +5475,14 @@ export interface Locale extends ILocale {
|
|||||||
* 有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。
|
* 有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。
|
||||||
*/
|
*/
|
||||||
"fanoutTimelineDbFallbackDescription": string;
|
"fanoutTimelineDbFallbackDescription": string;
|
||||||
|
/**
|
||||||
|
* 問い合わせ先URL
|
||||||
|
*/
|
||||||
|
"inquiryUrl": string;
|
||||||
|
/**
|
||||||
|
* サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定します。
|
||||||
|
*/
|
||||||
|
"inquiryUrlDescription": string;
|
||||||
};
|
};
|
||||||
"_accountMigration": {
|
"_accountMigration": {
|
||||||
/**
|
/**
|
||||||
@@ -7903,10 +7955,6 @@ export interface Locale extends ILocale {
|
|||||||
* ユーザーのプライベートな情報を見る
|
* ユーザーのプライベートな情報を見る
|
||||||
*/
|
*/
|
||||||
"read:admin:show-user": string;
|
"read:admin:show-user": string;
|
||||||
/**
|
|
||||||
* ユーザーのプライベートな情報を見る
|
|
||||||
*/
|
|
||||||
"read:admin:show-users": string;
|
|
||||||
/**
|
/**
|
||||||
* ユーザーを凍結する
|
* ユーザーを凍結する
|
||||||
*/
|
*/
|
||||||
@@ -9111,6 +9159,10 @@ export interface Locale extends ILocale {
|
|||||||
* カラムを追加
|
* カラムを追加
|
||||||
*/
|
*/
|
||||||
"addColumn": string;
|
"addColumn": string;
|
||||||
|
/**
|
||||||
|
* 新着ノート通知の設定
|
||||||
|
*/
|
||||||
|
"newNoteNotificationSettings": string;
|
||||||
/**
|
/**
|
||||||
* カラムの設定
|
* カラムの設定
|
||||||
*/
|
*/
|
||||||
|
@@ -1233,6 +1233,10 @@ useNativeUIForVideoAudioPlayer: "Riprodurre audio/video usando le funzionalità
|
|||||||
keepOriginalFilename: "Mantieni il nome file originale"
|
keepOriginalFilename: "Mantieni il nome file originale"
|
||||||
keepOriginalFilenameDescription: "Disattivandola, i file verranno caricati usando nomi casuali."
|
keepOriginalFilenameDescription: "Disattivandola, i file verranno caricati usando nomi casuali."
|
||||||
noDescription: "Manca la descrizione"
|
noDescription: "Manca la descrizione"
|
||||||
|
_delivery:
|
||||||
|
stop: "Sospensione"
|
||||||
|
_type:
|
||||||
|
none: "Pubblicazione"
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "Come giocare"
|
howToPlay: "Come giocare"
|
||||||
hold: "Tieni"
|
hold: "Tieni"
|
||||||
@@ -2025,7 +2029,6 @@ _permissions:
|
|||||||
"read:admin:server-info": "Vedere le informazioni sul server"
|
"read:admin:server-info": "Vedere le informazioni sul server"
|
||||||
"read:admin:show-moderation-log": "Vedere lo storico di moderazione"
|
"read:admin:show-moderation-log": "Vedere lo storico di moderazione"
|
||||||
"read:admin:show-user": "Vedere le informazioni private degli account utente"
|
"read:admin:show-user": "Vedere le informazioni private degli account utente"
|
||||||
"read:admin:show-users": "Vedere le informazioni private degli account utente"
|
|
||||||
"write:admin:suspend-user": "Sospendere i profili"
|
"write:admin:suspend-user": "Sospendere i profili"
|
||||||
"write:admin:unset-user-avatar": "Rimuovere la foto profilo dai profili"
|
"write:admin:unset-user-avatar": "Rimuovere la foto profilo dai profili"
|
||||||
"write:admin:unset-user-banner": "Rimuovere l'immagine testata dai profili"
|
"write:admin:unset-user-banner": "Rimuovere l'immagine testata dai profili"
|
||||||
|
@@ -316,6 +316,7 @@ selectFile: "ファイルを選択"
|
|||||||
selectFiles: "ファイルを選択"
|
selectFiles: "ファイルを選択"
|
||||||
selectFolder: "フォルダーを選択"
|
selectFolder: "フォルダーを選択"
|
||||||
selectFolders: "フォルダーを選択"
|
selectFolders: "フォルダーを選択"
|
||||||
|
fileNotSelected: "ファイルが選択されていません"
|
||||||
renameFile: "ファイル名を変更"
|
renameFile: "ファイル名を変更"
|
||||||
folderName: "フォルダー名"
|
folderName: "フォルダー名"
|
||||||
createFolder: "フォルダーを作成"
|
createFolder: "フォルダーを作成"
|
||||||
@@ -836,6 +837,7 @@ administration: "管理"
|
|||||||
accounts: "アカウント"
|
accounts: "アカウント"
|
||||||
switch: "切り替え"
|
switch: "切り替え"
|
||||||
noMaintainerInformationWarning: "管理者情報が設定されていません。"
|
noMaintainerInformationWarning: "管理者情報が設定されていません。"
|
||||||
|
noInquiryUrlWarning: "問い合わせ先URLが設定されていません。"
|
||||||
noBotProtectionWarning: "Botプロテクションが設定されていません。"
|
noBotProtectionWarning: "Botプロテクションが設定されていません。"
|
||||||
configure: "設定する"
|
configure: "設定する"
|
||||||
postToGallery: "ギャラリーへ投稿"
|
postToGallery: "ギャラリーへ投稿"
|
||||||
@@ -1024,7 +1026,8 @@ thisPostMayBeAnnoying: "この投稿は迷惑になる可能性があります
|
|||||||
thisPostMayBeAnnoyingHome: "ホームに投稿"
|
thisPostMayBeAnnoyingHome: "ホームに投稿"
|
||||||
thisPostMayBeAnnoyingCancel: "やめる"
|
thisPostMayBeAnnoyingCancel: "やめる"
|
||||||
thisPostMayBeAnnoyingIgnore: "このまま投稿"
|
thisPostMayBeAnnoyingIgnore: "このまま投稿"
|
||||||
collapseRenotes: "見たことのあるリノートを省略して表示"
|
collapseRenotes: "リノートのスマート省略"
|
||||||
|
collapseRenotesDescription: "リアクションやリノートをしたことがあるノートをたたんで表示します。"
|
||||||
internalServerError: "サーバー内部エラー"
|
internalServerError: "サーバー内部エラー"
|
||||||
internalServerErrorDescription: "サーバー内部で予期しないエラーが発生しました。"
|
internalServerErrorDescription: "サーバー内部で予期しないエラーが発生しました。"
|
||||||
copyErrorInfo: "エラー情報をコピー"
|
copyErrorInfo: "エラー情報をコピー"
|
||||||
@@ -1240,6 +1243,16 @@ noDescription: "説明文はありません"
|
|||||||
alwaysConfirmFollow: "フォローの際常に確認する"
|
alwaysConfirmFollow: "フォローの際常に確認する"
|
||||||
inquiry: "お問い合わせ"
|
inquiry: "お問い合わせ"
|
||||||
|
|
||||||
|
_delivery:
|
||||||
|
status: "配信状態"
|
||||||
|
stop: "配信停止"
|
||||||
|
resume: "配信再開"
|
||||||
|
_type:
|
||||||
|
none: "配信中"
|
||||||
|
manuallySuspended: "手動停止中"
|
||||||
|
goneSuspended: "サーバー削除のため停止中"
|
||||||
|
autoSuspendedForNotResponding: "サーバー応答なしのため停止中"
|
||||||
|
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "遊び方"
|
howToPlay: "遊び方"
|
||||||
hold: "ホールド"
|
hold: "ホールド"
|
||||||
@@ -1371,6 +1384,8 @@ _serverSettings:
|
|||||||
fanoutTimelineDescription: "有効にすると、各種タイムラインを取得する際のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。サーバーのメモリ容量が少ない場合、または動作が不安定な場合は無効にすることができます。"
|
fanoutTimelineDescription: "有効にすると、各種タイムラインを取得する際のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。サーバーのメモリ容量が少ない場合、または動作が不安定な場合は無効にすることができます。"
|
||||||
fanoutTimelineDbFallback: "データベースへのフォールバック"
|
fanoutTimelineDbFallback: "データベースへのフォールバック"
|
||||||
fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。"
|
fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。"
|
||||||
|
inquiryUrl: "問い合わせ先URL"
|
||||||
|
inquiryUrlDescription: "サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定します。"
|
||||||
|
|
||||||
_accountMigration:
|
_accountMigration:
|
||||||
moveFrom: "別のアカウントからこのアカウントに移行"
|
moveFrom: "別のアカウントからこのアカウントに移行"
|
||||||
@@ -2075,7 +2090,6 @@ _permissions:
|
|||||||
"read:admin:server-info": "サーバーの情報を見る"
|
"read:admin:server-info": "サーバーの情報を見る"
|
||||||
"read:admin:show-moderation-log": "モデレーションログを見る"
|
"read:admin:show-moderation-log": "モデレーションログを見る"
|
||||||
"read:admin:show-user": "ユーザーのプライベートな情報を見る"
|
"read:admin:show-user": "ユーザーのプライベートな情報を見る"
|
||||||
"read:admin:show-users": "ユーザーのプライベートな情報を見る"
|
|
||||||
"write:admin:suspend-user": "ユーザーを凍結する"
|
"write:admin:suspend-user": "ユーザーを凍結する"
|
||||||
"write:admin:unset-user-avatar": "ユーザーのアバターを削除する"
|
"write:admin:unset-user-avatar": "ユーザーのアバターを削除する"
|
||||||
"write:admin:unset-user-banner": "ユーザーのバーナーを削除する"
|
"write:admin:unset-user-banner": "ユーザーのバーナーを削除する"
|
||||||
@@ -2410,6 +2424,7 @@ _deck:
|
|||||||
alwaysShowMainColumn: "常にメインカラムを表示"
|
alwaysShowMainColumn: "常にメインカラムを表示"
|
||||||
columnAlign: "カラムの寄せ"
|
columnAlign: "カラムの寄せ"
|
||||||
addColumn: "カラムを追加"
|
addColumn: "カラムを追加"
|
||||||
|
newNoteNotificationSettings: "新着ノート通知の設定"
|
||||||
configureColumn: "カラムの設定"
|
configureColumn: "カラムの設定"
|
||||||
swapLeft: "左に移動"
|
swapLeft: "左に移動"
|
||||||
swapRight: "右に移動"
|
swapRight: "右に移動"
|
||||||
|
@@ -1235,6 +1235,10 @@ keepOriginalFilenameDescription: "この設定をオフにすると、アップ
|
|||||||
noDescription: "説明文はあらへんで"
|
noDescription: "説明文はあらへんで"
|
||||||
alwaysConfirmFollow: "フォローの際常に確認する"
|
alwaysConfirmFollow: "フォローの際常に確認する"
|
||||||
inquiry: "問い合わせ"
|
inquiry: "問い合わせ"
|
||||||
|
_delivery:
|
||||||
|
stop: "配信せぇへん"
|
||||||
|
_type:
|
||||||
|
none: "配信しとる"
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "遊び方"
|
howToPlay: "遊び方"
|
||||||
hold: "ホールド"
|
hold: "ホールド"
|
||||||
@@ -2032,7 +2036,6 @@ _permissions:
|
|||||||
"read:admin:server-info": "サーバーの情報見る"
|
"read:admin:server-info": "サーバーの情報見る"
|
||||||
"read:admin:show-moderation-log": "モデレーションログ見る"
|
"read:admin:show-moderation-log": "モデレーションログ見る"
|
||||||
"read:admin:show-user": "ユーザーのプライベートな情報見る"
|
"read:admin:show-user": "ユーザーのプライベートな情報見る"
|
||||||
"read:admin:show-users": "ユーザーのプライベートな情報見る"
|
|
||||||
"write:admin:suspend-user": "ユーザーを凍結"
|
"write:admin:suspend-user": "ユーザーを凍結"
|
||||||
"write:admin:unset-user-avatar": "ユーザーのアバターを削除"
|
"write:admin:unset-user-avatar": "ユーザーのアバターを削除"
|
||||||
"write:admin:unset-user-banner": "ユーザーのバナーを削除"
|
"write:admin:unset-user-banner": "ユーザーのバナーを削除"
|
||||||
|
@@ -649,6 +649,10 @@ replies: "답하기"
|
|||||||
renotes: "리노트"
|
renotes: "리노트"
|
||||||
attach: "옇기"
|
attach: "옇기"
|
||||||
surrender: "아이예"
|
surrender: "아이예"
|
||||||
|
_delivery:
|
||||||
|
stop: "고만 보내예"
|
||||||
|
_type:
|
||||||
|
none: "보내고 잇어예"
|
||||||
_initialAccountSetting:
|
_initialAccountSetting:
|
||||||
startTutorial: "길라잡이 하기"
|
startTutorial: "길라잡이 하기"
|
||||||
_initialTutorial:
|
_initialTutorial:
|
||||||
|
@@ -1230,6 +1230,10 @@ useTotp: "일회용 비밀번호 사용"
|
|||||||
useBackupCode: "백업 코드 사용"
|
useBackupCode: "백업 코드 사용"
|
||||||
launchApp: "앱 실행"
|
launchApp: "앱 실행"
|
||||||
useNativeUIForVideoAudioPlayer: "브라우저 UI에서 미디어 재생"
|
useNativeUIForVideoAudioPlayer: "브라우저 UI에서 미디어 재생"
|
||||||
|
_delivery:
|
||||||
|
stop: "정지됨"
|
||||||
|
_type:
|
||||||
|
none: "배포 중"
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "설명"
|
howToPlay: "설명"
|
||||||
hold: "홀드"
|
hold: "홀드"
|
||||||
@@ -2021,7 +2025,6 @@ _permissions:
|
|||||||
"read:admin:server-info": "서버 정보 보기"
|
"read:admin:server-info": "서버 정보 보기"
|
||||||
"read:admin:show-moderation-log": "조정 기록 보기"
|
"read:admin:show-moderation-log": "조정 기록 보기"
|
||||||
"read:admin:show-user": "사용자 개인정보 보기"
|
"read:admin:show-user": "사용자 개인정보 보기"
|
||||||
"read:admin:show-users": "사용자 개인정보 보기"
|
|
||||||
"write:admin:suspend-user": "사용자 정지하기"
|
"write:admin:suspend-user": "사용자 정지하기"
|
||||||
"write:admin:unset-user-avatar": "사용자 아바타 삭제하기"
|
"write:admin:unset-user-avatar": "사용자 아바타 삭제하기"
|
||||||
"write:admin:unset-user-banner": "사용자 배너 삭제하기"
|
"write:admin:unset-user-banner": "사용자 배너 삭제하기"
|
||||||
|
@@ -395,6 +395,10 @@ searchByGoogle: "ຄົ້ນຫາ"
|
|||||||
file: "ໄຟລ໌"
|
file: "ໄຟລ໌"
|
||||||
replies: "ຕອບໄປທີ"
|
replies: "ຕອບໄປທີ"
|
||||||
renotes: "Renote"
|
renotes: "Renote"
|
||||||
|
_delivery:
|
||||||
|
stop: "ໂຈະ"
|
||||||
|
_type:
|
||||||
|
none: "ການພິມເຜີຍແຜ່"
|
||||||
_role:
|
_role:
|
||||||
_priority:
|
_priority:
|
||||||
middle: "ປານກາງ"
|
middle: "ປານກາງ"
|
||||||
|
@@ -429,6 +429,10 @@ loggedInAsBot: "Momenteel als bot ingelogd"
|
|||||||
icon: "Avatar"
|
icon: "Avatar"
|
||||||
replies: "Antwoord"
|
replies: "Antwoord"
|
||||||
renotes: "Herdelen"
|
renotes: "Herdelen"
|
||||||
|
_delivery:
|
||||||
|
stop: "Opgeschort"
|
||||||
|
_type:
|
||||||
|
none: "Publiceren"
|
||||||
_email:
|
_email:
|
||||||
_follow:
|
_follow:
|
||||||
title: "volgde jou"
|
title: "volgde jou"
|
||||||
|
@@ -464,6 +464,8 @@ icon: "Avatar"
|
|||||||
replies: "Svar"
|
replies: "Svar"
|
||||||
renotes: "Renote"
|
renotes: "Renote"
|
||||||
surrender: "Avbryt"
|
surrender: "Avbryt"
|
||||||
|
_delivery:
|
||||||
|
stop: "Suspendert"
|
||||||
_initialAccountSetting:
|
_initialAccountSetting:
|
||||||
theseSettingsCanEditLater: "Du kan endre disse innstillingene senere."
|
theseSettingsCanEditLater: "Du kan endre disse innstillingene senere."
|
||||||
_achievements:
|
_achievements:
|
||||||
|
@@ -1023,6 +1023,10 @@ flip: "Odwróć"
|
|||||||
lastNDays: "W ciągu ostatnich {n} dni"
|
lastNDays: "W ciągu ostatnich {n} dni"
|
||||||
surrender: "Odrzuć"
|
surrender: "Odrzuć"
|
||||||
gameRetry: "Spróbuj ponownie"
|
gameRetry: "Spróbuj ponownie"
|
||||||
|
_delivery:
|
||||||
|
stop: "Zawieszono"
|
||||||
|
_type:
|
||||||
|
none: "Publikowanie"
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
_score:
|
_score:
|
||||||
score: "Wynik"
|
score: "Wynik"
|
||||||
|
@@ -1012,6 +1012,10 @@ keepScreenOn: "Manter a tela do dispositivo sempre ligada"
|
|||||||
flip: "Inversão"
|
flip: "Inversão"
|
||||||
lastNDays: "Últimos {n} dias"
|
lastNDays: "Últimos {n} dias"
|
||||||
surrender: "Cancelar"
|
surrender: "Cancelar"
|
||||||
|
_delivery:
|
||||||
|
stop: "Suspenso"
|
||||||
|
_type:
|
||||||
|
none: "Publicando"
|
||||||
_initialAccountSetting:
|
_initialAccountSetting:
|
||||||
followUsers: "Siga usuários que lhe interessam para criar a sua linha do tempo."
|
followUsers: "Siga usuários que lhe interessam para criar a sua linha do tempo."
|
||||||
_serverSettings:
|
_serverSettings:
|
||||||
|
@@ -651,6 +651,10 @@ show: "Arată"
|
|||||||
icon: "Avatar"
|
icon: "Avatar"
|
||||||
replies: "Răspunde"
|
replies: "Răspunde"
|
||||||
renotes: "Re-notează"
|
renotes: "Re-notează"
|
||||||
|
_delivery:
|
||||||
|
stop: "Suspendat"
|
||||||
|
_type:
|
||||||
|
none: "Publicare"
|
||||||
_role:
|
_role:
|
||||||
_priority:
|
_priority:
|
||||||
middle: "Mediu"
|
middle: "Mediu"
|
||||||
|
@@ -1099,6 +1099,10 @@ flip: "Переворот"
|
|||||||
code: "Код"
|
code: "Код"
|
||||||
lastNDays: "Последние {n} сут"
|
lastNDays: "Последние {n} сут"
|
||||||
surrender: "Этот пост не может быть отменен."
|
surrender: "Этот пост не может быть отменен."
|
||||||
|
_delivery:
|
||||||
|
stop: "Заморожено"
|
||||||
|
_type:
|
||||||
|
none: "Публикация"
|
||||||
_initialAccountSetting:
|
_initialAccountSetting:
|
||||||
accountCreated: "Аккаунт успешно создан!"
|
accountCreated: "Аккаунт успешно создан!"
|
||||||
letsStartAccountSetup: "Давайте настроим вашу учётную запись."
|
letsStartAccountSetup: "Давайте настроим вашу учётную запись."
|
||||||
|
@@ -922,6 +922,10 @@ renotes: "Preposlať"
|
|||||||
sourceCode: "Zdrojový kód"
|
sourceCode: "Zdrojový kód"
|
||||||
flip: "Preklopiť"
|
flip: "Preklopiť"
|
||||||
lastNDays: "Posledných {n} dní"
|
lastNDays: "Posledných {n} dní"
|
||||||
|
_delivery:
|
||||||
|
stop: "Zmrazené"
|
||||||
|
_type:
|
||||||
|
none: "Zverejňovanie"
|
||||||
_role:
|
_role:
|
||||||
priority: "Priorita"
|
priority: "Priorita"
|
||||||
_priority:
|
_priority:
|
||||||
|
@@ -488,6 +488,10 @@ dataSaver: "Databesparing"
|
|||||||
icon: "Profilbild"
|
icon: "Profilbild"
|
||||||
replies: "Svara"
|
replies: "Svara"
|
||||||
renotes: "Omnotera"
|
renotes: "Omnotera"
|
||||||
|
_delivery:
|
||||||
|
stop: "Suspenderad"
|
||||||
|
_type:
|
||||||
|
none: "Publiceras"
|
||||||
_achievements:
|
_achievements:
|
||||||
_types:
|
_types:
|
||||||
_open3windows:
|
_open3windows:
|
||||||
|
@@ -1235,6 +1235,10 @@ keepOriginalFilenameDescription: "หากปิดการตั้งค่
|
|||||||
noDescription: "ไม่มีข้อความอธิบาย"
|
noDescription: "ไม่มีข้อความอธิบาย"
|
||||||
alwaysConfirmFollow: "แสดงข้อความยืนยันเมื่อกดติดตาม"
|
alwaysConfirmFollow: "แสดงข้อความยืนยันเมื่อกดติดตาม"
|
||||||
inquiry: "ติดต่อเรา"
|
inquiry: "ติดต่อเรา"
|
||||||
|
_delivery:
|
||||||
|
stop: "ถูกระงับ"
|
||||||
|
_type:
|
||||||
|
none: "กำลังเผยแพร่"
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "วิธีเล่น"
|
howToPlay: "วิธีเล่น"
|
||||||
hold: "หยุดชั่วคราว"
|
hold: "หยุดชั่วคราว"
|
||||||
@@ -2032,7 +2036,6 @@ _permissions:
|
|||||||
"read:admin:server-info": "ดูข้อมูลเซิร์ฟเวอร์"
|
"read:admin:server-info": "ดูข้อมูลเซิร์ฟเวอร์"
|
||||||
"read:admin:show-moderation-log": "ดูปูมการแก้ไข"
|
"read:admin:show-moderation-log": "ดูปูมการแก้ไข"
|
||||||
"read:admin:show-user": "ดูข้อมูลส่วนตัวของผู้ใช้"
|
"read:admin:show-user": "ดูข้อมูลส่วนตัวของผู้ใช้"
|
||||||
"read:admin:show-users": "ดูข้อมูลส่วนตัวของผู้ใช้"
|
|
||||||
"write:admin:suspend-user": "ระงับผู้ใช้"
|
"write:admin:suspend-user": "ระงับผู้ใช้"
|
||||||
"write:admin:unset-user-avatar": "ลบอวตารผู้ใช้"
|
"write:admin:unset-user-avatar": "ลบอวตารผู้ใช้"
|
||||||
"write:admin:unset-user-banner": "ลบแบนเนอร์ผู้ใช้"
|
"write:admin:unset-user-banner": "ลบแบนเนอร์ผู้ใช้"
|
||||||
|
@@ -378,6 +378,10 @@ addMemo: "Kısa not ekle"
|
|||||||
icon: "Avatar"
|
icon: "Avatar"
|
||||||
replies: "yanıt"
|
replies: "yanıt"
|
||||||
renotes: "vazgeçme"
|
renotes: "vazgeçme"
|
||||||
|
_delivery:
|
||||||
|
stop: "Askıya alınmış"
|
||||||
|
_type:
|
||||||
|
none: "Paylaşım"
|
||||||
_accountDelete:
|
_accountDelete:
|
||||||
started: "Silme işlemi başlatıldı"
|
started: "Silme işlemi başlatıldı"
|
||||||
_email:
|
_email:
|
||||||
|
@@ -914,6 +914,10 @@ renotes: "Поширити"
|
|||||||
sourceCode: "Вихідний код"
|
sourceCode: "Вихідний код"
|
||||||
flip: "Перевернути"
|
flip: "Перевернути"
|
||||||
lastNDays: "Останні {n} днів"
|
lastNDays: "Останні {n} днів"
|
||||||
|
_delivery:
|
||||||
|
stop: "Призупинено"
|
||||||
|
_type:
|
||||||
|
none: "Публікація"
|
||||||
_achievements:
|
_achievements:
|
||||||
earnedAt: "Відкрито"
|
earnedAt: "Відкрито"
|
||||||
_types:
|
_types:
|
||||||
|
@@ -846,6 +846,10 @@ icon: "Avatar"
|
|||||||
replies: "Javob berish"
|
replies: "Javob berish"
|
||||||
renotes: "Qayta qayd etish"
|
renotes: "Qayta qayd etish"
|
||||||
flip: "Teskari"
|
flip: "Teskari"
|
||||||
|
_delivery:
|
||||||
|
stop: "To'xtatilgan"
|
||||||
|
_type:
|
||||||
|
none: "Yuborilmoqda"
|
||||||
_achievements:
|
_achievements:
|
||||||
_types:
|
_types:
|
||||||
_viewInstanceChart:
|
_viewInstanceChart:
|
||||||
|
@@ -1118,6 +1118,10 @@ pullDownToRefresh: "Kéo xuống để làm mới"
|
|||||||
cwNotationRequired: "Nếu \"Ẩn nội dung\" được bật thì cần phải có chú thích."
|
cwNotationRequired: "Nếu \"Ẩn nội dung\" được bật thì cần phải có chú thích."
|
||||||
lastNDays: "{n} ngày trước"
|
lastNDays: "{n} ngày trước"
|
||||||
surrender: "Từ chối"
|
surrender: "Từ chối"
|
||||||
|
_delivery:
|
||||||
|
stop: "Đã vô hiệu hóa"
|
||||||
|
_type:
|
||||||
|
none: "Đang đăng"
|
||||||
_announcement:
|
_announcement:
|
||||||
forExistingUsers: "Chỉ những người dùng đã tồn tại"
|
forExistingUsers: "Chỉ những người dùng đã tồn tại"
|
||||||
forExistingUsersDescription: "Nếu được bật, thông báo này sẽ chỉ hiển thị với những người dùng đã tồn tại vào lúc thông báo được tạo. Nếu tắt đi, những tài khoản mới đăng ký sau khi thông báo được đăng lên cũng sẽ thấy nó."
|
forExistingUsersDescription: "Nếu được bật, thông báo này sẽ chỉ hiển thị với những người dùng đã tồn tại vào lúc thông báo được tạo. Nếu tắt đi, những tài khoản mới đăng ký sau khi thông báo được đăng lên cũng sẽ thấy nó."
|
||||||
|
@@ -316,6 +316,7 @@ selectFile: "选择文件"
|
|||||||
selectFiles: "选择文件"
|
selectFiles: "选择文件"
|
||||||
selectFolder: "选择文件夹"
|
selectFolder: "选择文件夹"
|
||||||
selectFolders: "选择多个文件夹"
|
selectFolders: "选择多个文件夹"
|
||||||
|
fileNotSelected: "未选择文件"
|
||||||
renameFile: "重命名文件"
|
renameFile: "重命名文件"
|
||||||
folderName: "文件夹名称"
|
folderName: "文件夹名称"
|
||||||
createFolder: "创建文件夹"
|
createFolder: "创建文件夹"
|
||||||
@@ -471,6 +472,7 @@ retype: "重新输入"
|
|||||||
noteOf: "{user} 的帖子"
|
noteOf: "{user} 的帖子"
|
||||||
quoteAttached: "已引用"
|
quoteAttached: "已引用"
|
||||||
quoteQuestion: "是否引用此链接内容?"
|
quoteQuestion: "是否引用此链接内容?"
|
||||||
|
attachAsFileQuestion: "剪贴板内的文字过长。要转换为文本文件并添加吗?"
|
||||||
noMessagesYet: "现在没有新的聊天"
|
noMessagesYet: "现在没有新的聊天"
|
||||||
newMessageExists: "新信息"
|
newMessageExists: "新信息"
|
||||||
onlyOneFileCanBeAttached: "只能添加一个附件"
|
onlyOneFileCanBeAttached: "只能添加一个附件"
|
||||||
@@ -1024,6 +1026,7 @@ thisPostMayBeAnnoyingHome: "发到首页"
|
|||||||
thisPostMayBeAnnoyingCancel: "取消"
|
thisPostMayBeAnnoyingCancel: "取消"
|
||||||
thisPostMayBeAnnoyingIgnore: "就这样发布"
|
thisPostMayBeAnnoyingIgnore: "就这样发布"
|
||||||
collapseRenotes: "省略显示已经看过的转发内容"
|
collapseRenotes: "省略显示已经看过的转发内容"
|
||||||
|
collapseRenotesDescription: "将回应过或转贴过的贴子折叠表示。"
|
||||||
internalServerError: "内部服务器错误"
|
internalServerError: "内部服务器错误"
|
||||||
internalServerErrorDescription: "内部服务器发生了预期外的错误"
|
internalServerErrorDescription: "内部服务器发生了预期外的错误"
|
||||||
copyErrorInfo: "复制错误信息"
|
copyErrorInfo: "复制错误信息"
|
||||||
@@ -1238,6 +1241,15 @@ keepOriginalFilenameDescription: "若关闭此设置,上传文件时文件名
|
|||||||
noDescription: "没有描述"
|
noDescription: "没有描述"
|
||||||
alwaysConfirmFollow: "总是确认关注"
|
alwaysConfirmFollow: "总是确认关注"
|
||||||
inquiry: "联系我们"
|
inquiry: "联系我们"
|
||||||
|
_delivery:
|
||||||
|
status: "投递状态"
|
||||||
|
stop: "停止投递"
|
||||||
|
resume: "继续投递"
|
||||||
|
_type:
|
||||||
|
none: "投递中"
|
||||||
|
manuallySuspended: "手动停止中"
|
||||||
|
goneSuspended: "因服务器被删除而停止"
|
||||||
|
autoSuspendedForNotResponding: "因服务器无应答而停止"
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "游戏说明"
|
howToPlay: "游戏说明"
|
||||||
hold: "抓住"
|
hold: "抓住"
|
||||||
@@ -1696,8 +1708,10 @@ _role:
|
|||||||
roleAssignedTo: "已分配给手动角色"
|
roleAssignedTo: "已分配给手动角色"
|
||||||
isLocal: "是本地用户"
|
isLocal: "是本地用户"
|
||||||
isRemote: "是远程用户"
|
isRemote: "是远程用户"
|
||||||
|
isCat: "猫猫用户"
|
||||||
isBot: "机器人用户"
|
isBot: "机器人用户"
|
||||||
isSuspended: "停用的用户"
|
isSuspended: "停用的用户"
|
||||||
|
isLocked: "锁推用户"
|
||||||
isExplorable: "启用“使账号可见”的用户"
|
isExplorable: "启用“使账号可见”的用户"
|
||||||
createdLessThan: "账户创建时间少于"
|
createdLessThan: "账户创建时间少于"
|
||||||
createdMoreThan: "账户创建时间超过"
|
createdMoreThan: "账户创建时间超过"
|
||||||
@@ -2032,7 +2046,6 @@ _permissions:
|
|||||||
"read:admin:server-info": "查看服务器信息"
|
"read:admin:server-info": "查看服务器信息"
|
||||||
"read:admin:show-moderation-log": "查看管理日志"
|
"read:admin:show-moderation-log": "查看管理日志"
|
||||||
"read:admin:show-user": "查看用户的非公开信息"
|
"read:admin:show-user": "查看用户的非公开信息"
|
||||||
"read:admin:show-users": "查看用户的非公开信息"
|
|
||||||
"write:admin:suspend-user": "冻结用户"
|
"write:admin:suspend-user": "冻结用户"
|
||||||
"write:admin:unset-user-avatar": "删除用户头像"
|
"write:admin:unset-user-avatar": "删除用户头像"
|
||||||
"write:admin:unset-user-banner": "删除用户横幅"
|
"write:admin:unset-user-banner": "删除用户横幅"
|
||||||
@@ -2346,6 +2359,7 @@ _deck:
|
|||||||
alwaysShowMainColumn: "总是显示主列"
|
alwaysShowMainColumn: "总是显示主列"
|
||||||
columnAlign: "列对齐"
|
columnAlign: "列对齐"
|
||||||
addColumn: "添加列"
|
addColumn: "添加列"
|
||||||
|
newNoteNotificationSettings: "新帖子通知设定"
|
||||||
configureColumn: "列设置"
|
configureColumn: "列设置"
|
||||||
swapLeft: "向左移动"
|
swapLeft: "向左移动"
|
||||||
swapRight: "向右移动"
|
swapRight: "向右移动"
|
||||||
|
@@ -108,11 +108,14 @@ enterEmoji: "輸入表情符號"
|
|||||||
renote: "轉發"
|
renote: "轉發"
|
||||||
unrenote: "取消轉發"
|
unrenote: "取消轉發"
|
||||||
renoted: "轉發成功。"
|
renoted: "轉發成功。"
|
||||||
|
renotedToX: "轉發給 {name} 了。"
|
||||||
cantRenote: "無法轉發此貼文。"
|
cantRenote: "無法轉發此貼文。"
|
||||||
cantReRenote: "無法轉發之前已經轉發過的內容。"
|
cantReRenote: "無法轉發之前已經轉發過的內容。"
|
||||||
quote: "引用"
|
quote: "引用"
|
||||||
inChannelRenote: "在頻道內轉發"
|
inChannelRenote: "在頻道內轉發"
|
||||||
inChannelQuote: "在頻道內引用"
|
inChannelQuote: "在頻道內引用"
|
||||||
|
renoteToChannel: "轉發至頻道"
|
||||||
|
renoteToOtherChannel: "轉發至其他頻道"
|
||||||
pinnedNote: "已置頂的貼文"
|
pinnedNote: "已置頂的貼文"
|
||||||
pinned: "置頂"
|
pinned: "置頂"
|
||||||
you: "您"
|
you: "您"
|
||||||
@@ -169,7 +172,7 @@ cacheRemoteSensitiveFilesDescription: "若停用這個設定,則不會快取
|
|||||||
flagAsBot: "此使用者是機器人"
|
flagAsBot: "此使用者是機器人"
|
||||||
flagAsBotDescription: "如果本帳戶是由程式控制,請啟用此選項。啟用後,會作為標示幫助其他開發者防止機器人之間產生無限互動的行為,並會調整 Misskey 內部系統將本帳戶識別為機器人。"
|
flagAsBotDescription: "如果本帳戶是由程式控制,請啟用此選項。啟用後,會作為標示幫助其他開發者防止機器人之間產生無限互動的行為,並會調整 Misskey 內部系統將本帳戶識別為機器人。"
|
||||||
flagAsCat: "此帳戶是一隻貓,喵~~~!!!"
|
flagAsCat: "此帳戶是一隻貓,喵~~~!!!"
|
||||||
flagAsCatDescription: "如果想將本帳戶標示為一隻貓,請開啟此標示"
|
flagAsCatDescription: "喵喵喵??"
|
||||||
flagShowTimelineReplies: "在時間軸上顯示貼文的回覆"
|
flagShowTimelineReplies: "在時間軸上顯示貼文的回覆"
|
||||||
flagShowTimelineRepliesDescription: "啟用後,時間軸除了顯示使用者的貼文以外,還會顯示使用者對其他貼文的回覆。"
|
flagShowTimelineRepliesDescription: "啟用後,時間軸除了顯示使用者的貼文以外,還會顯示使用者對其他貼文的回覆。"
|
||||||
autoAcceptFollowed: "自動允許來自追隨中使用者的追隨請求"
|
autoAcceptFollowed: "自動允許來自追隨中使用者的追隨請求"
|
||||||
@@ -313,6 +316,7 @@ selectFile: "選擇檔案"
|
|||||||
selectFiles: "選擇檔案"
|
selectFiles: "選擇檔案"
|
||||||
selectFolder: "選擇資料夾"
|
selectFolder: "選擇資料夾"
|
||||||
selectFolders: "選擇資料夾"
|
selectFolders: "選擇資料夾"
|
||||||
|
fileNotSelected: "尚未選擇檔案"
|
||||||
renameFile: "重新命名檔案"
|
renameFile: "重新命名檔案"
|
||||||
folderName: "資料夾名稱"
|
folderName: "資料夾名稱"
|
||||||
createFolder: "新增資料夾"
|
createFolder: "新增資料夾"
|
||||||
@@ -366,7 +370,7 @@ enableRegistration: "開放新使用者註冊"
|
|||||||
invite: "邀請"
|
invite: "邀請"
|
||||||
driveCapacityPerLocalAccount: "每個本地使用者的雲端硬碟容量"
|
driveCapacityPerLocalAccount: "每個本地使用者的雲端硬碟容量"
|
||||||
driveCapacityPerRemoteAccount: "每個非本地用戶的雲端空間大小"
|
driveCapacityPerRemoteAccount: "每個非本地用戶的雲端空間大小"
|
||||||
inMb: "以Mbps為單位"
|
inMb: "以 MB 為單位"
|
||||||
bannerUrl: "橫幅圖片URL"
|
bannerUrl: "橫幅圖片URL"
|
||||||
backgroundImageUrl: "背景圖片的來源網址 "
|
backgroundImageUrl: "背景圖片的來源網址 "
|
||||||
basicInfo: "基本資訊"
|
basicInfo: "基本資訊"
|
||||||
@@ -378,12 +382,12 @@ pinnedClipId: "置頂的摘錄ID"
|
|||||||
pinnedNotes: "已置頂的貼文"
|
pinnedNotes: "已置頂的貼文"
|
||||||
hcaptcha: "hCaptcha"
|
hcaptcha: "hCaptcha"
|
||||||
enableHcaptcha: "啟用 hCaptcha"
|
enableHcaptcha: "啟用 hCaptcha"
|
||||||
hcaptchaSiteKey: "網站金鑰"
|
hcaptchaSiteKey: "hcaptchaSiteKey"
|
||||||
hcaptchaSecretKey: "金鑰"
|
hcaptchaSecretKey: "hcaptchaSecretKey"
|
||||||
mcaptcha: "mCaptcha"
|
mcaptcha: "mCaptcha"
|
||||||
enableMcaptcha: "啟用 mCaptcha"
|
enableMcaptcha: "啟用 mCaptcha"
|
||||||
mcaptchaSiteKey: "網站金鑰"
|
mcaptchaSiteKey: "網站金鑰"
|
||||||
mcaptchaSecretKey: "金鑰"
|
mcaptchaSecretKey: "私密金鑰"
|
||||||
mcaptchaInstanceUrl: "mCaptcha 的實例網址"
|
mcaptchaInstanceUrl: "mCaptcha 的實例網址"
|
||||||
recaptcha: "reCAPTCHA"
|
recaptcha: "reCAPTCHA"
|
||||||
enableRecaptcha: "啟用 reCAPTCHA"
|
enableRecaptcha: "啟用 reCAPTCHA"
|
||||||
@@ -391,8 +395,8 @@ recaptchaSiteKey: "網站金鑰"
|
|||||||
recaptchaSecretKey: "金鑰"
|
recaptchaSecretKey: "金鑰"
|
||||||
turnstile: "Turnstile"
|
turnstile: "Turnstile"
|
||||||
enableTurnstile: "啟用 Turnstile"
|
enableTurnstile: "啟用 Turnstile"
|
||||||
turnstileSiteKey: "網站金鑰"
|
turnstileSiteKey: "turnstileSiteKey"
|
||||||
turnstileSecretKey: "金鑰"
|
turnstileSecretKey: "turnstileSecretKey"
|
||||||
avoidMultiCaptchaConfirm: "使用多種驗證方式可能會造成干擾,您要關閉其他驗證方式嗎?您可以按「取消」保留多種驗證方式。"
|
avoidMultiCaptchaConfirm: "使用多種驗證方式可能會造成干擾,您要關閉其他驗證方式嗎?您可以按「取消」保留多種驗證方式。"
|
||||||
antennas: "天線"
|
antennas: "天線"
|
||||||
manageAntennas: "管理天線"
|
manageAntennas: "管理天線"
|
||||||
@@ -464,10 +468,11 @@ title: "標題"
|
|||||||
text: "文字"
|
text: "文字"
|
||||||
enable: "啟用"
|
enable: "啟用"
|
||||||
next: "下一步"
|
next: "下一步"
|
||||||
retype: "再次輸入"
|
retype: "重新輸入"
|
||||||
noteOf: "{user}的貼文"
|
noteOf: "{user}的貼文"
|
||||||
quoteAttached: "引用"
|
quoteAttached: "引用"
|
||||||
quoteQuestion: "是否要引用?"
|
quoteQuestion: "是否要引用?"
|
||||||
|
attachAsFileQuestion: "剪貼簿的文字較長。請問是否要將其以文字檔的方式附加呢?"
|
||||||
noMessagesYet: "沒有訊息"
|
noMessagesYet: "沒有訊息"
|
||||||
newMessageExists: "有新的訊息"
|
newMessageExists: "有新的訊息"
|
||||||
onlyOneFileCanBeAttached: "只能加入一個附件"
|
onlyOneFileCanBeAttached: "只能加入一個附件"
|
||||||
@@ -791,7 +796,7 @@ newVersionOfClientAvailable: "新版本的客戶端可用。"
|
|||||||
usageAmount: "使用量"
|
usageAmount: "使用量"
|
||||||
capacity: "容量"
|
capacity: "容量"
|
||||||
inUse: "已使用"
|
inUse: "已使用"
|
||||||
editCode: "編輯代碼"
|
editCode: "編輯程式碼"
|
||||||
apply: "套用"
|
apply: "套用"
|
||||||
receiveAnnouncementFromInstance: "接收來自伺服器的通知"
|
receiveAnnouncementFromInstance: "接收來自伺服器的通知"
|
||||||
emailNotification: "郵件通知"
|
emailNotification: "郵件通知"
|
||||||
@@ -1021,6 +1026,7 @@ thisPostMayBeAnnoyingHome: "發佈到首頁"
|
|||||||
thisPostMayBeAnnoyingCancel: "退出"
|
thisPostMayBeAnnoyingCancel: "退出"
|
||||||
thisPostMayBeAnnoyingIgnore: "直接發佈貼文"
|
thisPostMayBeAnnoyingIgnore: "直接發佈貼文"
|
||||||
collapseRenotes: "省略顯示已看過的轉發貼文"
|
collapseRenotes: "省略顯示已看過的轉發貼文"
|
||||||
|
collapseRenotesDescription: "將已做過反應和轉發的貼文折疊顯示。"
|
||||||
internalServerError: "內部伺服器錯誤"
|
internalServerError: "內部伺服器錯誤"
|
||||||
internalServerErrorDescription: "內部伺服器出現意外錯誤。"
|
internalServerErrorDescription: "內部伺服器出現意外錯誤。"
|
||||||
copyErrorInfo: "複製錯誤資訊"
|
copyErrorInfo: "複製錯誤資訊"
|
||||||
@@ -1077,7 +1083,7 @@ addMemo: "新增備註"
|
|||||||
editMemo: "編輯備註"
|
editMemo: "編輯備註"
|
||||||
reactionsList: "反應列表"
|
reactionsList: "反應列表"
|
||||||
renotesList: "轉發貼文列表"
|
renotesList: "轉發貼文列表"
|
||||||
notificationDisplay: "通知的顯示"
|
notificationDisplay: "通知"
|
||||||
leftTop: "左上"
|
leftTop: "左上"
|
||||||
rightTop: "右上"
|
rightTop: "右上"
|
||||||
leftBottom: "左下"
|
leftBottom: "左下"
|
||||||
@@ -1179,15 +1185,15 @@ repositoryUrlOrTarballRequired: "如果儲存庫不是公開的,則必須提
|
|||||||
feedback: "意見回饋"
|
feedback: "意見回饋"
|
||||||
feedbackUrl: "意見回饋 URL"
|
feedbackUrl: "意見回饋 URL"
|
||||||
impressum: "營運者資訊"
|
impressum: "營運者資訊"
|
||||||
impressumUrl: "營運者資訊網址"
|
impressumUrl: "營運者資訊 URL"
|
||||||
impressumDescription: "在德國與部份地區必須要明確顯示營運者資訊。"
|
impressumDescription: "在德國與部份地區必須要明確顯示營運者資訊。"
|
||||||
privacyPolicy: "隱私政策"
|
privacyPolicy: "隱私政策"
|
||||||
privacyPolicyUrl: "隱私政策網址"
|
privacyPolicyUrl: "隱私政策 URL"
|
||||||
tosAndPrivacyPolicy: "服務條款和隱私政策"
|
tosAndPrivacyPolicy: "服務條款和隱私政策"
|
||||||
avatarDecorations: "頭像裝飾"
|
avatarDecorations: "頭像裝飾"
|
||||||
attach: "裝上"
|
attach: "裝上"
|
||||||
detach: "取下"
|
detach: "取下"
|
||||||
detachAll: "移除所有裝飾"
|
detachAll: "全部移除"
|
||||||
angle: "角度"
|
angle: "角度"
|
||||||
flip: "翻轉"
|
flip: "翻轉"
|
||||||
showAvatarDecorations: "顯示頭像裝飾"
|
showAvatarDecorations: "顯示頭像裝飾"
|
||||||
@@ -1225,16 +1231,25 @@ enableHorizontalSwipe: "滑動切換時間軸"
|
|||||||
loading: "載入中"
|
loading: "載入中"
|
||||||
surrender: "退出"
|
surrender: "退出"
|
||||||
gameRetry: "再試一次"
|
gameRetry: "再試一次"
|
||||||
notUsePleaseLeaveBlank: "如不使用,請留空"
|
notUsePleaseLeaveBlank: "如果不使用的話請留白"
|
||||||
useTotp: "使用一次性密碼"
|
useTotp: "使用一次性密碼"
|
||||||
useBackupCode: "使用備用驗證碼"
|
useBackupCode: "使用備用驗證碼"
|
||||||
launchApp: "啟動 App"
|
launchApp: "啟動 APP"
|
||||||
useNativeUIForVideoAudioPlayer: "使用瀏覽器的 UI 播放影片與音訊"
|
useNativeUIForVideoAudioPlayer: "使用瀏覽器的 UI 播放影片與音訊"
|
||||||
keepOriginalFilename: "保留原始檔名"
|
keepOriginalFilename: "保留原始檔名"
|
||||||
keepOriginalFilenameDescription: "如果關閉此設置,上傳時檔案名稱會自動替換為隨機字串。"
|
keepOriginalFilenameDescription: "如果關閉此設置,上傳時檔案名稱會自動替換為隨機字串。"
|
||||||
noDescription: "沒有說明文字"
|
noDescription: "沒有說明文字"
|
||||||
alwaysConfirmFollow: "點擊追隨時總是顯示確認訊息"
|
alwaysConfirmFollow: "點擊追隨時總是顯示確認訊息"
|
||||||
inquiry: "聯絡我們"
|
inquiry: "聯絡我們"
|
||||||
|
_delivery:
|
||||||
|
status: "傳送狀態"
|
||||||
|
stop: "停止傳送"
|
||||||
|
resume: "恢復傳送"
|
||||||
|
_type:
|
||||||
|
none: "直播中"
|
||||||
|
manuallySuspended: "手動暫停中"
|
||||||
|
goneSuspended: "因為伺服器刪除所以暫停中"
|
||||||
|
autoSuspendedForNotResponding: "因為伺服器沒有回應所以暫停中"
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "玩法說明"
|
howToPlay: "玩法說明"
|
||||||
hold: "保留"
|
hold: "保留"
|
||||||
@@ -1243,7 +1258,7 @@ _bubbleGame:
|
|||||||
scoreYen: "賺取的金額"
|
scoreYen: "賺取的金額"
|
||||||
highScore: "最高分"
|
highScore: "最高分"
|
||||||
maxChain: "最大結合數"
|
maxChain: "最大結合數"
|
||||||
yen: "{yen} 日圓"
|
yen: "{yen}円"
|
||||||
estimatedQty: "{qty}個"
|
estimatedQty: "{qty}個"
|
||||||
scoreSweets: "飯糰 {onigiriQtyWithUnit}"
|
scoreSweets: "飯糰 {onigiriQtyWithUnit}"
|
||||||
_howToPlay:
|
_howToPlay:
|
||||||
@@ -1271,7 +1286,7 @@ _initialAccountSetting:
|
|||||||
privacySetting: "隱私設定"
|
privacySetting: "隱私設定"
|
||||||
theseSettingsCanEditLater: "這裡的設定可以在之後變更。"
|
theseSettingsCanEditLater: "這裡的設定可以在之後變更。"
|
||||||
youCanEditMoreSettingsInSettingsPageLater: "除此之外,還可以在「設定」頁面進行各種設定。之後請確認看看。"
|
youCanEditMoreSettingsInSettingsPageLater: "除此之外,還可以在「設定」頁面進行各種設定。之後請確認看看。"
|
||||||
followUsers: "為了構築時間軸,試著追蹤您感興趣的使用者吧。"
|
followUsers: "為了構築時間軸,試著追隨您感興趣的使用者吧。"
|
||||||
pushNotificationDescription: "啟用推送通知,就可以在設備上接收{name}的通知。"
|
pushNotificationDescription: "啟用推送通知,就可以在設備上接收{name}的通知。"
|
||||||
initialAccountSettingCompleted: "初始設定完成了!"
|
initialAccountSettingCompleted: "初始設定完成了!"
|
||||||
haveFun: "盡情享受{name}吧!"
|
haveFun: "盡情享受{name}吧!"
|
||||||
@@ -1326,7 +1341,7 @@ _initialTutorial:
|
|||||||
title: "隱藏內容(CW)"
|
title: "隱藏內容(CW)"
|
||||||
description: "將顯示「註釋」中寫入的內容而不是本文。按一下「顯示內容」以顯示本文。"
|
description: "將顯示「註釋」中寫入的內容而不是本文。按一下「顯示內容」以顯示本文。"
|
||||||
_exampleNote:
|
_exampleNote:
|
||||||
cw: "美食恐怖主義注意"
|
cw: "注意消夜文"
|
||||||
note: "我吃了一個巧克力甜甜圈🍩😋"
|
note: "我吃了一個巧克力甜甜圈🍩😋"
|
||||||
useCases: "伺服器的服務條款可能會規範特定的貼文需要使用隱藏內容,除此之外也會用在隱藏劇情洩漏與敏感內容的貼文。"
|
useCases: "伺服器的服務條款可能會規範特定的貼文需要使用隱藏內容,除此之外也會用在隱藏劇情洩漏與敏感內容的貼文。"
|
||||||
_howToMakeAttachmentsSensitive:
|
_howToMakeAttachmentsSensitive:
|
||||||
@@ -1351,7 +1366,7 @@ _serverRules:
|
|||||||
_serverSettings:
|
_serverSettings:
|
||||||
iconUrl: "圖示的 URL"
|
iconUrl: "圖示的 URL"
|
||||||
appIconDescription: "指定顯示 {host} 為應用程式時的圖示。"
|
appIconDescription: "指定顯示 {host} 為應用程式時的圖示。"
|
||||||
appIconUsageExample: "例如:漸進式網路應用程式(PWA)、於手機桌面新增書籤"
|
appIconUsageExample: "例如:PWA 或是在手機桌面作為書籤等"
|
||||||
appIconStyleRecommendation: "因為可能會裁剪成圓形或圓角,所以建議用單色填滿邊框及背景。"
|
appIconStyleRecommendation: "因為可能會裁剪成圓形或圓角,所以建議用單色填滿邊框及背景。"
|
||||||
appIconResolutionMustBe: "解析度必須為 {resolution}。"
|
appIconResolutionMustBe: "解析度必須為 {resolution}。"
|
||||||
manifestJsonOverride: "覆寫 manifest.json"
|
manifestJsonOverride: "覆寫 manifest.json"
|
||||||
@@ -1360,6 +1375,8 @@ _serverSettings:
|
|||||||
fanoutTimelineDescription: "如果啟用的話,檢索各個時間軸的性能會顯著提昇,資料庫的負荷也會減少。不過,Redis 的記憶體使用量會增加。如果伺服器的記憶體容量比較少或者運行不穩定,可以停用。"
|
fanoutTimelineDescription: "如果啟用的話,檢索各個時間軸的性能會顯著提昇,資料庫的負荷也會減少。不過,Redis 的記憶體使用量會增加。如果伺服器的記憶體容量比較少或者運行不穩定,可以停用。"
|
||||||
fanoutTimelineDbFallback: "資料庫的回退"
|
fanoutTimelineDbFallback: "資料庫的回退"
|
||||||
fanoutTimelineDbFallbackDescription: "若啟用,在時間軸沒有快取的情況下將執行回退處理以額外查詢資料庫。若停用,可以透過不執行回退處理來進一步減少伺服器的負荷,但會限制可取得的時間軸範圍。"
|
fanoutTimelineDbFallbackDescription: "若啟用,在時間軸沒有快取的情況下將執行回退處理以額外查詢資料庫。若停用,可以透過不執行回退處理來進一步減少伺服器的負荷,但會限制可取得的時間軸範圍。"
|
||||||
|
inquiryUrl: "聯絡表單網址"
|
||||||
|
inquiryUrlDescription: "指定伺服器運營者的聯絡表單網址或包含運營者聯絡資訊網頁的網址。"
|
||||||
_accountMigration:
|
_accountMigration:
|
||||||
moveFrom: "從其他帳戶遷移到這個帳戶"
|
moveFrom: "從其他帳戶遷移到這個帳戶"
|
||||||
moveFromSub: "為另一個帳戶建立別名"
|
moveFromSub: "為另一個帳戶建立別名"
|
||||||
@@ -1559,7 +1576,7 @@ _achievements:
|
|||||||
_postedAt0min0sec:
|
_postedAt0min0sec:
|
||||||
title: "報時"
|
title: "報時"
|
||||||
description: "在零分零秒發佈貼文"
|
description: "在零分零秒發佈貼文"
|
||||||
flavor: "啵、啵、啵、嗶ーー"
|
flavor: "啵.啵.啵.嗶ー"
|
||||||
_selfQuote:
|
_selfQuote:
|
||||||
title: "自我引用"
|
title: "自我引用"
|
||||||
description: "引用了自己的貼文"
|
description: "引用了自己的貼文"
|
||||||
@@ -1694,8 +1711,8 @@ _role:
|
|||||||
roleAssignedTo: "手動指派角色完成"
|
roleAssignedTo: "手動指派角色完成"
|
||||||
isLocal: "本地使用者"
|
isLocal: "本地使用者"
|
||||||
isRemote: "遠端使用者"
|
isRemote: "遠端使用者"
|
||||||
isCat: "使用者是貓"
|
isCat: "貓使用者"
|
||||||
isBot: "使用者是機器人"
|
isBot: "機器人使用者"
|
||||||
isSuspended: "被停權的使用者"
|
isSuspended: "被停權的使用者"
|
||||||
isLocked: "上鎖的使用者"
|
isLocked: "上鎖的使用者"
|
||||||
isExplorable: "開啟了「使您的帳戶更容易被找到」功能的使用者"
|
isExplorable: "開啟了「使您的帳戶更容易被找到」功能的使用者"
|
||||||
@@ -1857,7 +1874,7 @@ _theme:
|
|||||||
invalid: "佈景主題格式錯誤"
|
invalid: "佈景主題格式錯誤"
|
||||||
make: "製作佈景主題"
|
make: "製作佈景主題"
|
||||||
base: "基於"
|
base: "基於"
|
||||||
addConstant: "添加常數"
|
addConstant: "新增常數"
|
||||||
constant: "常數"
|
constant: "常數"
|
||||||
defaultValue: "預設值"
|
defaultValue: "預設值"
|
||||||
color: "顏色"
|
color: "顏色"
|
||||||
@@ -1936,16 +1953,16 @@ _ago:
|
|||||||
minutesAgo: "{n}分鐘前"
|
minutesAgo: "{n}分鐘前"
|
||||||
hoursAgo: "{n}小時前"
|
hoursAgo: "{n}小時前"
|
||||||
daysAgo: "{n}天前"
|
daysAgo: "{n}天前"
|
||||||
weeksAgo: "{n} 週前"
|
weeksAgo: "{n}周前"
|
||||||
monthsAgo: "{n}個月前"
|
monthsAgo: "{n}個月前"
|
||||||
yearsAgo: "{n}年前"
|
yearsAgo: "{n}年前"
|
||||||
invalid: "無"
|
invalid: "無"
|
||||||
_timeIn:
|
_timeIn:
|
||||||
seconds: "{n}秒後"
|
seconds: "{n}秒後"
|
||||||
minutes: "{n} 分後"
|
minutes: "{n}分鐘後"
|
||||||
hours: "{n}小時後"
|
hours: "{n}小時後"
|
||||||
days: "{n} 日後"
|
days: "{n}天後"
|
||||||
weeks: "{n} 週後"
|
weeks: "{n}周後"
|
||||||
months: "{n}個月後"
|
months: "{n}個月後"
|
||||||
years: "{n}年後"
|
years: "{n}年後"
|
||||||
_time:
|
_time:
|
||||||
@@ -2032,7 +2049,6 @@ _permissions:
|
|||||||
"read:admin:server-info": "查看伺服器的資訊"
|
"read:admin:server-info": "查看伺服器的資訊"
|
||||||
"read:admin:show-moderation-log": "查看審查紀錄"
|
"read:admin:show-moderation-log": "查看審查紀錄"
|
||||||
"read:admin:show-user": "查看使用者的私密資訊"
|
"read:admin:show-user": "查看使用者的私密資訊"
|
||||||
"read:admin:show-users": "查看使用者的私密資訊"
|
|
||||||
"write:admin:suspend-user": "凍結使用者"
|
"write:admin:suspend-user": "凍結使用者"
|
||||||
"write:admin:unset-user-avatar": "刪除使用者的頭像"
|
"write:admin:unset-user-avatar": "刪除使用者的頭像"
|
||||||
"write:admin:unset-user-banner": "刪除使用者的橫幅"
|
"write:admin:unset-user-banner": "刪除使用者的橫幅"
|
||||||
@@ -2085,13 +2101,13 @@ _antennaSources:
|
|||||||
userList: "來自特定清單中的貼文"
|
userList: "來自特定清單中的貼文"
|
||||||
userBlacklist: "除指定使用者外的所有貼文"
|
userBlacklist: "除指定使用者外的所有貼文"
|
||||||
_weekday:
|
_weekday:
|
||||||
sunday: "週日"
|
sunday: "星期天"
|
||||||
monday: "週一"
|
monday: "星期一"
|
||||||
tuesday: "週二"
|
tuesday: "星期二"
|
||||||
wednesday: "週三"
|
wednesday: "星期三"
|
||||||
thursday: "週四"
|
thursday: "星期四"
|
||||||
friday: "週五"
|
friday: "星期五"
|
||||||
saturday: "週六"
|
saturday: "星期六"
|
||||||
_widgets:
|
_widgets:
|
||||||
profile: "個人檔案"
|
profile: "個人檔案"
|
||||||
instanceInfo: "伺服器資訊"
|
instanceInfo: "伺服器資訊"
|
||||||
@@ -2173,7 +2189,7 @@ _postForm:
|
|||||||
e: "寫些什麼吧……"
|
e: "寫些什麼吧……"
|
||||||
f: "靜待發文……"
|
f: "靜待發文……"
|
||||||
_profile:
|
_profile:
|
||||||
name: "名稱"
|
name: "名字"
|
||||||
username: "使用者名稱"
|
username: "使用者名稱"
|
||||||
description: "關於我"
|
description: "關於我"
|
||||||
youCanIncludeHashtags: "你也可以在「關於我」中加上 #tag"
|
youCanIncludeHashtags: "你也可以在「關於我」中加上 #tag"
|
||||||
@@ -2247,7 +2263,7 @@ _play:
|
|||||||
_pages:
|
_pages:
|
||||||
newPage: "建立頁面"
|
newPage: "建立頁面"
|
||||||
editPage: "編輯頁面"
|
editPage: "編輯頁面"
|
||||||
readPage: "正檢視原始碼"
|
readPage: "正在檢視原始碼"
|
||||||
created: "頁面已建立"
|
created: "頁面已建立"
|
||||||
updated: "頁面已更新"
|
updated: "頁面已更新"
|
||||||
deleted: "頁面已被刪除"
|
deleted: "頁面已被刪除"
|
||||||
@@ -2274,7 +2290,7 @@ _pages:
|
|||||||
hideTitleWhenPinned: "被置頂於個人資料時隱藏頁面標題"
|
hideTitleWhenPinned: "被置頂於個人資料時隱藏頁面標題"
|
||||||
font: "字型"
|
font: "字型"
|
||||||
fontSerif: "襯線體"
|
fontSerif: "襯線體"
|
||||||
fontSansSerif: "無襯線體"
|
fontSansSerif: "黑體"
|
||||||
eyeCatchingImageSet: "設定封面影像"
|
eyeCatchingImageSet: "設定封面影像"
|
||||||
eyeCatchingImageRemove: "刪除封面影像"
|
eyeCatchingImageRemove: "刪除封面影像"
|
||||||
chooseBlock: "新增方塊"
|
chooseBlock: "新增方塊"
|
||||||
@@ -2346,6 +2362,7 @@ _deck:
|
|||||||
alwaysShowMainColumn: "總是顯示主欄"
|
alwaysShowMainColumn: "總是顯示主欄"
|
||||||
columnAlign: "對齊欄位"
|
columnAlign: "對齊欄位"
|
||||||
addColumn: "新增欄位"
|
addColumn: "新增欄位"
|
||||||
|
newNoteNotificationSettings: "新貼文通知的設定"
|
||||||
configureColumn: "欄位的設定"
|
configureColumn: "欄位的設定"
|
||||||
swapLeft: "向左移動"
|
swapLeft: "向左移動"
|
||||||
swapRight: "向右移動"
|
swapRight: "向右移動"
|
||||||
@@ -2384,7 +2401,7 @@ _drivecleaner:
|
|||||||
orderByCreatedAtAsc: "按新增日期降序排列"
|
orderByCreatedAtAsc: "按新增日期降序排列"
|
||||||
_webhookSettings:
|
_webhookSettings:
|
||||||
createWebhook: "建立 Webhook"
|
createWebhook: "建立 Webhook"
|
||||||
name: "名稱"
|
name: "名字"
|
||||||
secret: "密鑰"
|
secret: "密鑰"
|
||||||
events: "何時運行 Webhook"
|
events: "何時運行 Webhook"
|
||||||
active: "已啟用"
|
active: "已啟用"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2024.5.0-beta.3",
|
"version": "2024.5.0",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class NotRespondingSince1716345015347 {
|
||||||
|
name = 'NotRespondingSince1716345015347'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "instance" ADD "notRespondingSince" TIMESTAMP WITH TIME ZONE`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "notRespondingSince"`);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class SuspensionStateInsteadOfIsSspended1716345771510 {
|
||||||
|
name = 'SuspensionStateInsteadOfIsSspended1716345771510'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`CREATE TYPE "public"."instance_suspensionstate_enum" AS ENUM('none', 'manuallySuspended', 'goneSuspended', 'autoSuspendedForNotResponding')`);
|
||||||
|
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_34500da2e38ac393f7bb6b299c"`);
|
||||||
|
|
||||||
|
await queryRunner.query(`ALTER TABLE "instance" RENAME COLUMN "isSuspended" TO "suspensionState"`);
|
||||||
|
|
||||||
|
await queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "suspensionState" DROP DEFAULT`);
|
||||||
|
|
||||||
|
await queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "suspensionState" TYPE "public"."instance_suspensionstate_enum" USING (
|
||||||
|
CASE "suspensionState"
|
||||||
|
WHEN TRUE THEN 'manuallySuspended'::instance_suspensionstate_enum
|
||||||
|
ELSE 'none'::instance_suspensionstate_enum
|
||||||
|
END
|
||||||
|
)`);
|
||||||
|
|
||||||
|
await queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "suspensionState" SET DEFAULT 'none'`);
|
||||||
|
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_3ede46f507c87ad698051d56a8" ON "instance" ("suspensionState") `);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_3ede46f507c87ad698051d56a8"`);
|
||||||
|
|
||||||
|
await queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "suspensionState" DROP DEFAULT`);
|
||||||
|
|
||||||
|
await queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "suspensionState" TYPE boolean USING (
|
||||||
|
CASE "suspensionState"
|
||||||
|
WHEN 'none'::instance_suspensionstate_enum THEN FALSE
|
||||||
|
ELSE TRUE
|
||||||
|
END
|
||||||
|
)`);
|
||||||
|
|
||||||
|
await queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "suspensionState" SET DEFAULT false`);
|
||||||
|
|
||||||
|
await queryRunner.query(`ALTER TABLE "instance" RENAME COLUMN "suspensionState" TO "isSuspended"`);
|
||||||
|
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_34500da2e38ac393f7bb6b299c" ON "instance" ("isSuspended") `);
|
||||||
|
|
||||||
|
await queryRunner.query(`DROP TYPE "public"."instance_suspensionstate_enum"`);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class RemoveAntennaNotify1716450883149 {
|
||||||
|
name = 'RemoveAntennaNotify1716450883149'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "notify"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "antenna" ADD "notify" boolean NOT NULL`);
|
||||||
|
}
|
||||||
|
}
|
16
packages/backend/migration/1717117195275-inquiryUrl.js
Normal file
16
packages/backend/migration/1717117195275-inquiryUrl.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class InquiryUrl1717117195275 {
|
||||||
|
name = 'InquiryUrl1717117195275'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "inquiryUrl" character varying(1024)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "inquiryUrl"`);
|
||||||
|
}
|
||||||
|
}
|
@@ -4,7 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.10.0"
|
"node": "^20.10.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./built/boot/entry.js",
|
"start": "node ./built/boot/entry.js",
|
||||||
@@ -86,6 +86,8 @@
|
|||||||
"@nestjs/core": "10.3.8",
|
"@nestjs/core": "10.3.8",
|
||||||
"@nestjs/testing": "10.3.8",
|
"@nestjs/testing": "10.3.8",
|
||||||
"@peertube/http-signature": "1.7.0",
|
"@peertube/http-signature": "1.7.0",
|
||||||
|
"@sentry/node": "^8.5.0",
|
||||||
|
"@sentry/profiling-node": "^8.5.0",
|
||||||
"@simplewebauthn/server": "10.0.0",
|
"@simplewebauthn/server": "10.0.0",
|
||||||
"@sinonjs/fake-timers": "11.2.2",
|
"@sinonjs/fake-timers": "11.2.2",
|
||||||
"@smithy/node-http-handler": "2.5.0",
|
"@smithy/node-http-handler": "2.5.0",
|
||||||
|
@@ -15,6 +15,7 @@ import Logger from '@/logger.js';
|
|||||||
import { envOption } from '../env.js';
|
import { envOption } from '../env.js';
|
||||||
import { masterMain } from './master.js';
|
import { masterMain } from './master.js';
|
||||||
import { workerMain } from './worker.js';
|
import { workerMain } from './worker.js';
|
||||||
|
import { readyRef } from './ready.js';
|
||||||
|
|
||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
|
|
||||||
@@ -79,6 +80,8 @@ if (cluster.isWorker || envOption.disableClustering) {
|
|||||||
await workerMain();
|
await workerMain();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readyRef.value = true;
|
||||||
|
|
||||||
// ユニットテスト時にMisskeyが子プロセスで起動された時のため
|
// ユニットテスト時にMisskeyが子プロセスで起動された時のため
|
||||||
// それ以外のときは process.send は使えないので弾く
|
// それ以外のときは process.send は使えないので弾く
|
||||||
if (process.send) {
|
if (process.send) {
|
||||||
|
@@ -10,6 +10,8 @@ import * as os from 'node:os';
|
|||||||
import cluster from 'node:cluster';
|
import cluster from 'node:cluster';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import chalkTemplate from 'chalk-template';
|
import chalkTemplate from 'chalk-template';
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { nodeProfilingIntegration } from '@sentry/profiling-node';
|
||||||
import Logger from '@/logger.js';
|
import Logger from '@/logger.js';
|
||||||
import { loadConfig } from '@/config.js';
|
import { loadConfig } from '@/config.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
@@ -71,6 +73,24 @@ export async function masterMain() {
|
|||||||
|
|
||||||
bootLogger.succ('Misskey initialized');
|
bootLogger.succ('Misskey initialized');
|
||||||
|
|
||||||
|
if (config.sentryForBackend) {
|
||||||
|
Sentry.init({
|
||||||
|
integrations: [
|
||||||
|
...(config.sentryForBackend.enableNodeProfiling ? [nodeProfilingIntegration()] : []),
|
||||||
|
],
|
||||||
|
|
||||||
|
// Performance Monitoring
|
||||||
|
tracesSampleRate: 1.0, // Capture 100% of the transactions
|
||||||
|
|
||||||
|
// Set sampling rate for profiling - this is relative to tracesSampleRate
|
||||||
|
profilesSampleRate: 1.0,
|
||||||
|
|
||||||
|
maxBreadcrumbs: 0,
|
||||||
|
|
||||||
|
...config.sentryForBackend.options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (envOption.disableClustering) {
|
if (envOption.disableClustering) {
|
||||||
if (envOption.onlyServer) {
|
if (envOption.onlyServer) {
|
||||||
await server();
|
await server();
|
||||||
|
6
packages/backend/src/boot/ready.ts
Normal file
6
packages/backend/src/boot/ready.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const readyRef = { value: false };
|
@@ -7,6 +7,7 @@ import * as fs from 'node:fs';
|
|||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { dirname, resolve } from 'node:path';
|
import { dirname, resolve } from 'node:path';
|
||||||
import * as yaml from 'js-yaml';
|
import * as yaml from 'js-yaml';
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
import type { RedisOptions } from 'ioredis';
|
import type { RedisOptions } from 'ioredis';
|
||||||
|
|
||||||
type RedisOptionsSource = Partial<RedisOptions> & {
|
type RedisOptionsSource = Partial<RedisOptions> & {
|
||||||
@@ -56,6 +57,8 @@ type Source = {
|
|||||||
index: string;
|
index: string;
|
||||||
scope?: 'local' | 'global' | string[];
|
scope?: 'local' | 'global' | string[];
|
||||||
};
|
};
|
||||||
|
sentryForBackend?: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; };
|
||||||
|
sentryForFrontend?: { options: Partial<Sentry.NodeOptions> };
|
||||||
|
|
||||||
publishTarballInsteadOfProvideRepositoryUrl?: boolean;
|
publishTarballInsteadOfProvideRepositoryUrl?: boolean;
|
||||||
|
|
||||||
@@ -166,6 +169,8 @@ export type Config = {
|
|||||||
redisForPubsub: RedisOptions & RedisOptionsSource;
|
redisForPubsub: RedisOptions & RedisOptionsSource;
|
||||||
redisForJobQueue: RedisOptions & RedisOptionsSource;
|
redisForJobQueue: RedisOptions & RedisOptionsSource;
|
||||||
redisForTimelines: RedisOptions & RedisOptionsSource;
|
redisForTimelines: RedisOptions & RedisOptionsSource;
|
||||||
|
sentryForBackend: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; } | undefined;
|
||||||
|
sentryForFrontend: { options: Partial<Sentry.NodeOptions> } | undefined;
|
||||||
perChannelMaxNoteCacheCount: number;
|
perChannelMaxNoteCacheCount: number;
|
||||||
perUserNotificationsMaxCount: number;
|
perUserNotificationsMaxCount: number;
|
||||||
deactivateAntennaThreshold: number;
|
deactivateAntennaThreshold: number;
|
||||||
@@ -234,6 +239,8 @@ export function loadConfig(): Config {
|
|||||||
redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis,
|
redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis,
|
||||||
redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis,
|
redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis,
|
||||||
redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
|
redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
|
||||||
|
sentryForBackend: config.sentryForBackend,
|
||||||
|
sentryForFrontend: config.sentryForFrontend,
|
||||||
id: config.id,
|
id: config.id,
|
||||||
proxy: config.proxy,
|
proxy: config.proxy,
|
||||||
proxySmtp: config.proxySmtp,
|
proxySmtp: config.proxySmtp,
|
||||||
|
@@ -4,13 +4,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Brackets } from 'typeorm';
|
import { Brackets, EntityNotFoundError } from 'typeorm';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { MiUser } from '@/models/User.js';
|
import type { MiUser } from '@/models/User.js';
|
||||||
import type { AnnouncementReadsRepository, AnnouncementsRepository, MiAnnouncement, MiAnnouncementRead, UsersRepository } from '@/models/_.js';
|
import type { AnnouncementReadsRepository, AnnouncementsRepository, MiAnnouncement, MiAnnouncementRead, UsersRepository } from '@/models/_.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { Packed } from '@/misc/json-schema.js';
|
import { Packed } from '@/misc/json-schema.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntityService.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ export class AnnouncementService {
|
|||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private moderationLogService: ModerationLogService,
|
private moderationLogService: ModerationLogService,
|
||||||
|
private announcementEntityService: AnnouncementEntityService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +67,7 @@ export class AnnouncementService {
|
|||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async create(values: Partial<MiAnnouncement>, moderator?: MiUser): Promise<{ raw: MiAnnouncement; packed: Packed<'Announcement'> }> {
|
public async create(values: Partial<MiAnnouncement>, moderator?: MiUser): Promise<{ raw: MiAnnouncement; packed: Packed<'Announcement'> }> {
|
||||||
const announcement = await this.announcementsRepository.insert({
|
const announcement = await this.announcementsRepository.insertOne({
|
||||||
id: this.idService.gen(),
|
id: this.idService.gen(),
|
||||||
updatedAt: null,
|
updatedAt: null,
|
||||||
title: values.title,
|
title: values.title,
|
||||||
@@ -77,9 +79,9 @@ export class AnnouncementService {
|
|||||||
silence: values.silence,
|
silence: values.silence,
|
||||||
needConfirmationToRead: values.needConfirmationToRead,
|
needConfirmationToRead: values.needConfirmationToRead,
|
||||||
userId: values.userId,
|
userId: values.userId,
|
||||||
}).then(x => this.announcementsRepository.findOneByOrFail(x.identifiers[0]));
|
});
|
||||||
|
|
||||||
const packed = (await this.packMany([announcement]))[0];
|
const packed = await this.announcementEntityService.pack(announcement);
|
||||||
|
|
||||||
if (values.userId) {
|
if (values.userId) {
|
||||||
this.globalEventService.publishMainStream(values.userId, 'announcementCreated', {
|
this.globalEventService.publishMainStream(values.userId, 'announcementCreated', {
|
||||||
@@ -177,6 +179,24 @@ export class AnnouncementService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async getAnnouncement(announcementId: MiAnnouncement['id'], me: MiUser | null): Promise<Packed<'Announcement'>> {
|
||||||
|
const announcement = await this.announcementsRepository.findOneByOrFail({ id: announcementId });
|
||||||
|
if (me) {
|
||||||
|
if (announcement.userId && announcement.userId !== me.id) {
|
||||||
|
throw new EntityNotFoundError(this.announcementsRepository.metadata.target, { id: announcementId });
|
||||||
|
}
|
||||||
|
|
||||||
|
const read = await this.announcementReadsRepository.findOneBy({
|
||||||
|
announcementId: announcement.id,
|
||||||
|
userId: me.id,
|
||||||
|
});
|
||||||
|
return this.announcementEntityService.pack({ ...announcement, isRead: read !== null }, me);
|
||||||
|
} else {
|
||||||
|
return this.announcementEntityService.pack(announcement, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async read(user: MiUser, announcementId: MiAnnouncement['id']): Promise<void> {
|
public async read(user: MiUser, announcementId: MiAnnouncement['id']): Promise<void> {
|
||||||
try {
|
try {
|
||||||
@@ -193,29 +213,4 @@ export class AnnouncementService {
|
|||||||
this.globalEventService.publishMainStream(user.id, 'readAllAnnouncements');
|
this.globalEventService.publishMainStream(user.id, 'readAllAnnouncements');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public async packMany(
|
|
||||||
announcements: MiAnnouncement[],
|
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
|
||||||
options?: {
|
|
||||||
reads?: MiAnnouncementRead[];
|
|
||||||
},
|
|
||||||
): Promise<Packed<'Announcement'>[]> {
|
|
||||||
const reads = me ? (options?.reads ?? await this.getReads(me.id)) : [];
|
|
||||||
return announcements.map(announcement => ({
|
|
||||||
id: announcement.id,
|
|
||||||
createdAt: this.idService.parse(announcement.id).date.toISOString(),
|
|
||||||
updatedAt: announcement.updatedAt?.toISOString() ?? null,
|
|
||||||
text: announcement.text,
|
|
||||||
title: announcement.title,
|
|
||||||
imageUrl: announcement.imageUrl,
|
|
||||||
icon: announcement.icon,
|
|
||||||
display: announcement.display,
|
|
||||||
needConfirmationToRead: announcement.needConfirmationToRead,
|
|
||||||
silence: announcement.silence,
|
|
||||||
forYou: announcement.userId === me?.id,
|
|
||||||
isRead: reads.some(read => read.announcementId === announcement.id),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -55,10 +55,10 @@ export class AvatarDecorationService implements OnApplicationShutdown {
|
|||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async create(options: Partial<MiAvatarDecoration>, moderator?: MiUser): Promise<MiAvatarDecoration> {
|
public async create(options: Partial<MiAvatarDecoration>, moderator?: MiUser): Promise<MiAvatarDecoration> {
|
||||||
const created = await this.avatarDecorationsRepository.insert({
|
const created = await this.avatarDecorationsRepository.insertOne({
|
||||||
id: this.idService.gen(),
|
id: this.idService.gen(),
|
||||||
...options,
|
...options,
|
||||||
}).then(x => this.avatarDecorationsRepository.findOneByOrFail(x.identifiers[0]));
|
});
|
||||||
|
|
||||||
this.globalEventService.publishInternalEvent('avatarDecorationCreated', created);
|
this.globalEventService.publishInternalEvent('avatarDecorationCreated', created);
|
||||||
|
|
||||||
|
@@ -45,13 +45,13 @@ export class ClipService {
|
|||||||
throw new ClipService.TooManyClipsError();
|
throw new ClipService.TooManyClipsError();
|
||||||
}
|
}
|
||||||
|
|
||||||
const clip = await this.clipsRepository.insert({
|
const clip = await this.clipsRepository.insertOne({
|
||||||
id: this.idService.gen(),
|
id: this.idService.gen(),
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
name: name,
|
name: name,
|
||||||
isPublic: isPublic,
|
isPublic: isPublic,
|
||||||
description: description,
|
description: description,
|
||||||
}).then(x => this.clipsRepository.findOneByOrFail(x.identifiers[0]));
|
});
|
||||||
|
|
||||||
return clip;
|
return clip;
|
||||||
}
|
}
|
||||||
|
@@ -84,6 +84,7 @@ import ApRequestChart from './chart/charts/ap-request.js';
|
|||||||
import { ChartManagementService } from './chart/ChartManagementService.js';
|
import { ChartManagementService } from './chart/ChartManagementService.js';
|
||||||
|
|
||||||
import { AbuseUserReportEntityService } from './entities/AbuseUserReportEntityService.js';
|
import { AbuseUserReportEntityService } from './entities/AbuseUserReportEntityService.js';
|
||||||
|
import { AnnouncementEntityService } from './entities/AnnouncementEntityService.js';
|
||||||
import { AntennaEntityService } from './entities/AntennaEntityService.js';
|
import { AntennaEntityService } from './entities/AntennaEntityService.js';
|
||||||
import { AppEntityService } from './entities/AppEntityService.js';
|
import { AppEntityService } from './entities/AppEntityService.js';
|
||||||
import { AuthSessionEntityService } from './entities/AuthSessionEntityService.js';
|
import { AuthSessionEntityService } from './entities/AuthSessionEntityService.js';
|
||||||
@@ -223,6 +224,7 @@ const $ApRequestChart: Provider = { provide: 'ApRequestChart', useExisting: ApRe
|
|||||||
const $ChartManagementService: Provider = { provide: 'ChartManagementService', useExisting: ChartManagementService };
|
const $ChartManagementService: Provider = { provide: 'ChartManagementService', useExisting: ChartManagementService };
|
||||||
|
|
||||||
const $AbuseUserReportEntityService: Provider = { provide: 'AbuseUserReportEntityService', useExisting: AbuseUserReportEntityService };
|
const $AbuseUserReportEntityService: Provider = { provide: 'AbuseUserReportEntityService', useExisting: AbuseUserReportEntityService };
|
||||||
|
const $AnnouncementEntityService: Provider = { provide: 'AnnouncementEntityService', useExisting: AnnouncementEntityService };
|
||||||
const $AntennaEntityService: Provider = { provide: 'AntennaEntityService', useExisting: AntennaEntityService };
|
const $AntennaEntityService: Provider = { provide: 'AntennaEntityService', useExisting: AntennaEntityService };
|
||||||
const $AppEntityService: Provider = { provide: 'AppEntityService', useExisting: AppEntityService };
|
const $AppEntityService: Provider = { provide: 'AppEntityService', useExisting: AppEntityService };
|
||||||
const $AuthSessionEntityService: Provider = { provide: 'AuthSessionEntityService', useExisting: AuthSessionEntityService };
|
const $AuthSessionEntityService: Provider = { provide: 'AuthSessionEntityService', useExisting: AuthSessionEntityService };
|
||||||
@@ -363,6 +365,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
ChartManagementService,
|
ChartManagementService,
|
||||||
|
|
||||||
AbuseUserReportEntityService,
|
AbuseUserReportEntityService,
|
||||||
|
AnnouncementEntityService,
|
||||||
AntennaEntityService,
|
AntennaEntityService,
|
||||||
AppEntityService,
|
AppEntityService,
|
||||||
AuthSessionEntityService,
|
AuthSessionEntityService,
|
||||||
@@ -499,6 +502,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$ChartManagementService,
|
$ChartManagementService,
|
||||||
|
|
||||||
$AbuseUserReportEntityService,
|
$AbuseUserReportEntityService,
|
||||||
|
$AnnouncementEntityService,
|
||||||
$AntennaEntityService,
|
$AntennaEntityService,
|
||||||
$AppEntityService,
|
$AppEntityService,
|
||||||
$AuthSessionEntityService,
|
$AuthSessionEntityService,
|
||||||
@@ -635,6 +639,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
ChartManagementService,
|
ChartManagementService,
|
||||||
|
|
||||||
AbuseUserReportEntityService,
|
AbuseUserReportEntityService,
|
||||||
|
AnnouncementEntityService,
|
||||||
AntennaEntityService,
|
AntennaEntityService,
|
||||||
AppEntityService,
|
AppEntityService,
|
||||||
AuthSessionEntityService,
|
AuthSessionEntityService,
|
||||||
@@ -770,6 +775,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$ChartManagementService,
|
$ChartManagementService,
|
||||||
|
|
||||||
$AbuseUserReportEntityService,
|
$AbuseUserReportEntityService,
|
||||||
|
$AnnouncementEntityService,
|
||||||
$AntennaEntityService,
|
$AntennaEntityService,
|
||||||
$AppEntityService,
|
$AppEntityService,
|
||||||
$AuthSessionEntityService,
|
$AuthSessionEntityService,
|
||||||
|
@@ -68,7 +68,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||||||
localOnly: boolean;
|
localOnly: boolean;
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][];
|
roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][];
|
||||||
}, moderator?: MiUser): Promise<MiEmoji> {
|
}, moderator?: MiUser): Promise<MiEmoji> {
|
||||||
const emoji = await this.emojisRepository.insert({
|
const emoji = await this.emojisRepository.insertOne({
|
||||||
id: this.idService.gen(),
|
id: this.idService.gen(),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
name: data.name,
|
name: data.name,
|
||||||
@@ -82,7 +82,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||||||
isSensitive: data.isSensitive,
|
isSensitive: data.isSensitive,
|
||||||
localOnly: data.localOnly,
|
localOnly: data.localOnly,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||||
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
|
});
|
||||||
|
|
||||||
if (data.host == null) {
|
if (data.host == null) {
|
||||||
this.localEmojisCache.refresh();
|
this.localEmojisCache.refresh();
|
||||||
@@ -346,10 +346,11 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||||||
@bindThis
|
@bindThis
|
||||||
public async populateEmojis(emojiNames: string[], noteUserHost: string | null): Promise<Record<string, string>> {
|
public async populateEmojis(emojiNames: string[], noteUserHost: string | null): Promise<Record<string, string>> {
|
||||||
const emojis = await Promise.all(emojiNames.map(x => this.populateEmoji(x, noteUserHost)));
|
const emojis = await Promise.all(emojiNames.map(x => this.populateEmoji(x, noteUserHost)));
|
||||||
const res = {} as any;
|
const res = {} as Record<string, string>;
|
||||||
for (let i = 0; i < emojiNames.length; i++) {
|
for (let i = 0; i < emojiNames.length; i++) {
|
||||||
if (emojis[i] != null) {
|
const resolvedEmoji = emojis[i];
|
||||||
res[emojiNames[i]] = emojis[i];
|
if (resolvedEmoji != null) {
|
||||||
|
res[emojiNames[i]] = resolvedEmoji;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
@@ -220,7 +220,7 @@ export class DriveService {
|
|||||||
file.size = size;
|
file.size = size;
|
||||||
file.storedInternal = false;
|
file.storedInternal = false;
|
||||||
|
|
||||||
return await this.driveFilesRepository.insert(file).then(x => this.driveFilesRepository.findOneByOrFail(x.identifiers[0]));
|
return await this.driveFilesRepository.insertOne(file);
|
||||||
} else { // use internal storage
|
} else { // use internal storage
|
||||||
const accessKey = randomUUID();
|
const accessKey = randomUUID();
|
||||||
const thumbnailAccessKey = 'thumbnail-' + randomUUID();
|
const thumbnailAccessKey = 'thumbnail-' + randomUUID();
|
||||||
@@ -254,7 +254,7 @@ export class DriveService {
|
|||||||
file.md5 = hash;
|
file.md5 = hash;
|
||||||
file.size = size;
|
file.size = size;
|
||||||
|
|
||||||
return await this.driveFilesRepository.insert(file).then(x => this.driveFilesRepository.findOneByOrFail(x.identifiers[0]));
|
return await this.driveFilesRepository.insertOne(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,14 +497,20 @@ export class DriveService {
|
|||||||
|
|
||||||
if (user && !force) {
|
if (user && !force) {
|
||||||
// Check if there is a file with the same hash
|
// Check if there is a file with the same hash
|
||||||
const much = await this.driveFilesRepository.findOneBy({
|
const matched = await this.driveFilesRepository.findOneBy({
|
||||||
md5: info.md5,
|
md5: info.md5,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (much) {
|
if (matched) {
|
||||||
this.registerLogger.info(`file with same hash is found: ${much.id}`);
|
this.registerLogger.info(`file with same hash is found: ${matched.id}`);
|
||||||
return much;
|
if (sensitive && !matched.isSensitive) {
|
||||||
|
// The file is federated as sensitive for this time, but was federated as non-sensitive before.
|
||||||
|
// Therefore, update the file to sensitive.
|
||||||
|
await this.driveFilesRepository.update({ id: matched.id }, { isSensitive: true });
|
||||||
|
matched.isSensitive = true;
|
||||||
|
}
|
||||||
|
return matched;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -609,7 +615,7 @@ export class DriveService {
|
|||||||
file.type = info.type.mime;
|
file.type = info.type.mime;
|
||||||
file.storedInternal = false;
|
file.storedInternal = false;
|
||||||
|
|
||||||
file = await this.driveFilesRepository.insert(file).then(x => this.driveFilesRepository.findOneByOrFail(x.identifiers[0]));
|
file = await this.driveFilesRepository.insertOne(file);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// duplicate key error (when already registered)
|
// duplicate key error (when already registered)
|
||||||
if (isDuplicateKeyValueError(err)) {
|
if (isDuplicateKeyValueError(err)) {
|
||||||
|
@@ -55,11 +55,11 @@ export class FederatedInstanceService implements OnApplicationShutdown {
|
|||||||
const index = await this.instancesRepository.findOneBy({ host });
|
const index = await this.instancesRepository.findOneBy({ host });
|
||||||
|
|
||||||
if (index == null) {
|
if (index == null) {
|
||||||
const i = await this.instancesRepository.insert({
|
const i = await this.instancesRepository.insertOne({
|
||||||
id: this.idService.gen(),
|
id: this.idService.gen(),
|
||||||
host,
|
host,
|
||||||
firstRetrievedAt: new Date(),
|
firstRetrievedAt: new Date(),
|
||||||
}).then(x => this.instancesRepository.findOneByOrFail(x.identifiers[0]));
|
});
|
||||||
|
|
||||||
this.federatedInstanceCache.set(host, i);
|
this.federatedInstanceCache.set(host, i);
|
||||||
return i;
|
return i;
|
||||||
|
@@ -154,7 +154,7 @@ export class FetchInstanceMetadataService {
|
|||||||
throw new Error('No wellknown links');
|
throw new Error('No wellknown links');
|
||||||
}
|
}
|
||||||
|
|
||||||
const links = wellknown.links as any[];
|
const links = wellknown.links as ({ rel: string, href: string; })[];
|
||||||
|
|
||||||
const link1_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/1.0');
|
const link1_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/1.0');
|
||||||
const link2_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.0');
|
const link2_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.0');
|
||||||
|
@@ -53,11 +53,11 @@ export class RelayService {
|
|||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async addRelay(inbox: string): Promise<MiRelay> {
|
public async addRelay(inbox: string): Promise<MiRelay> {
|
||||||
const relay = await this.relaysRepository.insert({
|
const relay = await this.relaysRepository.insertOne({
|
||||||
id: this.idService.gen(),
|
id: this.idService.gen(),
|
||||||
inbox,
|
inbox,
|
||||||
status: 'requesting',
|
status: 'requesting',
|
||||||
}).then(x => this.relaysRepository.findOneByOrFail(x.identifiers[0]));
|
});
|
||||||
|
|
||||||
const relayActor = await this.getRelayActor();
|
const relayActor = await this.getRelayActor();
|
||||||
const follow = await this.apRendererService.renderFollowRelay(relay, relayActor);
|
const follow = await this.apRendererService.renderFollowRelay(relay, relayActor);
|
||||||
|
@@ -281,7 +281,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
|||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async matched(parentId: MiUser['id'], childId: MiUser['id'], options: { noIrregularRules: boolean; }): Promise<MiReversiGame> {
|
private async matched(parentId: MiUser['id'], childId: MiUser['id'], options: { noIrregularRules: boolean; }): Promise<MiReversiGame> {
|
||||||
const game = await this.reversiGamesRepository.insert({
|
const game = await this.reversiGamesRepository.insertOne({
|
||||||
id: this.idService.gen(),
|
id: this.idService.gen(),
|
||||||
user1Id: parentId,
|
user1Id: parentId,
|
||||||
user2Id: childId,
|
user2Id: childId,
|
||||||
@@ -294,10 +294,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
|||||||
bw: 'random',
|
bw: 'random',
|
||||||
isLlotheo: false,
|
isLlotheo: false,
|
||||||
noIrregularRules: options.noIrregularRules,
|
noIrregularRules: options.noIrregularRules,
|
||||||
}).then(x => this.reversiGamesRepository.findOneOrFail({
|
}, { relations: ['user1', 'user2'] });
|
||||||
where: { id: x.identifiers[0].id },
|
|
||||||
relations: ['user1', 'user2'],
|
|
||||||
}));
|
|
||||||
this.cacheGame(game);
|
this.cacheGame(game);
|
||||||
|
|
||||||
const packed = await this.reversiGameEntityService.packDetail(game);
|
const packed = await this.reversiGameEntityService.packDetail(game);
|
||||||
|
@@ -471,12 +471,12 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const created = await this.roleAssignmentsRepository.insert({
|
const created = await this.roleAssignmentsRepository.insertOne({
|
||||||
id: this.idService.gen(now),
|
id: this.idService.gen(now),
|
||||||
expiresAt: expiresAt,
|
expiresAt: expiresAt,
|
||||||
roleId: roleId,
|
roleId: roleId,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
}).then(x => this.roleAssignmentsRepository.findOneByOrFail(x.identifiers[0]));
|
});
|
||||||
|
|
||||||
this.rolesRepository.update(roleId, {
|
this.rolesRepository.update(roleId, {
|
||||||
lastUsedAt: new Date(),
|
lastUsedAt: new Date(),
|
||||||
@@ -558,7 +558,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||||||
@bindThis
|
@bindThis
|
||||||
public async create(values: Partial<MiRole>, moderator?: MiUser): Promise<MiRole> {
|
public async create(values: Partial<MiRole>, moderator?: MiUser): Promise<MiRole> {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
const created = await this.rolesRepository.insert({
|
const created = await this.rolesRepository.insertOne({
|
||||||
id: this.idService.gen(date.getTime()),
|
id: this.idService.gen(date.getTime()),
|
||||||
updatedAt: date,
|
updatedAt: date,
|
||||||
lastUsedAt: date,
|
lastUsedAt: date,
|
||||||
@@ -576,7 +576,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||||||
canEditMembersByModerator: values.canEditMembersByModerator,
|
canEditMembersByModerator: values.canEditMembersByModerator,
|
||||||
displayOrder: values.displayOrder,
|
displayOrder: values.displayOrder,
|
||||||
policies: values.policies,
|
policies: values.policies,
|
||||||
}).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0]));
|
});
|
||||||
|
|
||||||
this.globalEventService.publishInternalEvent('roleCreated', created);
|
this.globalEventService.publishInternalEvent('roleCreated', created);
|
||||||
|
|
||||||
|
@@ -517,7 +517,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||||||
followerId: follower.id,
|
followerId: follower.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const followRequest = await this.followRequestsRepository.insert({
|
const followRequest = await this.followRequestsRepository.insertOne({
|
||||||
id: this.idService.gen(),
|
id: this.idService.gen(),
|
||||||
followerId: follower.id,
|
followerId: follower.id,
|
||||||
followeeId: followee.id,
|
followeeId: followee.id,
|
||||||
@@ -531,7 +531,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||||||
followeeHost: followee.host,
|
followeeHost: followee.host,
|
||||||
followeeInbox: this.userEntityService.isRemoteUser(followee) ? followee.inbox : undefined,
|
followeeInbox: this.userEntityService.isRemoteUser(followee) ? followee.inbox : undefined,
|
||||||
followeeSharedInbox: this.userEntityService.isRemoteUser(followee) ? followee.sharedInbox : undefined,
|
followeeSharedInbox: this.userEntityService.isRemoteUser(followee) ? followee.sharedInbox : undefined,
|
||||||
}).then(x => this.followRequestsRepository.findOneByOrFail(x.identifiers[0]));
|
});
|
||||||
|
|
||||||
// Publish receiveRequest event
|
// Publish receiveRequest event
|
||||||
if (this.userEntityService.isLocalUser(followee)) {
|
if (this.userEntityService.isLocalUser(followee)) {
|
||||||
|
@@ -28,6 +28,7 @@ import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserR
|
|||||||
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 { isNotNull } from '@/misc/is-not-null.js';
|
import { isNotNull } from '@/misc/is-not-null.js';
|
||||||
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
|
import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
|
||||||
import { ApNoteService } from './models/ApNoteService.js';
|
import { ApNoteService } from './models/ApNoteService.js';
|
||||||
import { ApLoggerService } from './ApLoggerService.js';
|
import { ApLoggerService } from './ApLoggerService.js';
|
||||||
@@ -36,9 +37,8 @@ import { ApResolverService } from './ApResolverService.js';
|
|||||||
import { ApAudienceService } from './ApAudienceService.js';
|
import { ApAudienceService } from './ApAudienceService.js';
|
||||||
import { ApPersonService } from './models/ApPersonService.js';
|
import { ApPersonService } from './models/ApPersonService.js';
|
||||||
import { ApQuestionService } from './models/ApQuestionService.js';
|
import { ApQuestionService } from './models/ApQuestionService.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
|
||||||
import type { Resolver } from './ApResolverService.js';
|
import type { Resolver } from './ApResolverService.js';
|
||||||
import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove } from './type.js';
|
import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove, IPost } from './type.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApInboxService {
|
export class ApInboxService {
|
||||||
@@ -90,13 +90,15 @@ export class ApInboxService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async performActivity(actor: MiRemoteUser, activity: IObject): Promise<void> {
|
public async performActivity(actor: MiRemoteUser, activity: IObject): Promise<string | void> {
|
||||||
|
let result = undefined as string | void;
|
||||||
if (isCollectionOrOrderedCollection(activity)) {
|
if (isCollectionOrOrderedCollection(activity)) {
|
||||||
|
const results = [] as [string, string | void][];
|
||||||
const resolver = this.apResolverService.createResolver();
|
const resolver = this.apResolverService.createResolver();
|
||||||
for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) {
|
for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) {
|
||||||
const act = await resolver.resolve(item);
|
const act = await resolver.resolve(item);
|
||||||
try {
|
try {
|
||||||
await this.performOneActivity(actor, act);
|
results.push([getApId(item), await this.performOneActivity(actor, act)]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof Error || typeof err === 'string') {
|
if (err instanceof Error || typeof err === 'string') {
|
||||||
this.logger.error(err);
|
this.logger.error(err);
|
||||||
@@ -105,8 +107,13 @@ export class ApInboxService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasReason = results.some(([, reason]) => (reason != null && !reason.startsWith('ok')));
|
||||||
|
if (hasReason) {
|
||||||
|
result = results.map(([id, reason]) => `${id}: ${reason}`).join('\n');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
await this.performOneActivity(actor, activity);
|
result = await this.performOneActivity(actor, activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ついでにリモートユーザーの情報が古かったら更新しておく
|
// ついでにリモートユーザーの情報が古かったら更新しておく
|
||||||
@@ -117,42 +124,43 @@ export class ApInboxService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async performOneActivity(actor: MiRemoteUser, activity: IObject): Promise<void> {
|
public async performOneActivity(actor: MiRemoteUser, activity: IObject): Promise<string | void> {
|
||||||
if (actor.isSuspended) return;
|
if (actor.isSuspended) return;
|
||||||
|
|
||||||
if (isCreate(activity)) {
|
if (isCreate(activity)) {
|
||||||
await this.create(actor, activity);
|
return await this.create(actor, activity);
|
||||||
} else if (isDelete(activity)) {
|
} else if (isDelete(activity)) {
|
||||||
await this.delete(actor, activity);
|
return await this.delete(actor, activity);
|
||||||
} else if (isUpdate(activity)) {
|
} else if (isUpdate(activity)) {
|
||||||
await this.update(actor, activity);
|
return await this.update(actor, activity);
|
||||||
} else if (isFollow(activity)) {
|
} else if (isFollow(activity)) {
|
||||||
await this.follow(actor, activity);
|
return await this.follow(actor, activity);
|
||||||
} else if (isAccept(activity)) {
|
} else if (isAccept(activity)) {
|
||||||
await this.accept(actor, activity);
|
return await this.accept(actor, activity);
|
||||||
} else if (isReject(activity)) {
|
} else if (isReject(activity)) {
|
||||||
await this.reject(actor, activity);
|
return await this.reject(actor, activity);
|
||||||
} else if (isAdd(activity)) {
|
} else if (isAdd(activity)) {
|
||||||
await this.add(actor, activity).catch(err => this.logger.error(err));
|
return await this.add(actor, activity);
|
||||||
} else if (isRemove(activity)) {
|
} else if (isRemove(activity)) {
|
||||||
await this.remove(actor, activity).catch(err => this.logger.error(err));
|
return await this.remove(actor, activity);
|
||||||
} else if (isAnnounce(activity)) {
|
} else if (isAnnounce(activity)) {
|
||||||
await this.announce(actor, activity);
|
return await this.announce(actor, activity);
|
||||||
} else if (isLike(activity)) {
|
} else if (isLike(activity)) {
|
||||||
await this.like(actor, activity);
|
return await this.like(actor, activity);
|
||||||
} else if (isUndo(activity)) {
|
} else if (isUndo(activity)) {
|
||||||
await this.undo(actor, activity);
|
return await this.undo(actor, activity);
|
||||||
} else if (isBlock(activity)) {
|
} else if (isBlock(activity)) {
|
||||||
await this.block(actor, activity);
|
return await this.block(actor, activity);
|
||||||
} else if (isFlag(activity)) {
|
} else if (isFlag(activity)) {
|
||||||
await this.flag(actor, activity);
|
return await this.flag(actor, activity);
|
||||||
} else if (isMove(activity)) {
|
} else if (isMove(activity)) {
|
||||||
await this.move(actor, activity);
|
return await this.move(actor, activity);
|
||||||
} else {
|
} else {
|
||||||
this.logger.warn(`unrecognized activity type: ${activity.type}`);
|
return `unrecognized activity type: ${activity.type}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,38 +242,49 @@ export class ApInboxService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async add(actor: MiRemoteUser, activity: IAdd): Promise<void> {
|
private async add(actor: MiRemoteUser, activity: IAdd): Promise<string | void> {
|
||||||
if (actor.uri !== activity.actor) {
|
if (actor.uri !== activity.actor) {
|
||||||
throw new Error('invalid actor');
|
return 'invalid actor';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activity.target == null) {
|
if (activity.target == null) {
|
||||||
throw new Error('target is null');
|
return 'target is null';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activity.target === actor.featured) {
|
if (activity.target === actor.featured) {
|
||||||
const note = await this.apNoteService.resolveNote(activity.object);
|
const note = await this.apNoteService.resolveNote(activity.object);
|
||||||
if (note == null) throw new Error('note not found');
|
if (note == null) return 'note not found';
|
||||||
await this.notePiningService.addPinned(actor, note.id);
|
await this.notePiningService.addPinned(actor, note.id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`unknown target: ${activity.target}`);
|
return `unknown target: ${activity.target}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async announce(actor: MiRemoteUser, activity: IAnnounce): Promise<void> {
|
private async announce(actor: MiRemoteUser, activity: IAnnounce): Promise<string | void> {
|
||||||
const uri = getApId(activity);
|
const uri = getApId(activity);
|
||||||
|
|
||||||
this.logger.info(`Announce: ${uri}`);
|
this.logger.info(`Announce: ${uri}`);
|
||||||
|
|
||||||
const targetUri = getApId(activity.object);
|
const resolver = this.apResolverService.createResolver();
|
||||||
|
|
||||||
await this.announceNote(actor, activity, targetUri);
|
if (!activity.object) return 'skip: activity has no object property';
|
||||||
|
const targetUri = getApId(activity.object);
|
||||||
|
if (targetUri.startsWith('bear:')) return 'skip: bearcaps url not supported.';
|
||||||
|
|
||||||
|
const target = await resolver.resolve(activity.object).catch(e => {
|
||||||
|
this.logger.error(`Resolution failed: ${e}`);
|
||||||
|
return e;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isPost(target)) return await this.announceNote(actor, activity, target);
|
||||||
|
|
||||||
|
return `skip: unknown object type ${getApType(target)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async announceNote(actor: MiRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> {
|
private async announceNote(actor: MiRemoteUser, activity: IAnnounce, target: IPost): Promise<string | void> {
|
||||||
const uri = getApId(activity);
|
const uri = getApId(activity);
|
||||||
|
|
||||||
if (actor.isSuspended) {
|
if (actor.isSuspended) {
|
||||||
@@ -288,24 +307,21 @@ export class ApInboxService {
|
|||||||
// Announce対象をresolve
|
// Announce対象をresolve
|
||||||
let renote;
|
let renote;
|
||||||
try {
|
try {
|
||||||
renote = await this.apNoteService.resolveNote(targetUri);
|
renote = await this.apNoteService.resolveNote(target);
|
||||||
if (renote == null) throw new Error('announce target is null');
|
if (renote == null) return 'announce target is null';
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// 対象が4xxならスキップ
|
// 対象が4xxならスキップ
|
||||||
if (err instanceof StatusError) {
|
if (err instanceof StatusError) {
|
||||||
if (!err.isRetryable) {
|
if (!err.isRetryable) {
|
||||||
this.logger.warn(`Ignored announce target ${targetUri} - ${err.statusCode}`);
|
return `Ignored announce target ${target.id} - ${err.statusCode}`;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return `Error in announce target ${target.id} - ${err.statusCode}`;
|
||||||
this.logger.warn(`Error in announce target ${targetUri} - ${err.statusCode}`);
|
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!await this.noteEntityService.isVisibleForMe(renote, actor.id)) {
|
if (!await this.noteEntityService.isVisibleForMe(renote, actor.id)) {
|
||||||
this.logger.warn('skip: invalid actor for this activity');
|
return 'skip: invalid actor for this activity';
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.info(`Creating the (Re)Note: ${uri}`);
|
this.logger.info(`Creating the (Re)Note: ${uri}`);
|
||||||
@@ -314,8 +330,7 @@ export class ApInboxService {
|
|||||||
const createdAt = activity.published ? new Date(activity.published) : null;
|
const createdAt = activity.published ? new Date(activity.published) : null;
|
||||||
|
|
||||||
if (createdAt && createdAt < this.idService.parse(renote.id).date) {
|
if (createdAt && createdAt < this.idService.parse(renote.id).date) {
|
||||||
this.logger.warn('skip: malformed createdAt');
|
return 'skip: malformed createdAt';
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.noteCreateService.create(actor, {
|
await this.noteCreateService.create(actor, {
|
||||||
@@ -349,11 +364,15 @@ export class ApInboxService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async create(actor: MiRemoteUser, activity: ICreate): Promise<void> {
|
private async create(actor: MiRemoteUser, activity: ICreate): Promise<string | void> {
|
||||||
const uri = getApId(activity);
|
const uri = getApId(activity);
|
||||||
|
|
||||||
this.logger.info(`Create: ${uri}`);
|
this.logger.info(`Create: ${uri}`);
|
||||||
|
|
||||||
|
if (!activity.object) return 'skip: activity has no object property';
|
||||||
|
const targetUri = getApId(activity.object);
|
||||||
|
if (targetUri.startsWith('bear:')) return 'skip: bearcaps url not supported.';
|
||||||
|
|
||||||
// copy audiences between activity <=> object.
|
// copy audiences between activity <=> object.
|
||||||
if (typeof activity.object === 'object') {
|
if (typeof activity.object === 'object') {
|
||||||
const to = unique(concat([toArray(activity.to), toArray(activity.object.to)]));
|
const to = unique(concat([toArray(activity.to), toArray(activity.object.to)]));
|
||||||
@@ -380,7 +399,7 @@ export class ApInboxService {
|
|||||||
if (isPost(object)) {
|
if (isPost(object)) {
|
||||||
await this.createNote(resolver, actor, object, false, activity);
|
await this.createNote(resolver, actor, object, false, activity);
|
||||||
} else {
|
} else {
|
||||||
this.logger.warn(`Unknown type: ${getApType(object)}`);
|
return `Unknown type: ${getApType(object)}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -422,7 +441,7 @@ export class ApInboxService {
|
|||||||
@bindThis
|
@bindThis
|
||||||
private async delete(actor: MiRemoteUser, activity: IDelete): Promise<string> {
|
private async delete(actor: MiRemoteUser, activity: IDelete): Promise<string> {
|
||||||
if (actor.uri !== activity.actor) {
|
if (actor.uri !== activity.actor) {
|
||||||
throw new Error('invalid actor');
|
return 'invalid actor';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 削除対象objectのtype
|
// 削除対象objectのtype
|
||||||
@@ -581,29 +600,29 @@ export class ApInboxService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async remove(actor: MiRemoteUser, activity: IRemove): Promise<void> {
|
private async remove(actor: MiRemoteUser, activity: IRemove): Promise<string | void> {
|
||||||
if (actor.uri !== activity.actor) {
|
if (actor.uri !== activity.actor) {
|
||||||
throw new Error('invalid actor');
|
return 'invalid actor';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activity.target == null) {
|
if (activity.target == null) {
|
||||||
throw new Error('target is null');
|
return 'target is null';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activity.target === actor.featured) {
|
if (activity.target === actor.featured) {
|
||||||
const note = await this.apNoteService.resolveNote(activity.object);
|
const note = await this.apNoteService.resolveNote(activity.object);
|
||||||
if (note == null) throw new Error('note not found');
|
if (note == null) return 'note not found';
|
||||||
await this.notePiningService.removePinned(actor, note.id);
|
await this.notePiningService.removePinned(actor, note.id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`unknown target: ${activity.target}`);
|
return `unknown target: ${activity.target}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async undo(actor: MiRemoteUser, activity: IUndo): Promise<string> {
|
private async undo(actor: MiRemoteUser, activity: IUndo): Promise<string> {
|
||||||
if (actor.uri !== activity.actor) {
|
if (actor.uri !== activity.actor) {
|
||||||
throw new Error('invalid actor');
|
return 'invalid actor';
|
||||||
}
|
}
|
||||||
|
|
||||||
const uri = activity.id ?? activity;
|
const uri = activity.id ?? activity;
|
||||||
@@ -614,7 +633,7 @@ export class ApInboxService {
|
|||||||
|
|
||||||
const object = await resolver.resolve(activity.object).catch(e => {
|
const object = await resolver.resolve(activity.object).catch(e => {
|
||||||
this.logger.error(`Resolution failed: ${e}`);
|
this.logger.error(`Resolution failed: ${e}`);
|
||||||
throw e;
|
return e;
|
||||||
});
|
});
|
||||||
|
|
||||||
// don't queue because the sender may attempt again when timeout
|
// don't queue because the sender may attempt again when timeout
|
||||||
|
@@ -81,20 +81,20 @@ export class ApNoteService {
|
|||||||
const expectHost = this.utilityService.extractDbHost(uri);
|
const expectHost = this.utilityService.extractDbHost(uri);
|
||||||
|
|
||||||
if (!validPost.includes(getApType(object))) {
|
if (!validPost.includes(getApType(object))) {
|
||||||
return new Error(`invalid Note: invalid object type ${getApType(object)}`);
|
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: invalid object type ${getApType(object)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (object.id && this.utilityService.extractDbHost(object.id) !== expectHost) {
|
if (object.id && this.utilityService.extractDbHost(object.id) !== expectHost) {
|
||||||
return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`);
|
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const actualHost = object.attributedTo && this.utilityService.extractDbHost(getOneApId(object.attributedTo));
|
const actualHost = object.attributedTo && this.utilityService.extractDbHost(getOneApId(object.attributedTo));
|
||||||
if (object.attributedTo && actualHost !== expectHost) {
|
if (object.attributedTo && actualHost !== expectHost) {
|
||||||
return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`);
|
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (object.published && !this.idService.isSafeT(new Date(object.published).valueOf())) {
|
if (object.published && !this.idService.isSafeT(new Date(object.published).valueOf())) {
|
||||||
return new Error('invalid Note: published timestamp is malformed');
|
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', 'invalid Note: published timestamp is malformed');
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -407,7 +407,7 @@ export class ApNoteService {
|
|||||||
|
|
||||||
this.logger.info(`register emoji host=${host}, name=${name}`);
|
this.logger.info(`register emoji host=${host}, name=${name}`);
|
||||||
|
|
||||||
return await this.emojisRepository.insert({
|
return await this.emojisRepository.insertOne({
|
||||||
id: this.idService.gen(),
|
id: this.idService.gen(),
|
||||||
host,
|
host,
|
||||||
name,
|
name,
|
||||||
@@ -416,7 +416,7 @@ export class ApNoteService {
|
|||||||
publicUrl: tag.icon.url,
|
publicUrl: tag.icon.url,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
aliases: [],
|
aliases: [],
|
||||||
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
|
});
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -328,3 +328,4 @@ export const isAnnounce = (object: IObject): object is IAnnounce => getApType(ob
|
|||||||
export const isBlock = (object: IObject): object is IBlock => getApType(object) === 'Block';
|
export const isBlock = (object: IObject): object is IBlock => getApType(object) === 'Block';
|
||||||
export const isFlag = (object: IObject): object is IFlag => getApType(object) === 'Flag';
|
export const isFlag = (object: IObject): object is IFlag => getApType(object) === 'Flag';
|
||||||
export const isMove = (object: IObject): object is IMove => getApType(object) === 'Move';
|
export const isMove = (object: IObject): object is IMove => getApType(object) === 'Move';
|
||||||
|
export const isNote = (object: IObject): object is IPost => getApType(object) === 'Note';
|
||||||
|
@@ -14,7 +14,8 @@ import { EntitySchema, LessThan, Between } from 'typeorm';
|
|||||||
import { dateUTC, isTimeSame, isTimeBefore, subtractTime, addTime } from '@/misc/prelude/time.js';
|
import { dateUTC, isTimeSame, isTimeBefore, subtractTime, addTime } from '@/misc/prelude/time.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import type { Repository, DataSource } from 'typeorm';
|
import { MiRepository, miRepository } from '@/models/_.js';
|
||||||
|
import type { DataSource, Repository } from 'typeorm';
|
||||||
|
|
||||||
const COLUMN_PREFIX = '___' as const;
|
const COLUMN_PREFIX = '___' as const;
|
||||||
const UNIQUE_TEMP_COLUMN_PREFIX = 'unique_temp___' as const;
|
const UNIQUE_TEMP_COLUMN_PREFIX = 'unique_temp___' as const;
|
||||||
@@ -145,10 +146,10 @@ export default abstract class Chart<T extends Schema> {
|
|||||||
group: string | null;
|
group: string | null;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
// ↓にしたいけどfindOneとかで型エラーになる
|
// ↓にしたいけどfindOneとかで型エラーになる
|
||||||
//private repositoryForHour: Repository<RawRecord<T>>;
|
//private repositoryForHour: Repository<RawRecord<T>> & MiRepository<RawRecord<T>>;
|
||||||
//private repositoryForDay: Repository<RawRecord<T>>;
|
//private repositoryForDay: Repository<RawRecord<T>> & MiRepository<RawRecord<T>>;
|
||||||
private repositoryForHour: Repository<{ id: number; group?: string | null; date: number; }>;
|
private repositoryForHour: Repository<{ id: number; group?: string | null; date: number; }> & MiRepository<{ id: number; group?: string | null; date: number; }>;
|
||||||
private repositoryForDay: Repository<{ id: number; group?: string | null; date: number; }>;
|
private repositoryForDay: Repository<{ id: number; group?: string | null; date: number; }> & MiRepository<{ id: number; group?: string | null; date: number; }>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1日に一回程度実行されれば良いような計算処理を入れる(主にCASCADE削除などアプリケーション側で感知できない変動によるズレの修正用)
|
* 1日に一回程度実行されれば良いような計算処理を入れる(主にCASCADE削除などアプリケーション側で感知できない変動によるズレの修正用)
|
||||||
@@ -211,6 +212,10 @@ export default abstract class Chart<T extends Schema> {
|
|||||||
} {
|
} {
|
||||||
const createEntity = (span: 'hour' | 'day'): EntitySchema => new EntitySchema({
|
const createEntity = (span: 'hour' | 'day'): EntitySchema => new EntitySchema({
|
||||||
name:
|
name:
|
||||||
|
span === 'hour' ? `ChartX${name}` :
|
||||||
|
span === 'day' ? `ChartDayX${name}` :
|
||||||
|
new Error('not happen') as never,
|
||||||
|
tableName:
|
||||||
span === 'hour' ? `__chart__${camelToSnake(name)}` :
|
span === 'hour' ? `__chart__${camelToSnake(name)}` :
|
||||||
span === 'day' ? `__chart_day__${camelToSnake(name)}` :
|
span === 'day' ? `__chart_day__${camelToSnake(name)}` :
|
||||||
new Error('not happen') as never,
|
new Error('not happen') as never,
|
||||||
@@ -271,8 +276,8 @@ export default abstract class Chart<T extends Schema> {
|
|||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
|
|
||||||
const { hour, day } = Chart.schemaToEntity(name, schema, grouped);
|
const { hour, day } = Chart.schemaToEntity(name, schema, grouped);
|
||||||
this.repositoryForHour = db.getRepository<{ id: number; group?: string | null; date: number; }>(hour);
|
this.repositoryForHour = db.getRepository<{ id: number; group?: string | null; date: number; }>(hour).extend(miRepository as MiRepository<{ id: number; group?: string | null; date: number; }>);
|
||||||
this.repositoryForDay = db.getRepository<{ id: number; group?: string | null; date: number; }>(day);
|
this.repositoryForDay = db.getRepository<{ id: number; group?: string | null; date: number; }>(day).extend(miRepository as MiRepository<{ id: number; group?: string | null; date: number; }>);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
@@ -387,11 +392,11 @@ export default abstract class Chart<T extends Schema> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 新規ログ挿入
|
// 新規ログ挿入
|
||||||
log = await repository.insert({
|
log = await repository.insertOne({
|
||||||
date: date,
|
date: date,
|
||||||
...(group ? { group: group } : {}),
|
...(group ? { group: group } : {}),
|
||||||
...columns,
|
...columns,
|
||||||
}).then(x => repository.findOneByOrFail(x.identifiers[0])) as RawRecord<T>;
|
}) as RawRecord<T>;
|
||||||
|
|
||||||
this.logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): New commit created`);
|
this.logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): New commit created`);
|
||||||
|
|
||||||
|
@@ -10,6 +10,8 @@ import { awaitAll } from '@/misc/prelude/await-all.js';
|
|||||||
import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
|
import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { isNotNull } from '@/misc/is-not-null.js';
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
import { UserEntityService } from './UserEntityService.js';
|
import { UserEntityService } from './UserEntityService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -26,6 +28,11 @@ export class AbuseUserReportEntityService {
|
|||||||
@bindThis
|
@bindThis
|
||||||
public async pack(
|
public async pack(
|
||||||
src: MiAbuseUserReport['id'] | MiAbuseUserReport,
|
src: MiAbuseUserReport['id'] | MiAbuseUserReport,
|
||||||
|
hint?: {
|
||||||
|
packedReporter?: Packed<'UserDetailedNotMe'>,
|
||||||
|
packedTargetUser?: Packed<'UserDetailedNotMe'>,
|
||||||
|
packedAssignee?: Packed<'UserDetailedNotMe'>,
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
const report = typeof src === 'object' ? src : await this.abuseUserReportsRepository.findOneByOrFail({ id: src });
|
const report = typeof src === 'object' ? src : await this.abuseUserReportsRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
@@ -37,13 +44,13 @@ export class AbuseUserReportEntityService {
|
|||||||
reporterId: report.reporterId,
|
reporterId: report.reporterId,
|
||||||
targetUserId: report.targetUserId,
|
targetUserId: report.targetUserId,
|
||||||
assigneeId: report.assigneeId,
|
assigneeId: report.assigneeId,
|
||||||
reporter: this.userEntityService.pack(report.reporter ?? report.reporterId, null, {
|
reporter: hint?.packedReporter ?? this.userEntityService.pack(report.reporter ?? report.reporterId, null, {
|
||||||
schema: 'UserDetailedNotMe',
|
schema: 'UserDetailedNotMe',
|
||||||
}),
|
}),
|
||||||
targetUser: this.userEntityService.pack(report.targetUser ?? report.targetUserId, null, {
|
targetUser: hint?.packedTargetUser ?? this.userEntityService.pack(report.targetUser ?? report.targetUserId, null, {
|
||||||
schema: 'UserDetailedNotMe',
|
schema: 'UserDetailedNotMe',
|
||||||
}),
|
}),
|
||||||
assignee: report.assigneeId ? this.userEntityService.pack(report.assignee ?? report.assigneeId, null, {
|
assignee: report.assigneeId ? hint?.packedAssignee ?? this.userEntityService.pack(report.assignee ?? report.assigneeId, null, {
|
||||||
schema: 'UserDetailedNotMe',
|
schema: 'UserDetailedNotMe',
|
||||||
}) : null,
|
}) : null,
|
||||||
forwarded: report.forwarded,
|
forwarded: report.forwarded,
|
||||||
@@ -51,9 +58,24 @@ export class AbuseUserReportEntityService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
reports: any[],
|
reports: MiAbuseUserReport[],
|
||||||
) {
|
) {
|
||||||
return Promise.all(reports.map(x => this.pack(x)));
|
const _reporters = reports.map(({ reporter, reporterId }) => reporter ?? reporterId);
|
||||||
|
const _targetUsers = reports.map(({ targetUser, targetUserId }) => targetUser ?? targetUserId);
|
||||||
|
const _assignees = reports.map(({ assignee, assigneeId }) => assignee ?? assigneeId).filter(isNotNull);
|
||||||
|
const _userMap = await this.userEntityService.packMany(
|
||||||
|
[..._reporters, ..._targetUsers, ..._assignees],
|
||||||
|
null,
|
||||||
|
{ schema: 'UserDetailedNotMe' },
|
||||||
|
).then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(
|
||||||
|
reports.map(report => {
|
||||||
|
const packedReporter = _userMap.get(report.reporterId);
|
||||||
|
const packedTargetUser = _userMap.get(report.targetUserId);
|
||||||
|
const packedAssignee = report.assigneeId != null ? _userMap.get(report.assigneeId) : undefined;
|
||||||
|
return this.pack(report, { packedReporter, packedTargetUser, packedAssignee });
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import type { AnnouncementsRepository, AnnouncementReadsRepository, MiAnnouncement, MiUser } from '@/models/_.js';
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AnnouncementEntityService {
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.announcementsRepository)
|
||||||
|
private announcementsRepository: AnnouncementsRepository,
|
||||||
|
|
||||||
|
@Inject(DI.announcementReadsRepository)
|
||||||
|
private announcementReadsRepository: AnnouncementReadsRepository,
|
||||||
|
|
||||||
|
private idService: IdService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async pack(
|
||||||
|
src: MiAnnouncement['id'] | MiAnnouncement & { isRead?: boolean | null },
|
||||||
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
): Promise<Packed<'Announcement'>> {
|
||||||
|
const announcement = typeof src === 'object'
|
||||||
|
? src
|
||||||
|
: await this.announcementsRepository.findOneByOrFail({
|
||||||
|
id: src,
|
||||||
|
}) as MiAnnouncement & { isRead?: boolean | null };
|
||||||
|
|
||||||
|
if (me && announcement.isRead === undefined) {
|
||||||
|
announcement.isRead = await this.announcementReadsRepository
|
||||||
|
.countBy({
|
||||||
|
announcementId: announcement.id,
|
||||||
|
userId: me.id,
|
||||||
|
})
|
||||||
|
.then((count: number) => count > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: announcement.id,
|
||||||
|
createdAt: this.idService.parse(announcement.id).date.toISOString(),
|
||||||
|
updatedAt: announcement.updatedAt?.toISOString() ?? null,
|
||||||
|
title: announcement.title,
|
||||||
|
text: announcement.text,
|
||||||
|
imageUrl: announcement.imageUrl,
|
||||||
|
icon: announcement.icon,
|
||||||
|
display: announcement.display,
|
||||||
|
forYou: announcement.userId === me?.id,
|
||||||
|
needConfirmationToRead: announcement.needConfirmationToRead,
|
||||||
|
silence: announcement.silence,
|
||||||
|
isRead: announcement.isRead !== null ? announcement.isRead : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async packMany(
|
||||||
|
announcements: (MiAnnouncement['id'] | MiAnnouncement & { isRead?: boolean | null } | MiAnnouncement)[],
|
||||||
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
) : Promise<Packed<'Announcement'>[]> {
|
||||||
|
return (await Promise.allSettled(announcements.map(x => this.pack(x, me))))
|
||||||
|
.filter(result => result.status === 'fulfilled')
|
||||||
|
.map(result => (result as PromiseFulfilledResult<Packed<'Announcement'>>).value);
|
||||||
|
}
|
||||||
|
}
|
@@ -38,12 +38,12 @@ export class AntennaEntityService {
|
|||||||
users: antenna.users,
|
users: antenna.users,
|
||||||
caseSensitive: antenna.caseSensitive,
|
caseSensitive: antenna.caseSensitive,
|
||||||
localOnly: antenna.localOnly,
|
localOnly: antenna.localOnly,
|
||||||
notify: antenna.notify,
|
|
||||||
excludeBots: antenna.excludeBots,
|
excludeBots: antenna.excludeBots,
|
||||||
withReplies: antenna.withReplies,
|
withReplies: antenna.withReplies,
|
||||||
withFile: antenna.withFile,
|
withFile: antenna.withFile,
|
||||||
isActive: antenna.isActive,
|
isActive: antenna.isActive,
|
||||||
hasUnreadNote: false, // TODO
|
hasUnreadNote: false, // TODO
|
||||||
|
notify: false, // 後方互換性のため
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -29,6 +29,9 @@ export class BlockingEntityService {
|
|||||||
public async pack(
|
public async pack(
|
||||||
src: MiBlocking['id'] | MiBlocking,
|
src: MiBlocking['id'] | MiBlocking,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
hint?: {
|
||||||
|
blockee?: Packed<'UserDetailedNotMe'>,
|
||||||
|
},
|
||||||
): Promise<Packed<'Blocking'>> {
|
): Promise<Packed<'Blocking'>> {
|
||||||
const blocking = typeof src === 'object' ? src : await this.blockingsRepository.findOneByOrFail({ id: src });
|
const blocking = typeof src === 'object' ? src : await this.blockingsRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
@@ -36,17 +39,20 @@ export class BlockingEntityService {
|
|||||||
id: blocking.id,
|
id: blocking.id,
|
||||||
createdAt: this.idService.parse(blocking.id).date.toISOString(),
|
createdAt: this.idService.parse(blocking.id).date.toISOString(),
|
||||||
blockeeId: blocking.blockeeId,
|
blockeeId: blocking.blockeeId,
|
||||||
blockee: this.userEntityService.pack(blocking.blockeeId, me, {
|
blockee: hint?.blockee ?? this.userEntityService.pack(blocking.blockeeId, me, {
|
||||||
schema: 'UserDetailedNotMe',
|
schema: 'UserDetailedNotMe',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
blockings: any[],
|
blockings: MiBlocking[],
|
||||||
me: { id: MiUser['id'] },
|
me: { id: MiUser['id'] },
|
||||||
) {
|
) {
|
||||||
return Promise.all(blockings.map(x => this.pack(x, me)));
|
const _blockees = blockings.map(({ blockee, blockeeId }) => blockee ?? blockeeId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_blockees, me, { schema: 'UserDetailedNotMe' })
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(blockings.map(blocking => this.pack(blocking, me, { blockee: _userMap.get(blocking.blockeeId) })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -35,6 +35,9 @@ export class ClipEntityService {
|
|||||||
public async pack(
|
public async pack(
|
||||||
src: MiClip['id'] | MiClip,
|
src: MiClip['id'] | MiClip,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
hint?: {
|
||||||
|
packedUser?: Packed<'UserLite'>
|
||||||
|
},
|
||||||
): Promise<Packed<'Clip'>> {
|
): Promise<Packed<'Clip'>> {
|
||||||
const meId = me ? me.id : null;
|
const meId = me ? me.id : null;
|
||||||
const clip = typeof src === 'object' ? src : await this.clipsRepository.findOneByOrFail({ id: src });
|
const clip = typeof src === 'object' ? src : await this.clipsRepository.findOneByOrFail({ id: src });
|
||||||
@@ -44,7 +47,7 @@ export class ClipEntityService {
|
|||||||
createdAt: this.idService.parse(clip.id).date.toISOString(),
|
createdAt: this.idService.parse(clip.id).date.toISOString(),
|
||||||
lastClippedAt: clip.lastClippedAt ? clip.lastClippedAt.toISOString() : null,
|
lastClippedAt: clip.lastClippedAt ? clip.lastClippedAt.toISOString() : null,
|
||||||
userId: clip.userId,
|
userId: clip.userId,
|
||||||
user: this.userEntityService.pack(clip.user ?? clip.userId),
|
user: hint?.packedUser ?? this.userEntityService.pack(clip.user ?? clip.userId),
|
||||||
name: clip.name,
|
name: clip.name,
|
||||||
description: clip.description,
|
description: clip.description,
|
||||||
isPublic: clip.isPublic,
|
isPublic: clip.isPublic,
|
||||||
@@ -55,11 +58,14 @@ export class ClipEntityService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
clips: MiClip[],
|
clips: MiClip[],
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
) {
|
) {
|
||||||
return Promise.all(clips.map(x => this.pack(x, me)));
|
const _users = clips.map(({ user, userId }) => user ?? userId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_users, me)
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(clips.map(clip => this.pack(clip, me, { packedUser: _userMap.get(clip.userId) })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -222,6 +222,9 @@ export class DriveFileEntityService {
|
|||||||
public async packNullable(
|
public async packNullable(
|
||||||
src: MiDriveFile['id'] | MiDriveFile,
|
src: MiDriveFile['id'] | MiDriveFile,
|
||||||
options?: PackOptions,
|
options?: PackOptions,
|
||||||
|
hint?: {
|
||||||
|
packedUser?: Packed<'UserLite'>
|
||||||
|
},
|
||||||
): Promise<Packed<'DriveFile'> | null> {
|
): Promise<Packed<'DriveFile'> | null> {
|
||||||
const opts = Object.assign({
|
const opts = Object.assign({
|
||||||
detail: false,
|
detail: false,
|
||||||
@@ -249,7 +252,7 @@ export class DriveFileEntityService {
|
|||||||
detail: true,
|
detail: true,
|
||||||
}) : null,
|
}) : null,
|
||||||
userId: file.userId,
|
userId: file.userId,
|
||||||
user: (opts.withUser && file.userId) ? this.userEntityService.pack(file.userId) : null,
|
user: (opts.withUser && file.userId) ? hint?.packedUser ?? this.userEntityService.pack(file.userId) : null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,7 +261,10 @@ export class DriveFileEntityService {
|
|||||||
files: MiDriveFile[],
|
files: MiDriveFile[],
|
||||||
options?: PackOptions,
|
options?: PackOptions,
|
||||||
): Promise<Packed<'DriveFile'>[]> {
|
): Promise<Packed<'DriveFile'>[]> {
|
||||||
const items = await Promise.all(files.map(f => this.packNullable(f, options)));
|
const _user = files.map(({ user, userId }) => user ?? userId).filter(isNotNull);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_user)
|
||||||
|
.then(users => new Map(users.map(user => [user.id, user])));
|
||||||
|
const items = await Promise.all(files.map(f => this.packNullable(f, options, f.userId ? { packedUser: _userMap.get(f.userId) } : {})));
|
||||||
return items.filter(isNotNull);
|
return items.filter(isNotNull);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -33,6 +33,9 @@ export class FlashEntityService {
|
|||||||
public async pack(
|
public async pack(
|
||||||
src: MiFlash['id'] | MiFlash,
|
src: MiFlash['id'] | MiFlash,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
hint?: {
|
||||||
|
packedUser?: Packed<'UserLite'>
|
||||||
|
},
|
||||||
): Promise<Packed<'Flash'>> {
|
): Promise<Packed<'Flash'>> {
|
||||||
const meId = me ? me.id : null;
|
const meId = me ? me.id : null;
|
||||||
const flash = typeof src === 'object' ? src : await this.flashsRepository.findOneByOrFail({ id: src });
|
const flash = typeof src === 'object' ? src : await this.flashsRepository.findOneByOrFail({ id: src });
|
||||||
@@ -42,7 +45,7 @@ export class FlashEntityService {
|
|||||||
createdAt: this.idService.parse(flash.id).date.toISOString(),
|
createdAt: this.idService.parse(flash.id).date.toISOString(),
|
||||||
updatedAt: flash.updatedAt.toISOString(),
|
updatedAt: flash.updatedAt.toISOString(),
|
||||||
userId: flash.userId,
|
userId: flash.userId,
|
||||||
user: this.userEntityService.pack(flash.user ?? flash.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意
|
user: hint?.packedUser ?? this.userEntityService.pack(flash.user ?? flash.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意
|
||||||
title: flash.title,
|
title: flash.title,
|
||||||
summary: flash.summary,
|
summary: flash.summary,
|
||||||
script: flash.script,
|
script: flash.script,
|
||||||
@@ -52,11 +55,14 @@ export class FlashEntityService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
flashs: MiFlash[],
|
flashes: MiFlash[],
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
) {
|
) {
|
||||||
return Promise.all(flashs.map(x => this.pack(x, me)));
|
const _users = flashes.map(({ user, userId }) => user ?? userId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_users, me)
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(flashes.map(flash => this.pack(flash, me, { packedUser: _userMap.get(flash.userId) })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,6 +10,7 @@ import type { } from '@/models/Blocking.js';
|
|||||||
import type { MiUser } from '@/models/User.js';
|
import type { MiUser } from '@/models/User.js';
|
||||||
import type { MiFollowRequest } from '@/models/FollowRequest.js';
|
import type { MiFollowRequest } from '@/models/FollowRequest.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
import { UserEntityService } from './UserEntityService.js';
|
import { UserEntityService } from './UserEntityService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -26,14 +27,36 @@ export class FollowRequestEntityService {
|
|||||||
public async pack(
|
public async pack(
|
||||||
src: MiFollowRequest['id'] | MiFollowRequest,
|
src: MiFollowRequest['id'] | MiFollowRequest,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
hint?: {
|
||||||
|
packedFollower?: Packed<'UserLite'>,
|
||||||
|
packedFollowee?: Packed<'UserLite'>,
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
const request = typeof src === 'object' ? src : await this.followRequestsRepository.findOneByOrFail({ id: src });
|
const request = typeof src === 'object' ? src : await this.followRequestsRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: request.id,
|
id: request.id,
|
||||||
follower: await this.userEntityService.pack(request.followerId, me),
|
follower: hint?.packedFollower ?? await this.userEntityService.pack(request.followerId, me),
|
||||||
followee: await this.userEntityService.pack(request.followeeId, me),
|
followee: hint?.packedFollowee ?? await this.userEntityService.pack(request.followeeId, me),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async packMany(
|
||||||
|
requests: MiFollowRequest[],
|
||||||
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
) {
|
||||||
|
const _followers = requests.map(({ follower, followerId }) => follower ?? followerId);
|
||||||
|
const _followees = requests.map(({ followee, followeeId }) => followee ?? followeeId);
|
||||||
|
const _userMap = await this.userEntityService.packMany([..._followers, ..._followees], me)
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(
|
||||||
|
requests.map(req => {
|
||||||
|
const packedFollower = _userMap.get(req.followerId);
|
||||||
|
const packedFollowee = _userMap.get(req.followeeId);
|
||||||
|
return this.pack(req, me, { packedFollower, packedFollowee });
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -78,6 +78,10 @@ export class FollowingEntityService {
|
|||||||
populateFollowee?: boolean;
|
populateFollowee?: boolean;
|
||||||
populateFollower?: boolean;
|
populateFollower?: boolean;
|
||||||
},
|
},
|
||||||
|
hint?: {
|
||||||
|
packedFollowee?: Packed<'UserDetailedNotMe'>,
|
||||||
|
packedFollower?: Packed<'UserDetailedNotMe'>,
|
||||||
|
},
|
||||||
): Promise<Packed<'Following'>> {
|
): Promise<Packed<'Following'>> {
|
||||||
const following = typeof src === 'object' ? src : await this.followingsRepository.findOneByOrFail({ id: src });
|
const following = typeof src === 'object' ? src : await this.followingsRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
@@ -88,25 +92,35 @@ export class FollowingEntityService {
|
|||||||
createdAt: this.idService.parse(following.id).date.toISOString(),
|
createdAt: this.idService.parse(following.id).date.toISOString(),
|
||||||
followeeId: following.followeeId,
|
followeeId: following.followeeId,
|
||||||
followerId: following.followerId,
|
followerId: following.followerId,
|
||||||
followee: opts.populateFollowee ? this.userEntityService.pack(following.followee ?? following.followeeId, me, {
|
followee: opts.populateFollowee ? hint?.packedFollowee ?? this.userEntityService.pack(following.followee ?? following.followeeId, me, {
|
||||||
schema: 'UserDetailedNotMe',
|
schema: 'UserDetailedNotMe',
|
||||||
}) : undefined,
|
}) : undefined,
|
||||||
follower: opts.populateFollower ? this.userEntityService.pack(following.follower ?? following.followerId, me, {
|
follower: opts.populateFollower ? hint?.packedFollower ?? this.userEntityService.pack(following.follower ?? following.followerId, me, {
|
||||||
schema: 'UserDetailedNotMe',
|
schema: 'UserDetailedNotMe',
|
||||||
}) : undefined,
|
}) : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
followings: any[],
|
followings: MiFollowing[],
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
opts?: {
|
opts?: {
|
||||||
populateFollowee?: boolean;
|
populateFollowee?: boolean;
|
||||||
populateFollower?: boolean;
|
populateFollower?: boolean;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
return Promise.all(followings.map(x => this.pack(x, me, opts)));
|
const _followees = opts?.populateFollowee ? followings.map(({ followee, followeeId }) => followee ?? followeeId) : [];
|
||||||
|
const _followers = opts?.populateFollower ? followings.map(({ follower, followerId }) => follower ?? followerId) : [];
|
||||||
|
const _userMap = await this.userEntityService.packMany([..._followees, ..._followers], me, { schema: 'UserDetailedNotMe' })
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(
|
||||||
|
followings.map(following => {
|
||||||
|
const packedFollowee = opts?.populateFollowee ? _userMap.get(following.followeeId) : undefined;
|
||||||
|
const packedFollower = opts?.populateFollower ? _userMap.get(following.followerId) : undefined;
|
||||||
|
return this.pack(following, me, opts, { packedFollowee, packedFollower });
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -35,6 +35,9 @@ export class GalleryPostEntityService {
|
|||||||
public async pack(
|
public async pack(
|
||||||
src: MiGalleryPost['id'] | MiGalleryPost,
|
src: MiGalleryPost['id'] | MiGalleryPost,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
hint?: {
|
||||||
|
packedUser?: Packed<'UserLite'>
|
||||||
|
},
|
||||||
): Promise<Packed<'GalleryPost'>> {
|
): Promise<Packed<'GalleryPost'>> {
|
||||||
const meId = me ? me.id : null;
|
const meId = me ? me.id : null;
|
||||||
const post = typeof src === 'object' ? src : await this.galleryPostsRepository.findOneByOrFail({ id: src });
|
const post = typeof src === 'object' ? src : await this.galleryPostsRepository.findOneByOrFail({ id: src });
|
||||||
@@ -44,7 +47,7 @@ export class GalleryPostEntityService {
|
|||||||
createdAt: this.idService.parse(post.id).date.toISOString(),
|
createdAt: this.idService.parse(post.id).date.toISOString(),
|
||||||
updatedAt: post.updatedAt.toISOString(),
|
updatedAt: post.updatedAt.toISOString(),
|
||||||
userId: post.userId,
|
userId: post.userId,
|
||||||
user: this.userEntityService.pack(post.user ?? post.userId, me),
|
user: hint?.packedUser ?? this.userEntityService.pack(post.user ?? post.userId, me),
|
||||||
title: post.title,
|
title: post.title,
|
||||||
description: post.description,
|
description: post.description,
|
||||||
fileIds: post.fileIds,
|
fileIds: post.fileIds,
|
||||||
@@ -58,11 +61,14 @@ export class GalleryPostEntityService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
posts: MiGalleryPost[],
|
posts: MiGalleryPost[],
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
) {
|
) {
|
||||||
return Promise.all(posts.map(x => this.pack(x, me)));
|
const _users = posts.map(({ user, userId }) => user ?? userId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_users, me)
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(posts.map(post => this.pack(post, me, { packedUser: _userMap.get(post.userId) })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -39,7 +39,8 @@ export class InstanceEntityService {
|
|||||||
followingCount: instance.followingCount,
|
followingCount: instance.followingCount,
|
||||||
followersCount: instance.followersCount,
|
followersCount: instance.followersCount,
|
||||||
isNotResponding: instance.isNotResponding,
|
isNotResponding: instance.isNotResponding,
|
||||||
isSuspended: instance.isSuspended,
|
isSuspended: instance.suspensionState !== 'none',
|
||||||
|
suspensionState: instance.suspensionState,
|
||||||
isBlocked: this.utilityService.isBlockedHost(meta.blockedHosts, instance.host),
|
isBlocked: this.utilityService.isBlockedHost(meta.blockedHosts, instance.host),
|
||||||
softwareName: instance.softwareName,
|
softwareName: instance.softwareName,
|
||||||
softwareVersion: instance.softwareVersion,
|
softwareVersion: instance.softwareVersion,
|
||||||
|
@@ -12,6 +12,7 @@ import type { MiUser } from '@/models/User.js';
|
|||||||
import type { MiRegistrationTicket } from '@/models/RegistrationTicket.js';
|
import type { MiRegistrationTicket } from '@/models/RegistrationTicket.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { isNotNull } from '@/misc/is-not-null.js';
|
||||||
import { UserEntityService } from './UserEntityService.js';
|
import { UserEntityService } from './UserEntityService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -29,6 +30,10 @@ export class InviteCodeEntityService {
|
|||||||
public async pack(
|
public async pack(
|
||||||
src: MiRegistrationTicket['id'] | MiRegistrationTicket,
|
src: MiRegistrationTicket['id'] | MiRegistrationTicket,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
hints?: {
|
||||||
|
packedCreatedBy?: Packed<'UserLite'>,
|
||||||
|
packedUsedBy?: Packed<'UserLite'>,
|
||||||
|
},
|
||||||
): Promise<Packed<'InviteCode'>> {
|
): Promise<Packed<'InviteCode'>> {
|
||||||
const target = typeof src === 'object' ? src : await this.registrationTicketsRepository.findOneOrFail({
|
const target = typeof src === 'object' ? src : await this.registrationTicketsRepository.findOneOrFail({
|
||||||
where: {
|
where: {
|
||||||
@@ -42,18 +47,28 @@ export class InviteCodeEntityService {
|
|||||||
code: target.code,
|
code: target.code,
|
||||||
expiresAt: target.expiresAt ? target.expiresAt.toISOString() : null,
|
expiresAt: target.expiresAt ? target.expiresAt.toISOString() : null,
|
||||||
createdAt: this.idService.parse(target.id).date.toISOString(),
|
createdAt: this.idService.parse(target.id).date.toISOString(),
|
||||||
createdBy: target.createdBy ? await this.userEntityService.pack(target.createdBy, me) : null,
|
createdBy: target.createdBy ? hints?.packedCreatedBy ?? await this.userEntityService.pack(target.createdBy, me) : null,
|
||||||
usedBy: target.usedBy ? await this.userEntityService.pack(target.usedBy, me) : null,
|
usedBy: target.usedBy ? hints?.packedUsedBy ?? await this.userEntityService.pack(target.usedBy, me) : null,
|
||||||
usedAt: target.usedAt ? target.usedAt.toISOString() : null,
|
usedAt: target.usedAt ? target.usedAt.toISOString() : null,
|
||||||
used: !!target.usedAt,
|
used: !!target.usedAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
targets: any[],
|
tickets: MiRegistrationTicket[],
|
||||||
me: { id: MiUser['id'] },
|
me: { id: MiUser['id'] },
|
||||||
) {
|
) {
|
||||||
return Promise.all(targets.map(x => this.pack(x, me)));
|
const _createdBys = tickets.map(({ createdBy, createdById }) => createdBy ?? createdById).filter(isNotNull);
|
||||||
|
const _usedBys = tickets.map(({ usedBy, usedById }) => usedBy ?? usedById).filter(isNotNull);
|
||||||
|
const _userMap = await this.userEntityService.packMany([..._createdBys, ..._usedBys], me)
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(
|
||||||
|
tickets.map(ticket => {
|
||||||
|
const packedCreatedBy = ticket.createdById != null ? _userMap.get(ticket.createdById) : undefined;
|
||||||
|
const packedUsedBy = ticket.usedById != null ? _userMap.get(ticket.usedById) : undefined;
|
||||||
|
return this.pack(ticket, me, { packedCreatedBy, packedUsedBy });
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -67,6 +67,7 @@ export class MetaEntityService {
|
|||||||
feedbackUrl: instance.feedbackUrl,
|
feedbackUrl: instance.feedbackUrl,
|
||||||
impressumUrl: instance.impressumUrl,
|
impressumUrl: instance.impressumUrl,
|
||||||
privacyPolicyUrl: instance.privacyPolicyUrl,
|
privacyPolicyUrl: instance.privacyPolicyUrl,
|
||||||
|
inquiryUrl: instance.inquiryUrl,
|
||||||
disableRegistration: instance.disableRegistration,
|
disableRegistration: instance.disableRegistration,
|
||||||
emailRequiredForSignup: instance.emailRequiredForSignup,
|
emailRequiredForSignup: instance.emailRequiredForSignup,
|
||||||
enableHcaptcha: instance.enableHcaptcha,
|
enableHcaptcha: instance.enableHcaptcha,
|
||||||
|
@@ -8,9 +8,10 @@ import { DI } from '@/di-symbols.js';
|
|||||||
import type { ModerationLogsRepository } from '@/models/_.js';
|
import type { ModerationLogsRepository } from '@/models/_.js';
|
||||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||||
import type { } from '@/models/Blocking.js';
|
import type { } from '@/models/Blocking.js';
|
||||||
import type { MiModerationLog } from '@/models/ModerationLog.js';
|
import { MiModerationLog } from '@/models/ModerationLog.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
import { UserEntityService } from './UserEntityService.js';
|
import { UserEntityService } from './UserEntityService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -27,6 +28,9 @@ export class ModerationLogEntityService {
|
|||||||
@bindThis
|
@bindThis
|
||||||
public async pack(
|
public async pack(
|
||||||
src: MiModerationLog['id'] | MiModerationLog,
|
src: MiModerationLog['id'] | MiModerationLog,
|
||||||
|
hint?: {
|
||||||
|
packedUser?: Packed<'UserDetailedNotMe'>,
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
const log = typeof src === 'object' ? src : await this.moderationLogsRepository.findOneByOrFail({ id: src });
|
const log = typeof src === 'object' ? src : await this.moderationLogsRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
@@ -36,17 +40,20 @@ export class ModerationLogEntityService {
|
|||||||
type: log.type,
|
type: log.type,
|
||||||
info: log.info,
|
info: log.info,
|
||||||
userId: log.userId,
|
userId: log.userId,
|
||||||
user: this.userEntityService.pack(log.user ?? log.userId, null, {
|
user: hint?.packedUser ?? this.userEntityService.pack(log.user ?? log.userId, null, {
|
||||||
schema: 'UserDetailedNotMe',
|
schema: 'UserDetailedNotMe',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
reports: any[],
|
reports: MiModerationLog[],
|
||||||
) {
|
) {
|
||||||
return Promise.all(reports.map(x => this.pack(x)));
|
const _users = reports.map(({ user, userId }) => user ?? userId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_users, null, { schema: 'UserDetailedNotMe' })
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(reports.map(report => this.pack(report, { packedUser: _userMap.get(report.userId) })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -30,6 +30,9 @@ export class MutingEntityService {
|
|||||||
public async pack(
|
public async pack(
|
||||||
src: MiMuting['id'] | MiMuting,
|
src: MiMuting['id'] | MiMuting,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
hints?: {
|
||||||
|
packedMutee?: Packed<'UserDetailedNotMe'>,
|
||||||
|
},
|
||||||
): Promise<Packed<'Muting'>> {
|
): Promise<Packed<'Muting'>> {
|
||||||
const muting = typeof src === 'object' ? src : await this.mutingsRepository.findOneByOrFail({ id: src });
|
const muting = typeof src === 'object' ? src : await this.mutingsRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
@@ -38,18 +41,21 @@ export class MutingEntityService {
|
|||||||
createdAt: this.idService.parse(muting.id).date.toISOString(),
|
createdAt: this.idService.parse(muting.id).date.toISOString(),
|
||||||
expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null,
|
expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null,
|
||||||
muteeId: muting.muteeId,
|
muteeId: muting.muteeId,
|
||||||
mutee: this.userEntityService.pack(muting.muteeId, me, {
|
mutee: hints?.packedMutee ?? this.userEntityService.pack(muting.muteeId, me, {
|
||||||
schema: 'UserDetailedNotMe',
|
schema: 'UserDetailedNotMe',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
mutings: any[],
|
mutings: MiMuting[],
|
||||||
me: { id: MiUser['id'] },
|
me: { id: MiUser['id'] },
|
||||||
) {
|
) {
|
||||||
return Promise.all(mutings.map(x => this.pack(x, me)));
|
const _mutees = mutings.map(({ mutee, muteeId }) => mutee ?? muteeId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_mutees, me, { schema: 'UserDetailedNotMe' })
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(mutings.map(muting => this.pack(muting, me, { packedMutee: _userMap.get(muting.muteeId) })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -290,6 +290,7 @@ export class NoteEntityService implements OnModuleInit {
|
|||||||
_hint_?: {
|
_hint_?: {
|
||||||
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'>>
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
): Promise<Packed<'Note'>> {
|
): Promise<Packed<'Note'>> {
|
||||||
@@ -319,12 +320,13 @@ export class NoteEntityService implements OnModuleInit {
|
|||||||
.filter(x => x.startsWith(':') && x.includes('@') && !x.includes('@.')) // リモートカスタム絵文字のみ
|
.filter(x => x.startsWith(':') && x.includes('@') && !x.includes('@.')) // リモートカスタム絵文字のみ
|
||||||
.map(x => this.reactionService.decodeReaction(x).reaction.replaceAll(':', ''));
|
.map(x => this.reactionService.decodeReaction(x).reaction.replaceAll(':', ''));
|
||||||
const packedFiles = options?._hint_?.packedFiles;
|
const packedFiles = options?._hint_?.packedFiles;
|
||||||
|
const packedUsers = options?._hint_?.packedUsers;
|
||||||
|
|
||||||
const packed: Packed<'Note'> = await awaitAll({
|
const packed: Packed<'Note'> = await awaitAll({
|
||||||
id: note.id,
|
id: note.id,
|
||||||
createdAt: this.idService.parse(note.id).date.toISOString(),
|
createdAt: this.idService.parse(note.id).date.toISOString(),
|
||||||
userId: note.userId,
|
userId: note.userId,
|
||||||
user: this.userEntityService.pack(note.user ?? note.userId, me),
|
user: packedUsers?.get(note.userId) ?? this.userEntityService.pack(note.user ?? note.userId, me),
|
||||||
text: text,
|
text: text,
|
||||||
cw: note.cw,
|
cw: note.cw,
|
||||||
visibility: note.visibility,
|
visibility: note.visibility,
|
||||||
@@ -449,12 +451,20 @@ export class NoteEntityService implements OnModuleInit {
|
|||||||
// TODO: 本当は renote とか reply がないのに renoteId とか replyId があったらここで解決しておく
|
// TODO: 本当は renote とか reply がないのに renoteId とか replyId があったらここで解決しておく
|
||||||
const fileIds = notes.map(n => [n.fileIds, n.renote?.fileIds, n.reply?.fileIds]).flat(2).filter(isNotNull);
|
const fileIds = notes.map(n => [n.fileIds, n.renote?.fileIds, n.reply?.fileIds]).flat(2).filter(isNotNull);
|
||||||
const packedFiles = fileIds.length > 0 ? await this.driveFileEntityService.packManyByIdsMap(fileIds) : new Map();
|
const packedFiles = fileIds.length > 0 ? await this.driveFileEntityService.packManyByIdsMap(fileIds) : new Map();
|
||||||
|
const users = [
|
||||||
|
...notes.map(({ user, userId }) => user ?? userId),
|
||||||
|
...notes.map(({ replyUserId }) => replyUserId).filter(isNotNull),
|
||||||
|
...notes.map(({ renoteUserId }) => renoteUserId).filter(isNotNull),
|
||||||
|
];
|
||||||
|
const packedUsers = await this.userEntityService.packMany(users, me)
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
|
||||||
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_: {
|
||||||
myReactions: myReactionsMap,
|
myReactions: myReactionsMap,
|
||||||
packedFiles,
|
packedFiles,
|
||||||
|
packedUsers,
|
||||||
},
|
},
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
@@ -52,6 +52,9 @@ export class NoteReactionEntityService implements OnModuleInit {
|
|||||||
options?: {
|
options?: {
|
||||||
withNote: boolean;
|
withNote: boolean;
|
||||||
},
|
},
|
||||||
|
hints?: {
|
||||||
|
packedUser?: Packed<'UserLite'>
|
||||||
|
},
|
||||||
): Promise<Packed<'NoteReaction'>> {
|
): Promise<Packed<'NoteReaction'>> {
|
||||||
const opts = Object.assign({
|
const opts = Object.assign({
|
||||||
withNote: false,
|
withNote: false,
|
||||||
@@ -62,7 +65,7 @@ export class NoteReactionEntityService implements OnModuleInit {
|
|||||||
return {
|
return {
|
||||||
id: reaction.id,
|
id: reaction.id,
|
||||||
createdAt: this.idService.parse(reaction.id).date.toISOString(),
|
createdAt: this.idService.parse(reaction.id).date.toISOString(),
|
||||||
user: await this.userEntityService.pack(reaction.user ?? reaction.userId, me),
|
user: hints?.packedUser ?? await this.userEntityService.pack(reaction.user ?? reaction.userId, me),
|
||||||
type: this.reactionService.convertLegacyReaction(reaction.reaction),
|
type: this.reactionService.convertLegacyReaction(reaction.reaction),
|
||||||
...(opts.withNote ? {
|
...(opts.withNote ? {
|
||||||
note: await this.noteEntityService.pack(reaction.note ?? reaction.noteId, me),
|
note: await this.noteEntityService.pack(reaction.note ?? reaction.noteId, me),
|
||||||
@@ -81,7 +84,9 @@ export class NoteReactionEntityService implements OnModuleInit {
|
|||||||
const opts = Object.assign({
|
const opts = Object.assign({
|
||||||
withNote: false,
|
withNote: false,
|
||||||
}, options);
|
}, options);
|
||||||
|
const _users = reactions.map(({ user, userId }) => user ?? userId);
|
||||||
return Promise.all(reactions.map(reaction => this.pack(reaction, me, opts)));
|
const _userMap = await this.userEntityService.packMany(_users, me)
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(reactions.map(reaction => this.pack(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -40,6 +40,9 @@ export class PageEntityService {
|
|||||||
public async pack(
|
public async pack(
|
||||||
src: MiPage['id'] | MiPage,
|
src: MiPage['id'] | MiPage,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
hint?: {
|
||||||
|
packedUser?: Packed<'UserLite'>
|
||||||
|
},
|
||||||
): Promise<Packed<'Page'>> {
|
): Promise<Packed<'Page'>> {
|
||||||
const meId = me ? me.id : null;
|
const meId = me ? me.id : null;
|
||||||
const page = typeof src === 'object' ? src : await this.pagesRepository.findOneByOrFail({ id: src });
|
const page = typeof src === 'object' ? src : await this.pagesRepository.findOneByOrFail({ id: src });
|
||||||
@@ -91,7 +94,7 @@ export class PageEntityService {
|
|||||||
createdAt: this.idService.parse(page.id).date.toISOString(),
|
createdAt: this.idService.parse(page.id).date.toISOString(),
|
||||||
updatedAt: page.updatedAt.toISOString(),
|
updatedAt: page.updatedAt.toISOString(),
|
||||||
userId: page.userId,
|
userId: page.userId,
|
||||||
user: this.userEntityService.pack(page.user ?? page.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意
|
user: hint?.packedUser ?? this.userEntityService.pack(page.user ?? page.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意
|
||||||
content: page.content,
|
content: page.content,
|
||||||
variables: page.variables,
|
variables: page.variables,
|
||||||
title: page.title,
|
title: page.title,
|
||||||
@@ -110,11 +113,14 @@ export class PageEntityService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
pages: MiPage[],
|
pages: MiPage[],
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
) {
|
) {
|
||||||
return Promise.all(pages.map(x => this.pack(x, me)));
|
const _users = pages.map(({ user, userId }) => user ?? userId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_users, me)
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(pages.map(page => this.pack(page, me, { packedUser: _userMap.get(page.userId) })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -30,6 +30,9 @@ export class RenoteMutingEntityService {
|
|||||||
public async pack(
|
public async pack(
|
||||||
src: MiRenoteMuting['id'] | MiRenoteMuting,
|
src: MiRenoteMuting['id'] | MiRenoteMuting,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
hints?: {
|
||||||
|
packedMutee?: Packed<'UserDetailedNotMe'>
|
||||||
|
},
|
||||||
): Promise<Packed<'RenoteMuting'>> {
|
): Promise<Packed<'RenoteMuting'>> {
|
||||||
const muting = typeof src === 'object' ? src : await this.renoteMutingsRepository.findOneByOrFail({ id: src });
|
const muting = typeof src === 'object' ? src : await this.renoteMutingsRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
@@ -37,18 +40,21 @@ export class RenoteMutingEntityService {
|
|||||||
id: muting.id,
|
id: muting.id,
|
||||||
createdAt: this.idService.parse(muting.id).date.toISOString(),
|
createdAt: this.idService.parse(muting.id).date.toISOString(),
|
||||||
muteeId: muting.muteeId,
|
muteeId: muting.muteeId,
|
||||||
mutee: this.userEntityService.pack(muting.muteeId, me, {
|
mutee: hints?.packedMutee ?? this.userEntityService.pack(muting.muteeId, me, {
|
||||||
schema: 'UserDetailedNotMe',
|
schema: 'UserDetailedNotMe',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
mutings: any[],
|
mutings: MiRenoteMuting[],
|
||||||
me: { id: MiUser['id'] },
|
me: { id: MiUser['id'] },
|
||||||
) {
|
) {
|
||||||
return Promise.all(mutings.map(x => this.pack(x, me)));
|
const _users = mutings.map(({ mutee, muteeId }) => mutee ?? muteeId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_users, me, { schema: 'UserDetailedNotMe' })
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(mutings.map(muting => this.pack(muting, me, { packedMutee: _userMap.get(muting.muteeId) })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -28,13 +28,15 @@ export class ReversiGameEntityService {
|
|||||||
@bindThis
|
@bindThis
|
||||||
public async packDetail(
|
public async packDetail(
|
||||||
src: MiReversiGame['id'] | MiReversiGame,
|
src: MiReversiGame['id'] | MiReversiGame,
|
||||||
|
hint?: {
|
||||||
|
packedUser1?: Packed<'UserLite'>,
|
||||||
|
packedUser2?: Packed<'UserLite'>,
|
||||||
|
},
|
||||||
): Promise<Packed<'ReversiGameDetailed'>> {
|
): Promise<Packed<'ReversiGameDetailed'>> {
|
||||||
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
|
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
const users = await Promise.all([
|
const user1 = hint?.packedUser1 ?? await this.userEntityService.pack(game.user1 ?? game.user1Id);
|
||||||
this.userEntityService.pack(game.user1 ?? game.user1Id),
|
const user2 = hint?.packedUser2 ?? await this.userEntityService.pack(game.user2 ?? game.user2Id);
|
||||||
this.userEntityService.pack(game.user2 ?? game.user2Id),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return await awaitAll({
|
return await awaitAll({
|
||||||
id: game.id,
|
id: game.id,
|
||||||
@@ -49,10 +51,10 @@ export class ReversiGameEntityService {
|
|||||||
user2Ready: game.user2Ready,
|
user2Ready: game.user2Ready,
|
||||||
user1Id: game.user1Id,
|
user1Id: game.user1Id,
|
||||||
user2Id: game.user2Id,
|
user2Id: game.user2Id,
|
||||||
user1: users[0],
|
user1,
|
||||||
user2: users[1],
|
user2,
|
||||||
winnerId: game.winnerId,
|
winnerId: game.winnerId,
|
||||||
winner: game.winnerId ? users.find(u => u.id === game.winnerId)! : null,
|
winner: game.winnerId ? [user1, user2].find(u => u.id === game.winnerId)! : null,
|
||||||
surrenderedUserId: game.surrenderedUserId,
|
surrenderedUserId: game.surrenderedUserId,
|
||||||
timeoutUserId: game.timeoutUserId,
|
timeoutUserId: game.timeoutUserId,
|
||||||
black: game.black,
|
black: game.black,
|
||||||
@@ -68,22 +70,35 @@ export class ReversiGameEntityService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packDetailMany(
|
public async packDetailMany(
|
||||||
xs: MiReversiGame[],
|
games: MiReversiGame[],
|
||||||
) {
|
) {
|
||||||
return Promise.all(xs.map(x => this.packDetail(x)));
|
const _user1s = games.map(({ user1, user1Id }) => user1 ?? user1Id);
|
||||||
|
const _user2s = games.map(({ user2, user2Id }) => user2 ?? user2Id);
|
||||||
|
const _userMap = await this.userEntityService.packMany([..._user1s, ..._user2s])
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(
|
||||||
|
games.map(game => {
|
||||||
|
return this.packDetail(game, {
|
||||||
|
packedUser1: _userMap.get(game.user1Id),
|
||||||
|
packedUser2: _userMap.get(game.user2Id),
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async packLite(
|
public async packLite(
|
||||||
src: MiReversiGame['id'] | MiReversiGame,
|
src: MiReversiGame['id'] | MiReversiGame,
|
||||||
|
hint?: {
|
||||||
|
packedUser1?: Packed<'UserLite'>,
|
||||||
|
packedUser2?: Packed<'UserLite'>,
|
||||||
|
},
|
||||||
): Promise<Packed<'ReversiGameLite'>> {
|
): Promise<Packed<'ReversiGameLite'>> {
|
||||||
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
|
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
const users = await Promise.all([
|
const user1 = hint?.packedUser1 ?? await this.userEntityService.pack(game.user1 ?? game.user1Id);
|
||||||
this.userEntityService.pack(game.user1 ?? game.user1Id),
|
const user2 = hint?.packedUser2 ?? await this.userEntityService.pack(game.user2 ?? game.user2Id);
|
||||||
this.userEntityService.pack(game.user2 ?? game.user2Id),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return await awaitAll({
|
return await awaitAll({
|
||||||
id: game.id,
|
id: game.id,
|
||||||
@@ -94,10 +109,10 @@ export class ReversiGameEntityService {
|
|||||||
isEnded: game.isEnded,
|
isEnded: game.isEnded,
|
||||||
user1Id: game.user1Id,
|
user1Id: game.user1Id,
|
||||||
user2Id: game.user2Id,
|
user2Id: game.user2Id,
|
||||||
user1: users[0],
|
user1,
|
||||||
user2: users[1],
|
user2,
|
||||||
winnerId: game.winnerId,
|
winnerId: game.winnerId,
|
||||||
winner: game.winnerId ? users.find(u => u.id === game.winnerId)! : null,
|
winner: game.winnerId ? [user1, user2].find(u => u.id === game.winnerId)! : null,
|
||||||
surrenderedUserId: game.surrenderedUserId,
|
surrenderedUserId: game.surrenderedUserId,
|
||||||
timeoutUserId: game.timeoutUserId,
|
timeoutUserId: game.timeoutUserId,
|
||||||
black: game.black,
|
black: game.black,
|
||||||
@@ -111,10 +126,21 @@ export class ReversiGameEntityService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packLiteMany(
|
public async packLiteMany(
|
||||||
xs: MiReversiGame[],
|
games: MiReversiGame[],
|
||||||
) {
|
) {
|
||||||
return Promise.all(xs.map(x => this.packLite(x)));
|
const _user1s = games.map(({ user1, user1Id }) => user1 ?? user1Id);
|
||||||
|
const _user2s = games.map(({ user2, user2Id }) => user2 ?? user2Id);
|
||||||
|
const _userMap = await this.userEntityService.packMany([..._user1s, ..._user2s])
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(
|
||||||
|
games.map(game => {
|
||||||
|
return this.packLite(game, {
|
||||||
|
packedUser1: _userMap.get(game.user1Id),
|
||||||
|
packedUser2: _userMap.get(game.user2Id),
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -50,11 +50,14 @@ export class UserListEntityService {
|
|||||||
public async packMembershipsMany(
|
public async packMembershipsMany(
|
||||||
memberships: MiUserListMembership[],
|
memberships: MiUserListMembership[],
|
||||||
) {
|
) {
|
||||||
|
const _users = memberships.map(({ user, userId }) => user ?? userId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_users)
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
return Promise.all(memberships.map(async x => ({
|
return Promise.all(memberships.map(async x => ({
|
||||||
id: x.id,
|
id: x.id,
|
||||||
createdAt: this.idService.parse(x.id).date.toISOString(),
|
createdAt: this.idService.parse(x.id).date.toISOString(),
|
||||||
userId: x.userId,
|
userId: x.userId,
|
||||||
user: await this.userEntityService.pack(x.userId),
|
user: _userMap.get(x.userId) ?? await this.userEntityService.pack(x.userId),
|
||||||
withReplies: x.withReplies,
|
withReplies: x.withReplies,
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
@@ -90,9 +90,6 @@ export class MiAntenna {
|
|||||||
})
|
})
|
||||||
public expression: string | null;
|
public expression: string | null;
|
||||||
|
|
||||||
@Column('boolean')
|
|
||||||
public notify: boolean;
|
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column('boolean', {
|
@Column('boolean', {
|
||||||
default: true,
|
default: true,
|
||||||
|
@@ -81,13 +81,22 @@ export class MiInstance {
|
|||||||
public isNotResponding: boolean;
|
public isNotResponding: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* このインスタンスへの配信を停止するか
|
* このインスタンスと不通になった日時
|
||||||
|
*/
|
||||||
|
@Column('timestamp with time zone', {
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public notRespondingSince: Date | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* このインスタンスへの配信状態
|
||||||
*/
|
*/
|
||||||
@Index()
|
@Index()
|
||||||
@Column('boolean', {
|
@Column('enum', {
|
||||||
default: false,
|
default: 'none',
|
||||||
|
enum: ['none', 'manuallySuspended', 'goneSuspended', 'autoSuspendedForNotResponding'],
|
||||||
})
|
})
|
||||||
public isSuspended: boolean;
|
public suspensionState: 'none' | 'manuallySuspended' | 'goneSuspended' | 'autoSuspendedForNotResponding';
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 64, nullable: true,
|
length: 64, nullable: true,
|
||||||
|
@@ -376,6 +376,12 @@ export class MiMeta {
|
|||||||
})
|
})
|
||||||
public privacyPolicyUrl: string | null;
|
public privacyPolicyUrl: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public inquiryUrl: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 8192,
|
length: 8192,
|
||||||
nullable: true,
|
nullable: true,
|
||||||
|
@@ -5,409 +5,409 @@
|
|||||||
|
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiAvatarDecoration, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook, MiBubbleGameRecord, MiReversiGame } from './_.js';
|
import { MiRepository, MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiAvatarDecoration, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook, MiBubbleGameRecord, MiReversiGame, miRepository } from './_.js';
|
||||||
import type { DataSource } from 'typeorm';
|
import type { DataSource } from 'typeorm';
|
||||||
import type { Provider } from '@nestjs/common';
|
import type { Provider } from '@nestjs/common';
|
||||||
|
|
||||||
const $usersRepository: Provider = {
|
const $usersRepository: Provider = {
|
||||||
provide: DI.usersRepository,
|
provide: DI.usersRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiUser),
|
useFactory: (db: DataSource) => db.getRepository(MiUser).extend(miRepository as MiRepository<MiUser>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $notesRepository: Provider = {
|
const $notesRepository: Provider = {
|
||||||
provide: DI.notesRepository,
|
provide: DI.notesRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiNote),
|
useFactory: (db: DataSource) => db.getRepository(MiNote).extend(miRepository as MiRepository<MiNote>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $announcementsRepository: Provider = {
|
const $announcementsRepository: Provider = {
|
||||||
provide: DI.announcementsRepository,
|
provide: DI.announcementsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiAnnouncement),
|
useFactory: (db: DataSource) => db.getRepository(MiAnnouncement).extend(miRepository as MiRepository<MiAnnouncement>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $announcementReadsRepository: Provider = {
|
const $announcementReadsRepository: Provider = {
|
||||||
provide: DI.announcementReadsRepository,
|
provide: DI.announcementReadsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiAnnouncementRead),
|
useFactory: (db: DataSource) => db.getRepository(MiAnnouncementRead).extend(miRepository as MiRepository<MiAnnouncementRead>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $appsRepository: Provider = {
|
const $appsRepository: Provider = {
|
||||||
provide: DI.appsRepository,
|
provide: DI.appsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiApp),
|
useFactory: (db: DataSource) => db.getRepository(MiApp).extend(miRepository as MiRepository<MiApp>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $avatarDecorationsRepository: Provider = {
|
const $avatarDecorationsRepository: Provider = {
|
||||||
provide: DI.avatarDecorationsRepository,
|
provide: DI.avatarDecorationsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiAvatarDecoration),
|
useFactory: (db: DataSource) => db.getRepository(MiAvatarDecoration).extend(miRepository as MiRepository<MiAvatarDecoration>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $noteFavoritesRepository: Provider = {
|
const $noteFavoritesRepository: Provider = {
|
||||||
provide: DI.noteFavoritesRepository,
|
provide: DI.noteFavoritesRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiNoteFavorite),
|
useFactory: (db: DataSource) => db.getRepository(MiNoteFavorite).extend(miRepository as MiRepository<MiNoteFavorite>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $noteThreadMutingsRepository: Provider = {
|
const $noteThreadMutingsRepository: Provider = {
|
||||||
provide: DI.noteThreadMutingsRepository,
|
provide: DI.noteThreadMutingsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiNoteThreadMuting),
|
useFactory: (db: DataSource) => db.getRepository(MiNoteThreadMuting).extend(miRepository as MiRepository<MiNoteThreadMuting>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $noteReactionsRepository: Provider = {
|
const $noteReactionsRepository: Provider = {
|
||||||
provide: DI.noteReactionsRepository,
|
provide: DI.noteReactionsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiNoteReaction),
|
useFactory: (db: DataSource) => db.getRepository(MiNoteReaction).extend(miRepository as MiRepository<MiNoteReaction>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $noteUnreadsRepository: Provider = {
|
const $noteUnreadsRepository: Provider = {
|
||||||
provide: DI.noteUnreadsRepository,
|
provide: DI.noteUnreadsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiNoteUnread),
|
useFactory: (db: DataSource) => db.getRepository(MiNoteUnread).extend(miRepository as MiRepository<MiNoteUnread>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $pollsRepository: Provider = {
|
const $pollsRepository: Provider = {
|
||||||
provide: DI.pollsRepository,
|
provide: DI.pollsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiPoll),
|
useFactory: (db: DataSource) => db.getRepository(MiPoll).extend(miRepository as MiRepository<MiPoll>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $pollVotesRepository: Provider = {
|
const $pollVotesRepository: Provider = {
|
||||||
provide: DI.pollVotesRepository,
|
provide: DI.pollVotesRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiPollVote),
|
useFactory: (db: DataSource) => db.getRepository(MiPollVote).extend(miRepository as MiRepository<MiPollVote>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $userProfilesRepository: Provider = {
|
const $userProfilesRepository: Provider = {
|
||||||
provide: DI.userProfilesRepository,
|
provide: DI.userProfilesRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiUserProfile),
|
useFactory: (db: DataSource) => db.getRepository(MiUserProfile).extend(miRepository as MiRepository<MiUserProfile>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $userKeypairsRepository: Provider = {
|
const $userKeypairsRepository: Provider = {
|
||||||
provide: DI.userKeypairsRepository,
|
provide: DI.userKeypairsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiUserKeypair),
|
useFactory: (db: DataSource) => db.getRepository(MiUserKeypair).extend(miRepository as MiRepository<MiUserKeypair>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $userPendingsRepository: Provider = {
|
const $userPendingsRepository: Provider = {
|
||||||
provide: DI.userPendingsRepository,
|
provide: DI.userPendingsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiUserPending),
|
useFactory: (db: DataSource) => db.getRepository(MiUserPending).extend(miRepository as MiRepository<MiUserPending>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $userSecurityKeysRepository: Provider = {
|
const $userSecurityKeysRepository: Provider = {
|
||||||
provide: DI.userSecurityKeysRepository,
|
provide: DI.userSecurityKeysRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiUserSecurityKey),
|
useFactory: (db: DataSource) => db.getRepository(MiUserSecurityKey).extend(miRepository as MiRepository<MiUserSecurityKey>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $userPublickeysRepository: Provider = {
|
const $userPublickeysRepository: Provider = {
|
||||||
provide: DI.userPublickeysRepository,
|
provide: DI.userPublickeysRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiUserPublickey),
|
useFactory: (db: DataSource) => db.getRepository(MiUserPublickey).extend(miRepository as MiRepository<MiUserPublickey>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $userListsRepository: Provider = {
|
const $userListsRepository: Provider = {
|
||||||
provide: DI.userListsRepository,
|
provide: DI.userListsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiUserList),
|
useFactory: (db: DataSource) => db.getRepository(MiUserList).extend(miRepository as MiRepository<MiUserList>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $userListFavoritesRepository: Provider = {
|
const $userListFavoritesRepository: Provider = {
|
||||||
provide: DI.userListFavoritesRepository,
|
provide: DI.userListFavoritesRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiUserListFavorite),
|
useFactory: (db: DataSource) => db.getRepository(MiUserListFavorite).extend(miRepository as MiRepository<MiUserListFavorite>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $userListMembershipsRepository: Provider = {
|
const $userListMembershipsRepository: Provider = {
|
||||||
provide: DI.userListMembershipsRepository,
|
provide: DI.userListMembershipsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiUserListMembership),
|
useFactory: (db: DataSource) => db.getRepository(MiUserListMembership).extend(miRepository as MiRepository<MiUserListMembership>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $userNotePiningsRepository: Provider = {
|
const $userNotePiningsRepository: Provider = {
|
||||||
provide: DI.userNotePiningsRepository,
|
provide: DI.userNotePiningsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiUserNotePining),
|
useFactory: (db: DataSource) => db.getRepository(MiUserNotePining).extend(miRepository as MiRepository<MiUserNotePining>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $userIpsRepository: Provider = {
|
const $userIpsRepository: Provider = {
|
||||||
provide: DI.userIpsRepository,
|
provide: DI.userIpsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiUserIp),
|
useFactory: (db: DataSource) => db.getRepository(MiUserIp).extend(miRepository as MiRepository<MiUserIp>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $usedUsernamesRepository: Provider = {
|
const $usedUsernamesRepository: Provider = {
|
||||||
provide: DI.usedUsernamesRepository,
|
provide: DI.usedUsernamesRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiUsedUsername),
|
useFactory: (db: DataSource) => db.getRepository(MiUsedUsername).extend(miRepository as MiRepository<MiUsedUsername>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $followingsRepository: Provider = {
|
const $followingsRepository: Provider = {
|
||||||
provide: DI.followingsRepository,
|
provide: DI.followingsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiFollowing),
|
useFactory: (db: DataSource) => db.getRepository(MiFollowing).extend(miRepository as MiRepository<MiFollowing>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $followRequestsRepository: Provider = {
|
const $followRequestsRepository: Provider = {
|
||||||
provide: DI.followRequestsRepository,
|
provide: DI.followRequestsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiFollowRequest),
|
useFactory: (db: DataSource) => db.getRepository(MiFollowRequest).extend(miRepository as MiRepository<MiFollowRequest>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $instancesRepository: Provider = {
|
const $instancesRepository: Provider = {
|
||||||
provide: DI.instancesRepository,
|
provide: DI.instancesRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiInstance),
|
useFactory: (db: DataSource) => db.getRepository(MiInstance).extend(miRepository as MiRepository<MiInstance>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $emojisRepository: Provider = {
|
const $emojisRepository: Provider = {
|
||||||
provide: DI.emojisRepository,
|
provide: DI.emojisRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiEmoji),
|
useFactory: (db: DataSource) => db.getRepository(MiEmoji).extend(miRepository as MiRepository<MiEmoji>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $driveFilesRepository: Provider = {
|
const $driveFilesRepository: Provider = {
|
||||||
provide: DI.driveFilesRepository,
|
provide: DI.driveFilesRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiDriveFile),
|
useFactory: (db: DataSource) => db.getRepository(MiDriveFile).extend(miRepository as MiRepository<MiDriveFile>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $driveFoldersRepository: Provider = {
|
const $driveFoldersRepository: Provider = {
|
||||||
provide: DI.driveFoldersRepository,
|
provide: DI.driveFoldersRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiDriveFolder),
|
useFactory: (db: DataSource) => db.getRepository(MiDriveFolder).extend(miRepository as MiRepository<MiDriveFolder>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $metasRepository: Provider = {
|
const $metasRepository: Provider = {
|
||||||
provide: DI.metasRepository,
|
provide: DI.metasRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiMeta),
|
useFactory: (db: DataSource) => db.getRepository(MiMeta).extend(miRepository as MiRepository<MiMeta>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $mutingsRepository: Provider = {
|
const $mutingsRepository: Provider = {
|
||||||
provide: DI.mutingsRepository,
|
provide: DI.mutingsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiMuting),
|
useFactory: (db: DataSource) => db.getRepository(MiMuting).extend(miRepository as MiRepository<MiMuting>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $renoteMutingsRepository: Provider = {
|
const $renoteMutingsRepository: Provider = {
|
||||||
provide: DI.renoteMutingsRepository,
|
provide: DI.renoteMutingsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiRenoteMuting),
|
useFactory: (db: DataSource) => db.getRepository(MiRenoteMuting).extend(miRepository as MiRepository<MiRenoteMuting>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $blockingsRepository: Provider = {
|
const $blockingsRepository: Provider = {
|
||||||
provide: DI.blockingsRepository,
|
provide: DI.blockingsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiBlocking),
|
useFactory: (db: DataSource) => db.getRepository(MiBlocking).extend(miRepository as MiRepository<MiBlocking>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $swSubscriptionsRepository: Provider = {
|
const $swSubscriptionsRepository: Provider = {
|
||||||
provide: DI.swSubscriptionsRepository,
|
provide: DI.swSubscriptionsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiSwSubscription),
|
useFactory: (db: DataSource) => db.getRepository(MiSwSubscription).extend(miRepository as MiRepository<MiSwSubscription>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $hashtagsRepository: Provider = {
|
const $hashtagsRepository: Provider = {
|
||||||
provide: DI.hashtagsRepository,
|
provide: DI.hashtagsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiHashtag),
|
useFactory: (db: DataSource) => db.getRepository(MiHashtag).extend(miRepository as MiRepository<MiHashtag>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $abuseUserReportsRepository: Provider = {
|
const $abuseUserReportsRepository: Provider = {
|
||||||
provide: DI.abuseUserReportsRepository,
|
provide: DI.abuseUserReportsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiAbuseUserReport),
|
useFactory: (db: DataSource) => db.getRepository(MiAbuseUserReport).extend(miRepository as MiRepository<MiAbuseUserReport>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $registrationTicketsRepository: Provider = {
|
const $registrationTicketsRepository: Provider = {
|
||||||
provide: DI.registrationTicketsRepository,
|
provide: DI.registrationTicketsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiRegistrationTicket),
|
useFactory: (db: DataSource) => db.getRepository(MiRegistrationTicket).extend(miRepository as MiRepository<MiRegistrationTicket>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $authSessionsRepository: Provider = {
|
const $authSessionsRepository: Provider = {
|
||||||
provide: DI.authSessionsRepository,
|
provide: DI.authSessionsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiAuthSession),
|
useFactory: (db: DataSource) => db.getRepository(MiAuthSession).extend(miRepository as MiRepository<MiAuthSession>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $accessTokensRepository: Provider = {
|
const $accessTokensRepository: Provider = {
|
||||||
provide: DI.accessTokensRepository,
|
provide: DI.accessTokensRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiAccessToken),
|
useFactory: (db: DataSource) => db.getRepository(MiAccessToken).extend(miRepository as MiRepository<MiAccessToken>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $signinsRepository: Provider = {
|
const $signinsRepository: Provider = {
|
||||||
provide: DI.signinsRepository,
|
provide: DI.signinsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiSignin),
|
useFactory: (db: DataSource) => db.getRepository(MiSignin).extend(miRepository as MiRepository<MiSignin>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $pagesRepository: Provider = {
|
const $pagesRepository: Provider = {
|
||||||
provide: DI.pagesRepository,
|
provide: DI.pagesRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiPage),
|
useFactory: (db: DataSource) => db.getRepository(MiPage).extend(miRepository as MiRepository<MiPage>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $pageLikesRepository: Provider = {
|
const $pageLikesRepository: Provider = {
|
||||||
provide: DI.pageLikesRepository,
|
provide: DI.pageLikesRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiPageLike),
|
useFactory: (db: DataSource) => db.getRepository(MiPageLike).extend(miRepository as MiRepository<MiPageLike>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $galleryPostsRepository: Provider = {
|
const $galleryPostsRepository: Provider = {
|
||||||
provide: DI.galleryPostsRepository,
|
provide: DI.galleryPostsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiGalleryPost),
|
useFactory: (db: DataSource) => db.getRepository(MiGalleryPost).extend(miRepository as MiRepository<MiGalleryPost>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $galleryLikesRepository: Provider = {
|
const $galleryLikesRepository: Provider = {
|
||||||
provide: DI.galleryLikesRepository,
|
provide: DI.galleryLikesRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiGalleryLike),
|
useFactory: (db: DataSource) => db.getRepository(MiGalleryLike).extend(miRepository as MiRepository<MiGalleryLike>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $moderationLogsRepository: Provider = {
|
const $moderationLogsRepository: Provider = {
|
||||||
provide: DI.moderationLogsRepository,
|
provide: DI.moderationLogsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiModerationLog),
|
useFactory: (db: DataSource) => db.getRepository(MiModerationLog).extend(miRepository as MiRepository<MiModerationLog>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $clipsRepository: Provider = {
|
const $clipsRepository: Provider = {
|
||||||
provide: DI.clipsRepository,
|
provide: DI.clipsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiClip),
|
useFactory: (db: DataSource) => db.getRepository(MiClip).extend(miRepository as MiRepository<MiClip>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $clipNotesRepository: Provider = {
|
const $clipNotesRepository: Provider = {
|
||||||
provide: DI.clipNotesRepository,
|
provide: DI.clipNotesRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiClipNote),
|
useFactory: (db: DataSource) => db.getRepository(MiClipNote).extend(miRepository as MiRepository<MiClipNote>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $clipFavoritesRepository: Provider = {
|
const $clipFavoritesRepository: Provider = {
|
||||||
provide: DI.clipFavoritesRepository,
|
provide: DI.clipFavoritesRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiClipFavorite),
|
useFactory: (db: DataSource) => db.getRepository(MiClipFavorite).extend(miRepository as MiRepository<MiClipFavorite>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $antennasRepository: Provider = {
|
const $antennasRepository: Provider = {
|
||||||
provide: DI.antennasRepository,
|
provide: DI.antennasRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiAntenna),
|
useFactory: (db: DataSource) => db.getRepository(MiAntenna).extend(miRepository as MiRepository<MiAntenna>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $promoNotesRepository: Provider = {
|
const $promoNotesRepository: Provider = {
|
||||||
provide: DI.promoNotesRepository,
|
provide: DI.promoNotesRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiPromoNote),
|
useFactory: (db: DataSource) => db.getRepository(MiPromoNote).extend(miRepository as MiRepository<MiPromoNote>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $promoReadsRepository: Provider = {
|
const $promoReadsRepository: Provider = {
|
||||||
provide: DI.promoReadsRepository,
|
provide: DI.promoReadsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiPromoRead),
|
useFactory: (db: DataSource) => db.getRepository(MiPromoRead).extend(miRepository as MiRepository<MiPromoRead>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $relaysRepository: Provider = {
|
const $relaysRepository: Provider = {
|
||||||
provide: DI.relaysRepository,
|
provide: DI.relaysRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiRelay),
|
useFactory: (db: DataSource) => db.getRepository(MiRelay).extend(miRepository as MiRepository<MiRelay>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $channelsRepository: Provider = {
|
const $channelsRepository: Provider = {
|
||||||
provide: DI.channelsRepository,
|
provide: DI.channelsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiChannel),
|
useFactory: (db: DataSource) => db.getRepository(MiChannel).extend(miRepository as MiRepository<MiChannel>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $channelFollowingsRepository: Provider = {
|
const $channelFollowingsRepository: Provider = {
|
||||||
provide: DI.channelFollowingsRepository,
|
provide: DI.channelFollowingsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiChannelFollowing),
|
useFactory: (db: DataSource) => db.getRepository(MiChannelFollowing).extend(miRepository as MiRepository<MiChannelFollowing>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $channelFavoritesRepository: Provider = {
|
const $channelFavoritesRepository: Provider = {
|
||||||
provide: DI.channelFavoritesRepository,
|
provide: DI.channelFavoritesRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiChannelFavorite),
|
useFactory: (db: DataSource) => db.getRepository(MiChannelFavorite).extend(miRepository as MiRepository<MiChannelFavorite>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $registryItemsRepository: Provider = {
|
const $registryItemsRepository: Provider = {
|
||||||
provide: DI.registryItemsRepository,
|
provide: DI.registryItemsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiRegistryItem),
|
useFactory: (db: DataSource) => db.getRepository(MiRegistryItem).extend(miRepository as MiRepository<MiRegistryItem>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $webhooksRepository: Provider = {
|
const $webhooksRepository: Provider = {
|
||||||
provide: DI.webhooksRepository,
|
provide: DI.webhooksRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiWebhook),
|
useFactory: (db: DataSource) => db.getRepository(MiWebhook).extend(miRepository as MiRepository<MiWebhook>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $adsRepository: Provider = {
|
const $adsRepository: Provider = {
|
||||||
provide: DI.adsRepository,
|
provide: DI.adsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiAd),
|
useFactory: (db: DataSource) => db.getRepository(MiAd).extend(miRepository as MiRepository<MiAd>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $passwordResetRequestsRepository: Provider = {
|
const $passwordResetRequestsRepository: Provider = {
|
||||||
provide: DI.passwordResetRequestsRepository,
|
provide: DI.passwordResetRequestsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiPasswordResetRequest),
|
useFactory: (db: DataSource) => db.getRepository(MiPasswordResetRequest).extend(miRepository as MiRepository<MiPasswordResetRequest>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $retentionAggregationsRepository: Provider = {
|
const $retentionAggregationsRepository: Provider = {
|
||||||
provide: DI.retentionAggregationsRepository,
|
provide: DI.retentionAggregationsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiRetentionAggregation),
|
useFactory: (db: DataSource) => db.getRepository(MiRetentionAggregation).extend(miRepository as MiRepository<MiRetentionAggregation>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $flashsRepository: Provider = {
|
const $flashsRepository: Provider = {
|
||||||
provide: DI.flashsRepository,
|
provide: DI.flashsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiFlash),
|
useFactory: (db: DataSource) => db.getRepository(MiFlash).extend(miRepository as MiRepository<MiFlash>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $flashLikesRepository: Provider = {
|
const $flashLikesRepository: Provider = {
|
||||||
provide: DI.flashLikesRepository,
|
provide: DI.flashLikesRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiFlashLike),
|
useFactory: (db: DataSource) => db.getRepository(MiFlashLike).extend(miRepository as MiRepository<MiFlashLike>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $rolesRepository: Provider = {
|
const $rolesRepository: Provider = {
|
||||||
provide: DI.rolesRepository,
|
provide: DI.rolesRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiRole),
|
useFactory: (db: DataSource) => db.getRepository(MiRole).extend(miRepository as MiRepository<MiRole>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $roleAssignmentsRepository: Provider = {
|
const $roleAssignmentsRepository: Provider = {
|
||||||
provide: DI.roleAssignmentsRepository,
|
provide: DI.roleAssignmentsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiRoleAssignment),
|
useFactory: (db: DataSource) => db.getRepository(MiRoleAssignment).extend(miRepository as MiRepository<MiRoleAssignment>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $userMemosRepository: Provider = {
|
const $userMemosRepository: Provider = {
|
||||||
provide: DI.userMemosRepository,
|
provide: DI.userMemosRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiUserMemo),
|
useFactory: (db: DataSource) => db.getRepository(MiUserMemo).extend(miRepository as MiRepository<MiUserMemo>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $bubbleGameRecordsRepository: Provider = {
|
const $bubbleGameRecordsRepository: Provider = {
|
||||||
provide: DI.bubbleGameRecordsRepository,
|
provide: DI.bubbleGameRecordsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiBubbleGameRecord),
|
useFactory: (db: DataSource) => db.getRepository(MiBubbleGameRecord).extend(miRepository as MiRepository<MiBubbleGameRecord>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $reversiGamesRepository: Provider = {
|
const $reversiGamesRepository: Provider = {
|
||||||
provide: DI.reversiGamesRepository,
|
provide: DI.reversiGamesRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiReversiGame),
|
useFactory: (db: DataSource) => db.getRepository(MiReversiGame).extend(miRepository as MiRepository<MiReversiGame>),
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -3,6 +3,10 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { FindOneOptions, InsertQueryBuilder, ObjectLiteral, QueryRunner, ReplicationMode, Repository, SelectQueryBuilder } from 'typeorm';
|
||||||
|
import { RelationCountLoader } from 'typeorm/query-builder/relation-count/RelationCountLoader.js';
|
||||||
|
import { RelationIdLoader } from 'typeorm/query-builder/relation-id/RelationIdLoader.js';
|
||||||
|
import { RawSqlResultsToEntityTransformer } from 'typeorm/query-builder/transformer/RawSqlResultsToEntityTransformer.js';
|
||||||
import { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
|
import { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
|
||||||
import { MiAccessToken } from '@/models/AccessToken.js';
|
import { MiAccessToken } from '@/models/AccessToken.js';
|
||||||
import { MiAd } from '@/models/Ad.js';
|
import { MiAd } from '@/models/Ad.js';
|
||||||
@@ -70,8 +74,102 @@ import { MiFlashLike } from '@/models/FlashLike.js';
|
|||||||
import { MiUserListFavorite } from '@/models/UserListFavorite.js';
|
import { MiUserListFavorite } from '@/models/UserListFavorite.js';
|
||||||
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
|
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
|
||||||
import { MiReversiGame } from '@/models/ReversiGame.js';
|
import { MiReversiGame } from '@/models/ReversiGame.js';
|
||||||
|
import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
|
||||||
|
|
||||||
import type { Repository } from 'typeorm';
|
interface AsyncDisposableReference<T> extends AsyncDisposable {
|
||||||
|
readonly value: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SEEALSO: <https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#using-declarations-and-explicit-resource-management>
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-expect-error
|
||||||
|
Symbol.dispose ??= Symbol('Symbol.dispose');
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-expect-error
|
||||||
|
Symbol.asyncDispose ??= Symbol('Symbol.asyncDispose');
|
||||||
|
|
||||||
|
export interface MiRepository<T extends ObjectLiteral> {
|
||||||
|
createTableColumnNames(this: Repository<T> & MiRepository<T>, queryBuilder: InsertQueryBuilder<T>): string[];
|
||||||
|
createTableColumnNamesWithPrimaryKey(this: Repository<T> & MiRepository<T>, queryBuilder: InsertQueryBuilder<T>): string[];
|
||||||
|
insertOne(this: Repository<T> & MiRepository<T>, entity: QueryDeepPartialEntity<T>, findOptions?: Pick<FindOneOptions<T>, 'relations'>): Promise<T>;
|
||||||
|
selectAliasColumnNames(this: Repository<T> & MiRepository<T>, queryBuilder: InsertQueryBuilder<T>, builder: SelectQueryBuilder<T>): void;
|
||||||
|
useQueryRunner(this: Repository<T> & MiRepository<T>, mode: ReplicationMode): AsyncDisposableReference<QueryRunner>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const miRepository = {
|
||||||
|
createTableColumnNames(queryBuilder) {
|
||||||
|
// @ts-expect-error -- protected
|
||||||
|
const insertedColumns = queryBuilder.getInsertedColumns();
|
||||||
|
if (insertedColumns.length) {
|
||||||
|
return insertedColumns.map(column => column.databaseName);
|
||||||
|
}
|
||||||
|
if (!queryBuilder.expressionMap.mainAlias?.hasMetadata && !queryBuilder.expressionMap.insertColumns.length) {
|
||||||
|
// @ts-expect-error -- protected
|
||||||
|
const valueSets = queryBuilder.getValueSets();
|
||||||
|
if (valueSets.length === 1) {
|
||||||
|
return Object.keys(valueSets[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return queryBuilder.expressionMap.insertColumns;
|
||||||
|
},
|
||||||
|
createTableColumnNamesWithPrimaryKey(queryBuilder) {
|
||||||
|
const columnNames = this.createTableColumnNames(queryBuilder);
|
||||||
|
if (!columnNames.includes('id')) {
|
||||||
|
columnNames.unshift('id');
|
||||||
|
}
|
||||||
|
return columnNames;
|
||||||
|
},
|
||||||
|
async insertOne(entity, findOptions?) {
|
||||||
|
await using queryRunnerADR = this.useQueryRunner('master');
|
||||||
|
const queryRunner = queryRunnerADR.value;
|
||||||
|
const queryBuilder = this.createQueryBuilder(undefined, queryRunner).insert().values(entity);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
const mainAlias = queryBuilder.expressionMap.mainAlias!;
|
||||||
|
const name = mainAlias.name;
|
||||||
|
mainAlias.name = 't';
|
||||||
|
const columnNames = this.createTableColumnNamesWithPrimaryKey(queryBuilder);
|
||||||
|
queryBuilder.returning(columnNames.reduce((a, c) => `${a}, ${queryBuilder.escape(c)}`, '').slice(2));
|
||||||
|
const builder = this.createQueryBuilder(undefined, queryRunner).addCommonTableExpression(queryBuilder, 'cte', { columnNames });
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
builder.expressionMap.mainAlias!.tablePath = 'cte';
|
||||||
|
this.selectAliasColumnNames(queryBuilder, builder);
|
||||||
|
if (findOptions) {
|
||||||
|
builder.setFindOptions(findOptions);
|
||||||
|
}
|
||||||
|
const raw = await builder.execute();
|
||||||
|
mainAlias.name = name;
|
||||||
|
const relationId = await new RelationIdLoader(builder.connection, queryRunner, builder.expressionMap.relationIdAttributes).load(raw);
|
||||||
|
const relationCount = await new RelationCountLoader(builder.connection, queryRunner, builder.expressionMap.relationCountAttributes).load(raw);
|
||||||
|
const result = new RawSqlResultsToEntityTransformer(builder.expressionMap, builder.connection.driver, relationId, relationCount, queryRunner).transform(raw, mainAlias);
|
||||||
|
return result[0];
|
||||||
|
},
|
||||||
|
selectAliasColumnNames(queryBuilder, builder) {
|
||||||
|
let selectOrAddSelect = (selection: string, selectionAliasName?: string) => {
|
||||||
|
selectOrAddSelect = (selection, selectionAliasName) => builder.addSelect(selection, selectionAliasName);
|
||||||
|
return builder.select(selection, selectionAliasName);
|
||||||
|
};
|
||||||
|
for (const columnName of this.createTableColumnNamesWithPrimaryKey(queryBuilder)) {
|
||||||
|
selectOrAddSelect(`${builder.alias}.${columnName}`, `${builder.alias}_${columnName}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
useQueryRunner(mode) {
|
||||||
|
if (this.queryRunner?.getReplicationMode() === mode) {
|
||||||
|
return {
|
||||||
|
value: this.queryRunner,
|
||||||
|
[Symbol.asyncDispose]() {
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const queryRunner = this.manager.connection.createQueryRunner(mode);
|
||||||
|
return {
|
||||||
|
value: queryRunner,
|
||||||
|
[Symbol.asyncDispose]() {
|
||||||
|
return queryRunner.release();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
} satisfies MiRepository<ObjectLiteral>;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
MiAbuseUserReport,
|
MiAbuseUserReport,
|
||||||
@@ -143,70 +241,70 @@ export {
|
|||||||
MiReversiGame,
|
MiReversiGame,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AbuseUserReportsRepository = Repository<MiAbuseUserReport>;
|
export type AbuseUserReportsRepository = Repository<MiAbuseUserReport> & MiRepository<MiAbuseUserReport>;
|
||||||
export type AccessTokensRepository = Repository<MiAccessToken>;
|
export type AccessTokensRepository = Repository<MiAccessToken> & MiRepository<MiAccessToken>;
|
||||||
export type AdsRepository = Repository<MiAd>;
|
export type AdsRepository = Repository<MiAd> & MiRepository<MiAd>;
|
||||||
export type AnnouncementsRepository = Repository<MiAnnouncement>;
|
export type AnnouncementsRepository = Repository<MiAnnouncement> & MiRepository<MiAnnouncement>;
|
||||||
export type AnnouncementReadsRepository = Repository<MiAnnouncementRead>;
|
export type AnnouncementReadsRepository = Repository<MiAnnouncementRead> & MiRepository<MiAnnouncementRead>;
|
||||||
export type AntennasRepository = Repository<MiAntenna>;
|
export type AntennasRepository = Repository<MiAntenna> & MiRepository<MiAntenna>;
|
||||||
export type AppsRepository = Repository<MiApp>;
|
export type AppsRepository = Repository<MiApp> & MiRepository<MiApp>;
|
||||||
export type AvatarDecorationsRepository = Repository<MiAvatarDecoration>;
|
export type AvatarDecorationsRepository = Repository<MiAvatarDecoration> & MiRepository<MiAvatarDecoration>;
|
||||||
export type AuthSessionsRepository = Repository<MiAuthSession>;
|
export type AuthSessionsRepository = Repository<MiAuthSession> & MiRepository<MiAuthSession>;
|
||||||
export type BlockingsRepository = Repository<MiBlocking>;
|
export type BlockingsRepository = Repository<MiBlocking> & MiRepository<MiBlocking>;
|
||||||
export type ChannelFollowingsRepository = Repository<MiChannelFollowing>;
|
export type ChannelFollowingsRepository = Repository<MiChannelFollowing> & MiRepository<MiChannelFollowing>;
|
||||||
export type ChannelFavoritesRepository = Repository<MiChannelFavorite>;
|
export type ChannelFavoritesRepository = Repository<MiChannelFavorite> & MiRepository<MiChannelFavorite>;
|
||||||
export type ClipsRepository = Repository<MiClip>;
|
export type ClipsRepository = Repository<MiClip> & MiRepository<MiClip>;
|
||||||
export type ClipNotesRepository = Repository<MiClipNote>;
|
export type ClipNotesRepository = Repository<MiClipNote> & MiRepository<MiClipNote>;
|
||||||
export type ClipFavoritesRepository = Repository<MiClipFavorite>;
|
export type ClipFavoritesRepository = Repository<MiClipFavorite> & MiRepository<MiClipFavorite>;
|
||||||
export type DriveFilesRepository = Repository<MiDriveFile>;
|
export type DriveFilesRepository = Repository<MiDriveFile> & MiRepository<MiDriveFile>;
|
||||||
export type DriveFoldersRepository = Repository<MiDriveFolder>;
|
export type DriveFoldersRepository = Repository<MiDriveFolder> & MiRepository<MiDriveFolder>;
|
||||||
export type EmojisRepository = Repository<MiEmoji>;
|
export type EmojisRepository = Repository<MiEmoji> & MiRepository<MiEmoji>;
|
||||||
export type FollowingsRepository = Repository<MiFollowing>;
|
export type FollowingsRepository = Repository<MiFollowing> & MiRepository<MiFollowing>;
|
||||||
export type FollowRequestsRepository = Repository<MiFollowRequest>;
|
export type FollowRequestsRepository = Repository<MiFollowRequest> & MiRepository<MiFollowRequest>;
|
||||||
export type GalleryLikesRepository = Repository<MiGalleryLike>;
|
export type GalleryLikesRepository = Repository<MiGalleryLike> & MiRepository<MiGalleryLike>;
|
||||||
export type GalleryPostsRepository = Repository<MiGalleryPost>;
|
export type GalleryPostsRepository = Repository<MiGalleryPost> & MiRepository<MiGalleryPost>;
|
||||||
export type HashtagsRepository = Repository<MiHashtag>;
|
export type HashtagsRepository = Repository<MiHashtag> & MiRepository<MiHashtag>;
|
||||||
export type InstancesRepository = Repository<MiInstance>;
|
export type InstancesRepository = Repository<MiInstance> & MiRepository<MiInstance>;
|
||||||
export type MetasRepository = Repository<MiMeta>;
|
export type MetasRepository = Repository<MiMeta> & MiRepository<MiMeta>;
|
||||||
export type ModerationLogsRepository = Repository<MiModerationLog>;
|
export type ModerationLogsRepository = Repository<MiModerationLog> & MiRepository<MiModerationLog>;
|
||||||
export type MutingsRepository = Repository<MiMuting>;
|
export type MutingsRepository = Repository<MiMuting> & MiRepository<MiMuting>;
|
||||||
export type RenoteMutingsRepository = Repository<MiRenoteMuting>;
|
export type RenoteMutingsRepository = Repository<MiRenoteMuting> & MiRepository<MiRenoteMuting>;
|
||||||
export type NotesRepository = Repository<MiNote>;
|
export type NotesRepository = Repository<MiNote> & MiRepository<MiNote>;
|
||||||
export type NoteFavoritesRepository = Repository<MiNoteFavorite>;
|
export type NoteFavoritesRepository = Repository<MiNoteFavorite> & MiRepository<MiNoteFavorite>;
|
||||||
export type NoteReactionsRepository = Repository<MiNoteReaction>;
|
export type NoteReactionsRepository = Repository<MiNoteReaction> & MiRepository<MiNoteReaction>;
|
||||||
export type NoteThreadMutingsRepository = Repository<MiNoteThreadMuting>;
|
export type NoteThreadMutingsRepository = Repository<MiNoteThreadMuting> & MiRepository<MiNoteThreadMuting>;
|
||||||
export type NoteUnreadsRepository = Repository<MiNoteUnread>;
|
export type NoteUnreadsRepository = Repository<MiNoteUnread> & MiRepository<MiNoteUnread>;
|
||||||
export type PagesRepository = Repository<MiPage>;
|
export type PagesRepository = Repository<MiPage> & MiRepository<MiPage>;
|
||||||
export type PageLikesRepository = Repository<MiPageLike>;
|
export type PageLikesRepository = Repository<MiPageLike> & MiRepository<MiPageLike>;
|
||||||
export type PasswordResetRequestsRepository = Repository<MiPasswordResetRequest>;
|
export type PasswordResetRequestsRepository = Repository<MiPasswordResetRequest> & MiRepository<MiPasswordResetRequest>;
|
||||||
export type PollsRepository = Repository<MiPoll>;
|
export type PollsRepository = Repository<MiPoll> & MiRepository<MiPoll>;
|
||||||
export type PollVotesRepository = Repository<MiPollVote>;
|
export type PollVotesRepository = Repository<MiPollVote> & MiRepository<MiPollVote>;
|
||||||
export type PromoNotesRepository = Repository<MiPromoNote>;
|
export type PromoNotesRepository = Repository<MiPromoNote> & MiRepository<MiPromoNote>;
|
||||||
export type PromoReadsRepository = Repository<MiPromoRead>;
|
export type PromoReadsRepository = Repository<MiPromoRead> & MiRepository<MiPromoRead>;
|
||||||
export type RegistrationTicketsRepository = Repository<MiRegistrationTicket>;
|
export type RegistrationTicketsRepository = Repository<MiRegistrationTicket> & MiRepository<MiRegistrationTicket>;
|
||||||
export type RegistryItemsRepository = Repository<MiRegistryItem>;
|
export type RegistryItemsRepository = Repository<MiRegistryItem> & MiRepository<MiRegistryItem>;
|
||||||
export type RelaysRepository = Repository<MiRelay>;
|
export type RelaysRepository = Repository<MiRelay> & MiRepository<MiRelay>;
|
||||||
export type SigninsRepository = Repository<MiSignin>;
|
export type SigninsRepository = Repository<MiSignin> & MiRepository<MiSignin>;
|
||||||
export type SwSubscriptionsRepository = Repository<MiSwSubscription>;
|
export type SwSubscriptionsRepository = Repository<MiSwSubscription> & MiRepository<MiSwSubscription>;
|
||||||
export type UsedUsernamesRepository = Repository<MiUsedUsername>;
|
export type UsedUsernamesRepository = Repository<MiUsedUsername> & MiRepository<MiUsedUsername>;
|
||||||
export type UsersRepository = Repository<MiUser>;
|
export type UsersRepository = Repository<MiUser> & MiRepository<MiUser>;
|
||||||
export type UserIpsRepository = Repository<MiUserIp>;
|
export type UserIpsRepository = Repository<MiUserIp> & MiRepository<MiUserIp>;
|
||||||
export type UserKeypairsRepository = Repository<MiUserKeypair>;
|
export type UserKeypairsRepository = Repository<MiUserKeypair> & MiRepository<MiUserKeypair>;
|
||||||
export type UserListsRepository = Repository<MiUserList>;
|
export type UserListsRepository = Repository<MiUserList> & MiRepository<MiUserList>;
|
||||||
export type UserListFavoritesRepository = Repository<MiUserListFavorite>;
|
export type UserListFavoritesRepository = Repository<MiUserListFavorite> & MiRepository<MiUserListFavorite>;
|
||||||
export type UserListMembershipsRepository = Repository<MiUserListMembership>;
|
export type UserListMembershipsRepository = Repository<MiUserListMembership> & MiRepository<MiUserListMembership>;
|
||||||
export type UserNotePiningsRepository = Repository<MiUserNotePining>;
|
export type UserNotePiningsRepository = Repository<MiUserNotePining> & MiRepository<MiUserNotePining>;
|
||||||
export type UserPendingsRepository = Repository<MiUserPending>;
|
export type UserPendingsRepository = Repository<MiUserPending> & MiRepository<MiUserPending>;
|
||||||
export type UserProfilesRepository = Repository<MiUserProfile>;
|
export type UserProfilesRepository = Repository<MiUserProfile> & MiRepository<MiUserProfile>;
|
||||||
export type UserPublickeysRepository = Repository<MiUserPublickey>;
|
export type UserPublickeysRepository = Repository<MiUserPublickey> & MiRepository<MiUserPublickey>;
|
||||||
export type UserSecurityKeysRepository = Repository<MiUserSecurityKey>;
|
export type UserSecurityKeysRepository = Repository<MiUserSecurityKey> & MiRepository<MiUserSecurityKey>;
|
||||||
export type WebhooksRepository = Repository<MiWebhook>;
|
export type WebhooksRepository = Repository<MiWebhook> & MiRepository<MiWebhook>;
|
||||||
export type ChannelsRepository = Repository<MiChannel>;
|
export type ChannelsRepository = Repository<MiChannel> & MiRepository<MiChannel>;
|
||||||
export type RetentionAggregationsRepository = Repository<MiRetentionAggregation>;
|
export type RetentionAggregationsRepository = Repository<MiRetentionAggregation> & MiRepository<MiRetentionAggregation>;
|
||||||
export type RolesRepository = Repository<MiRole>;
|
export type RolesRepository = Repository<MiRole> & MiRepository<MiRole>;
|
||||||
export type RoleAssignmentsRepository = Repository<MiRoleAssignment>;
|
export type RoleAssignmentsRepository = Repository<MiRoleAssignment> & MiRepository<MiRoleAssignment>;
|
||||||
export type FlashsRepository = Repository<MiFlash>;
|
export type FlashsRepository = Repository<MiFlash> & MiRepository<MiFlash>;
|
||||||
export type FlashLikesRepository = Repository<MiFlashLike>;
|
export type FlashLikesRepository = Repository<MiFlashLike> & MiRepository<MiFlashLike>;
|
||||||
export type UserMemoRepository = Repository<MiUserMemo>;
|
export type UserMemoRepository = Repository<MiUserMemo> & MiRepository<MiUserMemo>;
|
||||||
export type BubbleGameRecordsRepository = Repository<MiBubbleGameRecord>;
|
export type BubbleGameRecordsRepository = Repository<MiBubbleGameRecord> & MiRepository<MiBubbleGameRecord>;
|
||||||
export type ReversiGamesRepository = Repository<MiReversiGame>;
|
export type ReversiGamesRepository = Repository<MiReversiGame> & MiRepository<MiReversiGame>;
|
||||||
|
@@ -72,10 +72,6 @@ export const packedAntennaSchema = {
|
|||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
notify: {
|
|
||||||
type: 'boolean',
|
|
||||||
optional: false, nullable: false,
|
|
||||||
},
|
|
||||||
excludeBots: {
|
excludeBots: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
@@ -99,5 +95,10 @@ export const packedAntennaSchema = {
|
|||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
notify: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
@@ -45,6 +45,11 @@ export const packedFederationInstanceSchema = {
|
|||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
suspensionState: {
|
||||||
|
type: 'string',
|
||||||
|
nullable: false, optional: false,
|
||||||
|
enum: ['none', 'manuallySuspended', 'goneSuspended', 'autoSuspendedForNotResponding'],
|
||||||
|
},
|
||||||
isBlocked: {
|
isBlocked: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
@@ -227,6 +227,10 @@ export const packedMetaLiteSchema = {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
},
|
},
|
||||||
|
inquiryUrl: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
serverRules: {
|
serverRules: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import * as Bull from 'bullmq';
|
import * as Bull from 'bullmq';
|
||||||
|
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 } from '@/models/_.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
@@ -62,7 +63,7 @@ export class DeliverProcessorService {
|
|||||||
if (suspendedHosts == null) {
|
if (suspendedHosts == null) {
|
||||||
suspendedHosts = await this.instancesRepository.find({
|
suspendedHosts = await this.instancesRepository.find({
|
||||||
where: {
|
where: {
|
||||||
isSuspended: true,
|
suspensionState: Not('none'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.suspendedHostsCache.set(suspendedHosts);
|
this.suspendedHostsCache.set(suspendedHosts);
|
||||||
@@ -79,6 +80,7 @@ export class DeliverProcessorService {
|
|||||||
if (i.isNotResponding) {
|
if (i.isNotResponding) {
|
||||||
this.federatedInstanceService.update(i.id, {
|
this.federatedInstanceService.update(i.id, {
|
||||||
isNotResponding: false,
|
isNotResponding: false,
|
||||||
|
notRespondingSince: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +100,15 @@ export class DeliverProcessorService {
|
|||||||
if (!i.isNotResponding) {
|
if (!i.isNotResponding) {
|
||||||
this.federatedInstanceService.update(i.id, {
|
this.federatedInstanceService.update(i.id, {
|
||||||
isNotResponding: true,
|
isNotResponding: true,
|
||||||
|
notRespondingSince: new Date(),
|
||||||
});
|
});
|
||||||
|
} else if (i.notRespondingSince) {
|
||||||
|
// 1週間以上不通ならサスペンド
|
||||||
|
if (i.suspensionState === 'none' && i.notRespondingSince.getTime() <= Date.now() - 1000 * 60 * 60 * 24 * 7) {
|
||||||
|
this.federatedInstanceService.update(i.id, {
|
||||||
|
suspensionState: 'autoSuspendedForNotResponding',
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.apRequestChart.deliverFail();
|
this.apRequestChart.deliverFail();
|
||||||
@@ -116,7 +126,7 @@ export class DeliverProcessorService {
|
|||||||
if (job.data.isSharedInbox && res.statusCode === 410) {
|
if (job.data.isSharedInbox && res.statusCode === 410) {
|
||||||
this.federatedInstanceService.fetch(host).then(i => {
|
this.federatedInstanceService.fetch(host).then(i => {
|
||||||
this.federatedInstanceService.update(i.id, {
|
this.federatedInstanceService.update(i.id, {
|
||||||
isSuspended: true,
|
suspensionState: 'goneSuspended',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
throw new Bull.UnrecoverableError(`${host} is gone`);
|
throw new Bull.UnrecoverableError(`${host} is gone`);
|
||||||
|
@@ -84,7 +84,6 @@ export class ExportAntennasProcessorService {
|
|||||||
excludeBots: antenna.excludeBots,
|
excludeBots: antenna.excludeBots,
|
||||||
withReplies: antenna.withReplies,
|
withReplies: antenna.withReplies,
|
||||||
withFile: antenna.withFile,
|
withFile: antenna.withFile,
|
||||||
notify: antenna.notify,
|
|
||||||
}));
|
}));
|
||||||
if (antennas.length - 1 !== index) {
|
if (antennas.length - 1 !== index) {
|
||||||
write(', ');
|
write(', ');
|
||||||
|
@@ -47,9 +47,8 @@ const validate = new Ajv().compile({
|
|||||||
excludeBots: { type: 'boolean' },
|
excludeBots: { type: 'boolean' },
|
||||||
withReplies: { type: 'boolean' },
|
withReplies: { type: 'boolean' },
|
||||||
withFile: { type: 'boolean' },
|
withFile: { type: 'boolean' },
|
||||||
notify: { type: 'boolean' },
|
|
||||||
},
|
},
|
||||||
required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile', 'notify'],
|
required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile'],
|
||||||
});
|
});
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -77,7 +76,7 @@ export class ImportAntennasProcessorService {
|
|||||||
this.logger.warn('Validation Failed');
|
this.logger.warn('Validation Failed');
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const result = await this.antennasRepository.insert({
|
const result = await this.antennasRepository.insertOne({
|
||||||
id: this.idService.gen(now.getTime()),
|
id: this.idService.gen(now.getTime()),
|
||||||
lastUsedAt: now,
|
lastUsedAt: now,
|
||||||
userId: job.data.user.id,
|
userId: job.data.user.id,
|
||||||
@@ -92,8 +91,7 @@ export class ImportAntennasProcessorService {
|
|||||||
excludeBots: antenna.excludeBots,
|
excludeBots: antenna.excludeBots,
|
||||||
withReplies: antenna.withReplies,
|
withReplies: antenna.withReplies,
|
||||||
withFile: antenna.withFile,
|
withFile: antenna.withFile,
|
||||||
notify: antenna.notify,
|
});
|
||||||
}).then(x => this.antennasRepository.findOneByOrFail(x.identifiers[0]));
|
|
||||||
this.logger.succ('Antenna created: ' + result.id);
|
this.logger.succ('Antenna created: ' + result.id);
|
||||||
this.globalEventService.publishInternalEvent('antennaCreated', result);
|
this.globalEventService.publishInternalEvent('antennaCreated', result);
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user