Compare commits
168 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8f65ca426 | ||
|
|
16b50fc6a9 | ||
|
|
fb54c58a66 | ||
|
|
3a924f3dc6 | ||
|
|
11d22c7b73 | ||
|
|
a879607479 | ||
|
|
98aef974df | ||
|
|
cf46816687 | ||
|
|
eee1e74174 | ||
|
|
8050f89d7e | ||
|
|
406e5d297b | ||
|
|
10634b3615 | ||
|
|
fd03e2e1a7 | ||
|
|
6cc86272f3 | ||
|
|
06b1250d47 | ||
|
|
31a7350a10 | ||
|
|
4129ac157a | ||
|
|
30cb791e93 | ||
|
|
1c57983bfd | ||
|
|
bdf08c8a54 | ||
|
|
0513ff8b4e | ||
|
|
62fe3bfb54 | ||
|
|
38a1d6693a | ||
|
|
d2eec3a9e4 | ||
|
|
1de774fa3d | ||
|
|
ed902658a9 | ||
|
|
acdcd7c623 | ||
|
|
b0344e07c4 | ||
|
|
9a6ce1e867 | ||
|
|
22a6bd6b22 | ||
|
|
38e6f3f776 | ||
|
|
ca75afe065 | ||
|
|
915ed39715 | ||
|
|
81fd94e635 | ||
|
|
05507a4bea | ||
|
|
d177f97928 | ||
|
|
30cb03a40d | ||
|
|
c685989e67 | ||
|
|
ee3f408c7d | ||
|
|
1eb35dd5bc | ||
|
|
15db0b8812 | ||
|
|
1b78c6a309 | ||
|
|
c713af8e23 | ||
|
|
bd6666173a | ||
|
|
02715f5d14 | ||
|
|
acd5e0b8f6 | ||
|
|
be2142bb13 | ||
|
|
4a703d7cf6 | ||
|
|
95470a40a7 | ||
|
|
56d4658b36 | ||
|
|
f68008b002 | ||
|
|
6a5ef5b6f2 | ||
|
|
95b9284e79 | ||
|
|
8317772436 | ||
|
|
0c0ae6ff90 | ||
|
|
d63b943116 | ||
|
|
dddbc1c894 | ||
|
|
f68c743f39 | ||
|
|
59255e11b8 | ||
|
|
3804c6e7ad | ||
|
|
527a13b77d | ||
|
|
a3423bad60 | ||
|
|
e3f9144608 | ||
|
|
231506772a | ||
|
|
db1098a180 | ||
|
|
7ce569424a | ||
|
|
9b5b3a4d1b | ||
|
|
6173cebdca | ||
|
|
6bb82cda37 | ||
|
|
dd60f1a533 | ||
|
|
45263f4cd0 | ||
|
|
747d323584 | ||
|
|
e707aadbcc | ||
|
|
7b012967d9 | ||
|
|
e126083e10 | ||
|
|
cbca48846c | ||
|
|
ca2ed0a59b | ||
|
|
5d22e113b2 | ||
|
|
ed3c137543 | ||
|
|
a35f0d43e4 | ||
|
|
153eed7d71 | ||
|
|
0d8e1c5421 | ||
|
|
d10d5a8d53 | ||
|
|
23f106a0c1 | ||
|
|
a7ee4aabcb | ||
|
|
d867fc00b6 | ||
|
|
60f504bbe2 | ||
|
|
f4e6d73a8a | ||
|
|
c066013c57 | ||
|
|
86f952e659 | ||
|
|
d36e44bc57 | ||
|
|
93629fb29d | ||
|
|
9ff088a830 | ||
|
|
55190e6b8b | ||
|
|
38391010af | ||
|
|
3d94dec7a9 | ||
|
|
636428c72e | ||
|
|
0717afc312 | ||
|
|
3d4a90b08a | ||
|
|
7cc555fd28 | ||
|
|
238d0fa667 | ||
|
|
30ff4592cc | ||
|
|
a979fb9207 | ||
|
|
f15f60d5b9 | ||
|
|
8c97c54cfa | ||
|
|
89a3195dfd | ||
|
|
1eaf287b9c | ||
|
|
9166a58c5f | ||
|
|
eef368abd0 | ||
|
|
f983e44d9e | ||
|
|
5be6438bbc | ||
|
|
d2e6b17a26 | ||
|
|
d1c2e4aaa0 | ||
|
|
6f1994c665 | ||
|
|
055dc6bb66 | ||
|
|
f06339b970 | ||
|
|
3370419b0a | ||
|
|
9bd4ad0fc1 | ||
|
|
23994d8f4e | ||
|
|
bb3c85d3a1 | ||
|
|
8328a66104 | ||
|
|
909ec8c38d | ||
|
|
b01a1b3c17 | ||
|
|
cbb779f60c | ||
|
|
8ddae83c40 | ||
|
|
1cc106b8de | ||
|
|
16eedb86a5 | ||
|
|
2b26a2f5e4 | ||
|
|
a65fa684ce | ||
|
|
0758c56c53 | ||
|
|
3c0dccc8b9 | ||
|
|
e18feb4183 | ||
|
|
9e464eaa63 | ||
|
|
8b352e4e56 | ||
|
|
26d85c8bed | ||
|
|
df924d18f1 | ||
|
|
177359689e | ||
|
|
b380dc53e1 | ||
|
|
8c30ca50a8 | ||
|
|
69afd0480e | ||
|
|
6d4d9f417b | ||
|
|
11ea1a2811 | ||
|
|
c15b75e477 | ||
|
|
ea9a95cd98 | ||
|
|
ed634b4b3d | ||
|
|
f9f115b66d | ||
|
|
02059fdee4 | ||
|
|
37c119e1f5 | ||
|
|
d75a7fd421 | ||
|
|
adf66154d2 | ||
|
|
9557579b67 | ||
|
|
341c42ebb9 | ||
|
|
574cfdfda6 | ||
|
|
0729678398 | ||
|
|
b545146bf9 | ||
|
|
7ae1d6511a | ||
|
|
0c4c4e7202 | ||
|
|
6a25015a78 | ||
|
|
ae5a72a2df | ||
|
|
baa45859c1 | ||
|
|
461f761aa6 | ||
|
|
6894e42674 | ||
|
|
22d6c7fe8a | ||
|
|
9e70b02648 | ||
|
|
64f5b2daa2 | ||
|
|
b2f6c67aa9 | ||
|
|
1c0ec222b4 | ||
|
|
80619260c1 |
@@ -103,6 +103,7 @@ redis:
|
|||||||
# port: 7700
|
# port: 7700
|
||||||
# apiKey: ''
|
# apiKey: ''
|
||||||
# ssl: true
|
# ssl: true
|
||||||
|
# index: ''
|
||||||
|
|
||||||
# ┌───────────────┐
|
# ┌───────────────┐
|
||||||
#───┘ ID generation └───────────────────────────────────────────
|
#───┘ ID generation └───────────────────────────────────────────
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ redis:
|
|||||||
# port: 7700
|
# port: 7700
|
||||||
# apiKey: ''
|
# apiKey: ''
|
||||||
# ssl: true
|
# ssl: true
|
||||||
|
# index: ''
|
||||||
|
|
||||||
# ┌───────────────┐
|
# ┌───────────────┐
|
||||||
#───┘ ID generation └───────────────────────────────────────────
|
#───┘ ID generation └───────────────────────────────────────────
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ redis:
|
|||||||
# port: 7700
|
# port: 7700
|
||||||
# apiKey: ''
|
# apiKey: ''
|
||||||
# ssl: true
|
# ssl: true
|
||||||
|
# index: ''
|
||||||
|
|
||||||
# ┌───────────────┐
|
# ┌───────────────┐
|
||||||
#───┘ ID generation └───────────────────────────────────────────
|
#───┘ ID generation └───────────────────────────────────────────
|
||||||
|
|||||||
1
.github/reviewer-lottery.yml
vendored
1
.github/reviewer-lottery.yml
vendored
@@ -6,5 +6,4 @@ groups:
|
|||||||
- syuilo
|
- syuilo
|
||||||
- acid-chicken
|
- acid-chicken
|
||||||
- EbiseLutica
|
- EbiseLutica
|
||||||
- rinsuki
|
|
||||||
- tamaina
|
- tamaina
|
||||||
|
|||||||
46
.github/workflows/storybook.yml
vendored
46
.github/workflows/storybook.yml
vendored
@@ -16,12 +16,22 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3.3.0
|
- uses: actions/checkout@v3.3.0
|
||||||
|
if: github.event_name != 'pull_request_target'
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Checkout HEAD
|
- uses: actions/checkout@v3.3.0
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request_target'
|
||||||
run: git checkout ${{ github.head_ref }}
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
submodules: true
|
||||||
|
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||||
|
- name: Checkout actual HEAD
|
||||||
|
if: github.event_name == 'pull_request_target'
|
||||||
|
id: rev
|
||||||
|
run: |
|
||||||
|
echo "base=$(git rev-list --parents -n1 HEAD | cut -d" " -f2)" >> $GITHUB_OUTPUT
|
||||||
|
git checkout $(git rev-list --parents -n1 HEAD | cut -d" " -f3)
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v2
|
||||||
with:
|
with:
|
||||||
@@ -41,12 +51,12 @@ jobs:
|
|||||||
- name: Build storybook
|
- name: Build storybook
|
||||||
run: pnpm --filter frontend build-storybook
|
run: pnpm --filter frontend build-storybook
|
||||||
- name: Publish to Chromatic
|
- name: Publish to Chromatic
|
||||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master'
|
if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/master'
|
||||||
run: pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static
|
run: pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static
|
||||||
env:
|
env:
|
||||||
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||||
- name: Publish to Chromatic
|
- name: Publish to Chromatic
|
||||||
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/master'
|
if: github.event_name != 'pull_request_target' && github.ref != 'refs/heads/master'
|
||||||
id: chromatic_push
|
id: chromatic_push
|
||||||
run: |
|
run: |
|
||||||
DIFF="${{ github.event.before }} HEAD"
|
DIFF="${{ github.event.before }} HEAD"
|
||||||
@@ -57,14 +67,18 @@ jobs:
|
|||||||
if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then
|
if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then
|
||||||
echo "skip=true" >> $GITHUB_OUTPUT
|
echo "skip=true" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static $(echo "$CHROMATIC_PARAMETER")
|
if pnpm --filter frontend chromatic -d storybook-static $(echo "$CHROMATIC_PARAMETER"); then
|
||||||
|
echo "success=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "success=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
env:
|
env:
|
||||||
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||||
- name: Publish to Chromatic
|
- name: Publish to Chromatic
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request_target'
|
||||||
id: chromatic_pull_request
|
id: chromatic_pull_request
|
||||||
run: |
|
run: |
|
||||||
DIFF="${{ github.base_ref }} HEAD"
|
DIFF="${{ steps.rev.outputs.base }} HEAD"
|
||||||
if [ "$DIFF" = "0000000000000000000000000000000000000000 HEAD" ]; then
|
if [ "$DIFF" = "0000000000000000000000000000000000000000 HEAD" ]; then
|
||||||
DIFF="HEAD"
|
DIFF="HEAD"
|
||||||
fi
|
fi
|
||||||
@@ -72,20 +86,24 @@ jobs:
|
|||||||
if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then
|
if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then
|
||||||
echo "skip=true" >> $GITHUB_OUTPUT
|
echo "skip=true" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static $(echo "$CHROMATIC_PARAMETER")
|
BRANCH="${{ github.event.pull_request.head.user.login }}:${{ github.event.pull_request.head.ref }}"
|
||||||
|
if [ "$BRANCH" = "misskey-dev:${{ github.event.pull_request.head.ref }}" ]; then
|
||||||
|
BRANCH="${{ github.event.pull_request.head.ref }}"
|
||||||
|
fi
|
||||||
|
pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static --branch-name $BRANCH $(echo "$CHROMATIC_PARAMETER")
|
||||||
env:
|
env:
|
||||||
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||||
- name: Notify that Chromatic will skip testing
|
- name: Notify that Chromatic detects changes
|
||||||
uses: actions/github-script@v6.4.0
|
uses: actions/github-script@v6.4.0
|
||||||
if: github.event_name == 'pull_request' && steps.chromatic_pull_request.outputs.skip == 'true'
|
if: github.event_name != 'pull_request_target' && steps.chromatic_push.outputs.success == 'false'
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
github.rest.issues.createComment({
|
github.rest.repos.createCommitComment({
|
||||||
issue_number: context.issue.number,
|
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
body: 'Chromatic will skip testing but you may still have to [review the changes on Chromatic](https://www.chromatic.com/pullrequests?appId=6428f7d7b962f0b79f97d6e4).'
|
commit_sha: context.sha,
|
||||||
|
body: 'Chromatic detects changes. Please [review the changes on Chromatic](https://www.chromatic.com/builds?appId=6428f7d7b962f0b79f97d6e4).'
|
||||||
})
|
})
|
||||||
- name: Upload Artifacts
|
- name: Upload Artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
|
|||||||
65
CHANGELOG.md
65
CHANGELOG.md
@@ -11,6 +11,70 @@
|
|||||||
-
|
-
|
||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
## 13.13.0
|
||||||
|
|
||||||
|
### General
|
||||||
|
- カスタム絵文字ごとにそれをリアクションとして使えるロールを設定できるように
|
||||||
|
- カスタム絵文字ごとに連合するかどうか設定できるように
|
||||||
|
- カスタム絵文字ごとにセンシティブフラグを設定できるように
|
||||||
|
- センシティブなカスタム絵文字のリアクションを受け入れない設定が可能に
|
||||||
|
- タイムラインにフォロイーの行った他人へのリプライを含めるかどうかの設定をアカウントに保存するのをやめるように
|
||||||
|
- 今後はAPI呼び出し時およびストリーミング接続時に設定するようになります
|
||||||
|
- リストを公開できるようになりました
|
||||||
|
|
||||||
|
### Client
|
||||||
|
- リアクションの取り消し/変更時に確認ダイアログを出すように
|
||||||
|
- 開発者モードを追加
|
||||||
|
- AiScriptを0.13.3に更新
|
||||||
|
- Deck UIを使用している場合、`/`以外にアクセスした際にZen UIで表示するように
|
||||||
|
- メインカラムを設置していない場合の問題を解決
|
||||||
|
- ハッシュタグのノート一覧ページから、そのハッシュタグで投稿するボタンを追加
|
||||||
|
- アカウント初期設定ウィザードに戻るボタンを追加
|
||||||
|
- アカウントの初期設定ウィザードにあとでボタンを追加
|
||||||
|
- Fix: URLプレビューで情報が取得できなかった際の挙動を修正
|
||||||
|
- Fix: Safari、Firefoxでの新規登録時、パスワードマネージャーにメールアドレスが登録されていた挙動を修正
|
||||||
|
- fix:ロールタイムラインが無効でも投稿が流れてしまう問題の修正
|
||||||
|
- fix:ロールタイムラインにて全ての投稿が流れてしまう問題の修正
|
||||||
|
|
||||||
|
### Server
|
||||||
|
- Fix: お知らせの画像URLを空にできない問題を修正
|
||||||
|
|
||||||
|
## 13.12.2
|
||||||
|
|
||||||
|
## NOTE
|
||||||
|
Meilisearchの設定に`index`が必要になりました。値はMisskeyサーバーのホスト名にすることをお勧めします(アルファベット、ハイフン、アンダーバーのみ使用可能)。例: `misskey-io`
|
||||||
|
過去に作成された`notes`インデックスは、`<index名>---notes`にリネームが必要です。例: `misskey-io---notes`
|
||||||
|
|
||||||
|
### General
|
||||||
|
- 投稿したコンテンツのAIによる学習を軽減するオプションを追加
|
||||||
|
|
||||||
|
### Client
|
||||||
|
- ユーザーを指定してのノート検索が可能に
|
||||||
|
- アカウント初期設定ウィザードにプライバシー設定を追加
|
||||||
|
- リテンション率チャートに折れ線グラフを追加
|
||||||
|
- Fix: ブラーエフェクトを有効にしている状態で高負荷になる問題を修正
|
||||||
|
- Fix: Pageにおいて画像ブロックに画像を設定できない問題を修正
|
||||||
|
- Fix: カラーバーがリプライには表示されないのを修正
|
||||||
|
- Fix: チャンネル内の検索ボックスが挙動不審な問題を修正
|
||||||
|
- Fix: リテンションチャートのレンダリングを修正
|
||||||
|
- Fix: リアクションエフェクトのレンダリングの問題を修正
|
||||||
|
|
||||||
|
### Server
|
||||||
|
- センシティブワードの登録にAnd、正規表現が使用できるようになりました。
|
||||||
|
- Fix: ひとつのMeilisearchサーバーを複数のMisskeyサーバーで使えない問題を修正
|
||||||
|
|
||||||
|
## 13.12.1
|
||||||
|
|
||||||
|
### Client
|
||||||
|
- プロフィール画面におけるモデレーションノートの表示を調整
|
||||||
|
- Fix: 一部ダイアログが表示されない問題を修正
|
||||||
|
- Fix: MkUserInfoのフォローボタンが変な位置にある問題を修正
|
||||||
|
|
||||||
|
### Server
|
||||||
|
- Fix: リモートサーバーの情報が更新できない問題を修正
|
||||||
|
- Fix: 13.11を経験しない状態で13.12にアップデートした場合ユーザープロフィール関連の画像が消失する問題を修正
|
||||||
|
|
||||||
## 13.12.0
|
## 13.12.0
|
||||||
|
|
||||||
### NOTE
|
### NOTE
|
||||||
@@ -51,6 +115,7 @@
|
|||||||
* 画像が全て隠れた状態で表示されるようになります
|
* 画像が全て隠れた状態で表示されるようになります
|
||||||
- 閲覧注意設定された画像は表示した状態でもそれが閲覧注意だと分かる表示をするように
|
- 閲覧注意設定された画像は表示した状態でもそれが閲覧注意だと分かる表示をするように
|
||||||
- モデレーターはノートに添付された画像上から直接NSFW設定できるように
|
- モデレーターはノートに添付された画像上から直接NSFW設定できるように
|
||||||
|
- 1枚だけのメディアリストの画像のアスペクト比を画像に応じて縦長にするように
|
||||||
- プロフィール設定「追加情報」の項目の削除と並び替えができるように
|
- プロフィール設定「追加情報」の項目の削除と並び替えができるように
|
||||||
- 新しい実績を追加
|
- 新しい実績を追加
|
||||||
- AiScriptを0.13.2に更新
|
- AiScriptを0.13.2に更新
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ redis:
|
|||||||
# port: 7700
|
# port: 7700
|
||||||
# apiKey: ''
|
# apiKey: ''
|
||||||
# ssl: true
|
# ssl: true
|
||||||
|
# index: ''
|
||||||
|
|
||||||
# ┌───────────────┐
|
# ┌───────────────┐
|
||||||
#───┘ ID generation └───────────────────────────────────────────
|
#───┘ ID generation └───────────────────────────────────────────
|
||||||
|
|||||||
@@ -169,16 +169,25 @@ describe('After user signed in', () => {
|
|||||||
cy.get('[data-cy-user-setup-user-description] textarea').type('ほげ');
|
cy.get('[data-cy-user-setup-user-description] textarea').type('ほげ');
|
||||||
// TODO: アイコン設定テスト
|
// TODO: アイコン設定テスト
|
||||||
|
|
||||||
|
cy.get('[data-cy-user-setup-back]').click();
|
||||||
|
cy.get('[data-cy-user-setup-continue]').click();
|
||||||
|
|
||||||
|
// プライバシー設定
|
||||||
|
|
||||||
|
cy.get('[data-cy-user-setup-back]').click();
|
||||||
cy.get('[data-cy-user-setup-continue]').click();
|
cy.get('[data-cy-user-setup-continue]').click();
|
||||||
|
|
||||||
// フォローはスキップ
|
// フォローはスキップ
|
||||||
|
|
||||||
|
cy.get('[data-cy-user-setup-back]').click();
|
||||||
cy.get('[data-cy-user-setup-continue]').click();
|
cy.get('[data-cy-user-setup-continue]').click();
|
||||||
|
|
||||||
// プッシュ通知設定はスキップ
|
// プッシュ通知設定はスキップ
|
||||||
|
|
||||||
|
cy.get('[data-cy-user-setup-back]').click();
|
||||||
cy.get('[data-cy-user-setup-continue]').click();
|
cy.get('[data-cy-user-setup-continue]').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy-user-setup-back]').click();
|
||||||
cy.get('[data-cy-user-setup-continue]').click();
|
cy.get('[data-cy-user-setup-continue]').click();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
DONATORS
|
|
||||||
========
|
|
||||||
The list of people who have sent donation for Misskey.
|
|
||||||
|
|
||||||
(In random order, honorific titles are omitted.)
|
|
||||||
|
|
||||||
* らふぁ
|
|
||||||
* 俺様
|
|
||||||
* なぎうり
|
|
||||||
* スルメ https://surume.tk/
|
|
||||||
* 藍
|
|
||||||
* 音船 https://otofune.me/
|
|
||||||
* aqz https://misskey.xyz/aqz
|
|
||||||
* kotodu "虚無創作中"
|
|
||||||
* Maya Minatsuki
|
|
||||||
* Knzk https://knzk.me/@Knzk
|
|
||||||
* ねじりわさび https://knzk.me/@y
|
|
||||||
* NCLS https://knzk.me/@imncls]
|
|
||||||
* こじま @skoji@sandbox.skoji.jp
|
|
||||||
|
|
||||||
:heart: Thanks for donating, guys!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
If your name is missing, please contact us!
|
|
||||||
@@ -19,6 +19,7 @@ noNotes: "لم يُعثر على أية ملاحظات"
|
|||||||
noNotifications: "ليس هناك أية اشعارات"
|
noNotifications: "ليس هناك أية اشعارات"
|
||||||
instance: "مثيل الخادم"
|
instance: "مثيل الخادم"
|
||||||
settings: "الاعدادات"
|
settings: "الاعدادات"
|
||||||
|
notificationSettings: "إعدادات الإشعارات"
|
||||||
basicSettings: "الاعدادات الأساسية"
|
basicSettings: "الاعدادات الأساسية"
|
||||||
otherSettings: "إعدادات أخرى"
|
otherSettings: "إعدادات أخرى"
|
||||||
openInWindow: "افتح في نافذة جديدة"
|
openInWindow: "افتح في نافذة جديدة"
|
||||||
@@ -127,6 +128,7 @@ unblockConfirm: "أمتأكد من إلغاء حجب هذا الحساب؟"
|
|||||||
suspendConfirm: "أمتأكد من تعليق الحساب؟"
|
suspendConfirm: "أمتأكد من تعليق الحساب؟"
|
||||||
unsuspendConfirm: "أمتأكد من إلغاء تعليق؟"
|
unsuspendConfirm: "أمتأكد من إلغاء تعليق؟"
|
||||||
selectList: "اختر قائمة"
|
selectList: "اختر قائمة"
|
||||||
|
selectChannel: "اختر قناة"
|
||||||
selectAntenna: "اختر هوائيًا"
|
selectAntenna: "اختر هوائيًا"
|
||||||
selectWidget: "اختر ودجة"
|
selectWidget: "اختر ودجة"
|
||||||
editWidgets: "عدّل الودجات"
|
editWidgets: "عدّل الودجات"
|
||||||
@@ -250,6 +252,9 @@ noMoreHistory: "لا يوجد المزيد من التاريخ"
|
|||||||
startMessaging: "ابدأ محادثة"
|
startMessaging: "ابدأ محادثة"
|
||||||
nUsersRead: "قرأه {n}"
|
nUsersRead: "قرأه {n}"
|
||||||
agreeTo: "اوافق على {0}"
|
agreeTo: "اوافق على {0}"
|
||||||
|
agree: "أقبل"
|
||||||
|
basicNotesBeforeCreateAccount: "ملاحظات مهمة"
|
||||||
|
termsOfService: "شروط الخدمة"
|
||||||
start: "البداية"
|
start: "البداية"
|
||||||
home: "الرئيسي"
|
home: "الرئيسي"
|
||||||
remoteUserCaution: "هذه المعلومات قد لا تكون مكتملة بما أن المستخدم من مثيل بعيد."
|
remoteUserCaution: "هذه المعلومات قد لا تكون مكتملة بما أن المستخدم من مثيل بعيد."
|
||||||
@@ -379,6 +384,8 @@ about: "عن"
|
|||||||
aboutMisskey: "عن Misskey"
|
aboutMisskey: "عن Misskey"
|
||||||
administrator: "المدير"
|
administrator: "المدير"
|
||||||
token: "الرمز المميز"
|
token: "الرمز المميز"
|
||||||
|
2fa: "الاستيثاق بعاملَيْن"
|
||||||
|
totp: "تطبيق استيثاق"
|
||||||
moderator: "مشرِف"
|
moderator: "مشرِف"
|
||||||
moderation: "الإشراف"
|
moderation: "الإشراف"
|
||||||
nUsersMentioned: "{n} مستخدمين أُشير إليهم"
|
nUsersMentioned: "{n} مستخدمين أُشير إليهم"
|
||||||
@@ -506,6 +513,7 @@ userSuspended: "عُلق هذا المستخدم."
|
|||||||
userSilenced: "كُتم هذا المستخدم."
|
userSilenced: "كُتم هذا المستخدم."
|
||||||
yourAccountSuspendedTitle: "هذا الحساب معلق"
|
yourAccountSuspendedTitle: "هذا الحساب معلق"
|
||||||
yourAccountSuspendedDescription: "عُلق الحساب بسبب انتهاك شروط خدمة المثيل و ما شابه. إذا أردت معرفة التفصيل تواصل مع مدير المثيل. رجاءً لا تنشئ حساب جديد."
|
yourAccountSuspendedDescription: "عُلق الحساب بسبب انتهاك شروط خدمة المثيل و ما شابه. إذا أردت معرفة التفصيل تواصل مع مدير المثيل. رجاءً لا تنشئ حساب جديد."
|
||||||
|
accountDeleted: "حُذف الحساب"
|
||||||
menu: "القائمة"
|
menu: "القائمة"
|
||||||
divider: "فاصل"
|
divider: "فاصل"
|
||||||
addItem: "إضافة عنصر"
|
addItem: "إضافة عنصر"
|
||||||
|
|||||||
@@ -990,6 +990,7 @@ rolesAssignedToMe: "Mir zugewiesene Rollen"
|
|||||||
resetPasswordConfirm: "Wirklich Passwort zurücksetzen?"
|
resetPasswordConfirm: "Wirklich Passwort zurücksetzen?"
|
||||||
sensitiveWords: "Sensible Wörter"
|
sensitiveWords: "Sensible Wörter"
|
||||||
sensitiveWordsDescription: "Die Notizsichtbarkeit aller Notizen, die diese Wörter enthalten, wird automatisch auf \"Startseite\" gesetzt. Durch Zeilenumbrüche können mehrere konfiguriert werden."
|
sensitiveWordsDescription: "Die Notizsichtbarkeit aller Notizen, die diese Wörter enthalten, wird automatisch auf \"Startseite\" gesetzt. Durch Zeilenumbrüche können mehrere konfiguriert werden."
|
||||||
|
sensitiveWordsDescription2: "Durch die Verwendung von Leerzeichen können AND-Verknüpfungen angegeben werden und durch das Umgeben von Schrägstrichen können reguläre Ausdrücke verwendet werden."
|
||||||
notesSearchNotAvailable: "Die Notizsuche ist nicht verfügbar."
|
notesSearchNotAvailable: "Die Notizsuche ist nicht verfügbar."
|
||||||
license: "Lizenz"
|
license: "Lizenz"
|
||||||
unfavoriteConfirm: "Wirklich aus Favoriten entfernen?"
|
unfavoriteConfirm: "Wirklich aus Favoriten entfernen?"
|
||||||
@@ -1038,10 +1039,16 @@ thisChannelArchived: "Dieser Kanal wurde archiviert."
|
|||||||
displayOfNote: "Anzeige von Notizen"
|
displayOfNote: "Anzeige von Notizen"
|
||||||
initialAccountSetting: "Kontoeinrichtung"
|
initialAccountSetting: "Kontoeinrichtung"
|
||||||
youFollowing: "Gefolgt"
|
youFollowing: "Gefolgt"
|
||||||
|
preventAiLearning: "Verwendung in machinellem Lernen (Generative bzw. Prediktive AI/KI) ablehnen"
|
||||||
|
preventAiLearningDescription: "Fordert Crawler auf, gepostetes Text- oder Bildmaterial usw. nicht in Datensätzen für maschinelles Lernen (Generative bzw. Prediktive AI/KI) zu verwenden. Dies wird durch das Hinzufügen einer \"noai\"-Flag in der HTML-Antwort des jeweiligen Inhalts erreicht. Da diese Flag jedoch ignoriert werden kann, ist eine vollständige Verhinderung hierdurch nicht möglich."
|
||||||
|
options: "Optionen"
|
||||||
|
specifyUser: "Spezifischer Benutzer"
|
||||||
_initialAccountSetting:
|
_initialAccountSetting:
|
||||||
accountCreated: "Dein Konto wurde erfolgreich erstellt!"
|
accountCreated: "Dein Konto wurde erfolgreich erstellt!"
|
||||||
|
letsStartAccountSetup: "Lass uns nun dein Konto einrichten."
|
||||||
letsFillYourProfile: "Lass uns zuerst dein Profil einrichten."
|
letsFillYourProfile: "Lass uns zuerst dein Profil einrichten."
|
||||||
profileSetting: "Profileinstellungen"
|
profileSetting: "Profileinstellungen"
|
||||||
|
privacySetting: "Privatsphäreneinstellungen"
|
||||||
theseSettingsCanEditLater: "Diese Einstellungen kannst du jederzeit ändern."
|
theseSettingsCanEditLater: "Diese Einstellungen kannst du jederzeit ändern."
|
||||||
youCanEditMoreSettingsInSettingsPageLater: "In den Einstellungen findest du noch viele weitere Optionen. Schau dort später mal vorbei."
|
youCanEditMoreSettingsInSettingsPageLater: "In den Einstellungen findest du noch viele weitere Optionen. Schau dort später mal vorbei."
|
||||||
followUsers: "Folge zuerst ein paar Nutzern, um deine Chronik zu füllen."
|
followUsers: "Folge zuerst ein paar Nutzern, um deine Chronik zu füllen."
|
||||||
|
|||||||
@@ -990,6 +990,7 @@ rolesAssignedToMe: "Roles assigned to me"
|
|||||||
resetPasswordConfirm: "Really reset your password?"
|
resetPasswordConfirm: "Really reset your password?"
|
||||||
sensitiveWords: "Sensitive words"
|
sensitiveWords: "Sensitive words"
|
||||||
sensitiveWordsDescription: "The visibility of all notes containing any of the configured words will be set to \"Home\" automatically. You can list multiple by separating them via line breaks."
|
sensitiveWordsDescription: "The visibility of all notes containing any of the configured words will be set to \"Home\" automatically. You can list multiple by separating them via line breaks."
|
||||||
|
sensitiveWordsDescription2: "Using spaces will create AND expressions and surrounding keywords with slashes will turn them into a regular expression."
|
||||||
notesSearchNotAvailable: "Note search is unavailable."
|
notesSearchNotAvailable: "Note search is unavailable."
|
||||||
license: "License"
|
license: "License"
|
||||||
unfavoriteConfirm: "Really remove from favorites?"
|
unfavoriteConfirm: "Really remove from favorites?"
|
||||||
@@ -1036,20 +1037,26 @@ channelArchiveConfirmTitle: "Really archive {name}?"
|
|||||||
channelArchiveConfirmDescription: "An archived channel won't appear in the channel list or search results anymore. New posts can also not be added to it anymore."
|
channelArchiveConfirmDescription: "An archived channel won't appear in the channel list or search results anymore. New posts can also not be added to it anymore."
|
||||||
thisChannelArchived: "This channel has been archived."
|
thisChannelArchived: "This channel has been archived."
|
||||||
displayOfNote: "Note display"
|
displayOfNote: "Note display"
|
||||||
initialAccountSetting: "Profile configuration"
|
initialAccountSetting: "Profile setup"
|
||||||
youFollowing: "Followed"
|
youFollowing: "Followed"
|
||||||
|
preventAiLearning: "Reject usage in Machine Learning (Generative AI)"
|
||||||
|
preventAiLearningDescription: "Requests crawlers to not use posted text or image material etc. in machine learning (Predictive / Generative AI) data sets. This is achieved by adding a \"noai\" HTML-Response flag to the respective content. A complete prevention can however not be achieved through this flag, as it may simply be ignored."
|
||||||
|
options: "Options"
|
||||||
|
specifyUser: "Specific user"
|
||||||
_initialAccountSetting:
|
_initialAccountSetting:
|
||||||
accountCreated: "Your account was successfully created!"
|
accountCreated: "Your account was successfully created!"
|
||||||
|
letsStartAccountSetup: "For starters, let's set up your profile."
|
||||||
letsFillYourProfile: "First, let's set up your profile."
|
letsFillYourProfile: "First, let's set up your profile."
|
||||||
profileSetting: "Profile settings"
|
profileSetting: "Profile settings"
|
||||||
|
privacySetting: "Privacy settings"
|
||||||
theseSettingsCanEditLater: "You can always change these settings later."
|
theseSettingsCanEditLater: "You can always change these settings later."
|
||||||
youCanEditMoreSettingsInSettingsPageLater: "There are many more settings you can configure from the \"Settings\" page. Be sure to visit it later."
|
youCanEditMoreSettingsInSettingsPageLater: "There are many more settings you can configure from the \"Settings\" page. Be sure to visit it later."
|
||||||
followUsers: "Try following some users that interest you to build up your timeline."
|
followUsers: "Try following some users that interest you to build up your timeline."
|
||||||
pushNotificationDescription: "Enabling push notifications will allow you to receive notifications from {name} directly on your device."
|
pushNotificationDescription: "Enabling push notifications will allow you to receive notifications from {name} directly on your device."
|
||||||
initialAccountSettingCompleted: "Profile configuration complete!"
|
initialAccountSettingCompleted: "Profile setup complete!"
|
||||||
haveFun: "Enjoy {name}!"
|
haveFun: "Enjoy {name}!"
|
||||||
ifYouNeedLearnMore: "If you'd like to learn more about how to use {name} (Misskey), please visit {link}."
|
ifYouNeedLearnMore: "If you'd like to learn more about how to use {name} (Misskey), please visit {link}."
|
||||||
skipAreYouSure: "Really skip profile configuration?"
|
skipAreYouSure: "Really skip profile setup?"
|
||||||
_serverRules:
|
_serverRules:
|
||||||
description: "A set of rules to be displayed before registration. Setting a summary of the Terms of Service is recommended."
|
description: "A set of rules to be displayed before registration. Setting a summary of the Terms of Service is recommended."
|
||||||
_accountMigration:
|
_accountMigration:
|
||||||
@@ -1321,7 +1328,7 @@ _role:
|
|||||||
isConditionalRole: "This is a conditional role."
|
isConditionalRole: "This is a conditional role."
|
||||||
isPublic: "Public role"
|
isPublic: "Public role"
|
||||||
descriptionOfIsPublic: "Anyone will be able to view a list of users assigned to this role. In addition, this role will be displayed in the profiles of assigned users."
|
descriptionOfIsPublic: "Anyone will be able to view a list of users assigned to this role. In addition, this role will be displayed in the profiles of assigned users."
|
||||||
options: "Role options"
|
options: "Options"
|
||||||
policies: "Policies"
|
policies: "Policies"
|
||||||
baseRole: "Role template"
|
baseRole: "Role template"
|
||||||
useBaseValue: "Use role template value"
|
useBaseValue: "Use role template value"
|
||||||
|
|||||||
@@ -993,6 +993,7 @@ accountMigration: "Migración de cuenta"
|
|||||||
accountMoved: "Este usuario se ha mudado a una nueva cuenta:"
|
accountMoved: "Este usuario se ha mudado a una nueva cuenta:"
|
||||||
horizontal: "Horizontal"
|
horizontal: "Horizontal"
|
||||||
youFollowing: "Siguiendo"
|
youFollowing: "Siguiendo"
|
||||||
|
options: "Opción"
|
||||||
_accountMigration:
|
_accountMigration:
|
||||||
moveFrom: "Trasladar de otra cuenta a ésta"
|
moveFrom: "Trasladar de otra cuenta a ésta"
|
||||||
moveFromLabel: "Cuenta desde la que se realiza el traslado:"
|
moveFromLabel: "Cuenta desde la que se realiza el traslado:"
|
||||||
|
|||||||
@@ -452,6 +452,7 @@ native: "Natif"
|
|||||||
disableDrawer: "Les menus ne s'affichent pas dans le tiroir"
|
disableDrawer: "Les menus ne s'affichent pas dans le tiroir"
|
||||||
noHistory: "Pas d'historique"
|
noHistory: "Pas d'historique"
|
||||||
signinHistory: "Historique de connexion"
|
signinHistory: "Historique de connexion"
|
||||||
|
enableAdvancedMfm: "Activer la MFM avancée"
|
||||||
doing: "En cours..."
|
doing: "En cours..."
|
||||||
category: "Catégorie"
|
category: "Catégorie"
|
||||||
tags: "Étiquettes"
|
tags: "Étiquettes"
|
||||||
@@ -846,6 +847,7 @@ rateLimitExceeded: "Limite de taux dépassée"
|
|||||||
cropImage: "Recadrer l'image"
|
cropImage: "Recadrer l'image"
|
||||||
cropImageAsk: "Voulez-vous recadrer cette image ?"
|
cropImageAsk: "Voulez-vous recadrer cette image ?"
|
||||||
cropYes: "Rogner"
|
cropYes: "Rogner"
|
||||||
|
cropNo: "Utiliser en l'état"
|
||||||
file: "Fichiers"
|
file: "Fichiers"
|
||||||
recentNHours: "Dernières {n} heures"
|
recentNHours: "Dernières {n} heures"
|
||||||
recentNDays: "Derniers {n} jours"
|
recentNDays: "Derniers {n} jours"
|
||||||
@@ -912,6 +914,7 @@ color: "Couleur"
|
|||||||
manageCustomEmojis: "Gestion des émojis personnalisés"
|
manageCustomEmojis: "Gestion des émojis personnalisés"
|
||||||
preset: "Préréglage"
|
preset: "Préréglage"
|
||||||
selectFromPresets: "Sélectionner à partir des préréglages"
|
selectFromPresets: "Sélectionner à partir des préréglages"
|
||||||
|
thisPostMayBeAnnoyingCancel: "Annuler"
|
||||||
license: "Licence"
|
license: "Licence"
|
||||||
video: "Vidéo"
|
video: "Vidéo"
|
||||||
videos: "Vidéos"
|
videos: "Vidéos"
|
||||||
@@ -925,6 +928,7 @@ leftBottom: "En bas à gauche"
|
|||||||
rightBottom: "En bas à droite"
|
rightBottom: "En bas à droite"
|
||||||
vertical: "Vertical"
|
vertical: "Vertical"
|
||||||
horizontal: "Latéral"
|
horizontal: "Latéral"
|
||||||
|
serverRules: "Règles du serveur"
|
||||||
youFollowing: "Abonné·e"
|
youFollowing: "Abonné·e"
|
||||||
_achievements:
|
_achievements:
|
||||||
_types:
|
_types:
|
||||||
@@ -934,10 +938,13 @@ _achievements:
|
|||||||
_notes100000:
|
_notes100000:
|
||||||
title: "ALL YOUR NOTE ARE BELONG TO US"
|
title: "ALL YOUR NOTE ARE BELONG TO US"
|
||||||
_login3:
|
_login3:
|
||||||
|
title: "Débutant Ⅰ"
|
||||||
description: "Se connecter pour un total de 3 jours"
|
description: "Se connecter pour un total de 3 jours"
|
||||||
_login7:
|
_login7:
|
||||||
|
title: "Débutant Ⅱ"
|
||||||
description: "Se connecter pour un total de 7 jours"
|
description: "Se connecter pour un total de 7 jours"
|
||||||
_login15:
|
_login15:
|
||||||
|
title: "Débutant Ⅲ"
|
||||||
description: "Se connecter pour un total de 15 jours"
|
description: "Se connecter pour un total de 15 jours"
|
||||||
_login30:
|
_login30:
|
||||||
description: "Se connecter pour un total de 30 jours"
|
description: "Se connecter pour un total de 30 jours"
|
||||||
@@ -945,6 +952,22 @@ _achievements:
|
|||||||
description: "Se connecter pour un total de 60 jours"
|
description: "Se connecter pour un total de 60 jours"
|
||||||
_login100:
|
_login100:
|
||||||
description: "Se connecter pour un total de 100 jours"
|
description: "Se connecter pour un total de 100 jours"
|
||||||
|
_login200:
|
||||||
|
description: "Se connecter pour un total de 200 jours"
|
||||||
|
_login300:
|
||||||
|
description: "Se connecter pour un total de 300 jours"
|
||||||
|
_login400:
|
||||||
|
description: "Se connecter pour un total de 400 jours"
|
||||||
|
_login500:
|
||||||
|
description: "Se connecter pour un total de 500 jours"
|
||||||
|
_login600:
|
||||||
|
description: "Se connecter pour un total de 600 jours"
|
||||||
|
_login700:
|
||||||
|
description: "Se connecter pour un total de 700 jours"
|
||||||
|
_login800:
|
||||||
|
description: "Se connecter pour un total de 800 jours"
|
||||||
|
_login900:
|
||||||
|
description: "Se connecter pour un total de 900 jours"
|
||||||
_login1000:
|
_login1000:
|
||||||
flavor: "Merci d'utiliser Misskey !"
|
flavor: "Merci d'utiliser Misskey !"
|
||||||
_markedAsCat:
|
_markedAsCat:
|
||||||
@@ -954,8 +977,12 @@ _achievements:
|
|||||||
title: "Beaucoup d'amis"
|
title: "Beaucoup d'amis"
|
||||||
_followers10:
|
_followers10:
|
||||||
title: "Abonnez-moi !"
|
title: "Abonnez-moi !"
|
||||||
|
_iLoveMisskey:
|
||||||
|
title: "J’adore Misskey"
|
||||||
_viewInstanceChart:
|
_viewInstanceChart:
|
||||||
title: "Analyste"
|
title: "Analyste"
|
||||||
|
_loggedInOnBirthday:
|
||||||
|
title: "Joyeux Anniversaire !"
|
||||||
_loggedInOnNewYearsDay:
|
_loggedInOnNewYearsDay:
|
||||||
title: "Bonne année !"
|
title: "Bonne année !"
|
||||||
_role:
|
_role:
|
||||||
|
|||||||
72
locales/generateDTS.js
Normal file
72
locales/generateDTS.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const yaml = require('js-yaml');
|
||||||
|
const ts = require('typescript');
|
||||||
|
|
||||||
|
function createMembers(record) {
|
||||||
|
return Object.entries(record)
|
||||||
|
.map(([k, v]) => ts.factory.createPropertySignature(
|
||||||
|
undefined,
|
||||||
|
ts.factory.createStringLiteral(k),
|
||||||
|
undefined,
|
||||||
|
typeof v === 'string'
|
||||||
|
? ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
|
||||||
|
: ts.factory.createTypeLiteralNode(createMembers(v)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function generateDTS() {
|
||||||
|
const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8'));
|
||||||
|
const members = createMembers(locale);
|
||||||
|
const elements = [
|
||||||
|
ts.factory.createInterfaceDeclaration(
|
||||||
|
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
||||||
|
ts.factory.createIdentifier('Locale'),
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
members,
|
||||||
|
),
|
||||||
|
ts.factory.createVariableStatement(
|
||||||
|
[ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
|
||||||
|
ts.factory.createVariableDeclarationList(
|
||||||
|
[ts.factory.createVariableDeclaration(
|
||||||
|
ts.factory.createIdentifier('locales'),
|
||||||
|
undefined,
|
||||||
|
ts.factory.createTypeLiteralNode([ts.factory.createIndexSignature(
|
||||||
|
undefined,
|
||||||
|
[ts.factory.createParameterDeclaration(
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
ts.factory.createIdentifier('lang'),
|
||||||
|
undefined,
|
||||||
|
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
||||||
|
undefined,
|
||||||
|
)],
|
||||||
|
ts.factory.createTypeReferenceNode(
|
||||||
|
ts.factory.createIdentifier('Locale'),
|
||||||
|
undefined,
|
||||||
|
),
|
||||||
|
)]),
|
||||||
|
undefined,
|
||||||
|
)],
|
||||||
|
ts.NodeFlags.Const | ts.NodeFlags.Ambient | ts.NodeFlags.ContextFlags,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ts.factory.createExportAssignment(
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
|
ts.factory.createIdentifier('locales'),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
const printed = ts.createPrinter({
|
||||||
|
newLine: ts.NewLineKind.LineFeed,
|
||||||
|
}).printList(
|
||||||
|
ts.ListFormat.MultiLine,
|
||||||
|
ts.factory.createNodeArray(elements),
|
||||||
|
ts.createSourceFile('index.d.ts', '', ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS),
|
||||||
|
);
|
||||||
|
|
||||||
|
fs.writeFileSync(`${__dirname}/index.d.ts`, `/* eslint-disable */
|
||||||
|
// This file is generated by locales/generateDTS.js
|
||||||
|
// Do not edit this file directly.
|
||||||
|
${printed}`, 'utf-8');
|
||||||
|
}
|
||||||
@@ -955,6 +955,7 @@ disableFederationConfirmWarn: "Mematikan federasi tidak membuat kiriman menjadi
|
|||||||
disableFederationOk: "Matikan federasi"
|
disableFederationOk: "Matikan federasi"
|
||||||
horizontal: "Horisontal"
|
horizontal: "Horisontal"
|
||||||
youFollowing: "Mengikuti"
|
youFollowing: "Mengikuti"
|
||||||
|
options: "Opsi peran"
|
||||||
_achievements:
|
_achievements:
|
||||||
earnedAt: "Terbuka pada"
|
earnedAt: "Terbuka pada"
|
||||||
_types:
|
_types:
|
||||||
|
|||||||
2149
locales/index.d.ts
vendored
2149
locales/index.d.ts
vendored
File diff suppressed because it is too large
Load Diff
@@ -1020,6 +1020,7 @@ pleaseConfirmBelowBeforeSignup: "Ai sensi del regolamento EU 679/2016 GDPR, auto
|
|||||||
pleaseAgreeAllToContinue: "Per continuare, occorre selezionare ed essere d'accordo su tutto."
|
pleaseAgreeAllToContinue: "Per continuare, occorre selezionare ed essere d'accordo su tutto."
|
||||||
continue: "Continua"
|
continue: "Continua"
|
||||||
youFollowing: "Seguiti"
|
youFollowing: "Seguiti"
|
||||||
|
options: "Opzioni del ruolo"
|
||||||
_serverRules:
|
_serverRules:
|
||||||
description: "In Europa è necessario mostrare l'informativa sul trattamento dei dati personali, prima della registrazione al servizio."
|
description: "In Europa è necessario mostrare l'informativa sul trattamento dei dati personali, prima della registrazione al servizio."
|
||||||
_accountMigration:
|
_accountMigration:
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ addToList: "リストに追加"
|
|||||||
sendMessage: "メッセージを送信"
|
sendMessage: "メッセージを送信"
|
||||||
copyRSS: "RSSをコピー"
|
copyRSS: "RSSをコピー"
|
||||||
copyUsername: "ユーザー名をコピー"
|
copyUsername: "ユーザー名をコピー"
|
||||||
|
copyUserId: "ユーザーIDをコピー"
|
||||||
|
copyNoteId: "ノートIDをコピー"
|
||||||
searchUser: "ユーザーを検索"
|
searchUser: "ユーザーを検索"
|
||||||
reply: "返信"
|
reply: "返信"
|
||||||
loadMore: "もっと見る"
|
loadMore: "もっと見る"
|
||||||
@@ -689,7 +691,7 @@ no: "いいえ"
|
|||||||
driveFilesCount: "ドライブのファイル数"
|
driveFilesCount: "ドライブのファイル数"
|
||||||
driveUsage: "ドライブ使用量"
|
driveUsage: "ドライブ使用量"
|
||||||
noCrawle: "クローラーによるインデックスを拒否"
|
noCrawle: "クローラーによるインデックスを拒否"
|
||||||
noCrawleDescription: "検索エンジンにあなたのユーザーページ、ノート、Pagesなどのコンテンツを登録(インデックス)しないよう要請します。"
|
noCrawleDescription: "外部の検索エンジンにあなたのユーザーページ、ノート、Pagesなどのコンテンツを登録(インデックス)しないよう要求します。"
|
||||||
lockedAccountInfo: "フォローを承認制にしても、ノートの公開範囲を「フォロワー」にしない限り、誰でもあなたのノートを見ることができます。"
|
lockedAccountInfo: "フォローを承認制にしても、ノートの公開範囲を「フォロワー」にしない限り、誰でもあなたのノートを見ることができます。"
|
||||||
alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にする"
|
alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にする"
|
||||||
loadRawImages: "添付画像のサムネイルをオリジナル画質にする"
|
loadRawImages: "添付画像のサムネイルをオリジナル画質にする"
|
||||||
@@ -790,6 +792,7 @@ noMaintainerInformationWarning: "管理者情報が設定されていません
|
|||||||
noBotProtectionWarning: "Botプロテクションが設定されていません。"
|
noBotProtectionWarning: "Botプロテクションが設定されていません。"
|
||||||
configure: "設定する"
|
configure: "設定する"
|
||||||
postToGallery: "ギャラリーへ投稿"
|
postToGallery: "ギャラリーへ投稿"
|
||||||
|
postToHashtag: "このハッシュタグで投稿"
|
||||||
gallery: "ギャラリー"
|
gallery: "ギャラリー"
|
||||||
recentPosts: "最近の投稿"
|
recentPosts: "最近の投稿"
|
||||||
popularPosts: "人気の投稿"
|
popularPosts: "人気の投稿"
|
||||||
@@ -823,6 +826,7 @@ translatedFrom: "{x}から翻訳"
|
|||||||
accountDeletionInProgress: "アカウントの削除が進行中です"
|
accountDeletionInProgress: "アカウントの削除が進行中です"
|
||||||
usernameInfo: "サーバー上であなたのアカウントを一意に識別するための名前。アルファベット(a~z, A~Z)、数字(0~9)、およびアンダーバー(_)が使用できます。ユーザー名は後から変更することは出来ません。"
|
usernameInfo: "サーバー上であなたのアカウントを一意に識別するための名前。アルファベット(a~z, A~Z)、数字(0~9)、およびアンダーバー(_)が使用できます。ユーザー名は後から変更することは出来ません。"
|
||||||
aiChanMode: "藍モード"
|
aiChanMode: "藍モード"
|
||||||
|
devMode: "開発者モード"
|
||||||
keepCw: "CWを維持する"
|
keepCw: "CWを維持する"
|
||||||
pubSub: "Pub/Subのアカウント"
|
pubSub: "Pub/Subのアカウント"
|
||||||
lastCommunication: "直近の通信"
|
lastCommunication: "直近の通信"
|
||||||
@@ -832,6 +836,8 @@ breakFollow: "フォロワーを解除"
|
|||||||
breakFollowConfirm: "フォロワー解除しますか?"
|
breakFollowConfirm: "フォロワー解除しますか?"
|
||||||
itsOn: "オンになっています"
|
itsOn: "オンになっています"
|
||||||
itsOff: "オフになっています"
|
itsOff: "オフになっています"
|
||||||
|
on: "オン"
|
||||||
|
off: "オフ"
|
||||||
emailRequiredForSignup: "アカウント登録にメールアドレスを必須にする"
|
emailRequiredForSignup: "アカウント登録にメールアドレスを必須にする"
|
||||||
unread: "未読"
|
unread: "未読"
|
||||||
filter: "フィルタ"
|
filter: "フィルタ"
|
||||||
@@ -985,11 +991,14 @@ postToTheChannel: "チャンネルに投稿"
|
|||||||
cannotBeChangedLater: "後から変更できません。"
|
cannotBeChangedLater: "後から変更できません。"
|
||||||
reactionAcceptance: "リアクションの受け入れ"
|
reactionAcceptance: "リアクションの受け入れ"
|
||||||
likeOnly: "いいねのみ"
|
likeOnly: "いいねのみ"
|
||||||
likeOnlyForRemote: "リモートからはいいねのみ"
|
likeOnlyForRemote: "全て (リモートはいいねのみ)"
|
||||||
|
nonSensitiveOnly: "非センシティブのみ"
|
||||||
|
nonSensitiveOnlyForLocalLikeOnlyForRemote: "非センシティブのみ (リモートはいいねのみ)"
|
||||||
rolesAssignedToMe: "自分に割り当てられたロール"
|
rolesAssignedToMe: "自分に割り当てられたロール"
|
||||||
resetPasswordConfirm: "パスワードリセットしますか?"
|
resetPasswordConfirm: "パスワードリセットしますか?"
|
||||||
sensitiveWords: "センシティブワード"
|
sensitiveWords: "センシティブワード"
|
||||||
sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。"
|
sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。"
|
||||||
|
sensitiveWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
|
||||||
notesSearchNotAvailable: "ノート検索は利用できません。"
|
notesSearchNotAvailable: "ノート検索は利用できません。"
|
||||||
license: "ライセンス"
|
license: "ライセンス"
|
||||||
unfavoriteConfirm: "お気に入り解除しますか?"
|
unfavoriteConfirm: "お気に入り解除しますか?"
|
||||||
@@ -1038,12 +1047,26 @@ thisChannelArchived: "このチャンネルはアーカイブされています
|
|||||||
displayOfNote: "ノートの表示"
|
displayOfNote: "ノートの表示"
|
||||||
initialAccountSetting: "初期設定"
|
initialAccountSetting: "初期設定"
|
||||||
youFollowing: "フォロー中"
|
youFollowing: "フォロー中"
|
||||||
|
preventAiLearning: "生成AIによる学習を拒否"
|
||||||
|
preventAiLearningDescription: "外部の文章生成AIや画像生成AIに対して、投稿したノートや画像などのコンテンツを学習の対象にしないように要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されますが、この要求に従うかはそのAI次第であるため、学習を完全に防止するものではありません。"
|
||||||
|
options: "オプション"
|
||||||
|
specifyUser: "ユーザー指定"
|
||||||
|
failedToPreviewUrl: "プレビューできません"
|
||||||
|
update: "更新"
|
||||||
|
rolesThatCanBeUsedThisEmojiAsReaction: "リアクションとして使えるロール"
|
||||||
|
rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "ロールの指定が一つもない場合、誰でもリアクションとして使えます。"
|
||||||
|
rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "ロールは公開ロールである必要があります。"
|
||||||
|
cancelReactionConfirm: "リアクションを取り消しますか?"
|
||||||
|
changeReactionConfirm: "リアクションを変更しますか?"
|
||||||
|
later: "あとで"
|
||||||
|
goToMisskey: "Misskeyへ"
|
||||||
|
|
||||||
_initialAccountSetting:
|
_initialAccountSetting:
|
||||||
accountCreated: "アカウントの作成が完了しました!"
|
accountCreated: "アカウントの作成が完了しました!"
|
||||||
letsStartAccountSetup: "アカウントの初期設定を行いましょう。"
|
letsStartAccountSetup: "アカウントの初期設定を行いましょう。"
|
||||||
letsFillYourProfile: "まずはあなたのプロフィールを設定しましょう。"
|
letsFillYourProfile: "まずはあなたのプロフィールを設定しましょう。"
|
||||||
profileSetting: "プロフィール設定"
|
profileSetting: "プロフィール設定"
|
||||||
|
privacySetting: "プライバシー設定"
|
||||||
theseSettingsCanEditLater: "これらの設定は後から変更できます。"
|
theseSettingsCanEditLater: "これらの設定は後から変更できます。"
|
||||||
youCanEditMoreSettingsInSettingsPageLater: "この他にも様々な設定を「設定」ページから行えます。ぜひ後で確認してみてください。"
|
youCanEditMoreSettingsInSettingsPageLater: "この他にも様々な設定を「設定」ページから行えます。ぜひ後で確認してみてください。"
|
||||||
followUsers: "タイムラインを構築するため、気になるユーザーをフォローしてみましょう。"
|
followUsers: "タイムラインを構築するため、気になるユーザーをフォローしてみましょう。"
|
||||||
@@ -1052,6 +1075,7 @@ _initialAccountSetting:
|
|||||||
haveFun: "{name}をお楽しみください!"
|
haveFun: "{name}をお楽しみください!"
|
||||||
ifYouNeedLearnMore: "{name}(Misskey)の使い方などを詳しく知るには{link}をご覧ください。"
|
ifYouNeedLearnMore: "{name}(Misskey)の使い方などを詳しく知るには{link}をご覧ください。"
|
||||||
skipAreYouSure: "初期設定をスキップしますか?"
|
skipAreYouSure: "初期設定をスキップしますか?"
|
||||||
|
laterAreYouSure: "初期設定をあとでやり直しますか?"
|
||||||
|
|
||||||
_serverRules:
|
_serverRules:
|
||||||
description: "新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。"
|
description: "新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。"
|
||||||
|
|||||||
@@ -689,7 +689,7 @@ no: "あかん"
|
|||||||
driveFilesCount: "ドライブのファイル数"
|
driveFilesCount: "ドライブのファイル数"
|
||||||
driveUsage: "ドライブ使用量やで"
|
driveUsage: "ドライブ使用量やで"
|
||||||
noCrawle: "クローラーによるインデックスを拒否するで"
|
noCrawle: "クローラーによるインデックスを拒否するで"
|
||||||
noCrawleDescription: "検索エンジンにあんたのユーザーページ、ノート、Pagesとかのコンテンツを登録(インデックス)せんように頼むで。邪魔すんねんやったら帰って〜。"
|
noCrawleDescription: "検索エンジンにあんたのユーザーページ、ノート、Pagesとかのコンテンツを登録(インデックス)せぇへんように頼むで。"
|
||||||
lockedAccountInfo: "フォローを承認制にしとっても、ノートの公開範囲を「フォロワー」にせぇへん限り、誰でもあんたのノートを見れるで。"
|
lockedAccountInfo: "フォローを承認制にしとっても、ノートの公開範囲を「フォロワー」にせぇへん限り、誰でもあんたのノートを見れるで。"
|
||||||
alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にするで"
|
alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にするで"
|
||||||
loadRawImages: "添付画像のサムネイルをオリジナル画質にするで"
|
loadRawImages: "添付画像のサムネイルをオリジナル画質にするで"
|
||||||
@@ -990,6 +990,7 @@ rolesAssignedToMe: "自分に割り当てられたロール"
|
|||||||
resetPasswordConfirm: "パスワード作り直すんでええな?"
|
resetPasswordConfirm: "パスワード作り直すんでええな?"
|
||||||
sensitiveWords: "けったいな単語"
|
sensitiveWords: "けったいな単語"
|
||||||
sensitiveWordsDescription: "設定した単語が入っとるノートの公開範囲をホームにしたるわ。改行で区切ったら複数設定できるで。"
|
sensitiveWordsDescription: "設定した単語が入っとるノートの公開範囲をホームにしたるわ。改行で区切ったら複数設定できるで。"
|
||||||
|
sensitiveWordsDescription2: "スペースで区切るとAND指定、キーワードをスラッシュで囲んだら正規表現や。"
|
||||||
notesSearchNotAvailable: "ノート検索は使われへんで。"
|
notesSearchNotAvailable: "ノート検索は使われへんで。"
|
||||||
license: "ライセンス"
|
license: "ライセンス"
|
||||||
unfavoriteConfirm: "ほんまに気に入らんの?"
|
unfavoriteConfirm: "ほんまに気に入らんの?"
|
||||||
@@ -1038,10 +1039,16 @@ thisChannelArchived: "このチャンネル、アーカイブされとるで。"
|
|||||||
displayOfNote: "ノートの表示"
|
displayOfNote: "ノートの表示"
|
||||||
initialAccountSetting: "初期設定"
|
initialAccountSetting: "初期設定"
|
||||||
youFollowing: "フォロー中やで"
|
youFollowing: "フォロー中やで"
|
||||||
|
preventAiLearning: "生成AIの学習に使わんといて"
|
||||||
|
preventAiLearningDescription: "他の文章生成AIとか画像生成AIに、投稿したノートとか画像なんかを勝手に使わんように頼むで。具体的にはnoaiフラグをHTMLレスポンスに含めるんやけど、これ聞いてくれるんはAIの気分次第やから、使われる可能性もちょっとはあるな。"
|
||||||
|
options: "オプション"
|
||||||
|
specifyUser: "ユーザー指定"
|
||||||
_initialAccountSetting:
|
_initialAccountSetting:
|
||||||
accountCreated: "アカウント作り終わったで。"
|
accountCreated: "アカウント作り終わったで。"
|
||||||
|
letsStartAccountSetup: "アカウントの初期設定をしよか。"
|
||||||
letsFillYourProfile: "最初はあんたのプロフィールを設定しよか。"
|
letsFillYourProfile: "最初はあんたのプロフィールを設定しよか。"
|
||||||
profileSetting: "プロフィール設定"
|
profileSetting: "プロフィール設定"
|
||||||
|
privacySetting: "プライバシー設定"
|
||||||
theseSettingsCanEditLater: "この設定はあとから変えれるで。"
|
theseSettingsCanEditLater: "この設定はあとから変えれるで。"
|
||||||
youCanEditMoreSettingsInSettingsPageLater: "これ以外にもいろんな設定を「設定」ページからできるで。後で確認してみてな。"
|
youCanEditMoreSettingsInSettingsPageLater: "これ以外にもいろんな設定を「設定」ページからできるで。後で確認してみてな。"
|
||||||
followUsers: "タイムラインを構築するために、気になるユーザーをフォローしてみ。"
|
followUsers: "タイムラインを構築するために、気になるユーザーをフォローしてみ。"
|
||||||
@@ -1060,11 +1067,12 @@ _accountMigration:
|
|||||||
moveTo: "このアカウントをさらのアカウントに引っ越すで"
|
moveTo: "このアカウントをさらのアカウントに引っ越すで"
|
||||||
moveToLabel: "引っ越し先のアカウント:"
|
moveToLabel: "引っ越し先のアカウント:"
|
||||||
moveCannotBeUndone: "アカウントを移行すると、取り消すことはできへんくなります。"
|
moveCannotBeUndone: "アカウントを移行すると、取り消すことはできへんくなります。"
|
||||||
moveAccountDescription: "この操作は戻されへんで。まず引っ越し先のアカウントでこのアカウントへのエイリアスが作れたか確認してきなはれや。エイリアスができてたら、引っ越し先のアカウントをこんな風に入力してくれへんか?:@person@instance.com"
|
moveAccountDescription: "おニューのアカウントに移行すんで。\n ・フォロワーがおニューの方を勝手にフォローすんで。\n ・このアカウントからのフォローはまるまる全部解除されんで。\n ・このアカウントでノート作れへんようになるで。\n\nフォロワーの移行は勝手にこっちでやっとくけど、フォローの移行は自分でしてや。移行前にこのアカウントでフォローエクスポートして、移行したあとすぐにおニューのところでインポートしてくれな。\nリストとかミュート、あとブロックもおんなじや。自分で移行してな。\n\n(この説明はこのサーバー、つまりMisskey v13.12.0から後の仕様や。Mastodonとか他のActivityPubソフトやとちょっと挙動が違うこともあんで。)"
|
||||||
moveAccountHowTo: "アカウントの引っ越しには、まず引っ越し先のアカウントで自分のアカウントに対しエイリアスを作成しなはれや。\nエイリアス作成した後、引っ越し先のアカウントを次のように入力してくれへんか?:@username@server.example.com"
|
moveAccountHowTo: "アカウントの引っ越しには、まず引っ越し先のアカウントで自分のアカウントに対しエイリアスを作成しなはれや。\nエイリアス作成した後、引っ越し先のアカウントを次のように入力してくれへんか?:@username@server.example.com"
|
||||||
startMigration: "引っ越しする"
|
startMigration: "引っ越しする"
|
||||||
migrationConfirm: "ほんまにこのアカウントを {account} に引っ越すんか?一回引っ越してもうたら取り消されへんし、二度とこのアカウントを元に戻されへんくなるで。\nそれと、引っ越し先のアカウントでエイリアスが作れたかちゃ~んと確認しーや?"
|
migrationConfirm: "ほんまにこのアカウントを {account} に引っ越すんか?一回引っ越してもうたら取り消されへんし、二度とこのアカウントを元に戻されへんくなるで。\nそれと、引っ越し先のアカウントでエイリアスが作れたかちゃ~んと確認しーや?"
|
||||||
movedAndCannotBeUndone: "\nアカウントはもう引っ越されてます。\n引っ越しを取り消すことはできまへん。"
|
movedAndCannotBeUndone: "\nアカウントはもう引っ越されてます。\n引っ越しを取り消すことはできまへん。"
|
||||||
|
postMigrationNote: "このアカウントからのフォロー解除は移行操作から丸一日経ったら実行されんで。\nこのアカウントのフォロー・フォロワー数はどっちも0や。フォローの解除はされへんから、あんたのフォロワーはこのアカウントのフォロワー向けの投稿をこの後も見れるで。"
|
||||||
movedTo: "引っ越し先のアカウント:"
|
movedTo: "引っ越し先のアカウント:"
|
||||||
_achievements:
|
_achievements:
|
||||||
earnedAt: "貰った日ぃ"
|
earnedAt: "貰った日ぃ"
|
||||||
|
|||||||
@@ -1038,8 +1038,10 @@ thisChannelArchived: "이 채널은 아카이브되었습니다."
|
|||||||
displayOfNote: "노트 표시"
|
displayOfNote: "노트 표시"
|
||||||
initialAccountSetting: "초기 설정"
|
initialAccountSetting: "초기 설정"
|
||||||
youFollowing: "팔로잉"
|
youFollowing: "팔로잉"
|
||||||
|
options: "옵션"
|
||||||
_initialAccountSetting:
|
_initialAccountSetting:
|
||||||
accountCreated: "계정 생성이 완료되었습니다!"
|
accountCreated: "계정 생성이 완료되었습니다!"
|
||||||
|
letsStartAccountSetup: "계정의 초기 설정을 진행합니다."
|
||||||
letsFillYourProfile: "우선 나의 프로필을 설정해 보아요."
|
letsFillYourProfile: "우선 나의 프로필을 설정해 보아요."
|
||||||
profileSetting: "프로필 설정"
|
profileSetting: "프로필 설정"
|
||||||
theseSettingsCanEditLater: "이 설정들은 나중에도 변경할 수 있습니다."
|
theseSettingsCanEditLater: "이 설정들은 나중에도 변경할 수 있습니다."
|
||||||
@@ -1049,6 +1051,7 @@ _initialAccountSetting:
|
|||||||
initialAccountSettingCompleted: "초기 설정을 모두 마쳤습니다!"
|
initialAccountSettingCompleted: "초기 설정을 모두 마쳤습니다!"
|
||||||
haveFun: "{name}와 함께 즐거운 시간 보내세요!"
|
haveFun: "{name}와 함께 즐거운 시간 보내세요!"
|
||||||
ifYouNeedLearnMore: "{name}(Misskey)의 사용 방법에 대해 자세히 알아보려면 {link}를 참고해 주세요."
|
ifYouNeedLearnMore: "{name}(Misskey)의 사용 방법에 대해 자세히 알아보려면 {link}를 참고해 주세요."
|
||||||
|
skipAreYouSure: "초기 설정을 넘기시겠습니까?"
|
||||||
_serverRules:
|
_serverRules:
|
||||||
description: "회원 가입 이전에 간단하게 표시할 서버 규칙입니다. 이용 약관의 요약으로 구성하는 것을 추천합니다."
|
description: "회원 가입 이전에 간단하게 표시할 서버 규칙입니다. 이용 약관의 요약으로 구성하는 것을 추천합니다."
|
||||||
_accountMigration:
|
_accountMigration:
|
||||||
@@ -1238,6 +1241,7 @@ _achievements:
|
|||||||
title: "잠깐 쉬어"
|
title: "잠깐 쉬어"
|
||||||
description: "클라이언트를 시작하고 30분이 경과하였습니다"
|
description: "클라이언트를 시작하고 30분이 경과하였습니다"
|
||||||
_client60min:
|
_client60min:
|
||||||
|
title: "No \"Miss\" in Misskey"
|
||||||
description: "클라이언트를 시작하고 60분이 경과하였습니다"
|
description: "클라이언트를 시작하고 60분이 경과하였습니다"
|
||||||
_noteDeletedWithin1min:
|
_noteDeletedWithin1min:
|
||||||
title: "있었는데요 없었습니다"
|
title: "있었는데요 없었습니다"
|
||||||
@@ -1597,6 +1601,16 @@ _time:
|
|||||||
minute: "분"
|
minute: "분"
|
||||||
hour: "시간"
|
hour: "시간"
|
||||||
day: "일"
|
day: "일"
|
||||||
|
_timelineTutorial:
|
||||||
|
title: "Misskey의 사용 방법"
|
||||||
|
step1_1: "이것은 '타임라인'입니다. {name}에 게시된 '노트'가 시간 순서대로 표시됩니다."
|
||||||
|
step1_2: "타임라인은 몇 가지 종류로 나뉩니다. 그 중에 '홈 타임라인'은 내가 팔로우한 사람의 노트가 표시되며, '로컬 타임라인'에는 {name} 의 모든 노트가 표시됩니다."
|
||||||
|
step2_1: "그럼 시험삼아 노트를 작성해 봅시다. 화면에 있는 연필 버튼을 눌러 보세요."
|
||||||
|
step2_2: "첫 노트이니까 자기소개, 혹은 가볍게 \"안녕 {name}\"라고 올려 보는 건 어떨까요?"
|
||||||
|
step3_1: "노트 작성을 끝내셨나요?"
|
||||||
|
step3_2: "당신의 노트가 타임라인에 표시되어 있다면 성공입니다."
|
||||||
|
step4_1: "노트에는 '리액션'을 붙일 수 있습니다."
|
||||||
|
step4_2: "리액션을 붙이려면, 노트의 \"+\" 버튼을 클릭하고 원하는 이모지를 선택합니다."
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "이미 설정이 완료되었습니다."
|
alreadyRegistered: "이미 설정이 완료되었습니다."
|
||||||
registerTOTP: "인증 앱 설정 시작"
|
registerTOTP: "인증 앱 설정 시작"
|
||||||
|
|||||||
@@ -278,6 +278,7 @@ video: "Video"
|
|||||||
videos: "Videoer"
|
videos: "Videoer"
|
||||||
continue: "Fortsett"
|
continue: "Fortsett"
|
||||||
youFollowing: "Følger"
|
youFollowing: "Følger"
|
||||||
|
options: "Alternativ"
|
||||||
_initialAccountSetting:
|
_initialAccountSetting:
|
||||||
theseSettingsCanEditLater: "Du kan endre disse innstillingene senere."
|
theseSettingsCanEditLater: "Du kan endre disse innstillingene senere."
|
||||||
_achievements:
|
_achievements:
|
||||||
|
|||||||
@@ -1006,6 +1006,7 @@ videos: "Видео"
|
|||||||
dataSaver: "Экономия трафика"
|
dataSaver: "Экономия трафика"
|
||||||
horizontal: "Сбоку"
|
horizontal: "Сбоку"
|
||||||
youFollowing: "Подписки"
|
youFollowing: "Подписки"
|
||||||
|
options: "Настройки ролей"
|
||||||
_achievements:
|
_achievements:
|
||||||
earnedAt: "Разблокировано в"
|
earnedAt: "Разблокировано в"
|
||||||
_types:
|
_types:
|
||||||
|
|||||||
@@ -1031,6 +1031,7 @@ preservedUsernames: "ชื่อผู้ใช้ที่สงวนไว
|
|||||||
preservedUsernamesDescription: "ลิสต์ชื่อผู้ใช้ที่จะสำรองโดยคั่นด้วยการแบ่งบรรทัดนั้น เพราะสิ่งเหล่านี้จะไม่สามารถทำได้ในระหว่างการสร้างบัญชีตามปกติ บัญชีที่มีอยู่แล้วนั้นโดยใช้ชื่อผู้ใช้เหล่านี้จะไม่ได้รับผลกระทบอะไร"
|
preservedUsernamesDescription: "ลิสต์ชื่อผู้ใช้ที่จะสำรองโดยคั่นด้วยการแบ่งบรรทัดนั้น เพราะสิ่งเหล่านี้จะไม่สามารถทำได้ในระหว่างการสร้างบัญชีตามปกติ บัญชีที่มีอยู่แล้วนั้นโดยใช้ชื่อผู้ใช้เหล่านี้จะไม่ได้รับผลกระทบอะไร"
|
||||||
createNoteFromTheFile: "เรียบเรียงโน้ตจากไฟล์นี้"
|
createNoteFromTheFile: "เรียบเรียงโน้ตจากไฟล์นี้"
|
||||||
youFollowing: "ติดตามแล้ว"
|
youFollowing: "ติดตามแล้ว"
|
||||||
|
options: "ตัวเลือกบทบาท"
|
||||||
_serverRules:
|
_serverRules:
|
||||||
description: "ชุดของกฎที่จะแสดงก่อนการลงทะเบียนเราขอแนะนำให้ตั้งค่าสรุปข้อกำหนดในการให้บริการ"
|
description: "ชุดของกฎที่จะแสดงก่อนการลงทะเบียนเราขอแนะนำให้ตั้งค่าสรุปข้อกำหนดในการให้บริการ"
|
||||||
_accountMigration:
|
_accountMigration:
|
||||||
|
|||||||
@@ -1030,6 +1030,13 @@ continue: "继续"
|
|||||||
preservedUsernames: "保留的用户名"
|
preservedUsernames: "保留的用户名"
|
||||||
createNoteFromTheFile: "从文件创建帖子"
|
createNoteFromTheFile: "从文件创建帖子"
|
||||||
youFollowing: "正在关注"
|
youFollowing: "正在关注"
|
||||||
|
options: "选项"
|
||||||
|
_initialAccountSetting:
|
||||||
|
accountCreated: "账户创建完成了!"
|
||||||
|
letsStartAccountSetup: "来进行帐户的初始设置吧。"
|
||||||
|
letsFillYourProfile: "首先,来设定你的个人档案吧!"
|
||||||
|
profileSetting: "个人资料设置"
|
||||||
|
theseSettingsCanEditLater: "也可以在稍后修改这里的设置。"
|
||||||
_serverRules:
|
_serverRules:
|
||||||
description: "在新用户注册前显示服务器的简单规则。推荐显示服务条款的主要内容。"
|
description: "在新用户注册前显示服务器的简单规则。推荐显示服务条款的主要内容。"
|
||||||
_accountMigration:
|
_accountMigration:
|
||||||
@@ -1038,8 +1045,11 @@ _accountMigration:
|
|||||||
moveFromDescription: "如果迁移时需要继承其他账户的关注者,请在此创造别名。此操作需要在实行迁移之前完成!请如已下输入需要迁移的账户:@person@instance.com"
|
moveFromDescription: "如果迁移时需要继承其他账户的关注者,请在此创造别名。此操作需要在实行迁移之前完成!请如已下输入需要迁移的账户:@person@instance.com"
|
||||||
moveTo: "把这个账户迁移到新的账户"
|
moveTo: "把这个账户迁移到新的账户"
|
||||||
moveToLabel: "迁移后的账户"
|
moveToLabel: "迁移后的账户"
|
||||||
|
moveCannotBeUndone: "一旦迁移账户,就无法撤销。"
|
||||||
moveAccountDescription: "此操作无法取消。请先确认您已在迁移后的账户上,为此账户创造了别名。创造别名后,请如以下输入您的迁移后的账户:@person@instance.com"
|
moveAccountDescription: "此操作无法取消。请先确认您已在迁移后的账户上,为此账户创造了别名。创造别名后,请如以下输入您的迁移后的账户:@person@instance.com"
|
||||||
|
startMigration: "迁移"
|
||||||
migrationConfirm: "确定要把此账户迁移到{account}吗?一旦确定后,此操作无法取消,此账户也无法以原来的状态使用。\n同时,请确认迁移后的账户,已创造别名。"
|
migrationConfirm: "确定要把此账户迁移到{account}吗?一旦确定后,此操作无法取消,此账户也无法以原来的状态使用。\n同时,请确认迁移后的账户,已创造别名。"
|
||||||
|
movedAndCannotBeUndone: "该账户已被迁移。\n迁移操作无法撤销。"
|
||||||
movedTo: "迁移后的账户"
|
movedTo: "迁移后的账户"
|
||||||
_achievements:
|
_achievements:
|
||||||
earnedAt: "达成时间"
|
earnedAt: "达成时间"
|
||||||
@@ -1572,6 +1582,9 @@ _time:
|
|||||||
minute: "分"
|
minute: "分"
|
||||||
hour: "小时"
|
hour: "小时"
|
||||||
day: "日"
|
day: "日"
|
||||||
|
_timelineTutorial:
|
||||||
|
step3_1: "将想说的话发出去了吗?"
|
||||||
|
step3_2: "太棒了!现在你可以在你的时间线中看到刚刚发布的帖子了。"
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "此设备已被注册"
|
alreadyRegistered: "此设备已被注册"
|
||||||
registerTOTP: "开始设置认证应用"
|
registerTOTP: "开始设置认证应用"
|
||||||
|
|||||||
@@ -990,6 +990,7 @@ rolesAssignedToMe: "指派給自己的角色"
|
|||||||
resetPasswordConfirm: "重設密碼?"
|
resetPasswordConfirm: "重設密碼?"
|
||||||
sensitiveWords: "敏感詞"
|
sensitiveWords: "敏感詞"
|
||||||
sensitiveWordsDescription: "將含有設定詞彙的貼文可見性設為發送至首頁。可以用換行來進行複數的設定。"
|
sensitiveWordsDescription: "將含有設定詞彙的貼文可見性設為發送至首頁。可以用換行來進行複數的設定。"
|
||||||
|
sensitiveWordsDescription2: "用空格分隔關鍵詞構成AND格式,用斜線包圍關鍵字構成正規表達式。"
|
||||||
notesSearchNotAvailable: "無法使用搜尋貼文功能。"
|
notesSearchNotAvailable: "無法使用搜尋貼文功能。"
|
||||||
license: "授權"
|
license: "授權"
|
||||||
unfavoriteConfirm: "要取消收錄我的最愛嗎?"
|
unfavoriteConfirm: "要取消收錄我的最愛嗎?"
|
||||||
@@ -1036,7 +1037,24 @@ channelArchiveConfirmTitle: "要封存{name}嗎?"
|
|||||||
channelArchiveConfirmDescription: "封存以後,在頻道列表與搜索結果中不會顯示,也無法發布新的貼文。"
|
channelArchiveConfirmDescription: "封存以後,在頻道列表與搜索結果中不會顯示,也無法發布新的貼文。"
|
||||||
thisChannelArchived: "這個頻道已被封存。"
|
thisChannelArchived: "這個頻道已被封存。"
|
||||||
displayOfNote: "顯示貼文"
|
displayOfNote: "顯示貼文"
|
||||||
youFollowing: "關注中"
|
initialAccountSetting: "初始設定"
|
||||||
|
youFollowing: "追隨中"
|
||||||
|
preventAiLearning: "拒絕接受產生式AI的學習"
|
||||||
|
preventAiLearningDescription: "要求外部的文章產生AI或圖像產生AI不以發布的貼文和圖像等內容為學習對象。這是透過在HTML響應中包含noai旗標來實現的,但不能完全防止AI的學習,因為這要看該AI是否遵守這個要求。"
|
||||||
|
options: "選項"
|
||||||
|
_initialAccountSetting:
|
||||||
|
accountCreated: "帳戶已建立完成!"
|
||||||
|
letsStartAccountSetup: "來進行帳戶的初始設定吧。"
|
||||||
|
letsFillYourProfile: "首先,來設定您的個人檔案吧。"
|
||||||
|
profileSetting: "個人檔案設定"
|
||||||
|
theseSettingsCanEditLater: "這裡的設定可以在之後變更。"
|
||||||
|
youCanEditMoreSettingsInSettingsPageLater: "除此之外,還可以在「設定」頁面進行各種設定。之後請確認看看。"
|
||||||
|
followUsers: "為了構築時間軸,試著追蹤您感興趣的使用者吧。"
|
||||||
|
pushNotificationDescription: "啟用推送通知,就可以在設備上接收{name}的通知。"
|
||||||
|
initialAccountSettingCompleted: "初始設定完成了!"
|
||||||
|
haveFun: "盡情享受{name}吧!"
|
||||||
|
ifYouNeedLearnMore: "關於如何使用{name}(Misskey)的詳細資訊,請見{link}。"
|
||||||
|
skipAreYouSure: "要略過初始設定嗎?"
|
||||||
_serverRules:
|
_serverRules:
|
||||||
description: "設定伺服器的簡要規則,在新的註冊之前顯示。建議的內容是使用條款的摘要。"
|
description: "設定伺服器的簡要規則,在新的註冊之前顯示。建議的內容是使用條款的摘要。"
|
||||||
_accountMigration:
|
_accountMigration:
|
||||||
@@ -1464,7 +1482,7 @@ _channel:
|
|||||||
removeBanner: "移除橫幅圖像"
|
removeBanner: "移除橫幅圖像"
|
||||||
featured: "熱門貼文"
|
featured: "熱門貼文"
|
||||||
owned: "管理中"
|
owned: "管理中"
|
||||||
following: "關注中"
|
following: "追隨中"
|
||||||
usersCount: "有{n}人參與"
|
usersCount: "有{n}人參與"
|
||||||
notesCount: "有{n}個貼文"
|
notesCount: "有{n}個貼文"
|
||||||
nameAndDescription: "名稱與說明"
|
nameAndDescription: "名稱與說明"
|
||||||
@@ -1586,6 +1604,16 @@ _time:
|
|||||||
minute: "分鐘"
|
minute: "分鐘"
|
||||||
hour: "小時"
|
hour: "小時"
|
||||||
day: "日"
|
day: "日"
|
||||||
|
_timelineTutorial:
|
||||||
|
title: "Misskey的使用方法"
|
||||||
|
step1_1: "這個畫面是「時間軸」。發布到{name}的「貼文」按照時間順序顯示。"
|
||||||
|
step1_2: "時間軸有多種類型,例如在「首頁時間軸」中流動的是您追蹤的人的貼文;而在「本地時間軸」流動的是{name}全體的貼文。"
|
||||||
|
step2_1: "試試看,發布個貼文吧!按畫面上鉛筆圖示的按鈕開啟表格。"
|
||||||
|
step2_2: "初次貼文的內容,建議包括自我介紹以及「開始使用{name}」。"
|
||||||
|
step3_1: "貼文發出去了嗎?"
|
||||||
|
step3_2: "如果你的貼文出現在時間軸上,就代表發文成功。"
|
||||||
|
step4_1: "可以對貼文標記「反應」。"
|
||||||
|
step4_2: "點擊貼文的「+」圖示,即可選擇喜好的表情符號來標記反應。"
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "此設備已經被註冊過了"
|
alreadyRegistered: "此設備已經被註冊過了"
|
||||||
registerTOTP: "開始設定驗證應用程式"
|
registerTOTP: "開始設定驗證應用程式"
|
||||||
|
|||||||
12
package.json
12
package.json
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "13.12.0",
|
"version": "13.13.0-beta.3",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/misskey-dev/misskey.git"
|
"url": "https://github.com/misskey-dev/misskey.git"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@8.3.1",
|
"packageManager": "pnpm@8.5.1",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/frontend",
|
"packages/frontend",
|
||||||
"packages/backend",
|
"packages/backend",
|
||||||
@@ -56,11 +56,11 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/gulp": "4.0.10",
|
"@types/gulp": "4.0.10",
|
||||||
"@types/gulp-rename": "2.0.1",
|
"@types/gulp-rename": "2.0.1",
|
||||||
"@typescript-eslint/eslint-plugin": "5.59.2",
|
"@typescript-eslint/eslint-plugin": "5.59.5",
|
||||||
"@typescript-eslint/parser": "5.59.2",
|
"@typescript-eslint/parser": "5.59.5",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "12.11.0",
|
"cypress": "12.13.0",
|
||||||
"eslint": "8.39.0",
|
"eslint": "8.40.0",
|
||||||
"start-server-and-test": "2.0.0"
|
"start-server-and-test": "2.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export class PreventAiLarning1683682889948 {
|
||||||
|
name = 'PreventAiLarning1683682889948'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" ADD "preventAiLarning" boolean NOT NULL DEFAULT true`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "preventAiLarning"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export class PublicReactionsDefaultTrue1683683083083 {
|
||||||
|
name = 'PublicReactionsDefaultTrue1683683083083'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "publicReactions" SET DEFAULT true`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "publicReactions" SET DEFAULT false`);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
packages/backend/migration/1683789676867-fix-typo.js
Normal file
11
packages/backend/migration/1683789676867-fix-typo.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export class FixTypo1683789676867 {
|
||||||
|
name = 'FixTypo1683789676867'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" RENAME COLUMN "preventAiLarning" TO "preventAiLearning"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" RENAME COLUMN "preventAiLearning" TO "preventAiLarning"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
packages/backend/migration/1683847157541-UserList.js
Normal file
13
packages/backend/migration/1683847157541-UserList.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export class UserList1683847157541 {
|
||||||
|
name = 'UserList1683847157541'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_list" ADD "isPublic" boolean NOT NULL DEFAULT false`);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_48a00f08598662b9ca540521eb" ON "user_list" ("isPublic") `);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_48a00f08598662b9ca540521eb"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_list" DROP COLUMN "isPublic"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
export class UserListFavorites1683869758873 {
|
||||||
|
name = 'UserListFavorites1683869758873'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`CREATE TABLE "user_list_favorite" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "userListId" character varying(32) NOT NULL, CONSTRAINT "PK_c0974b21e18502a4c8178e09fe6" PRIMARY KEY ("id"))`);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_016f613dc4feb807e03e3e7da9" ON "user_list_favorite" ("userId") `);
|
||||||
|
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_d6765a8c2a4c17c33f9d7f948b" ON "user_list_favorite" ("userId", "userListId") `);
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_list_favorite" ADD CONSTRAINT "FK_016f613dc4feb807e03e3e7da92" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_list_favorite" ADD CONSTRAINT "FK_4d52b20bfe32c8552e7a61e80d2" FOREIGN KEY ("userListId") REFERENCES "user_list"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_list_favorite" DROP CONSTRAINT "FK_4d52b20bfe32c8552e7a61e80d2"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_list_favorite" DROP CONSTRAINT "FK_016f613dc4feb807e03e3e7da92"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_d6765a8c2a4c17c33f9d7f948b"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_016f613dc4feb807e03e3e7da9"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "user_list_favorite"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export class RemoveShowTimelineReplies1684206886988 {
|
||||||
|
name = 'RemoveShowTimelineReplies1684206886988'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "showTimelineReplies"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user" ADD "showTimelineReplies" boolean NOT NULL DEFAULT false`);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
packages/backend/migration/1684386446061-emoji-improve.js
Normal file
15
packages/backend/migration/1684386446061-emoji-improve.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export class EmojiImprove1684386446061 {
|
||||||
|
name = 'EmojiImprove1684386446061'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "emoji" ADD "localOnly" boolean NOT NULL DEFAULT false`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "emoji" ADD "isSensitive" boolean NOT NULL DEFAULT false`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "emoji" ADD "roleIdsThatCanBeUsedThisEmojiAsReaction" character varying(128) array NOT NULL DEFAULT '{}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "roleIdsThatCanBeUsedThisEmojiAsReaction"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "isSensitive"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "localOnly"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,40 +35,41 @@
|
|||||||
"@swc/core-win32-x64-msvc": "1.3.56",
|
"@swc/core-win32-x64-msvc": "1.3.56",
|
||||||
"@tensorflow/tfjs": "4.4.0",
|
"@tensorflow/tfjs": "4.4.0",
|
||||||
"@tensorflow/tfjs-node": "4.4.0",
|
"@tensorflow/tfjs-node": "4.4.0",
|
||||||
"slacc-android-arm-eabi": "0.0.7",
|
"slacc-android-arm-eabi": "0.0.9",
|
||||||
"slacc-android-arm64": "0.0.7",
|
"slacc-android-arm64": "0.0.9",
|
||||||
"slacc-darwin-arm64": "0.0.7",
|
"slacc-darwin-arm64": "0.0.9",
|
||||||
"slacc-darwin-universal": "0.0.7",
|
"slacc-darwin-universal": "0.0.9",
|
||||||
"slacc-darwin-x64": "0.0.7",
|
"slacc-darwin-x64": "0.0.9",
|
||||||
"slacc-linux-arm-gnueabihf": "0.0.7",
|
"slacc-freebsd-x64": "0.0.9",
|
||||||
"slacc-linux-arm64-gnu": "0.0.7",
|
"slacc-linux-arm-gnueabihf": "0.0.9",
|
||||||
"slacc-linux-arm64-musl": "0.0.7",
|
"slacc-linux-arm64-gnu": "0.0.9",
|
||||||
"slacc-linux-x64-gnu": "0.0.7",
|
"slacc-linux-arm64-musl": "0.0.9",
|
||||||
"slacc-win32-arm64-msvc": "0.0.7",
|
"slacc-linux-x64-gnu": "0.0.9",
|
||||||
"slacc-win32-x64-msvc": "0.0.7"
|
"slacc-win32-arm64-msvc": "0.0.9",
|
||||||
|
"slacc-win32-x64-msvc": "0.0.9"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "3.321.1",
|
"@aws-sdk/client-s3": "3.321.1",
|
||||||
"@aws-sdk/lib-storage": "3.321.1",
|
"@aws-sdk/lib-storage": "3.321.1",
|
||||||
"@aws-sdk/node-http-handler": "3.321.1",
|
"@aws-sdk/node-http-handler": "3.321.1",
|
||||||
"@bull-board/api": "5.1.2",
|
"@bull-board/api": "5.2.0",
|
||||||
"@bull-board/fastify": "5.1.2",
|
"@bull-board/fastify": "5.2.0",
|
||||||
"@bull-board/ui": "5.1.2",
|
"@bull-board/ui": "5.2.0",
|
||||||
"@discordapp/twemoji": "14.1.2",
|
"@discordapp/twemoji": "14.1.2",
|
||||||
"@fastify/accepts": "4.1.0",
|
"@fastify/accepts": "4.1.0",
|
||||||
"@fastify/cookie": "8.3.0",
|
"@fastify/cookie": "8.3.0",
|
||||||
"@fastify/cors": "8.2.1",
|
"@fastify/cors": "8.2.1",
|
||||||
"@fastify/http-proxy": "9.0.0",
|
"@fastify/http-proxy": "9.1.0",
|
||||||
"@fastify/multipart": "7.6.0",
|
"@fastify/multipart": "7.6.0",
|
||||||
"@fastify/static": "6.10.1",
|
"@fastify/static": "6.10.1",
|
||||||
"@fastify/view": "7.4.1",
|
"@fastify/view": "7.4.1",
|
||||||
"@nestjs/common": "9.4.0",
|
"@nestjs/common": "9.4.2",
|
||||||
"@nestjs/core": "9.4.0",
|
"@nestjs/core": "9.4.2",
|
||||||
"@nestjs/testing": "9.4.0",
|
"@nestjs/testing": "9.4.2",
|
||||||
"@peertube/http-signature": "1.7.0",
|
"@peertube/http-signature": "1.7.0",
|
||||||
"@sinonjs/fake-timers": "10.0.2",
|
"@sinonjs/fake-timers": "10.0.2",
|
||||||
"@swc/cli": "0.1.62",
|
"@swc/cli": "0.1.62",
|
||||||
"@swc/core": "1.3.56",
|
"@swc/core": "1.3.59",
|
||||||
"accepts": "1.3.8",
|
"accepts": "1.3.8",
|
||||||
"ajv": "8.12.0",
|
"ajv": "8.12.0",
|
||||||
"archiver": "5.3.1",
|
"archiver": "5.3.1",
|
||||||
@@ -89,11 +90,11 @@
|
|||||||
"escape-regexp": "0.0.1",
|
"escape-regexp": "0.0.1",
|
||||||
"fastify": "4.17.0",
|
"fastify": "4.17.0",
|
||||||
"feed": "4.2.2",
|
"feed": "4.2.2",
|
||||||
"file-type": "18.3.0",
|
"file-type": "18.4.0",
|
||||||
"fluent-ffmpeg": "2.1.2",
|
"fluent-ffmpeg": "2.1.2",
|
||||||
"form-data": "4.0.0",
|
"form-data": "4.0.0",
|
||||||
"got": "12.6.0",
|
"got": "12.6.0",
|
||||||
"happy-dom": "9.10.2",
|
"happy-dom": "9.19.2",
|
||||||
"hpagent": "1.2.0",
|
"hpagent": "1.2.0",
|
||||||
"ioredis": "5.3.2",
|
"ioredis": "5.3.2",
|
||||||
"ip-cidr": "3.1.0",
|
"ip-cidr": "3.1.0",
|
||||||
@@ -102,21 +103,21 @@
|
|||||||
"jsdom": "21.1.1",
|
"jsdom": "21.1.1",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"jsonld": "8.1.1",
|
"jsonld": "8.1.1",
|
||||||
"meilisearch": "0.32.3",
|
|
||||||
"jsrsasign": "10.8.6",
|
"jsrsasign": "10.8.6",
|
||||||
|
"meilisearch": "0.32.4",
|
||||||
"mfm-js": "0.23.3",
|
"mfm-js": "0.23.3",
|
||||||
"mime-types": "2.1.35",
|
"mime-types": "2.1.35",
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
"ms": "3.0.0-canary.1",
|
"ms": "3.0.0-canary.1",
|
||||||
"nested-property": "4.0.0",
|
"nested-property": "4.0.0",
|
||||||
"node-fetch": "3.3.1",
|
"node-fetch": "3.3.1",
|
||||||
"nodemailer": "6.9.1",
|
"nodemailer": "6.9.2",
|
||||||
"nsfwjs": "2.4.2",
|
"nsfwjs": "2.4.2",
|
||||||
"oauth": "0.10.0",
|
"oauth": "0.10.0",
|
||||||
"os-utils": "0.0.14",
|
"os-utils": "0.0.14",
|
||||||
"otpauth": "9.1.1",
|
"otpauth": "9.1.2",
|
||||||
"parse5": "7.1.2",
|
"parse5": "7.1.2",
|
||||||
"pg": "8.10.0",
|
"pg": "8.11.0",
|
||||||
"private-ip": "3.0.0",
|
"private-ip": "3.0.0",
|
||||||
"probe-image-size": "7.2.3",
|
"probe-image-size": "7.2.3",
|
||||||
"promise-limit": "2.7.0",
|
"promise-limit": "2.7.0",
|
||||||
@@ -136,10 +137,10 @@
|
|||||||
"s-age": "1.1.2",
|
"s-age": "1.1.2",
|
||||||
"sanitize-html": "2.10.0",
|
"sanitize-html": "2.10.0",
|
||||||
"seedrandom": "3.0.5",
|
"seedrandom": "3.0.5",
|
||||||
"semver": "7.5.0",
|
"semver": "7.5.1",
|
||||||
"sharp": "0.32.1",
|
"sharp": "0.32.1",
|
||||||
"sharp-read-bmp": "github:misskey-dev/sharp-read-bmp",
|
"sharp-read-bmp": "github:misskey-dev/sharp-read-bmp",
|
||||||
"slacc": "0.0.7",
|
"slacc": "0.0.9",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"stringz": "2.1.0",
|
"stringz": "2.1.0",
|
||||||
"summaly": "github:misskey-dev/summaly",
|
"summaly": "github:misskey-dev/summaly",
|
||||||
@@ -149,7 +150,7 @@
|
|||||||
"tsc-alias": "1.8.6",
|
"tsc-alias": "1.8.6",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"twemoji-parser": "14.0.0",
|
"twemoji-parser": "14.0.0",
|
||||||
"typeorm": "0.3.15",
|
"typeorm": "0.3.16",
|
||||||
"typescript": "5.0.4",
|
"typescript": "5.0.4",
|
||||||
"ulid": "2.3.0",
|
"ulid": "2.3.0",
|
||||||
"unzipper": "0.10.11",
|
"unzipper": "0.10.11",
|
||||||
@@ -178,11 +179,11 @@
|
|||||||
"@types/jsonld": "1.5.8",
|
"@types/jsonld": "1.5.8",
|
||||||
"@types/jsrsasign": "10.5.8",
|
"@types/jsrsasign": "10.5.8",
|
||||||
"@types/mime-types": "2.1.1",
|
"@types/mime-types": "2.1.1",
|
||||||
"@types/node": "18.16.3",
|
"@types/node": "20.2.3",
|
||||||
"@types/node-fetch": "3.0.3",
|
"@types/node-fetch": "3.0.3",
|
||||||
"@types/nodemailer": "6.4.7",
|
"@types/nodemailer": "6.4.8",
|
||||||
"@types/oauth": "0.9.1",
|
"@types/oauth": "0.9.1",
|
||||||
"@types/pg": "8.6.6",
|
"@types/pg": "8.10.1",
|
||||||
"@types/pug": "2.0.6",
|
"@types/pug": "2.0.6",
|
||||||
"@types/punycode": "2.1.0",
|
"@types/punycode": "2.1.0",
|
||||||
"@types/qrcode": "1.5.0",
|
"@types/qrcode": "1.5.0",
|
||||||
@@ -191,22 +192,22 @@
|
|||||||
"@types/redis": "4.0.11",
|
"@types/redis": "4.0.11",
|
||||||
"@types/rename": "1.0.4",
|
"@types/rename": "1.0.4",
|
||||||
"@types/sanitize-html": "2.9.0",
|
"@types/sanitize-html": "2.9.0",
|
||||||
"@types/semver": "7.3.13",
|
"@types/semver": "7.5.0",
|
||||||
"@types/sharp": "0.32.0",
|
"@types/sharp": "0.32.0",
|
||||||
"@types/sinonjs__fake-timers": "8.1.2",
|
"@types/sinonjs__fake-timers": "8.1.2",
|
||||||
"@types/tinycolor2": "1.4.3",
|
"@types/tinycolor2": "1.4.3",
|
||||||
"@types/tmp": "0.2.3",
|
"@types/tmp": "0.2.3",
|
||||||
"@types/unzipper": "0.10.5",
|
"@types/unzipper": "0.10.6",
|
||||||
"@types/uuid": "9.0.1",
|
"@types/uuid": "9.0.1",
|
||||||
"@types/vary": "1.1.0",
|
"@types/vary": "1.1.0",
|
||||||
"@types/web-push": "3.3.2",
|
"@types/web-push": "3.3.2",
|
||||||
"@types/websocket": "1.0.5",
|
"@types/websocket": "1.0.5",
|
||||||
"@types/ws": "8.5.4",
|
"@types/ws": "8.5.4",
|
||||||
"@typescript-eslint/eslint-plugin": "5.59.2",
|
"@typescript-eslint/eslint-plugin": "5.59.5",
|
||||||
"@typescript-eslint/parser": "5.59.2",
|
"@typescript-eslint/parser": "5.59.5",
|
||||||
"aws-sdk-client-mock": "^2.1.1",
|
"aws-sdk-client-mock": "2.1.1",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint": "8.39.0",
|
"eslint": "8.40.0",
|
||||||
"eslint-plugin-import": "2.27.5",
|
"eslint-plugin-import": "2.27.5",
|
||||||
"execa": "6.1.0",
|
"execa": "6.1.0",
|
||||||
"jest": "29.5.0",
|
"jest": "29.5.0",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as Redis from 'ioredis';
|
|||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
import { MeiliSearch } from 'meilisearch';
|
import { MeiliSearch } from 'meilisearch';
|
||||||
import { DI } from './di-symbols.js';
|
import { DI } from './di-symbols.js';
|
||||||
import { loadConfig } from './config.js';
|
import { Config, loadConfig } from './config.js';
|
||||||
import { createPostgresDataSource } from './postgres.js';
|
import { createPostgresDataSource } from './postgres.js';
|
||||||
import { RepositoryModule } from './models/RepositoryModule.js';
|
import { RepositoryModule } from './models/RepositoryModule.js';
|
||||||
import type { Provider, OnApplicationShutdown } from '@nestjs/common';
|
import type { Provider, OnApplicationShutdown } from '@nestjs/common';
|
||||||
@@ -25,7 +25,7 @@ const $db: Provider = {
|
|||||||
|
|
||||||
const $meilisearch: Provider = {
|
const $meilisearch: Provider = {
|
||||||
provide: DI.meilisearch,
|
provide: DI.meilisearch,
|
||||||
useFactory: (config) => {
|
useFactory: (config: Config) => {
|
||||||
if (config.meilisearch) {
|
if (config.meilisearch) {
|
||||||
return new MeiliSearch({
|
return new MeiliSearch({
|
||||||
host: `${config.meilisearch.ssl ? 'https' : 'http' }://${config.meilisearch.host}:${config.meilisearch.port}`,
|
host: `${config.meilisearch.ssl ? 'https' : 'http' }://${config.meilisearch.host}:${config.meilisearch.port}`,
|
||||||
@@ -40,7 +40,7 @@ const $meilisearch: Provider = {
|
|||||||
|
|
||||||
const $redis: Provider = {
|
const $redis: Provider = {
|
||||||
provide: DI.redis,
|
provide: DI.redis,
|
||||||
useFactory: (config) => {
|
useFactory: (config: Config) => {
|
||||||
return new Redis.Redis({
|
return new Redis.Redis({
|
||||||
port: config.redis.port,
|
port: config.redis.port,
|
||||||
host: config.redis.host,
|
host: config.redis.host,
|
||||||
@@ -55,7 +55,7 @@ const $redis: Provider = {
|
|||||||
|
|
||||||
const $redisForPub: Provider = {
|
const $redisForPub: Provider = {
|
||||||
provide: DI.redisForPub,
|
provide: DI.redisForPub,
|
||||||
useFactory: (config) => {
|
useFactory: (config: Config) => {
|
||||||
const redis = new Redis.Redis({
|
const redis = new Redis.Redis({
|
||||||
port: config.redisForPubsub.port,
|
port: config.redisForPubsub.port,
|
||||||
host: config.redisForPubsub.host,
|
host: config.redisForPubsub.host,
|
||||||
@@ -71,7 +71,7 @@ const $redisForPub: Provider = {
|
|||||||
|
|
||||||
const $redisForSub: Provider = {
|
const $redisForSub: Provider = {
|
||||||
provide: DI.redisForSub,
|
provide: DI.redisForSub,
|
||||||
useFactory: (config) => {
|
useFactory: (config: Config) => {
|
||||||
const redis = new Redis.Redis({
|
const redis = new Redis.Redis({
|
||||||
port: config.redisForPubsub.port,
|
port: config.redisForPubsub.port,
|
||||||
host: config.redisForPubsub.host,
|
host: config.redisForPubsub.host,
|
||||||
|
|||||||
@@ -18,10 +18,12 @@ export async function server() {
|
|||||||
const serverService = app.get(ServerService);
|
const serverService = app.get(ServerService);
|
||||||
await serverService.launch();
|
await serverService.launch();
|
||||||
|
|
||||||
app.get(ChartManagementService).start();
|
if (process.env.NODE_ENV !== 'test') {
|
||||||
app.get(JanitorService).start();
|
app.get(ChartManagementService).start();
|
||||||
app.get(QueueStatsService).start();
|
app.get(JanitorService).start();
|
||||||
app.get(ServerStatsService).start();
|
app.get(QueueStatsService).start();
|
||||||
|
app.get(ServerStatsService).start();
|
||||||
|
}
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ export type Source = {
|
|||||||
port: string;
|
port: string;
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
ssl?: boolean;
|
ssl?: boolean;
|
||||||
|
index: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
proxy?: string;
|
proxy?: string;
|
||||||
@@ -143,7 +144,7 @@ export function loadConfig() {
|
|||||||
const clientManifestExists = fs.existsSync(_dirname + '/../../../built/_vite_/manifest.json');
|
const clientManifestExists = fs.existsSync(_dirname + '/../../../built/_vite_/manifest.json');
|
||||||
const clientManifest = clientManifestExists ?
|
const clientManifest = clientManifestExists ?
|
||||||
JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_vite_/manifest.json`, 'utf-8'))
|
JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_vite_/manifest.json`, 'utf-8'))
|
||||||
: { 'src/init.ts': { file: 'src/init.ts' } };
|
: { 'src/_boot_.ts': { file: 'src/_boot_.ts' } };
|
||||||
const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source;
|
const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source;
|
||||||
|
|
||||||
const mixin = {} as Mixin;
|
const mixin = {} as Mixin;
|
||||||
@@ -164,7 +165,7 @@ export function loadConfig() {
|
|||||||
mixin.authUrl = `${mixin.scheme}://${mixin.host}/auth`;
|
mixin.authUrl = `${mixin.scheme}://${mixin.host}/auth`;
|
||||||
mixin.driveUrl = `${mixin.scheme}://${mixin.host}/files`;
|
mixin.driveUrl = `${mixin.scheme}://${mixin.host}/files`;
|
||||||
mixin.userAgent = `Misskey/${meta.version} (${config.url})`;
|
mixin.userAgent = `Misskey/${meta.version} (${config.url})`;
|
||||||
mixin.clientEntry = clientManifest['src/init.ts'];
|
mixin.clientEntry = clientManifest['src/_boot_.ts'];
|
||||||
mixin.clientManifestExists = clientManifestExists;
|
mixin.clientManifestExists = clientManifestExists;
|
||||||
|
|
||||||
const externalMediaProxy = config.mediaProxy ?
|
const externalMediaProxy = config.mediaProxy ?
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
|
|||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import type { DriveFile } from '@/models/entities/DriveFile.js';
|
import type { DriveFile } from '@/models/entities/DriveFile.js';
|
||||||
import type { Emoji } from '@/models/entities/Emoji.js';
|
import type { Emoji } from '@/models/entities/Emoji.js';
|
||||||
import type { EmojisRepository } from '@/models/index.js';
|
import type { EmojisRepository, Role } from '@/models/index.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
|
import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
@@ -15,6 +15,8 @@ import type { Config } from '@/config.js';
|
|||||||
import { query } from '@/misc/prelude/url.js';
|
import { query } from '@/misc/prelude/url.js';
|
||||||
import type { Serialized } from '@/server/api/stream/types.js';
|
import type { Serialized } from '@/server/api/stream/types.js';
|
||||||
|
|
||||||
|
const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CustomEmojiService {
|
export class CustomEmojiService {
|
||||||
private cache: MemoryKVCache<Emoji | null>;
|
private cache: MemoryKVCache<Emoji | null>;
|
||||||
@@ -63,6 +65,9 @@ export class CustomEmojiService {
|
|||||||
aliases: string[];
|
aliases: string[];
|
||||||
host: string | null;
|
host: string | null;
|
||||||
license: string | null;
|
license: string | null;
|
||||||
|
isSensitive: boolean;
|
||||||
|
localOnly: boolean;
|
||||||
|
roleIdsThatCanBeUsedThisEmojiAsReaction: Role['id'][];
|
||||||
}): Promise<Emoji> {
|
}): Promise<Emoji> {
|
||||||
const emoji = await this.emojisRepository.insert({
|
const emoji = await this.emojisRepository.insert({
|
||||||
id: this.idService.genId(),
|
id: this.idService.genId(),
|
||||||
@@ -75,6 +80,9 @@ export class CustomEmojiService {
|
|||||||
publicUrl: data.driveFile.webpublicUrl ?? data.driveFile.url,
|
publicUrl: data.driveFile.webpublicUrl ?? data.driveFile.url,
|
||||||
type: data.driveFile.webpublicType ?? data.driveFile.type,
|
type: data.driveFile.webpublicType ?? data.driveFile.type,
|
||||||
license: data.license,
|
license: data.license,
|
||||||
|
isSensitive: data.isSensitive,
|
||||||
|
localOnly: data.localOnly,
|
||||||
|
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||||
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
|
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
|
||||||
|
|
||||||
if (data.host == null) {
|
if (data.host == null) {
|
||||||
@@ -90,10 +98,14 @@ export class CustomEmojiService {
|
|||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async update(id: Emoji['id'], data: {
|
public async update(id: Emoji['id'], data: {
|
||||||
|
driveFile?: DriveFile;
|
||||||
name?: string;
|
name?: string;
|
||||||
category?: string | null;
|
category?: string | null;
|
||||||
aliases?: string[];
|
aliases?: string[];
|
||||||
license?: string | null;
|
license?: string | null;
|
||||||
|
isSensitive?: boolean;
|
||||||
|
localOnly?: boolean;
|
||||||
|
roleIdsThatCanBeUsedThisEmojiAsReaction?: Role['id'][];
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
|
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
|
||||||
const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() });
|
const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() });
|
||||||
@@ -105,6 +117,12 @@ export class CustomEmojiService {
|
|||||||
category: data.category,
|
category: data.category,
|
||||||
aliases: data.aliases,
|
aliases: data.aliases,
|
||||||
license: data.license,
|
license: data.license,
|
||||||
|
isSensitive: data.isSensitive,
|
||||||
|
localOnly: data.localOnly,
|
||||||
|
originalUrl: data.driveFile != null ? data.driveFile.url : undefined,
|
||||||
|
publicUrl: data.driveFile != null ? (data.driveFile.webpublicUrl ?? data.driveFile.url) : undefined,
|
||||||
|
type: data.driveFile != null ? (data.driveFile.webpublicType ?? data.driveFile.type) : undefined,
|
||||||
|
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.localEmojisCache.refresh();
|
this.localEmojisCache.refresh();
|
||||||
@@ -259,7 +277,7 @@ export class CustomEmojiService {
|
|||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public parseEmojiStr(emojiName: string, noteUserHost: string | null) {
|
public parseEmojiStr(emojiName: string, noteUserHost: string | null) {
|
||||||
const match = emojiName.match(/^(\w+)(?:@([\w.-]+))?$/);
|
const match = emojiName.match(parseEmojiStrRegexp);
|
||||||
if (!match) return { name: null, host: null };
|
if (!match) return { name: null, host: null };
|
||||||
|
|
||||||
const name = match[1];
|
const name = match[1];
|
||||||
|
|||||||
@@ -74,9 +74,7 @@ export class FederatedInstanceService {
|
|||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response.raw[0];
|
return response.raw[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
const updated = result.raw[0];
|
|
||||||
|
|
||||||
this.federatedInstanceCache.set(updated.host, updated);
|
this.federatedInstanceCache.set(result.host, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export class MfmService {
|
|||||||
if (hashtagNames && href && hashtagNames.map(x => x.toLowerCase()).includes(txt.toLowerCase())) {
|
if (hashtagNames && href && hashtagNames.map(x => x.toLowerCase()).includes(txt.toLowerCase())) {
|
||||||
text += txt;
|
text += txt;
|
||||||
// メンション
|
// メンション
|
||||||
} else if (txt.startsWith('@') && !(rel && rel.value.match(/^me /))) {
|
} else if (txt.startsWith('@') && !(rel && rel.value.startsWith('me '))) {
|
||||||
const part = txt.split('@');
|
const part = txt.split('@');
|
||||||
|
|
||||||
if (part.length === 2 && href) {
|
if (part.length === 2 && href) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as mfm from 'mfm-js';
|
|||||||
import { In, DataSource } from 'typeorm';
|
import { In, DataSource } from 'typeorm';
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
import RE2 from 're2';
|
||||||
import { extractMentions } from '@/misc/extract-mentions.js';
|
import { extractMentions } from '@/misc/extract-mentions.js';
|
||||||
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
|
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
|
||||||
import { extractHashtags } from '@/misc/extract-hashtags.js';
|
import { extractHashtags } from '@/misc/extract-hashtags.js';
|
||||||
@@ -238,7 +239,8 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||||||
if (data.channel != null) data.localOnly = true;
|
if (data.channel != null) data.localOnly = true;
|
||||||
|
|
||||||
if (data.visibility === 'public' && data.channel == null) {
|
if (data.visibility === 'public' && data.channel == null) {
|
||||||
if ((data.text != null) && (await this.metaService.fetch()).sensitiveWords.some(w => data.text!.includes(w))) {
|
const sensitiveWords = (await this.metaService.fetch()).sensitiveWords;
|
||||||
|
if (this.isSensitive(data, sensitiveWords)) {
|
||||||
data.visibility = 'home';
|
data.visibility = 'home';
|
||||||
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
|
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
|
||||||
data.visibility = 'home';
|
data.visibility = 'home';
|
||||||
@@ -670,6 +672,31 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||||||
// Register to search database
|
// Register to search database
|
||||||
this.index(note);
|
this.index(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private isSensitive(note: Option, sensitiveWord: string[]): boolean {
|
||||||
|
if (sensitiveWord.length > 0) {
|
||||||
|
const text = note.cw ?? note.text ?? '';
|
||||||
|
if (text === '') return false;
|
||||||
|
const matched = sensitiveWord.some(filter => {
|
||||||
|
// represents RegExp
|
||||||
|
const regexp = filter.match(/^\/(.+)\/(.*)$/);
|
||||||
|
// This should never happen due to input sanitisation.
|
||||||
|
if (!regexp) {
|
||||||
|
const words = filter.split(' ');
|
||||||
|
return words.every(keyword => text.includes(keyword));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return new RE2(regexp[1], regexp[2]).test(text);
|
||||||
|
} catch (err) {
|
||||||
|
// This should never happen due to input sanitisation.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (matched) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private incRenoteCount(renote: Note) {
|
private incRenoteCount(renote: Note) {
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ export class QueryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public generateRepliesQuery(q: SelectQueryBuilder<any>, me?: Pick<User, 'id' | 'showTimelineReplies'> | null): void {
|
public generateRepliesQuery(q: SelectQueryBuilder<any>, withReplies: boolean, me?: Pick<User, 'id'> | null): void {
|
||||||
if (me == null) {
|
if (me == null) {
|
||||||
q.andWhere(new Brackets(qb => { qb
|
q.andWhere(new Brackets(qb => { qb
|
||||||
.where('note.replyId IS NULL') // 返信ではない
|
.where('note.replyId IS NULL') // 返信ではない
|
||||||
@@ -217,7 +217,7 @@ export class QueryService {
|
|||||||
.andWhere('note.replyUserId = note.userId');
|
.andWhere('note.replyUserId = note.userId');
|
||||||
}));
|
}));
|
||||||
}));
|
}));
|
||||||
} else if (!me.showTimelineReplies) {
|
} else if (!withReplies) {
|
||||||
q.andWhere(new Brackets(qb => { qb
|
q.andWhere(new Brackets(qb => { qb
|
||||||
.where('note.replyId IS NULL') // 返信ではない
|
.where('note.replyId IS NULL') // 返信ではない
|
||||||
.orWhere('note.replyUserId = :meId', { meId: me.id }) // 返信だけど自分のノートへの返信
|
.orWhere('note.replyUserId = :meId', { meId: me.id }) // 返信だけど自分のノートへの返信
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { setTimeout } from 'node:timers/promises';
|
||||||
|
import { Inject, Module, OnApplicationShutdown } from '@nestjs/common';
|
||||||
import Bull from 'bull';
|
import Bull from 'bull';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
@@ -41,9 +42,9 @@ export type SystemQueue = Bull.Queue<Record<string, unknown>>;
|
|||||||
export type EndedPollNotificationQueue = Bull.Queue<EndedPollNotificationJobData>;
|
export type EndedPollNotificationQueue = Bull.Queue<EndedPollNotificationJobData>;
|
||||||
export type DeliverQueue = Bull.Queue<DeliverJobData>;
|
export type DeliverQueue = Bull.Queue<DeliverJobData>;
|
||||||
export type InboxQueue = Bull.Queue<InboxJobData>;
|
export type InboxQueue = Bull.Queue<InboxJobData>;
|
||||||
export type DbQueue = Bull.Queue<DbJobData<keyof DbJobMap>>;
|
export type DbQueue = Bull.Queue;
|
||||||
export type RelationshipQueue = Bull.Queue<RelationshipJobData>;
|
export type RelationshipQueue = Bull.Queue<RelationshipJobData>;
|
||||||
export type ObjectStorageQueue = Bull.Queue<ObjectStorageJobData>;
|
export type ObjectStorageQueue = Bull.Queue;
|
||||||
export type WebhookDeliverQueue = Bull.Queue<WebhookDeliverJobData>;
|
export type WebhookDeliverQueue = Bull.Queue<WebhookDeliverJobData>;
|
||||||
|
|
||||||
const $system: Provider = {
|
const $system: Provider = {
|
||||||
@@ -118,4 +119,36 @@ const $webhookDeliver: Provider = {
|
|||||||
$webhookDeliver,
|
$webhookDeliver,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class QueueModule {}
|
export class QueueModule implements OnApplicationShutdown {
|
||||||
|
constructor(
|
||||||
|
@Inject('queue:system') public systemQueue: SystemQueue,
|
||||||
|
@Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue,
|
||||||
|
@Inject('queue:deliver') public deliverQueue: DeliverQueue,
|
||||||
|
@Inject('queue:inbox') public inboxQueue: InboxQueue,
|
||||||
|
@Inject('queue:db') public dbQueue: DbQueue,
|
||||||
|
@Inject('queue:relationship') public relationshipQueue: RelationshipQueue,
|
||||||
|
@Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue,
|
||||||
|
@Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async onApplicationShutdown(signal: string): Promise<void> {
|
||||||
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
// XXX:
|
||||||
|
// Shutting down the existing connections causes errors on Jest as
|
||||||
|
// Misskey has asynchronous postgres/redis connections that are not
|
||||||
|
// awaited.
|
||||||
|
// Let's wait for some random time for them to finish.
|
||||||
|
await setTimeout(5000);
|
||||||
|
}
|
||||||
|
await Promise.all([
|
||||||
|
this.systemQueue.close(),
|
||||||
|
this.endedPollNotificationQueue.close(),
|
||||||
|
this.deliverQueue.close(),
|
||||||
|
this.inboxQueue.close(),
|
||||||
|
this.dbQueue.close(),
|
||||||
|
this.relationshipQueue.close(),
|
||||||
|
this.objectStorageQueue.close(),
|
||||||
|
this.webhookDeliverQueue.close(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { bindThis } from '@/decorators.js';
|
|||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
|
||||||
const FALLBACK = '❤';
|
const FALLBACK = '❤';
|
||||||
|
|
||||||
@@ -54,6 +55,9 @@ type DecodedReaction = {
|
|||||||
host?: string | null;
|
host?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isCustomEmojiRegexp = /^:([\w+-]+)(?:@\.)?:$/;
|
||||||
|
const decodeCustomEmojiRegexp = /^:([\w+-]+)(?:@([\w.-]+))?:$/;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ReactionService {
|
export class ReactionService {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -72,6 +76,7 @@ export class ReactionService {
|
|||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
private customEmojiService: CustomEmojiService,
|
private customEmojiService: CustomEmojiService,
|
||||||
|
private roleService: RoleService,
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
private userBlockingService: UserBlockingService,
|
private userBlockingService: UserBlockingService,
|
||||||
@@ -85,7 +90,7 @@ export class ReactionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async create(user: { id: User['id']; host: User['host']; isBot: User['isBot'] }, note: Note, reaction?: string | null) {
|
public async create(user: { id: User['id']; host: User['host']; isBot: User['isBot'] }, note: Note, _reaction?: string | null) {
|
||||||
// Check blocking
|
// Check blocking
|
||||||
if (note.userId !== user.id) {
|
if (note.userId !== user.id) {
|
||||||
const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id);
|
const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id);
|
||||||
@@ -99,10 +104,41 @@ export class ReactionService {
|
|||||||
throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.');
|
throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote') && (user.host != null))) {
|
let reaction = _reaction ?? FALLBACK;
|
||||||
|
|
||||||
|
if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && (user.host != null))) {
|
||||||
reaction = '❤️';
|
reaction = '❤️';
|
||||||
} else {
|
} else if (_reaction) {
|
||||||
reaction = await this.toDbReaction(reaction, user.host);
|
const custom = reaction.match(isCustomEmojiRegexp);
|
||||||
|
if (custom) {
|
||||||
|
const reacterHost = this.utilityService.toPunyNullable(user.host);
|
||||||
|
|
||||||
|
const name = custom[1];
|
||||||
|
const emoji = reacterHost == null
|
||||||
|
? (await this.customEmojiService.localEmojisCache.fetch()).get(name)
|
||||||
|
: await this.emojisRepository.findOneBy({
|
||||||
|
host: reacterHost,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (emoji) {
|
||||||
|
if (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0 || (await this.roleService.getUserRoles(user.id)).some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.includes(r.id))) {
|
||||||
|
reaction = reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`;
|
||||||
|
|
||||||
|
// センシティブ
|
||||||
|
if ((note.reactionAcceptance === 'nonSensitiveOnly') && emoji.isSensitive) {
|
||||||
|
reaction = FALLBACK;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// リアクションとして使う権限がない
|
||||||
|
reaction = FALLBACK;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reaction = FALLBACK;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reaction = this.normalize(reaction ?? null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const record: NoteReaction = {
|
const record: NoteReaction = {
|
||||||
@@ -288,11 +324,9 @@ export class ReactionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async toDbReaction(reaction?: string | null, reacterHost?: string | null): Promise<string> {
|
public normalize(reaction: string | null): string {
|
||||||
if (reaction == null) return FALLBACK;
|
if (reaction == null) return FALLBACK;
|
||||||
|
|
||||||
reacterHost = this.utilityService.toPunyNullable(reacterHost);
|
|
||||||
|
|
||||||
// 文字列タイプのリアクションを絵文字に変換
|
// 文字列タイプのリアクションを絵文字に変換
|
||||||
if (Object.keys(legacies).includes(reaction)) return legacies[reaction];
|
if (Object.keys(legacies).includes(reaction)) return legacies[reaction];
|
||||||
|
|
||||||
@@ -306,25 +340,12 @@ export class ReactionService {
|
|||||||
return unicode.match('\u200d') ? unicode : unicode.replace(/\ufe0f/g, '');
|
return unicode.match('\u200d') ? unicode : unicode.replace(/\ufe0f/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
const custom = reaction.match(/^:([\w+-]+)(?:@\.)?:$/);
|
|
||||||
if (custom) {
|
|
||||||
const name = custom[1];
|
|
||||||
const emoji = reacterHost == null
|
|
||||||
? (await this.customEmojiService.localEmojisCache.fetch()).get(name)
|
|
||||||
: await this.emojisRepository.findOneBy({
|
|
||||||
host: reacterHost,
|
|
||||||
name,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (emoji) return reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return FALLBACK;
|
return FALLBACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public decodeReaction(str: string): DecodedReaction {
|
public decodeReaction(str: string): DecodedReaction {
|
||||||
const custom = str.match(/^:([\w+-]+)(?:@([\w.-]+))?:$/);
|
const custom = str.match(decodeCustomEmojiRegexp);
|
||||||
|
|
||||||
if (custom) {
|
if (custom) {
|
||||||
const name = custom[1];
|
const name = custom[1];
|
||||||
|
|||||||
@@ -306,6 +306,14 @@ export class RoleService implements OnApplicationShutdown {
|
|||||||
return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isAdministrator);
|
return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isAdministrator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async isExplorable(role: { id: Role['id']} | null): Promise<boolean> {
|
||||||
|
if (role == null) return false;
|
||||||
|
const check = await this.rolesRepository.findOneBy({ id: role.id });
|
||||||
|
if (check == null) return false;
|
||||||
|
return check.isExplorable;
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getModeratorIds(includeAdmins = true): Promise<User['id'][]> {
|
public async getModeratorIds(includeAdmins = true): Promise<User['id'][]> {
|
||||||
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
|
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export class SearchService {
|
|||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
) {
|
) {
|
||||||
if (meilisearch) {
|
if (meilisearch) {
|
||||||
this.meilisearchNoteIndex = meilisearch.index('notes');
|
this.meilisearchNoteIndex = meilisearch.index(`${config.meilisearch!.index}---notes`);
|
||||||
this.meilisearchNoteIndex.updateSettings({
|
this.meilisearchNoteIndex.updateSettings({
|
||||||
searchableAttributes: [
|
searchableAttributes: [
|
||||||
'text',
|
'text',
|
||||||
@@ -82,6 +82,7 @@ export class SearchService {
|
|||||||
'userId',
|
'userId',
|
||||||
'userHost',
|
'userHost',
|
||||||
'channelId',
|
'channelId',
|
||||||
|
'tags',
|
||||||
],
|
],
|
||||||
typoTolerance: {
|
typoTolerance: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@@ -107,6 +108,7 @@ export class SearchService {
|
|||||||
channelId: note.channelId,
|
channelId: note.channelId,
|
||||||
cw: note.cw,
|
cw: note.cw,
|
||||||
text: note.text,
|
text: note.text,
|
||||||
|
tags: note.tags,
|
||||||
}], {
|
}], {
|
||||||
primaryKey: 'id',
|
primaryKey: 'id',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ type IWebFinger = {
|
|||||||
subject: string;
|
subject: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const urlRegex = /^https?:\/\//;
|
||||||
|
const mRegex = /^([^@]+)@(.*)/;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WebfingerService {
|
export class WebfingerService {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -35,12 +38,12 @@ export class WebfingerService {
|
|||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private genUrl(query: string): string {
|
private genUrl(query: string): string {
|
||||||
if (query.match(/^https?:\/\//)) {
|
if (query.match(urlRegex)) {
|
||||||
const u = new URL(query);
|
const u = new URL(query);
|
||||||
return `${u.protocol}//${u.hostname}/.well-known/webfinger?` + urlQuery({ resource: query });
|
return `${u.protocol}//${u.hostname}/.well-known/webfinger?` + urlQuery({ resource: query });
|
||||||
}
|
}
|
||||||
|
|
||||||
const m = query.match(/^([^@]+)@(.*)/);
|
const m = query.match(mRegex);
|
||||||
if (m) {
|
if (m) {
|
||||||
const hostname = m[2];
|
const hostname = m[2];
|
||||||
const useHttp = process.env.MISSKEY_WEBFINGER_USE_HTTP && process.env.MISSKEY_WEBFINGER_USE_HTTP.toLowerCase() === 'true';
|
const useHttp = process.env.MISSKEY_WEBFINGER_USE_HTTP && process.env.MISSKEY_WEBFINGER_USE_HTTP.toLowerCase() === 'true';
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ export class ApRendererService {
|
|||||||
const name = reaction.replaceAll(':', '');
|
const name = reaction.replaceAll(':', '');
|
||||||
const emoji = (await this.customEmojiService.localEmojisCache.fetch()).get(name);
|
const emoji = (await this.customEmojiService.localEmojisCache.fetch()).get(name);
|
||||||
|
|
||||||
if (emoji) object.tag = [this.renderEmoji(emoji)];
|
if (emoji && !emoji.localOnly) object.tag = [this.renderEmoji(emoji)];
|
||||||
}
|
}
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
@@ -400,7 +400,7 @@ export class ApRendererService {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const emojis = await this.getEmojis(note.emojis);
|
const emojis = await this.getEmojis(note.emojis);
|
||||||
const apemojis = emojis.map(emoji => this.renderEmoji(emoji));
|
const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji));
|
||||||
|
|
||||||
const tag = [
|
const tag = [
|
||||||
...hashtagTags,
|
...hashtagTags,
|
||||||
@@ -479,7 +479,7 @@ export class ApRendererService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const emojis = await this.getEmojis(user.emojis);
|
const emojis = await this.getEmojis(user.emojis);
|
||||||
const apemojis = emojis.map(emoji => this.renderEmoji(emoji));
|
const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji));
|
||||||
|
|
||||||
const hashtagTags = (user.tags ?? []).map(tag => this.renderHashtag(tag));
|
const hashtagTags = (user.tags ?? []).map(tag => this.renderHashtag(tag));
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ import type { UserEntityService } from '@/core/entities/UserEntityService.js';
|
|||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||||
|
import type { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||||
|
import { checkHttps } from '@/misc/check-https.js';
|
||||||
import { getApId, getApType, getOneApHrefNullable, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js';
|
import { getApId, getApType, getOneApHrefNullable, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js';
|
||||||
import { extractApHashtags } from './tag.js';
|
import { extractApHashtags } from './tag.js';
|
||||||
import type { OnModuleInit } from '@nestjs/common';
|
import type { OnModuleInit } from '@nestjs/common';
|
||||||
@@ -42,8 +44,6 @@ import type { ApLoggerService } from '../ApLoggerService.js';
|
|||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||||
import type { ApImageService } from './ApImageService.js';
|
import type { ApImageService } from './ApImageService.js';
|
||||||
import type { IActor, IObject } from '../type.js';
|
import type { IActor, IObject } from '../type.js';
|
||||||
import type { AccountMoveService } from '@/core/AccountMoveService.js';
|
|
||||||
import { checkHttps } from '@/misc/check-https.js';
|
|
||||||
|
|
||||||
const nameLength = 128;
|
const nameLength = 128;
|
||||||
const summaryLength = 2048;
|
const summaryLength = 2048;
|
||||||
@@ -306,7 +306,6 @@ export class ApPersonService implements OnModuleInit {
|
|||||||
tags,
|
tags,
|
||||||
isBot,
|
isBot,
|
||||||
isCat: (person as any).isCat === true,
|
isCat: (person as any).isCat === true,
|
||||||
showTimelineReplies: false,
|
|
||||||
})) as RemoteUser;
|
})) as RemoteUser;
|
||||||
|
|
||||||
await transactionalEntityManager.save(new UserProfile({
|
await transactionalEntityManager.save(new UserProfile({
|
||||||
@@ -696,7 +695,7 @@ export class ApPersonService implements OnModuleInit {
|
|||||||
if (!dst.alsoKnownAs || dst.alsoKnownAs.length === 0) {
|
if (!dst.alsoKnownAs || dst.alsoKnownAs.length === 0) {
|
||||||
return 'skip: dst.alsoKnownAs is empty';
|
return 'skip: dst.alsoKnownAs is empty';
|
||||||
}
|
}
|
||||||
if (!dst.alsoKnownAs?.includes(src.uri)) {
|
if (!dst.alsoKnownAs.includes(src.uri)) {
|
||||||
return 'skip: alsoKnownAs does not include from.uri';
|
return 'skip: alsoKnownAs does not include from.uri';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ export class EmojiEntityService {
|
|||||||
category: emoji.category,
|
category: emoji.category,
|
||||||
// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
|
// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
|
||||||
url: emoji.publicUrl || emoji.originalUrl,
|
url: emoji.publicUrl || emoji.originalUrl,
|
||||||
|
isSensitive: emoji.isSensitive ? true : undefined,
|
||||||
|
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +53,9 @@ export class EmojiEntityService {
|
|||||||
// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
|
// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
|
||||||
url: emoji.publicUrl || emoji.originalUrl,
|
url: emoji.publicUrl || emoji.originalUrl,
|
||||||
license: emoji.license,
|
license: emoji.license,
|
||||||
|
isSensitive: emoji.isSensitive,
|
||||||
|
localOnly: emoji.localOnly,
|
||||||
|
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -306,6 +306,24 @@ export class UserEntityService implements OnModuleInit {
|
|||||||
|
|
||||||
const user = typeof src === 'object' ? src : await this.usersRepository.findOneByOrFail({ id: src });
|
const user = typeof src === 'object' ? src : await this.usersRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
|
// migration
|
||||||
|
if (user.avatarId != null && user.avatarUrl === null) {
|
||||||
|
const avatar = await this.driveFilesRepository.findOneByOrFail({ id: user.avatarId });
|
||||||
|
user.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, 'avatar');
|
||||||
|
this.usersRepository.update(user.id, {
|
||||||
|
avatarUrl: user.avatarUrl,
|
||||||
|
avatarBlurhash: avatar.blurhash,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (user.bannerId != null && user.bannerUrl === null) {
|
||||||
|
const banner = await this.driveFilesRepository.findOneByOrFail({ id: user.bannerId });
|
||||||
|
user.bannerUrl = this.driveFileEntityService.getPublicUrl(banner);
|
||||||
|
this.usersRepository.update(user.id, {
|
||||||
|
bannerUrl: user.bannerUrl,
|
||||||
|
bannerBlurhash: banner.blurhash,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const meId = me ? me.id : null;
|
const meId = me ? me.id : null;
|
||||||
const isMe = meId === user.id;
|
const isMe = meId === user.id;
|
||||||
const iAmModerator = me ? await this.roleService.isModerator(me as User) : false;
|
const iAmModerator = me ? await this.roleService.isModerator(me as User) : false;
|
||||||
@@ -427,6 +445,7 @@ export class UserEntityService implements OnModuleInit {
|
|||||||
carefulBot: profile!.carefulBot,
|
carefulBot: profile!.carefulBot,
|
||||||
autoAcceptFollowed: profile!.autoAcceptFollowed,
|
autoAcceptFollowed: profile!.autoAcceptFollowed,
|
||||||
noCrawle: profile!.noCrawle,
|
noCrawle: profile!.noCrawle,
|
||||||
|
preventAiLearning: profile!.preventAiLearning,
|
||||||
isExplorable: user.isExplorable,
|
isExplorable: user.isExplorable,
|
||||||
isDeleted: user.isDeleted,
|
isDeleted: user.isDeleted,
|
||||||
hideOnlineStatus: user.hideOnlineStatus,
|
hideOnlineStatus: user.hideOnlineStatus,
|
||||||
@@ -447,7 +466,6 @@ export class UserEntityService implements OnModuleInit {
|
|||||||
mutedInstances: profile!.mutedInstances,
|
mutedInstances: profile!.mutedInstances,
|
||||||
mutingNotificationTypes: profile!.mutingNotificationTypes,
|
mutingNotificationTypes: profile!.mutingNotificationTypes,
|
||||||
emailNotificationTypes: profile!.emailNotificationTypes,
|
emailNotificationTypes: profile!.emailNotificationTypes,
|
||||||
showTimelineReplies: user.showTimelineReplies ?? falsy,
|
|
||||||
achievements: profile!.achievements,
|
achievements: profile!.achievements,
|
||||||
loggedInDays: profile!.loggedInDates.length,
|
loggedInDays: profile!.loggedInDates.length,
|
||||||
policies: this.roleService.getUserPolicies(user.id),
|
policies: this.roleService.getUserPolicies(user.id),
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export class UserListEntityService {
|
|||||||
createdAt: userList.createdAt.toISOString(),
|
createdAt: userList.createdAt.toISOString(),
|
||||||
name: userList.name,
|
name: userList.name,
|
||||||
userIds: users.map(x => x.userId),
|
userIds: users.map(x => x.userId),
|
||||||
|
isPublic: userList.isPublic,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export const DI = {
|
|||||||
userSecurityKeysRepository: Symbol('userSecurityKeysRepository'),
|
userSecurityKeysRepository: Symbol('userSecurityKeysRepository'),
|
||||||
userPublickeysRepository: Symbol('userPublickeysRepository'),
|
userPublickeysRepository: Symbol('userPublickeysRepository'),
|
||||||
userListsRepository: Symbol('userListsRepository'),
|
userListsRepository: Symbol('userListsRepository'),
|
||||||
|
userListFavoritesRepository: Symbol('userListFavoritesRepository'),
|
||||||
userListJoiningsRepository: Symbol('userListJoiningsRepository'),
|
userListJoiningsRepository: Symbol('userListJoiningsRepository'),
|
||||||
userNotePiningsRepository: Symbol('userNotePiningsRepository'),
|
userNotePiningsRepository: Symbol('userNotePiningsRepository'),
|
||||||
userIpsRepository: Symbol('userIpsRepository'),
|
userIpsRepository: Symbol('userIpsRepository'),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite, UserMemo } from './index.js';
|
import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite, UserMemo, UserListFavorite } from './index.js';
|
||||||
import type { DataSource } from 'typeorm';
|
import type { DataSource } from 'typeorm';
|
||||||
import type { Provider } from '@nestjs/common';
|
import type { Provider } from '@nestjs/common';
|
||||||
|
|
||||||
@@ -112,6 +112,12 @@ const $userListsRepository: Provider = {
|
|||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const $userListFavoritesRepository: Provider = {
|
||||||
|
provide: DI.userListFavoritesRepository,
|
||||||
|
useFactory: (db: DataSource) => db.getRepository(UserListFavorite),
|
||||||
|
inject: [DI.db],
|
||||||
|
};
|
||||||
|
|
||||||
const $userListJoiningsRepository: Provider = {
|
const $userListJoiningsRepository: Provider = {
|
||||||
provide: DI.userListJoiningsRepository,
|
provide: DI.userListJoiningsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(UserListJoining),
|
useFactory: (db: DataSource) => db.getRepository(UserListJoining),
|
||||||
@@ -416,6 +422,7 @@ const $userMemosRepository: Provider = {
|
|||||||
$userSecurityKeysRepository,
|
$userSecurityKeysRepository,
|
||||||
$userPublickeysRepository,
|
$userPublickeysRepository,
|
||||||
$userListsRepository,
|
$userListsRepository,
|
||||||
|
$userListFavoritesRepository,
|
||||||
$userListJoiningsRepository,
|
$userListJoiningsRepository,
|
||||||
$userNotePiningsRepository,
|
$userNotePiningsRepository,
|
||||||
$userIpsRepository,
|
$userIpsRepository,
|
||||||
@@ -483,6 +490,7 @@ const $userMemosRepository: Provider = {
|
|||||||
$userSecurityKeysRepository,
|
$userSecurityKeysRepository,
|
||||||
$userPublickeysRepository,
|
$userPublickeysRepository,
|
||||||
$userListsRepository,
|
$userListsRepository,
|
||||||
|
$userListFavoritesRepository,
|
||||||
$userListJoiningsRepository,
|
$userListJoiningsRepository,
|
||||||
$userNotePiningsRepository,
|
$userNotePiningsRepository,
|
||||||
$userIpsRepository,
|
$userIpsRepository,
|
||||||
|
|||||||
@@ -60,4 +60,20 @@ export class Emoji {
|
|||||||
length: 1024, nullable: true,
|
length: 1024, nullable: true,
|
||||||
})
|
})
|
||||||
public license: string | null;
|
public license: string | null;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
public localOnly: boolean;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
public isSensitive: boolean;
|
||||||
|
|
||||||
|
// TODO: 定期ジョブで存在しなくなったロールIDを除去するようにする
|
||||||
|
@Column('varchar', {
|
||||||
|
array: true, length: 128, default: '{}',
|
||||||
|
})
|
||||||
|
public roleIdsThatCanBeUsedThisEmojiAsReaction: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export class Note {
|
|||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 64, nullable: true,
|
length: 64, nullable: true,
|
||||||
})
|
})
|
||||||
public reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | null;
|
public reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null;
|
||||||
|
|
||||||
@Column('smallint', {
|
@Column('smallint', {
|
||||||
default: 0,
|
default: 0,
|
||||||
|
|||||||
@@ -232,12 +232,6 @@ export class User {
|
|||||||
})
|
})
|
||||||
public followersUri: string | null;
|
public followersUri: string | null;
|
||||||
|
|
||||||
@Column('boolean', {
|
|
||||||
default: false,
|
|
||||||
comment: 'Whether to show users replying to other users in the timeline.',
|
|
||||||
})
|
|
||||||
public showTimelineReplies: boolean;
|
|
||||||
|
|
||||||
@Index({ unique: true })
|
@Index({ unique: true })
|
||||||
@Column('char', {
|
@Column('char', {
|
||||||
length: 16, nullable: true, unique: true,
|
length: 16, nullable: true, unique: true,
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ export class UserList {
|
|||||||
})
|
})
|
||||||
public userId: User['id'];
|
public userId: User['id'];
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
public isPublic: boolean;
|
||||||
|
|
||||||
@ManyToOne(type => User, {
|
@ManyToOne(type => User, {
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
})
|
})
|
||||||
|
|||||||
33
packages/backend/src/models/entities/UserListFavorite.ts
Normal file
33
packages/backend/src/models/entities/UserListFavorite.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||||
|
import { id } from '../id.js';
|
||||||
|
import { User } from './User.js';
|
||||||
|
import { UserList } from './UserList.js';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
@Index(['userId', 'userListId'], { unique: true })
|
||||||
|
export class UserListFavorite {
|
||||||
|
@PrimaryColumn(id())
|
||||||
|
public id: string;
|
||||||
|
|
||||||
|
@Column('timestamp with time zone')
|
||||||
|
public createdAt: Date;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column(id())
|
||||||
|
public userId: User['id'];
|
||||||
|
|
||||||
|
@ManyToOne(type => User, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public user: User | null;
|
||||||
|
|
||||||
|
@Column(id())
|
||||||
|
public userListId: UserList['id'];
|
||||||
|
|
||||||
|
@ManyToOne(type => UserList, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public userList: UserList | null;
|
||||||
|
}
|
||||||
@@ -76,7 +76,7 @@ export class UserProfile {
|
|||||||
public emailNotificationTypes: string[];
|
public emailNotificationTypes: string[];
|
||||||
|
|
||||||
@Column('boolean', {
|
@Column('boolean', {
|
||||||
default: false,
|
default: true,
|
||||||
})
|
})
|
||||||
public publicReactions: boolean;
|
public publicReactions: boolean;
|
||||||
|
|
||||||
@@ -147,6 +147,11 @@ export class UserProfile {
|
|||||||
})
|
})
|
||||||
public noCrawle: boolean;
|
public noCrawle: boolean;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: true,
|
||||||
|
})
|
||||||
|
public preventAiLearning: boolean;
|
||||||
|
|
||||||
@Column('boolean', {
|
@Column('boolean', {
|
||||||
default: false,
|
default: false,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import { User } from '@/models/entities/User.js';
|
|||||||
import { UserIp } from '@/models/entities/UserIp.js';
|
import { UserIp } from '@/models/entities/UserIp.js';
|
||||||
import { UserKeypair } from '@/models/entities/UserKeypair.js';
|
import { UserKeypair } from '@/models/entities/UserKeypair.js';
|
||||||
import { UserList } from '@/models/entities/UserList.js';
|
import { UserList } from '@/models/entities/UserList.js';
|
||||||
|
import { UserListFavorite } from './entities/UserListFavorite.js';
|
||||||
import { UserListJoining } from '@/models/entities/UserListJoining.js';
|
import { UserListJoining } from '@/models/entities/UserListJoining.js';
|
||||||
import { UserNotePining } from '@/models/entities/UserNotePining.js';
|
import { UserNotePining } from '@/models/entities/UserNotePining.js';
|
||||||
import { UserPending } from '@/models/entities/UserPending.js';
|
import { UserPending } from '@/models/entities/UserPending.js';
|
||||||
@@ -117,6 +118,7 @@ export {
|
|||||||
UserIp,
|
UserIp,
|
||||||
UserKeypair,
|
UserKeypair,
|
||||||
UserList,
|
UserList,
|
||||||
|
UserListFavorite,
|
||||||
UserListJoining,
|
UserListJoining,
|
||||||
UserNotePining,
|
UserNotePining,
|
||||||
UserPending,
|
UserPending,
|
||||||
@@ -184,6 +186,7 @@ export type UsersRepository = Repository<User>;
|
|||||||
export type UserIpsRepository = Repository<UserIp>;
|
export type UserIpsRepository = Repository<UserIp>;
|
||||||
export type UserKeypairsRepository = Repository<UserKeypair>;
|
export type UserKeypairsRepository = Repository<UserKeypair>;
|
||||||
export type UserListsRepository = Repository<UserList>;
|
export type UserListsRepository = Repository<UserList>;
|
||||||
|
export type UserListFavoritesRepository = Repository<UserListFavorite>;
|
||||||
export type UserListJoiningsRepository = Repository<UserListJoining>;
|
export type UserListJoiningsRepository = Repository<UserListJoining>;
|
||||||
export type UserNotePiningsRepository = Repository<UserNotePining>;
|
export type UserNotePiningsRepository = Repository<UserNotePining>;
|
||||||
export type UserPendingsRepository = Repository<UserPending>;
|
export type UserPendingsRepository = Repository<UserPending>;
|
||||||
|
|||||||
@@ -22,6 +22,19 @@ export const packedEmojiSimpleSchema = {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
isSensitive: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: true, nullable: false,
|
||||||
|
},
|
||||||
|
roleIdsThatCanBeUsedThisEmojiAsReaction: {
|
||||||
|
type: 'array',
|
||||||
|
optional: true, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@@ -63,5 +76,22 @@ export const packedEmojiDetailedSchema = {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
},
|
},
|
||||||
|
isSensitive: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
localOnly: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
roleIdsThatCanBeUsedThisEmojiAsReaction: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -25,5 +25,10 @@ export const packedUserListSchema = {
|
|||||||
format: 'id',
|
format: 'id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
isPublic: {
|
||||||
|
type: 'boolean',
|
||||||
|
nullable: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -302,7 +302,11 @@ export const packedMeDetailedOnlySchema = {
|
|||||||
},
|
},
|
||||||
noCrawle: {
|
noCrawle: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
nullable: true, optional: false,
|
nullable: false, optional: false,
|
||||||
|
},
|
||||||
|
preventAiLearning: {
|
||||||
|
type: 'boolean',
|
||||||
|
nullable: false, optional: false,
|
||||||
},
|
},
|
||||||
isExplorable: {
|
isExplorable: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ import { User } from '@/models/entities/User.js';
|
|||||||
import { UserIp } from '@/models/entities/UserIp.js';
|
import { UserIp } from '@/models/entities/UserIp.js';
|
||||||
import { UserKeypair } from '@/models/entities/UserKeypair.js';
|
import { UserKeypair } from '@/models/entities/UserKeypair.js';
|
||||||
import { UserList } from '@/models/entities/UserList.js';
|
import { UserList } from '@/models/entities/UserList.js';
|
||||||
|
import { UserListFavorite } from '@/models/entities/UserListFavorite.js';
|
||||||
import { UserListJoining } from '@/models/entities/UserListJoining.js';
|
import { UserListJoining } from '@/models/entities/UserListJoining.js';
|
||||||
import { UserNotePining } from '@/models/entities/UserNotePining.js';
|
import { UserNotePining } from '@/models/entities/UserNotePining.js';
|
||||||
import { UserPending } from '@/models/entities/UserPending.js';
|
import { UserPending } from '@/models/entities/UserPending.js';
|
||||||
@@ -132,6 +133,7 @@ export const entities = [
|
|||||||
UserKeypair,
|
UserKeypair,
|
||||||
UserPublickey,
|
UserPublickey,
|
||||||
UserList,
|
UserList,
|
||||||
|
UserListFavorite,
|
||||||
UserListJoining,
|
UserListJoining,
|
||||||
UserNotePining,
|
UserNotePining,
|
||||||
UserSecurityKey,
|
UserSecurityKey,
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import type { Config } from '@/config.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js';
|
|
||||||
import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmojisProcessorService.js';
|
|
||||||
import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js';
|
|
||||||
import { ExportFollowingProcessorService } from './processors/ExportFollowingProcessorService.js';
|
|
||||||
import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js';
|
|
||||||
import { ExportBlockingProcessorService } from './processors/ExportBlockingProcessorService.js';
|
|
||||||
import { ExportUserListsProcessorService } from './processors/ExportUserListsProcessorService.js';
|
|
||||||
import { ExportAntennasProcessorService } from './processors/ExportAntennasProcessorService.js';
|
|
||||||
import { ImportFollowingProcessorService } from './processors/ImportFollowingProcessorService.js';
|
|
||||||
import { ImportMutingProcessorService } from './processors/ImportMutingProcessorService.js';
|
|
||||||
import { ImportBlockingProcessorService } from './processors/ImportBlockingProcessorService.js';
|
|
||||||
import { ImportUserListsProcessorService } from './processors/ImportUserListsProcessorService.js';
|
|
||||||
import { ImportCustomEmojisProcessorService } from './processors/ImportCustomEmojisProcessorService.js';
|
|
||||||
import { ImportAntennasProcessorService } from './processors/ImportAntennasProcessorService.js';
|
|
||||||
import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js';
|
|
||||||
import { ExportFavoritesProcessorService } from './processors/ExportFavoritesProcessorService.js';
|
|
||||||
import type Bull from 'bull';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class DbQueueProcessorsService {
|
|
||||||
constructor(
|
|
||||||
@Inject(DI.config)
|
|
||||||
private config: Config,
|
|
||||||
|
|
||||||
private deleteDriveFilesProcessorService: DeleteDriveFilesProcessorService,
|
|
||||||
private exportCustomEmojisProcessorService: ExportCustomEmojisProcessorService,
|
|
||||||
private exportNotesProcessorService: ExportNotesProcessorService,
|
|
||||||
private exportFavoritesProcessorService: ExportFavoritesProcessorService,
|
|
||||||
private exportFollowingProcessorService: ExportFollowingProcessorService,
|
|
||||||
private exportMutingProcessorService: ExportMutingProcessorService,
|
|
||||||
private exportBlockingProcessorService: ExportBlockingProcessorService,
|
|
||||||
private exportUserListsProcessorService: ExportUserListsProcessorService,
|
|
||||||
private exportAntennasProcessorService: ExportAntennasProcessorService,
|
|
||||||
private importFollowingProcessorService: ImportFollowingProcessorService,
|
|
||||||
private importMutingProcessorService: ImportMutingProcessorService,
|
|
||||||
private importBlockingProcessorService: ImportBlockingProcessorService,
|
|
||||||
private importUserListsProcessorService: ImportUserListsProcessorService,
|
|
||||||
private importCustomEmojisProcessorService: ImportCustomEmojisProcessorService,
|
|
||||||
private importAntennasProcessorService: ImportAntennasProcessorService,
|
|
||||||
private deleteAccountProcessorService: DeleteAccountProcessorService,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public start(q: Bull.Queue): void {
|
|
||||||
q.process('deleteDriveFiles', (job, done) => this.deleteDriveFilesProcessorService.process(job, done));
|
|
||||||
q.process('exportCustomEmojis', (job, done) => this.exportCustomEmojisProcessorService.process(job, done));
|
|
||||||
q.process('exportNotes', (job, done) => this.exportNotesProcessorService.process(job, done));
|
|
||||||
q.process('exportFavorites', (job, done) => this.exportFavoritesProcessorService.process(job, done));
|
|
||||||
q.process('exportFollowing', (job, done) => this.exportFollowingProcessorService.process(job, done));
|
|
||||||
q.process('exportMuting', (job, done) => this.exportMutingProcessorService.process(job, done));
|
|
||||||
q.process('exportBlocking', (job, done) => this.exportBlockingProcessorService.process(job, done));
|
|
||||||
q.process('exportUserLists', (job, done) => this.exportUserListsProcessorService.process(job, done));
|
|
||||||
q.process('exportAntennas', (job, done) => this.exportAntennasProcessorService.process(job, done));
|
|
||||||
q.process('importFollowing', (job, done) => this.importFollowingProcessorService.process(job, done));
|
|
||||||
q.process('importFollowingToDb', (job) => this.importFollowingProcessorService.processDb(job));
|
|
||||||
q.process('importMuting', (job, done) => this.importMutingProcessorService.process(job, done));
|
|
||||||
q.process('importBlocking', (job, done) => this.importBlockingProcessorService.process(job, done));
|
|
||||||
q.process('importBlockingToDb', (job) => this.importBlockingProcessorService.processDb(job));
|
|
||||||
q.process('importUserLists', (job, done) => this.importUserListsProcessorService.process(job, done));
|
|
||||||
q.process('importCustomEmojis', (job, done) => this.importCustomEmojisProcessorService.process(job, done));
|
|
||||||
q.process('importAntennas', (job, done) => this.importAntennasProcessorService.process(job, done));
|
|
||||||
q.process('deleteAccount', (job) => this.deleteAccountProcessorService.process(job));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import type { Config } from '@/config.js';
|
|
||||||
import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js';
|
|
||||||
import { DeleteFileProcessorService } from './processors/DeleteFileProcessorService.js';
|
|
||||||
import type Bull from 'bull';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ObjectStorageQueueProcessorsService {
|
|
||||||
constructor(
|
|
||||||
@Inject(DI.config)
|
|
||||||
private config: Config,
|
|
||||||
|
|
||||||
private deleteFileProcessorService: DeleteFileProcessorService,
|
|
||||||
private cleanRemoteFilesProcessorService: CleanRemoteFilesProcessorService,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public start(q: Bull.Queue): void {
|
|
||||||
q.process('deleteFile', 16, (job) => this.deleteFileProcessorService.process(job));
|
|
||||||
q.process('cleanRemoteFiles', 16, (job, done) => this.cleanRemoteFilesProcessorService.process(job, done));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,14 +3,10 @@ import { CoreModule } from '@/core/CoreModule.js';
|
|||||||
import { GlobalModule } from '@/GlobalModule.js';
|
import { GlobalModule } from '@/GlobalModule.js';
|
||||||
import { QueueLoggerService } from './QueueLoggerService.js';
|
import { QueueLoggerService } from './QueueLoggerService.js';
|
||||||
import { QueueProcessorService } from './QueueProcessorService.js';
|
import { QueueProcessorService } from './QueueProcessorService.js';
|
||||||
import { DbQueueProcessorsService } from './DbQueueProcessorsService.js';
|
|
||||||
import { RelationshipQueueProcessorsService } from './RelationshipQueueProcessorsService.js';
|
|
||||||
import { ObjectStorageQueueProcessorsService } from './ObjectStorageQueueProcessorsService.js';
|
|
||||||
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
|
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
|
||||||
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
|
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
|
||||||
import { InboxProcessorService } from './processors/InboxProcessorService.js';
|
import { InboxProcessorService } from './processors/InboxProcessorService.js';
|
||||||
import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js';
|
import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js';
|
||||||
import { SystemQueueProcessorsService } from './SystemQueueProcessorsService.js';
|
|
||||||
import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
|
import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
|
||||||
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
|
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
|
||||||
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
||||||
@@ -68,10 +64,6 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
|
|||||||
DeleteFileProcessorService,
|
DeleteFileProcessorService,
|
||||||
CleanRemoteFilesProcessorService,
|
CleanRemoteFilesProcessorService,
|
||||||
RelationshipProcessorService,
|
RelationshipProcessorService,
|
||||||
SystemQueueProcessorsService,
|
|
||||||
ObjectStorageQueueProcessorsService,
|
|
||||||
DbQueueProcessorsService,
|
|
||||||
RelationshipQueueProcessorsService,
|
|
||||||
WebhookDeliverProcessorService,
|
WebhookDeliverProcessorService,
|
||||||
EndedPollNotificationProcessorService,
|
EndedPollNotificationProcessorService,
|
||||||
DeliverProcessorService,
|
DeliverProcessorService,
|
||||||
|
|||||||
@@ -5,15 +5,36 @@ import type Logger from '@/logger.js';
|
|||||||
import { QueueService } from '@/core/QueueService.js';
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { getJobInfo } from './get-job-info.js';
|
import { getJobInfo } from './get-job-info.js';
|
||||||
import { SystemQueueProcessorsService } from './SystemQueueProcessorsService.js';
|
|
||||||
import { ObjectStorageQueueProcessorsService } from './ObjectStorageQueueProcessorsService.js';
|
|
||||||
import { DbQueueProcessorsService } from './DbQueueProcessorsService.js';
|
|
||||||
import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js';
|
import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js';
|
||||||
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
|
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
|
||||||
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
|
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
|
||||||
import { InboxProcessorService } from './processors/InboxProcessorService.js';
|
import { InboxProcessorService } from './processors/InboxProcessorService.js';
|
||||||
|
import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js';
|
||||||
|
import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmojisProcessorService.js';
|
||||||
|
import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js';
|
||||||
|
import { ExportFollowingProcessorService } from './processors/ExportFollowingProcessorService.js';
|
||||||
|
import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js';
|
||||||
|
import { ExportBlockingProcessorService } from './processors/ExportBlockingProcessorService.js';
|
||||||
|
import { ExportUserListsProcessorService } from './processors/ExportUserListsProcessorService.js';
|
||||||
|
import { ExportAntennasProcessorService } from './processors/ExportAntennasProcessorService.js';
|
||||||
|
import { ImportFollowingProcessorService } from './processors/ImportFollowingProcessorService.js';
|
||||||
|
import { ImportMutingProcessorService } from './processors/ImportMutingProcessorService.js';
|
||||||
|
import { ImportBlockingProcessorService } from './processors/ImportBlockingProcessorService.js';
|
||||||
|
import { ImportUserListsProcessorService } from './processors/ImportUserListsProcessorService.js';
|
||||||
|
import { ImportCustomEmojisProcessorService } from './processors/ImportCustomEmojisProcessorService.js';
|
||||||
|
import { ImportAntennasProcessorService } from './processors/ImportAntennasProcessorService.js';
|
||||||
|
import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js';
|
||||||
|
import { ExportFavoritesProcessorService } from './processors/ExportFavoritesProcessorService.js';
|
||||||
|
import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js';
|
||||||
|
import { DeleteFileProcessorService } from './processors/DeleteFileProcessorService.js';
|
||||||
|
import { RelationshipProcessorService } from './processors/RelationshipProcessorService.js';
|
||||||
|
import { TickChartsProcessorService } from './processors/TickChartsProcessorService.js';
|
||||||
|
import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js';
|
||||||
|
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
|
||||||
|
import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
|
||||||
|
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
||||||
|
import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
|
||||||
import { QueueLoggerService } from './QueueLoggerService.js';
|
import { QueueLoggerService } from './QueueLoggerService.js';
|
||||||
import { RelationshipQueueProcessorsService } from './RelationshipQueueProcessorsService.js';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QueueProcessorService {
|
export class QueueProcessorService {
|
||||||
@@ -25,14 +46,35 @@ export class QueueProcessorService {
|
|||||||
|
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
private systemQueueProcessorsService: SystemQueueProcessorsService,
|
|
||||||
private objectStorageQueueProcessorsService: ObjectStorageQueueProcessorsService,
|
|
||||||
private dbQueueProcessorsService: DbQueueProcessorsService,
|
|
||||||
private relationshipQueueProcessorsService: RelationshipQueueProcessorsService,
|
|
||||||
private webhookDeliverProcessorService: WebhookDeliverProcessorService,
|
private webhookDeliverProcessorService: WebhookDeliverProcessorService,
|
||||||
private endedPollNotificationProcessorService: EndedPollNotificationProcessorService,
|
private endedPollNotificationProcessorService: EndedPollNotificationProcessorService,
|
||||||
private deliverProcessorService: DeliverProcessorService,
|
private deliverProcessorService: DeliverProcessorService,
|
||||||
private inboxProcessorService: InboxProcessorService,
|
private inboxProcessorService: InboxProcessorService,
|
||||||
|
private deleteDriveFilesProcessorService: DeleteDriveFilesProcessorService,
|
||||||
|
private exportCustomEmojisProcessorService: ExportCustomEmojisProcessorService,
|
||||||
|
private exportNotesProcessorService: ExportNotesProcessorService,
|
||||||
|
private exportFavoritesProcessorService: ExportFavoritesProcessorService,
|
||||||
|
private exportFollowingProcessorService: ExportFollowingProcessorService,
|
||||||
|
private exportMutingProcessorService: ExportMutingProcessorService,
|
||||||
|
private exportBlockingProcessorService: ExportBlockingProcessorService,
|
||||||
|
private exportUserListsProcessorService: ExportUserListsProcessorService,
|
||||||
|
private exportAntennasProcessorService: ExportAntennasProcessorService,
|
||||||
|
private importFollowingProcessorService: ImportFollowingProcessorService,
|
||||||
|
private importMutingProcessorService: ImportMutingProcessorService,
|
||||||
|
private importBlockingProcessorService: ImportBlockingProcessorService,
|
||||||
|
private importUserListsProcessorService: ImportUserListsProcessorService,
|
||||||
|
private importCustomEmojisProcessorService: ImportCustomEmojisProcessorService,
|
||||||
|
private importAntennasProcessorService: ImportAntennasProcessorService,
|
||||||
|
private deleteAccountProcessorService: DeleteAccountProcessorService,
|
||||||
|
private deleteFileProcessorService: DeleteFileProcessorService,
|
||||||
|
private cleanRemoteFilesProcessorService: CleanRemoteFilesProcessorService,
|
||||||
|
private relationshipProcessorService: RelationshipProcessorService,
|
||||||
|
private tickChartsProcessorService: TickChartsProcessorService,
|
||||||
|
private resyncChartsProcessorService: ResyncChartsProcessorService,
|
||||||
|
private cleanChartsProcessorService: CleanChartsProcessorService,
|
||||||
|
private aggregateRetentionProcessorService: AggregateRetentionProcessorService,
|
||||||
|
private checkExpiredMutingsProcessorService: CheckExpiredMutingsProcessorService,
|
||||||
|
private cleanProcessorService: CleanProcessorService,
|
||||||
) {
|
) {
|
||||||
this.logger = this.queueLoggerService.logger;
|
this.logger = this.queueLoggerService.logger;
|
||||||
}
|
}
|
||||||
@@ -119,14 +161,6 @@ export class QueueProcessorService {
|
|||||||
.on('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
.on('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
||||||
.on('stalled', (job) => webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
|
.on('stalled', (job) => webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
|
||||||
|
|
||||||
this.queueService.deliverQueue.process(this.config.deliverJobConcurrency ?? 128, (job) => this.deliverProcessorService.process(job));
|
|
||||||
this.queueService.inboxQueue.process(this.config.inboxJobConcurrency ?? 16, (job) => this.inboxProcessorService.process(job));
|
|
||||||
this.queueService.endedPollNotificationQueue.process((job, done) => this.endedPollNotificationProcessorService.process(job, done));
|
|
||||||
this.queueService.webhookDeliverQueue.process(64, (job) => this.webhookDeliverProcessorService.process(job));
|
|
||||||
this.dbQueueProcessorsService.start(this.queueService.dbQueue);
|
|
||||||
this.relationshipQueueProcessorsService.start(this.queueService.relationshipQueue);
|
|
||||||
this.objectStorageQueueProcessorsService.start(this.queueService.objectStorageQueue);
|
|
||||||
|
|
||||||
this.queueService.systemQueue.add('tickCharts', {
|
this.queueService.systemQueue.add('tickCharts', {
|
||||||
}, {
|
}, {
|
||||||
repeat: { cron: '55 * * * *' },
|
repeat: { cron: '55 * * * *' },
|
||||||
@@ -163,6 +197,46 @@ export class QueueProcessorService {
|
|||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.systemQueueProcessorsService.start(this.queueService.systemQueue);
|
this.queueService.deliverQueue.process(this.config.deliverJobConcurrency ?? 128, (job) => this.deliverProcessorService.process(job));
|
||||||
|
this.queueService.inboxQueue.process(this.config.inboxJobConcurrency ?? 16, (job) => this.inboxProcessorService.process(job));
|
||||||
|
this.queueService.endedPollNotificationQueue.process((job, done) => this.endedPollNotificationProcessorService.process(job, done));
|
||||||
|
this.queueService.webhookDeliverQueue.process(64, (job) => this.webhookDeliverProcessorService.process(job));
|
||||||
|
|
||||||
|
this.queueService.dbQueue.process('deleteDriveFiles', (job, done) => this.deleteDriveFilesProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('exportCustomEmojis', (job, done) => this.exportCustomEmojisProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('exportNotes', (job, done) => this.exportNotesProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('exportFavorites', (job, done) => this.exportFavoritesProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('exportFollowing', (job, done) => this.exportFollowingProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('exportMuting', (job, done) => this.exportMutingProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('exportBlocking', (job, done) => this.exportBlockingProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('exportUserLists', (job, done) => this.exportUserListsProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('exportAntennas', (job, done) => this.exportAntennasProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('importFollowing', (job, done) => this.importFollowingProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('importFollowingToDb', (job) => this.importFollowingProcessorService.processDb(job));
|
||||||
|
this.queueService.dbQueue.process('importMuting', (job, done) => this.importMutingProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('importBlocking', (job, done) => this.importBlockingProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('importBlockingToDb', (job) => this.importBlockingProcessorService.processDb(job));
|
||||||
|
this.queueService.dbQueue.process('importUserLists', (job, done) => this.importUserListsProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('importCustomEmojis', (job, done) => this.importCustomEmojisProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('importAntennas', (job, done) => this.importAntennasProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('deleteAccount', (job) => this.deleteAccountProcessorService.process(job));
|
||||||
|
|
||||||
|
this.queueService.objectStorageQueue.process('deleteFile', 16, (job) => this.deleteFileProcessorService.process(job));
|
||||||
|
this.queueService.objectStorageQueue.process('cleanRemoteFiles', 16, (job, done) => this.cleanRemoteFilesProcessorService.process(job, done));
|
||||||
|
|
||||||
|
{
|
||||||
|
const maxJobs = this.config.relashionshipJobConcurrency ?? 16;
|
||||||
|
this.queueService.relationshipQueue.process('follow', maxJobs, (job) => this.relationshipProcessorService.processFollow(job));
|
||||||
|
this.queueService.relationshipQueue.process('unfollow', maxJobs, (job) => this.relationshipProcessorService.processUnfollow(job));
|
||||||
|
this.queueService.relationshipQueue.process('block', maxJobs, (job) => this.relationshipProcessorService.processBlock(job));
|
||||||
|
this.queueService.relationshipQueue.process('unblock', maxJobs, (job) => this.relationshipProcessorService.processUnblock(job));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.queueService.systemQueue.process('tickCharts', (job, done) => this.tickChartsProcessorService.process(job, done));
|
||||||
|
this.queueService.systemQueue.process('resyncCharts', (job, done) => this.resyncChartsProcessorService.process(job, done));
|
||||||
|
this.queueService.systemQueue.process('cleanCharts', (job, done) => this.cleanChartsProcessorService.process(job, done));
|
||||||
|
this.queueService.systemQueue.process('aggregateRetention', (job, done) => this.aggregateRetentionProcessorService.process(job, done));
|
||||||
|
this.queueService.systemQueue.process('checkExpiredMutings', (job, done) => this.checkExpiredMutingsProcessorService.process(job, done));
|
||||||
|
this.queueService.systemQueue.process('clean', (job, done) => this.cleanProcessorService.process(job, done));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
import { RelationshipProcessorService } from './processors/RelationshipProcessorService.js';
|
|
||||||
import type Bull from 'bull';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import type { Config } from '@/config.js';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class RelationshipQueueProcessorsService {
|
|
||||||
constructor(
|
|
||||||
@Inject(DI.config)
|
|
||||||
private config: Config,
|
|
||||||
|
|
||||||
private relationshipProcessorService: RelationshipProcessorService,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public start(q: Bull.Queue): void {
|
|
||||||
const maxJobs = this.config.relashionshipJobConcurrency ?? 16;
|
|
||||||
q.process('follow', maxJobs, (job) => this.relationshipProcessorService.processFollow(job));
|
|
||||||
q.process('unfollow', maxJobs, (job) => this.relationshipProcessorService.processUnfollow(job));
|
|
||||||
q.process('block', maxJobs, (job) => this.relationshipProcessorService.processBlock(job));
|
|
||||||
q.process('unblock', maxJobs, (job) => this.relationshipProcessorService.processUnblock(job));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import type { Config } from '@/config.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
import { TickChartsProcessorService } from './processors/TickChartsProcessorService.js';
|
|
||||||
import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js';
|
|
||||||
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
|
|
||||||
import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
|
|
||||||
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
|
||||||
import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
|
|
||||||
import type Bull from 'bull';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class SystemQueueProcessorsService {
|
|
||||||
constructor(
|
|
||||||
@Inject(DI.config)
|
|
||||||
private config: Config,
|
|
||||||
|
|
||||||
private tickChartsProcessorService: TickChartsProcessorService,
|
|
||||||
private resyncChartsProcessorService: ResyncChartsProcessorService,
|
|
||||||
private cleanChartsProcessorService: CleanChartsProcessorService,
|
|
||||||
private aggregateRetentionProcessorService: AggregateRetentionProcessorService,
|
|
||||||
private checkExpiredMutingsProcessorService: CheckExpiredMutingsProcessorService,
|
|
||||||
private cleanProcessorService: CleanProcessorService,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public start(q: Bull.Queue): void {
|
|
||||||
q.process('tickCharts', (job, done) => this.tickChartsProcessorService.process(job, done));
|
|
||||||
q.process('resyncCharts', (job, done) => this.resyncChartsProcessorService.process(job, done));
|
|
||||||
q.process('cleanCharts', (job, done) => this.cleanChartsProcessorService.process(job, done));
|
|
||||||
q.process('aggregateRetention', (job, done) => this.aggregateRetentionProcessorService.process(job, done));
|
|
||||||
q.process('checkExpiredMutings', (job, done) => this.checkExpiredMutingsProcessorService.process(job, done));
|
|
||||||
q.process('clean', (job, done) => this.cleanProcessorService.process(job, done));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -107,6 +107,9 @@ export class ImportCustomEmojisProcessorService {
|
|||||||
aliases: emojiInfo.aliases,
|
aliases: emojiInfo.aliases,
|
||||||
driveFile,
|
driveFile,
|
||||||
license: emojiInfo.license,
|
license: emojiInfo.license,
|
||||||
|
isSensitive: emojiInfo.isSensitive,
|
||||||
|
localOnly: emojiInfo.localOnly,
|
||||||
|
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -585,7 +585,7 @@ export class ActivityPubServerService {
|
|||||||
name: request.params.emoji,
|
name: request.params.emoji,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (emoji == null) {
|
if (emoji == null || emoji.localOnly) {
|
||||||
reply.code(404);
|
reply.code(404);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -321,6 +321,9 @@ import * as ep___users_lists_pull from './endpoints/users/lists/pull.js';
|
|||||||
import * as ep___users_lists_push from './endpoints/users/lists/push.js';
|
import * as ep___users_lists_push from './endpoints/users/lists/push.js';
|
||||||
import * as ep___users_lists_show from './endpoints/users/lists/show.js';
|
import * as ep___users_lists_show from './endpoints/users/lists/show.js';
|
||||||
import * as ep___users_lists_update from './endpoints/users/lists/update.js';
|
import * as ep___users_lists_update from './endpoints/users/lists/update.js';
|
||||||
|
import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js';
|
||||||
|
import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js';
|
||||||
|
import * as ep___users_lists_create_from_public from './endpoints/users/lists/create-from-public.js';
|
||||||
import * as ep___users_notes from './endpoints/users/notes.js';
|
import * as ep___users_notes from './endpoints/users/notes.js';
|
||||||
import * as ep___users_pages from './endpoints/users/pages.js';
|
import * as ep___users_pages from './endpoints/users/pages.js';
|
||||||
import * as ep___users_reactions from './endpoints/users/reactions.js';
|
import * as ep___users_reactions from './endpoints/users/reactions.js';
|
||||||
@@ -659,6 +662,9 @@ const $users_lists_pull: Provider = { provide: 'ep:users/lists/pull', useClass:
|
|||||||
const $users_lists_push: Provider = { provide: 'ep:users/lists/push', useClass: ep___users_lists_push.default };
|
const $users_lists_push: Provider = { provide: 'ep:users/lists/push', useClass: ep___users_lists_push.default };
|
||||||
const $users_lists_show: Provider = { provide: 'ep:users/lists/show', useClass: ep___users_lists_show.default };
|
const $users_lists_show: Provider = { provide: 'ep:users/lists/show', useClass: ep___users_lists_show.default };
|
||||||
const $users_lists_update: Provider = { provide: 'ep:users/lists/update', useClass: ep___users_lists_update.default };
|
const $users_lists_update: Provider = { provide: 'ep:users/lists/update', useClass: ep___users_lists_update.default };
|
||||||
|
const $users_lists_favorite: Provider = { provide: 'ep:users/lists/favorite', useClass: ep___users_lists_favorite.default };
|
||||||
|
const $users_lists_unfavorite: Provider = { provide: 'ep:users/lists/unfavorite', useClass: ep___users_lists_unfavorite.default };
|
||||||
|
const $users_lists_create_from_public: Provider = { provide: 'ep:users/lists/create-from-public', useClass: ep___users_lists_create_from_public.default };
|
||||||
const $users_notes: Provider = { provide: 'ep:users/notes', useClass: ep___users_notes.default };
|
const $users_notes: Provider = { provide: 'ep:users/notes', useClass: ep___users_notes.default };
|
||||||
const $users_pages: Provider = { provide: 'ep:users/pages', useClass: ep___users_pages.default };
|
const $users_pages: Provider = { provide: 'ep:users/pages', useClass: ep___users_pages.default };
|
||||||
const $users_reactions: Provider = { provide: 'ep:users/reactions', useClass: ep___users_reactions.default };
|
const $users_reactions: Provider = { provide: 'ep:users/reactions', useClass: ep___users_reactions.default };
|
||||||
@@ -1001,6 +1007,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
|||||||
$users_lists_push,
|
$users_lists_push,
|
||||||
$users_lists_show,
|
$users_lists_show,
|
||||||
$users_lists_update,
|
$users_lists_update,
|
||||||
|
$users_lists_favorite,
|
||||||
|
$users_lists_unfavorite,
|
||||||
|
$users_lists_create_from_public,
|
||||||
$users_notes,
|
$users_notes,
|
||||||
$users_pages,
|
$users_pages,
|
||||||
$users_reactions,
|
$users_reactions,
|
||||||
@@ -1335,6 +1344,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
|||||||
$users_lists_push,
|
$users_lists_push,
|
||||||
$users_lists_show,
|
$users_lists_show,
|
||||||
$users_lists_update,
|
$users_lists_update,
|
||||||
|
$users_lists_favorite,
|
||||||
|
$users_lists_unfavorite,
|
||||||
|
$users_lists_create_from_public,
|
||||||
$users_notes,
|
$users_notes,
|
||||||
$users_pages,
|
$users_pages,
|
||||||
$users_reactions,
|
$users_reactions,
|
||||||
|
|||||||
@@ -320,6 +320,9 @@ import * as ep___users_lists_list from './endpoints/users/lists/list.js';
|
|||||||
import * as ep___users_lists_pull from './endpoints/users/lists/pull.js';
|
import * as ep___users_lists_pull from './endpoints/users/lists/pull.js';
|
||||||
import * as ep___users_lists_push from './endpoints/users/lists/push.js';
|
import * as ep___users_lists_push from './endpoints/users/lists/push.js';
|
||||||
import * as ep___users_lists_show from './endpoints/users/lists/show.js';
|
import * as ep___users_lists_show from './endpoints/users/lists/show.js';
|
||||||
|
import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js';
|
||||||
|
import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js';
|
||||||
|
import * as ep___users_lists_create_from_public from './endpoints/users/lists/create-from-public.js';
|
||||||
import * as ep___users_lists_update from './endpoints/users/lists/update.js';
|
import * as ep___users_lists_update from './endpoints/users/lists/update.js';
|
||||||
import * as ep___users_notes from './endpoints/users/notes.js';
|
import * as ep___users_notes from './endpoints/users/notes.js';
|
||||||
import * as ep___users_pages from './endpoints/users/pages.js';
|
import * as ep___users_pages from './endpoints/users/pages.js';
|
||||||
@@ -656,7 +659,10 @@ const eps = [
|
|||||||
['users/lists/pull', ep___users_lists_pull],
|
['users/lists/pull', ep___users_lists_pull],
|
||||||
['users/lists/push', ep___users_lists_push],
|
['users/lists/push', ep___users_lists_push],
|
||||||
['users/lists/show', ep___users_lists_show],
|
['users/lists/show', ep___users_lists_show],
|
||||||
|
['users/lists/favorite', ep___users_lists_favorite],
|
||||||
|
['users/lists/unfavorite', ep___users_lists_unfavorite],
|
||||||
['users/lists/update', ep___users_lists_update],
|
['users/lists/update', ep___users_lists_update],
|
||||||
|
['users/lists/create-from-public', ep___users_lists_create_from_public],
|
||||||
['users/notes', ep___users_notes],
|
['users/notes', ep___users_notes],
|
||||||
['users/pages', ep___users_pages],
|
['users/pages', ep___users_pages],
|
||||||
['users/reactions', ep___users_reactions],
|
['users/reactions', ep___users_reactions],
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const paramDef = {
|
|||||||
id: { type: 'string', format: 'misskey:id' },
|
id: { type: 'string', format: 'misskey:id' },
|
||||||
title: { type: 'string', minLength: 1 },
|
title: { type: 'string', minLength: 1 },
|
||||||
text: { type: 'string', minLength: 1 },
|
text: { type: 'string', minLength: 1 },
|
||||||
imageUrl: { type: 'string', nullable: true, minLength: 1 },
|
imageUrl: { type: 'string', nullable: true, minLength: 0 },
|
||||||
},
|
},
|
||||||
required: ['id', 'title', 'text', 'imageUrl'],
|
required: ['id', 'title', 'text', 'imageUrl'],
|
||||||
} as const;
|
} as const;
|
||||||
@@ -46,7 +46,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
title: ps.title,
|
title: ps.title,
|
||||||
text: ps.text,
|
text: ps.text,
|
||||||
imageUrl: ps.imageUrl,
|
/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 空の文字列の場合、nullを渡すようにするため */
|
||||||
|
imageUrl: ps.imageUrl || null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,9 +25,24 @@ export const meta = {
|
|||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
|
name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
|
||||||
fileId: { type: 'string', format: 'misskey:id' },
|
fileId: { type: 'string', format: 'misskey:id' },
|
||||||
|
category: {
|
||||||
|
type: 'string',
|
||||||
|
nullable: true,
|
||||||
|
description: 'Use `null` to reset the category.',
|
||||||
|
},
|
||||||
|
aliases: { type: 'array', items: {
|
||||||
|
type: 'string',
|
||||||
|
} },
|
||||||
|
license: { type: 'string', nullable: true },
|
||||||
|
isSensitive: { type: 'boolean' },
|
||||||
|
localOnly: { type: 'boolean' },
|
||||||
|
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
|
||||||
|
type: 'string',
|
||||||
|
} },
|
||||||
},
|
},
|
||||||
required: ['fileId'],
|
required: ['name', 'fileId'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// TODO: ロジックをサービスに切り出す
|
// TODO: ロジックをサービスに切り出す
|
||||||
@@ -45,18 +60,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
const driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||||
|
|
||||||
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
|
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
|
||||||
|
|
||||||
const name = driveFile.name.split('.')[0].match(/^[a-z0-9_]+$/) ? driveFile.name.split('.')[0] : `_${rndstr('a-z0-9', 8)}_`;
|
|
||||||
|
|
||||||
const emoji = await this.customEmojiService.add({
|
const emoji = await this.customEmojiService.add({
|
||||||
driveFile,
|
driveFile,
|
||||||
name,
|
name: ps.name,
|
||||||
category: null,
|
category: ps.category ?? null,
|
||||||
aliases: [],
|
aliases: ps.aliases ?? [],
|
||||||
host: null,
|
host: null,
|
||||||
license: null,
|
license: ps.license ?? null,
|
||||||
|
isSensitive: ps.isSensitive ?? false,
|
||||||
|
localOnly: ps.localOnly ?? false,
|
||||||
|
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.moderationLogService.insertModerationLog(me, 'addEmoji', {
|
this.moderationLogService.insertModerationLog(me, 'addEmoji', {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||||
|
import type { DriveFilesRepository } from '@/models/index.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
import { ApiError } from '../../../error.js';
|
import { ApiError } from '../../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
@@ -15,6 +17,11 @@ export const meta = {
|
|||||||
code: 'NO_SUCH_EMOJI',
|
code: 'NO_SUCH_EMOJI',
|
||||||
id: '684dec9d-a8c2-4364-9aa8-456c49cb1dc8',
|
id: '684dec9d-a8c2-4364-9aa8-456c49cb1dc8',
|
||||||
},
|
},
|
||||||
|
noSuchFile: {
|
||||||
|
message: 'No such file.',
|
||||||
|
code: 'NO_SUCH_FILE',
|
||||||
|
id: '14fb9fd9-0731-4e2f-aeb9-f09e4740333d',
|
||||||
|
},
|
||||||
sameNameEmojiExists: {
|
sameNameEmojiExists: {
|
||||||
message: 'Emoji that have same name already exists.',
|
message: 'Emoji that have same name already exists.',
|
||||||
code: 'SAME_NAME_EMOJI_EXISTS',
|
code: 'SAME_NAME_EMOJI_EXISTS',
|
||||||
@@ -28,6 +35,7 @@ export const paramDef = {
|
|||||||
properties: {
|
properties: {
|
||||||
id: { type: 'string', format: 'misskey:id' },
|
id: { type: 'string', format: 'misskey:id' },
|
||||||
name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
|
name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
|
||||||
|
fileId: { type: 'string', format: 'misskey:id' },
|
||||||
category: {
|
category: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
nullable: true,
|
nullable: true,
|
||||||
@@ -37,6 +45,11 @@ export const paramDef = {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
} },
|
} },
|
||||||
license: { type: 'string', nullable: true },
|
license: { type: 'string', nullable: true },
|
||||||
|
isSensitive: { type: 'boolean' },
|
||||||
|
localOnly: { type: 'boolean' },
|
||||||
|
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
|
||||||
|
type: 'string',
|
||||||
|
} },
|
||||||
},
|
},
|
||||||
required: ['id', 'name', 'aliases'],
|
required: ['id', 'name', 'aliases'],
|
||||||
} as const;
|
} as const;
|
||||||
@@ -45,14 +58,28 @@ export const paramDef = {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.driveFilesRepository)
|
||||||
|
private driveFilesRepository: DriveFilesRepository,
|
||||||
|
|
||||||
private customEmojiService: CustomEmojiService,
|
private customEmojiService: CustomEmojiService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
let driveFile;
|
||||||
|
|
||||||
|
if (ps.fileId) {
|
||||||
|
driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||||
|
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
|
||||||
|
}
|
||||||
|
|
||||||
await this.customEmojiService.update(ps.id, {
|
await this.customEmojiService.update(ps.id, {
|
||||||
|
driveFile,
|
||||||
name: ps.name,
|
name: ps.name,
|
||||||
category: ps.category ?? null,
|
category: ps.category ?? null,
|
||||||
aliases: ps.aliases,
|
aliases: ps.aliases,
|
||||||
license: ps.license ?? null,
|
license: ps.license ?? null,
|
||||||
|
isSensitive: ps.isSensitive,
|
||||||
|
localOnly: ps.localOnly,
|
||||||
|
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||||||
emailVerified: profile.emailVerified,
|
emailVerified: profile.emailVerified,
|
||||||
autoAcceptFollowed: profile.autoAcceptFollowed,
|
autoAcceptFollowed: profile.autoAcceptFollowed,
|
||||||
noCrawle: profile.noCrawle,
|
noCrawle: profile.noCrawle,
|
||||||
|
preventAiLearning: profile.preventAiLearning,
|
||||||
alwaysMarkNsfw: profile.alwaysMarkNsfw,
|
alwaysMarkNsfw: profile.alwaysMarkNsfw,
|
||||||
autoSensitive: profile.autoSensitive,
|
autoSensitive: profile.autoSensitive,
|
||||||
carefulBot: profile.carefulBot,
|
carefulBot: profile.carefulBot,
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export const meta = {
|
|||||||
message: 'This feature is restricted by your role.',
|
message: 'This feature is restricted by your role.',
|
||||||
code: 'RESTRICTED_BY_ROLE',
|
code: 'RESTRICTED_BY_ROLE',
|
||||||
id: '8feff0ba-5ab5-585b-31f4-4df816663fad',
|
id: '8feff0ba-5ab5-585b-31f4-4df816663fad',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
@@ -138,9 +138,9 @@ export const paramDef = {
|
|||||||
carefulBot: { type: 'boolean' },
|
carefulBot: { type: 'boolean' },
|
||||||
autoAcceptFollowed: { type: 'boolean' },
|
autoAcceptFollowed: { type: 'boolean' },
|
||||||
noCrawle: { type: 'boolean' },
|
noCrawle: { type: 'boolean' },
|
||||||
|
preventAiLearning: { type: 'boolean' },
|
||||||
isBot: { type: 'boolean' },
|
isBot: { type: 'boolean' },
|
||||||
isCat: { type: 'boolean' },
|
isCat: { type: 'boolean' },
|
||||||
showTimelineReplies: { type: 'boolean' },
|
|
||||||
injectFeaturedNote: { type: 'boolean' },
|
injectFeaturedNote: { type: 'boolean' },
|
||||||
receiveAnnouncementEmail: { type: 'boolean' },
|
receiveAnnouncementEmail: { type: 'boolean' },
|
||||||
alwaysMarkNsfw: { type: 'boolean' },
|
alwaysMarkNsfw: { type: 'boolean' },
|
||||||
@@ -238,10 +238,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||||||
if (typeof ps.hideOnlineStatus === 'boolean') updates.hideOnlineStatus = ps.hideOnlineStatus;
|
if (typeof ps.hideOnlineStatus === 'boolean') updates.hideOnlineStatus = ps.hideOnlineStatus;
|
||||||
if (typeof ps.publicReactions === 'boolean') profileUpdates.publicReactions = ps.publicReactions;
|
if (typeof ps.publicReactions === 'boolean') profileUpdates.publicReactions = ps.publicReactions;
|
||||||
if (typeof ps.isBot === 'boolean') updates.isBot = ps.isBot;
|
if (typeof ps.isBot === 'boolean') updates.isBot = ps.isBot;
|
||||||
if (typeof ps.showTimelineReplies === 'boolean') updates.showTimelineReplies = ps.showTimelineReplies;
|
|
||||||
if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot;
|
if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot;
|
||||||
if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
|
if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
|
||||||
if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle;
|
if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle;
|
||||||
|
if (typeof ps.preventAiLearning === 'boolean') profileUpdates.preventAiLearning = ps.preventAiLearning;
|
||||||
if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat;
|
if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat;
|
||||||
if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
|
if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
|
||||||
if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
|
if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export const paramDef = {
|
|||||||
} },
|
} },
|
||||||
cw: { type: 'string', nullable: true, maxLength: 100 },
|
cw: { type: 'string', nullable: true, maxLength: 100 },
|
||||||
localOnly: { type: 'boolean', default: false },
|
localOnly: { type: 'boolean', default: false },
|
||||||
reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote'], default: null },
|
reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null },
|
||||||
noExtractMentions: { type: 'boolean', default: false },
|
noExtractMentions: { type: 'boolean', default: false },
|
||||||
noExtractHashtags: { type: 'boolean', default: false },
|
noExtractHashtags: { type: 'boolean', default: false },
|
||||||
noExtractEmojis: { type: 'boolean', default: false },
|
noExtractEmojis: { type: 'boolean', default: false },
|
||||||
|
|||||||
@@ -34,11 +34,8 @@ export const meta = {
|
|||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
withFiles: {
|
withFiles: { type: 'boolean', default: false },
|
||||||
type: 'boolean',
|
withReplies: { type: 'boolean', default: false },
|
||||||
default: false,
|
|
||||||
description: 'Only show notes that have attached files.',
|
|
||||||
},
|
|
||||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||||
sinceId: { type: 'string', format: 'misskey:id' },
|
sinceId: { type: 'string', format: 'misskey:id' },
|
||||||
untilId: { type: 'string', format: 'misskey:id' },
|
untilId: { type: 'string', format: 'misskey:id' },
|
||||||
@@ -78,7 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||||||
.leftJoinAndSelect('reply.user', 'replyUser')
|
.leftJoinAndSelect('reply.user', 'replyUser')
|
||||||
.leftJoinAndSelect('renote.user', 'renoteUser');
|
.leftJoinAndSelect('renote.user', 'renoteUser');
|
||||||
|
|
||||||
this.queryService.generateRepliesQuery(query, me);
|
this.queryService.generateRepliesQuery(query, ps.withReplies, me);
|
||||||
if (me) {
|
if (me) {
|
||||||
this.queryService.generateMutedUserQuery(query, me);
|
this.queryService.generateMutedUserQuery(query, me);
|
||||||
this.queryService.generateMutedNoteQuery(query, me);
|
this.queryService.generateMutedNoteQuery(query, me);
|
||||||
|
|||||||
@@ -46,11 +46,8 @@ export const paramDef = {
|
|||||||
includeMyRenotes: { type: 'boolean', default: true },
|
includeMyRenotes: { type: 'boolean', default: true },
|
||||||
includeRenotedMyNotes: { type: 'boolean', default: true },
|
includeRenotedMyNotes: { type: 'boolean', default: true },
|
||||||
includeLocalRenotes: { type: 'boolean', default: true },
|
includeLocalRenotes: { type: 'boolean', default: true },
|
||||||
withFiles: {
|
withFiles: { type: 'boolean', default: false },
|
||||||
type: 'boolean',
|
withReplies: { type: 'boolean', default: false },
|
||||||
default: false,
|
|
||||||
description: 'Only show notes that have attached files.',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
required: [],
|
required: [],
|
||||||
} as const;
|
} as const;
|
||||||
@@ -98,7 +95,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||||||
.setParameters(followingQuery.getParameters());
|
.setParameters(followingQuery.getParameters());
|
||||||
|
|
||||||
this.queryService.generateChannelQuery(query, me);
|
this.queryService.generateChannelQuery(query, me);
|
||||||
this.queryService.generateRepliesQuery(query, me);
|
this.queryService.generateRepliesQuery(query, ps.withReplies, me);
|
||||||
this.queryService.generateVisibilityQuery(query, me);
|
this.queryService.generateVisibilityQuery(query, me);
|
||||||
this.queryService.generateMutedUserQuery(query, me);
|
this.queryService.generateMutedUserQuery(query, me);
|
||||||
this.queryService.generateMutedNoteQuery(query, me);
|
this.queryService.generateMutedNoteQuery(query, me);
|
||||||
|
|||||||
@@ -36,11 +36,8 @@ export const meta = {
|
|||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
withFiles: {
|
withFiles: { type: 'boolean', default: false },
|
||||||
type: 'boolean',
|
withReplies: { type: 'boolean', default: false },
|
||||||
default: false,
|
|
||||||
description: 'Only show notes that have attached files.',
|
|
||||||
},
|
|
||||||
fileType: { type: 'array', items: {
|
fileType: { type: 'array', items: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
} },
|
} },
|
||||||
@@ -86,7 +83,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||||||
.leftJoinAndSelect('renote.user', 'renoteUser');
|
.leftJoinAndSelect('renote.user', 'renoteUser');
|
||||||
|
|
||||||
this.queryService.generateChannelQuery(query, me);
|
this.queryService.generateChannelQuery(query, me);
|
||||||
this.queryService.generateRepliesQuery(query, me);
|
this.queryService.generateRepliesQuery(query, ps.withReplies, me);
|
||||||
this.queryService.generateVisibilityQuery(query, me);
|
this.queryService.generateVisibilityQuery(query, me);
|
||||||
if (me) this.queryService.generateMutedUserQuery(query, me);
|
if (me) this.queryService.generateMutedUserQuery(query, me);
|
||||||
if (me) this.queryService.generateMutedNoteQuery(query, me);
|
if (me) this.queryService.generateMutedNoteQuery(query, me);
|
||||||
|
|||||||
@@ -35,11 +35,8 @@ export const paramDef = {
|
|||||||
includeMyRenotes: { type: 'boolean', default: true },
|
includeMyRenotes: { type: 'boolean', default: true },
|
||||||
includeRenotedMyNotes: { type: 'boolean', default: true },
|
includeRenotedMyNotes: { type: 'boolean', default: true },
|
||||||
includeLocalRenotes: { type: 'boolean', default: true },
|
includeLocalRenotes: { type: 'boolean', default: true },
|
||||||
withFiles: {
|
withFiles: { type: 'boolean', default: false },
|
||||||
type: 'boolean',
|
withReplies: { type: 'boolean', default: false },
|
||||||
default: false,
|
|
||||||
description: 'Only show notes that have attached files.',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
required: [],
|
required: [],
|
||||||
} as const;
|
} as const;
|
||||||
@@ -84,7 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.queryService.generateChannelQuery(query, me);
|
this.queryService.generateChannelQuery(query, me);
|
||||||
this.queryService.generateRepliesQuery(query, me);
|
this.queryService.generateRepliesQuery(query, ps.withReplies, me);
|
||||||
this.queryService.generateVisibilityQuery(query, me);
|
this.queryService.generateVisibilityQuery(query, me);
|
||||||
this.queryService.generateMutedUserQuery(query, me);
|
this.queryService.generateMutedUserQuery(query, me);
|
||||||
this.queryService.generateMutedNoteQuery(query, me);
|
this.queryService.generateMutedNoteQuery(query, me);
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||||||
|
|
||||||
const query = this.notesRepository.createQueryBuilder('note')
|
const query = this.notesRepository.createQueryBuilder('note')
|
||||||
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
|
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
|
||||||
|
.andWhere('(note.visibility = \'public\')')
|
||||||
.innerJoinAndSelect('note.user', 'user')
|
.innerJoinAndSelect('note.user', 'user')
|
||||||
.leftJoinAndSelect('note.reply', 'reply')
|
.leftJoinAndSelect('note.reply', 'reply')
|
||||||
.leftJoinAndSelect('note.renote', 'renote')
|
.leftJoinAndSelect('note.renote', 'renote')
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/index.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import type { UserList } from '@/models/entities/UserList.js';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { GetterService } from '@/server/api/GetterService.js';
|
||||||
|
import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { ApiError } from '@/server/api/error.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { UserListService } from '@/core/UserListService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
requireCredential: true,
|
||||||
|
prohibitMoved: true,
|
||||||
|
res: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
ref: 'UserList',
|
||||||
|
},
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
tooManyUserLists: {
|
||||||
|
message: 'You cannot create user list any more.',
|
||||||
|
code: 'TOO_MANY_USERLISTS',
|
||||||
|
id: 'e9c105b2-c595-47de-97fb-7f7c2c33e92f',
|
||||||
|
},
|
||||||
|
noSuchList: {
|
||||||
|
message: 'No such list.',
|
||||||
|
code: 'NO_SUCH_LIST',
|
||||||
|
id: '9292f798-6175-4f7d-93f4-b6742279667d',
|
||||||
|
},
|
||||||
|
noSuchUser: {
|
||||||
|
message: 'No such user.',
|
||||||
|
code: 'NO_SUCH_USER',
|
||||||
|
id: '13c457db-a8cb-4d88-b70a-211ceeeabb5f',
|
||||||
|
},
|
||||||
|
|
||||||
|
alreadyAdded: {
|
||||||
|
message: 'That user has already been added to that list.',
|
||||||
|
code: 'ALREADY_ADDED',
|
||||||
|
id: 'c3ad6fdb-692b-47ee-a455-7bd12c7af615',
|
||||||
|
},
|
||||||
|
|
||||||
|
youHaveBeenBlocked: {
|
||||||
|
message: 'You cannot push this user because you have been blocked by this user.',
|
||||||
|
code: 'YOU_HAVE_BEEN_BLOCKED',
|
||||||
|
id: 'a2497f2a-2389-439c-8626-5298540530f4',
|
||||||
|
},
|
||||||
|
|
||||||
|
tooManyUsers: {
|
||||||
|
message: 'You can not push users any more.',
|
||||||
|
code: 'TOO_MANY_USERS',
|
||||||
|
id: '1845ea77-38d1-426e-8e4e-8b83b24f5bd7',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: { type: 'string', minLength: 1, maxLength: 100 },
|
||||||
|
listId: { type: 'string', format: 'misskey:id' },
|
||||||
|
},
|
||||||
|
required: ['name', 'listId'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.userListsRepository)
|
||||||
|
private userListsRepository: UserListsRepository,
|
||||||
|
|
||||||
|
@Inject(DI.userListJoiningsRepository)
|
||||||
|
private userListJoiningsRepository: UserListJoiningsRepository,
|
||||||
|
|
||||||
|
@Inject(DI.blockingsRepository)
|
||||||
|
private blockingsRepository: BlockingsRepository,
|
||||||
|
|
||||||
|
private userListService: UserListService,
|
||||||
|
private userListEntityService: UserListEntityService,
|
||||||
|
private idService: IdService,
|
||||||
|
private getterService: GetterService,
|
||||||
|
private roleService: RoleService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const list = await this.userListsRepository.findOneBy({
|
||||||
|
id: ps.listId,
|
||||||
|
isPublic: true,
|
||||||
|
});
|
||||||
|
if (list === null) throw new ApiError(meta.errors.noSuchList);
|
||||||
|
const currentCount = await this.userListsRepository.countBy({
|
||||||
|
userId: me.id,
|
||||||
|
});
|
||||||
|
if (currentCount > (await this.roleService.getUserPolicies(me.id)).userListLimit) {
|
||||||
|
throw new ApiError(meta.errors.tooManyUserLists);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userList = await this.userListsRepository.insert({
|
||||||
|
id: this.idService.genId(),
|
||||||
|
createdAt: new Date(),
|
||||||
|
userId: me.id,
|
||||||
|
name: ps.name,
|
||||||
|
} as UserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0]));
|
||||||
|
|
||||||
|
const users = (await this.userListJoiningsRepository.findBy({
|
||||||
|
userListId: ps.listId,
|
||||||
|
})).map(x => x.userId);
|
||||||
|
|
||||||
|
for (const user of users) {
|
||||||
|
const currentUser = await this.getterService.getUser(user).catch(err => {
|
||||||
|
if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentUser.id !== me.id) {
|
||||||
|
const block = await this.blockingsRepository.findOneBy({
|
||||||
|
blockerId: currentUser.id,
|
||||||
|
blockeeId: me.id,
|
||||||
|
});
|
||||||
|
if (block) {
|
||||||
|
throw new ApiError(meta.errors.youHaveBeenBlocked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const exist = await this.userListJoiningsRepository.findOneBy({
|
||||||
|
userListId: userList.id,
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exist) {
|
||||||
|
throw new ApiError(meta.errors.alreadyAdded);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.userListService.push(currentUser, userList, me);
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof UserListService.TooManyUsersError) {
|
||||||
|
throw new ApiError(meta.errors.tooManyUsers);
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return await this.userListEntityService.pack(userList);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import type { UserListFavoritesRepository, UserListsRepository } from '@/models/index.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { ApiError } from '@/server/api/error.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
requireCredential: true,
|
||||||
|
errors: {
|
||||||
|
noSuchList: {
|
||||||
|
message: 'No such user list.',
|
||||||
|
code: 'NO_SUCH_USER_LIST',
|
||||||
|
id: '7dbaf3cf-7b42-4b8f-b431-b3919e580dbe',
|
||||||
|
},
|
||||||
|
|
||||||
|
alreadyFavorited: {
|
||||||
|
message: 'The list has already been favorited.',
|
||||||
|
code: 'ALREADY_FAVORITED',
|
||||||
|
id: '6425bba0-985b-461e-af1b-518070e72081',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
listId: { type: 'string', format: 'misskey:id' },
|
||||||
|
},
|
||||||
|
required: ['listId'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable() // eslint-disable-next-line import/no-default-export
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
|
constructor (
|
||||||
|
@Inject(DI.userListsRepository)
|
||||||
|
private userListsRepository: UserListsRepository,
|
||||||
|
|
||||||
|
@Inject(DI.userListFavoritesRepository)
|
||||||
|
private userListFavoritesRepository: UserListFavoritesRepository,
|
||||||
|
private idService: IdService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const userList = await this.userListsRepository.findOneBy({
|
||||||
|
id: ps.listId,
|
||||||
|
isPublic: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (userList === null) {
|
||||||
|
throw new ApiError(meta.errors.noSuchList);
|
||||||
|
}
|
||||||
|
|
||||||
|
const exist = await this.userListFavoritesRepository.findOneBy({
|
||||||
|
userId: me.id,
|
||||||
|
userListId: ps.listId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exist !== null) {
|
||||||
|
throw new ApiError(meta.errors.alreadyFavorited);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.userListFavoritesRepository.insert({
|
||||||
|
id: this.idService.genId(),
|
||||||
|
createdAt: new Date(),
|
||||||
|
userId: me.id,
|
||||||
|
userListId: ps.listId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { UserListsRepository } from '@/models/index.js';
|
import type { UserListsRepository, UsersRepository } from '@/models/index.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
|
import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
|
||||||
|
import { ApiError } from '@/server/api/error.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['lists', 'account'],
|
tags: ['lists', 'account'],
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: false,
|
||||||
|
|
||||||
kind: 'read:account',
|
kind: 'read:account',
|
||||||
|
|
||||||
@@ -22,26 +23,58 @@ export const meta = {
|
|||||||
ref: 'UserList',
|
ref: 'UserList',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
errors: {
|
||||||
|
noSuchUser: {
|
||||||
|
message: 'No such user.',
|
||||||
|
code: 'NO_SUCH_USER',
|
||||||
|
id: 'a8af4a82-0980-4cc4-a6af-8b0ffd54465e',
|
||||||
|
},
|
||||||
|
remoteUser: {
|
||||||
|
message: 'Not allowed to load the remote user\'s list',
|
||||||
|
code: 'REMOTE_USER_NOT_ALLOWED',
|
||||||
|
id: '53858f1b-3315-4a01-81b7-db9b48d4b79a',
|
||||||
|
},
|
||||||
|
invalidParam: {
|
||||||
|
message: 'Invalid param.',
|
||||||
|
code: 'INVALID_PARAM',
|
||||||
|
id: 'ab36de0e-29e9-48cb-9732-d82f1281620d',
|
||||||
|
},
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {},
|
properties: {
|
||||||
|
userId: { type: 'string', format: 'misskey:id' },
|
||||||
|
},
|
||||||
required: [],
|
required: [],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
@Injectable() // eslint-disable-next-line import/no-default-export
|
||||||
@Injectable()
|
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.usersRepository)
|
||||||
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
@Inject(DI.userListsRepository)
|
@Inject(DI.userListsRepository)
|
||||||
private userListsRepository: UserListsRepository,
|
private userListsRepository: UserListsRepository,
|
||||||
|
|
||||||
private userListEntityService: UserListEntityService,
|
private userListEntityService: UserListEntityService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const userLists = await this.userListsRepository.findBy({
|
if (typeof ps.userId !== 'undefined') {
|
||||||
|
const user = await this.usersRepository.findOneBy({ id: ps.userId });
|
||||||
|
if (user === null) throw new ApiError(meta.errors.noSuchUser);
|
||||||
|
if (user.host !== null) throw new ApiError(meta.errors.remoteUser);
|
||||||
|
} else if (me === null) {
|
||||||
|
throw new ApiError(meta.errors.invalidParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userLists = await this.userListsRepository.findBy(typeof ps.userId === 'undefined' && me !== null ? {
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
|
} : {
|
||||||
|
userId: ps.userId,
|
||||||
|
isPublic: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return await Promise.all(userLists.map(x => this.userListEntityService.pack(x)));
|
return await Promise.all(userLists.map(x => this.userListEntityService.pack(x)));
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { UserListsRepository } from '@/models/index.js';
|
import type { UserListsRepository, UserListFavoritesRepository } from '@/models/index.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
|
import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
@@ -8,7 +8,7 @@ import { ApiError } from '../../../error.js';
|
|||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['lists', 'account'],
|
tags: ['lists', 'account'],
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: false,
|
||||||
|
|
||||||
kind: 'read:account',
|
kind: 'read:account',
|
||||||
|
|
||||||
@@ -33,31 +33,54 @@ export const paramDef = {
|
|||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
listId: { type: 'string', format: 'misskey:id' },
|
listId: { type: 'string', format: 'misskey:id' },
|
||||||
|
forPublic: { type: 'boolean', default: false },
|
||||||
},
|
},
|
||||||
required: ['listId'],
|
required: ['listId'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
@Injectable() // eslint-disable-next-line import/no-default-export
|
||||||
@Injectable()
|
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.userListsRepository)
|
@Inject(DI.userListsRepository)
|
||||||
private userListsRepository: UserListsRepository,
|
private userListsRepository: UserListsRepository,
|
||||||
|
|
||||||
|
@Inject(DI.userListFavoritesRepository)
|
||||||
|
private userListFavoritesRepository: UserListFavoritesRepository,
|
||||||
|
|
||||||
private userListEntityService: UserListEntityService,
|
private userListEntityService: UserListEntityService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const additionalProperties: Partial<{ likedCount: number, isLiked: boolean }> = {};
|
||||||
// Fetch the list
|
// Fetch the list
|
||||||
const userList = await this.userListsRepository.findOneBy({
|
const userList = await this.userListsRepository.findOneBy(!ps.forPublic && me !== null ? {
|
||||||
id: ps.listId,
|
id: ps.listId,
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
|
} : {
|
||||||
|
id: ps.listId,
|
||||||
|
isPublic: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (userList == null) {
|
if (userList == null) {
|
||||||
throw new ApiError(meta.errors.noSuchList);
|
throw new ApiError(meta.errors.noSuchList);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.userListEntityService.pack(userList);
|
if (ps.forPublic && userList.isPublic) {
|
||||||
|
additionalProperties.likedCount = await this.userListFavoritesRepository.countBy({
|
||||||
|
userListId: ps.listId,
|
||||||
|
});
|
||||||
|
if (me !== null) {
|
||||||
|
additionalProperties.isLiked = (await this.userListFavoritesRepository.findOneBy({
|
||||||
|
userId: me.id,
|
||||||
|
userListId: ps.listId,
|
||||||
|
}) !== null);
|
||||||
|
} else {
|
||||||
|
additionalProperties.isLiked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...await this.userListEntityService.pack(userList),
|
||||||
|
...additionalProperties,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import type { UserListFavoritesRepository, UserListsRepository } from '@/models/index.js';
|
||||||
|
import { ApiError } from '@/server/api/error.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
requireCredential: true,
|
||||||
|
errors: {
|
||||||
|
noSuchList: {
|
||||||
|
message: 'No such user list.',
|
||||||
|
code: 'NO_SUCH_USER_LIST',
|
||||||
|
id: 'baedb33e-76b8-4b0c-86a8-9375c0a7b94b',
|
||||||
|
},
|
||||||
|
|
||||||
|
notFavorited: {
|
||||||
|
message: 'You have not favorited the list.',
|
||||||
|
code: 'ALREADY_FAVORITED',
|
||||||
|
id: '835c4b27-463d-4cfa-969b-a9058678d465',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
listId: { type: 'string', format: 'misskey:id' },
|
||||||
|
},
|
||||||
|
required: ['listId'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable() // eslint-disable-next-line import/no-default-export
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
|
constructor (
|
||||||
|
@Inject(DI.userListsRepository)
|
||||||
|
private userListsRepository: UserListsRepository,
|
||||||
|
|
||||||
|
@Inject(DI.userListFavoritesRepository)
|
||||||
|
private userListFavoritesRepository: UserListFavoritesRepository,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const userList = await this.userListsRepository.findOneBy({
|
||||||
|
id: ps.listId,
|
||||||
|
isPublic: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (userList === null) {
|
||||||
|
throw new ApiError(meta.errors.noSuchList);
|
||||||
|
}
|
||||||
|
|
||||||
|
const exist = await this.userListFavoritesRepository.findOneBy({
|
||||||
|
userListId: ps.listId,
|
||||||
|
userId: me.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exist === null) {
|
||||||
|
throw new ApiError(meta.errors.notFavorited);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.userListFavoritesRepository.delete({ id: exist.id });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,8 +34,9 @@ export const paramDef = {
|
|||||||
properties: {
|
properties: {
|
||||||
listId: { type: 'string', format: 'misskey:id' },
|
listId: { type: 'string', format: 'misskey:id' },
|
||||||
name: { type: 'string', minLength: 1, maxLength: 100 },
|
name: { type: 'string', minLength: 1, maxLength: 100 },
|
||||||
|
isPublic: { type: 'boolean' },
|
||||||
},
|
},
|
||||||
required: ['listId', 'name'],
|
required: ['listId'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
// eslint-disable-next-line import/no-default-export
|
||||||
@@ -48,7 +49,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||||||
private userListEntityService: UserListEntityService,
|
private userListEntityService: UserListEntityService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
// Fetch the list
|
|
||||||
const userList = await this.userListsRepository.findOneBy({
|
const userList = await this.userListsRepository.findOneBy({
|
||||||
id: ps.listId,
|
id: ps.listId,
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
@@ -60,6 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||||||
|
|
||||||
await this.userListsRepository.update(userList.id, {
|
await this.userListsRepository.update(userList.id, {
|
||||||
name: ps.name,
|
name: ps.name,
|
||||||
|
isPublic: ps.isPublic,
|
||||||
});
|
});
|
||||||
|
|
||||||
return await this.userListEntityService.pack(userList.id);
|
return await this.userListEntityService.pack(userList.id);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class GlobalTimelineChannel extends Channel {
|
|||||||
public readonly chName = 'globalTimeline';
|
public readonly chName = 'globalTimeline';
|
||||||
public static shouldShare = true;
|
public static shouldShare = true;
|
||||||
public static requireCredential = false;
|
public static requireCredential = false;
|
||||||
|
private withReplies: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
@@ -31,6 +32,8 @@ class GlobalTimelineChannel extends Channel {
|
|||||||
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
|
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
|
||||||
if (!policies.gtlAvailable) return;
|
if (!policies.gtlAvailable) return;
|
||||||
|
|
||||||
|
this.withReplies = params.withReplies as boolean;
|
||||||
|
|
||||||
// Subscribe events
|
// Subscribe events
|
||||||
this.subscriber.on('notesStream', this.onNote);
|
this.subscriber.on('notesStream', this.onNote);
|
||||||
}
|
}
|
||||||
@@ -54,7 +57,7 @@ class GlobalTimelineChannel extends Channel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 関係ない返信は除外
|
// 関係ない返信は除外
|
||||||
if (note.reply && !this.user!.showTimelineReplies) {
|
if (note.reply && !this.withReplies) {
|
||||||
const reply = note.reply;
|
const reply = note.reply;
|
||||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||||
if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
|
if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class HomeTimelineChannel extends Channel {
|
|||||||
public readonly chName = 'homeTimeline';
|
public readonly chName = 'homeTimeline';
|
||||||
public static shouldShare = true;
|
public static shouldShare = true;
|
||||||
public static requireCredential = true;
|
public static requireCredential = true;
|
||||||
|
private withReplies: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
@@ -24,6 +25,8 @@ class HomeTimelineChannel extends Channel {
|
|||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async init(params: any) {
|
public async init(params: any) {
|
||||||
|
this.withReplies = params.withReplies as boolean;
|
||||||
|
|
||||||
this.subscriber.on('notesStream', this.onNote);
|
this.subscriber.on('notesStream', this.onNote);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +66,7 @@ class HomeTimelineChannel extends Channel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 関係ない返信は除外
|
// 関係ない返信は除外
|
||||||
if (note.reply && !this.user!.showTimelineReplies) {
|
if (note.reply && !this.withReplies) {
|
||||||
const reply = note.reply;
|
const reply = note.reply;
|
||||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||||
if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
|
if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class HybridTimelineChannel extends Channel {
|
|||||||
public readonly chName = 'hybridTimeline';
|
public readonly chName = 'hybridTimeline';
|
||||||
public static shouldShare = true;
|
public static shouldShare = true;
|
||||||
public static requireCredential = true;
|
public static requireCredential = true;
|
||||||
|
private withReplies: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
@@ -31,6 +32,8 @@ class HybridTimelineChannel extends Channel {
|
|||||||
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
|
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
|
||||||
if (!policies.ltlAvailable) return;
|
if (!policies.ltlAvailable) return;
|
||||||
|
|
||||||
|
this.withReplies = params.withReplies as boolean;
|
||||||
|
|
||||||
// Subscribe events
|
// Subscribe events
|
||||||
this.subscriber.on('notesStream', this.onNote);
|
this.subscriber.on('notesStream', this.onNote);
|
||||||
}
|
}
|
||||||
@@ -75,7 +78,7 @@ class HybridTimelineChannel extends Channel {
|
|||||||
if (isInstanceMuted(note, new Set<string>(this.userProfile!.mutedInstances ?? []))) return;
|
if (isInstanceMuted(note, new Set<string>(this.userProfile!.mutedInstances ?? []))) return;
|
||||||
|
|
||||||
// 関係ない返信は除外
|
// 関係ない返信は除外
|
||||||
if (note.reply && !this.user!.showTimelineReplies) {
|
if (note.reply && !this.withReplies) {
|
||||||
const reply = note.reply;
|
const reply = note.reply;
|
||||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||||
if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
|
if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class LocalTimelineChannel extends Channel {
|
|||||||
public readonly chName = 'localTimeline';
|
public readonly chName = 'localTimeline';
|
||||||
public static shouldShare = true;
|
public static shouldShare = true;
|
||||||
public static requireCredential = false;
|
public static requireCredential = false;
|
||||||
|
private withReplies: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
@@ -30,6 +31,8 @@ class LocalTimelineChannel extends Channel {
|
|||||||
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
|
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
|
||||||
if (!policies.ltlAvailable) return;
|
if (!policies.ltlAvailable) return;
|
||||||
|
|
||||||
|
this.withReplies = params.withReplies as boolean;
|
||||||
|
|
||||||
// Subscribe events
|
// Subscribe events
|
||||||
this.subscriber.on('notesStream', this.onNote);
|
this.subscriber.on('notesStream', this.onNote);
|
||||||
}
|
}
|
||||||
@@ -54,7 +57,7 @@ class LocalTimelineChannel extends Channel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 関係ない返信は除外
|
// 関係ない返信は除外
|
||||||
if (note.reply && this.user && !this.user.showTimelineReplies) {
|
if (note.reply && this.user && !this.withReplies) {
|
||||||
const reply = note.reply;
|
const reply = note.reply;
|
||||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||||
if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
|
if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
|
||||||
|
|||||||
@@ -5,15 +5,17 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
|||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import Channel from '../channel.js';
|
import Channel from '../channel.js';
|
||||||
import { StreamMessages } from '../types.js';
|
import { StreamMessages } from '../types.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
|
||||||
class RoleTimelineChannel extends Channel {
|
class RoleTimelineChannel extends Channel {
|
||||||
public readonly chName = 'roleTimeline';
|
public readonly chName = 'roleTimeline';
|
||||||
public static shouldShare = false;
|
public static shouldShare = false;
|
||||||
public static requireCredential = false;
|
public static requireCredential = false;
|
||||||
private roleId: string;
|
private roleId: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
|
private roleservice: RoleService,
|
||||||
|
|
||||||
id: string,
|
id: string,
|
||||||
connection: Channel['connection'],
|
connection: Channel['connection'],
|
||||||
@@ -34,6 +36,11 @@ class RoleTimelineChannel extends Channel {
|
|||||||
if (data.type === 'note') {
|
if (data.type === 'note') {
|
||||||
const note = data.body;
|
const note = data.body;
|
||||||
|
|
||||||
|
if (!(await this.roleservice.isExplorable({ id: this.roleId }))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (note.visibility !== 'public') return;
|
||||||
|
|
||||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||||
if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
|
if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
|
||||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||||
@@ -61,6 +68,7 @@ export class RoleTimelineChannelService {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
|
private roleservice: RoleService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +76,7 @@ export class RoleTimelineChannelService {
|
|||||||
public create(id: string, connection: Channel['connection']): RoleTimelineChannel {
|
public create(id: string, connection: Channel['connection']): RoleTimelineChannel {
|
||||||
return new RoleTimelineChannel(
|
return new RoleTimelineChannel(
|
||||||
this.noteEntityService,
|
this.noteEntityService,
|
||||||
|
this.roleservice,
|
||||||
id,
|
id,
|
||||||
connection,
|
connection,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ export default class Connection {
|
|||||||
|
|
||||||
const ch: Channel = channelService.create(id, this);
|
const ch: Channel = channelService.create(id, this);
|
||||||
this.channels.push(ch);
|
this.channels.push(ch);
|
||||||
ch.init(params);
|
ch.init(params ?? {});
|
||||||
|
|
||||||
if (pong) {
|
if (pong) {
|
||||||
this.sendMessageToWs('connected', {
|
this.sendMessageToWs('connected', {
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ import { RoleService } from '@/core/RoleService.js';
|
|||||||
import manifest from './manifest.json' assert { type: 'json' };
|
import manifest from './manifest.json' assert { type: 'json' };
|
||||||
import { FeedService } from './FeedService.js';
|
import { FeedService } from './FeedService.js';
|
||||||
import { UrlPreviewService } from './UrlPreviewService.js';
|
import { UrlPreviewService } from './UrlPreviewService.js';
|
||||||
import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
|
|
||||||
import { ClientLoggerService } from './ClientLoggerService.js';
|
import { ClientLoggerService } from './ClientLoggerService.js';
|
||||||
|
import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
|
||||||
|
|
||||||
const _filename = fileURLToPath(import.meta.url);
|
const _filename = fileURLToPath(import.meta.url);
|
||||||
const _dirname = dirname(_filename);
|
const _dirname = dirname(_filename);
|
||||||
@@ -423,6 +423,10 @@ export class ClientServerService {
|
|||||||
: [];
|
: [];
|
||||||
|
|
||||||
reply.header('Cache-Control', 'public, max-age=15');
|
reply.header('Cache-Control', 'public, max-age=15');
|
||||||
|
if (profile.preventAiLearning) {
|
||||||
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
|
reply.header('X-Robots-Tag', 'noai');
|
||||||
|
}
|
||||||
return await reply.view('user', {
|
return await reply.view('user', {
|
||||||
user, profile, me,
|
user, profile, me,
|
||||||
avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
|
avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
|
||||||
@@ -467,6 +471,10 @@ export class ClientServerService {
|
|||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId });
|
||||||
const meta = await this.metaService.fetch();
|
const meta = await this.metaService.fetch();
|
||||||
reply.header('Cache-Control', 'public, max-age=15');
|
reply.header('Cache-Control', 'public, max-age=15');
|
||||||
|
if (profile.preventAiLearning) {
|
||||||
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
|
reply.header('X-Robots-Tag', 'noai');
|
||||||
|
}
|
||||||
return await reply.view('note', {
|
return await reply.view('note', {
|
||||||
note: _note,
|
note: _note,
|
||||||
profile,
|
profile,
|
||||||
@@ -506,6 +514,10 @@ export class ClientServerService {
|
|||||||
} else {
|
} else {
|
||||||
reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
|
reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||||
}
|
}
|
||||||
|
if (profile.preventAiLearning) {
|
||||||
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
|
reply.header('X-Robots-Tag', 'noai');
|
||||||
|
}
|
||||||
return await reply.view('page', {
|
return await reply.view('page', {
|
||||||
page: _page,
|
page: _page,
|
||||||
profile,
|
profile,
|
||||||
@@ -530,6 +542,10 @@ export class ClientServerService {
|
|||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: flash.userId });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: flash.userId });
|
||||||
const meta = await this.metaService.fetch();
|
const meta = await this.metaService.fetch();
|
||||||
reply.header('Cache-Control', 'public, max-age=15');
|
reply.header('Cache-Control', 'public, max-age=15');
|
||||||
|
if (profile.preventAiLearning) {
|
||||||
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
|
reply.header('X-Robots-Tag', 'noai');
|
||||||
|
}
|
||||||
return await reply.view('flash', {
|
return await reply.view('flash', {
|
||||||
flash: _flash,
|
flash: _flash,
|
||||||
profile,
|
profile,
|
||||||
@@ -554,6 +570,10 @@ export class ClientServerService {
|
|||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: clip.userId });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: clip.userId });
|
||||||
const meta = await this.metaService.fetch();
|
const meta = await this.metaService.fetch();
|
||||||
reply.header('Cache-Control', 'public, max-age=15');
|
reply.header('Cache-Control', 'public, max-age=15');
|
||||||
|
if (profile.preventAiLearning) {
|
||||||
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
|
reply.header('X-Robots-Tag', 'noai');
|
||||||
|
}
|
||||||
return await reply.view('clip', {
|
return await reply.view('clip', {
|
||||||
clip: _clip,
|
clip: _clip,
|
||||||
profile,
|
profile,
|
||||||
@@ -576,6 +596,10 @@ export class ClientServerService {
|
|||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: post.userId });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: post.userId });
|
||||||
const meta = await this.metaService.fetch();
|
const meta = await this.metaService.fetch();
|
||||||
reply.header('Cache-Control', 'public, max-age=15');
|
reply.header('Cache-Control', 'public, max-age=15');
|
||||||
|
if (profile.preventAiLearning) {
|
||||||
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
|
reply.header('X-Robots-Tag', 'noai');
|
||||||
|
}
|
||||||
return await reply.view('gallery-post', {
|
return await reply.view('gallery-post', {
|
||||||
post: _post,
|
post: _post,
|
||||||
profile,
|
profile,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user