Compare commits

...

62 commits

Author SHA1 Message Date
Amelia Yukii
a149211bb4 merge: Another batch of UI tweaks (!393)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/393

Approved-by: Amelia Yukii <amelia.yukii@shourai.de>
Approved-by: Marie <marie@kaifa.ch>
Approved-by: dakkar <dakkar@thenautilus.net>
2024-02-04 12:31:02 +00:00
ShittyKopper
592f77371f upd: rename SkNoteSub property 2024-02-04 15:21:57 +03:00
ShittyKopper
3107345d3c fix: local instance ticker bug that fails to render notes in dev mode 2024-02-04 15:21:57 +03:00
ShittyKopper
5ef8e3123b upd: only enable hover effect on SkNoteDetailed and replies 2024-02-04 15:21:57 +03:00
ShittyKopper
c61adcf61d upd: expandAllCws also expands all long posts on first click
this mimics glitch-soc behavior
2024-02-04 15:21:57 +03:00
ShittyKopper
bffac25c57 upd: smoothly resize reply avatars depending on container size 2024-02-04 15:21:57 +03:00
ShittyKopper
7461332d0f upd: add hover effect to quote posts 2024-02-04 15:21:57 +03:00
ShittyKopper
3818e9531f upd: make note footer wider on smaller screens
3cc8320eb7

Co-authored-by: blueb <ihateblueb@proton.me>
2024-02-04 15:21:57 +03:00
ShittyKopper
1bc2da2bda upd: make avatars smaller on replies to allow more space for text
i'm unsure about the batch of @container blocks at the end of the CSS,
they seem like leftovers from a previous implementation. removing them
didn't seem to change anything but i may be overlooking something.
2024-02-04 15:21:57 +03:00
ShittyKopper
1baec59efd fix: missing icon in MkMediaVideo 2024-02-04 15:21:57 +03:00
ShittyKopper
44a8249404 fix: blue highlight on chromium when using click to open 2024-02-04 15:21:57 +03:00
ShittyKopper
bc99318707 fix: mentions wrapping 2024-02-04 15:21:57 +03:00
ShittyKopper
cd52686fd3 fix: click to open on quotes 2024-02-04 15:21:57 +03:00
ShittyKopper
02c9c42075 fix: links triggering clickToOpen 2024-02-04 15:21:57 +03:00
blueb
fae3af4342 fix: adjust alignment and margin of icon in detailed note tabs 2024-02-04 15:21:57 +03:00
ShittyKopper
f40c3a2d9f fix: duplicate root dom element in dev mode 2024-02-04 12:51:14 +03:00
ShittyKopper
a4439ed803 fix: bring back the ability to hover on videos to show alt text 2024-02-04 12:51:14 +03:00
ShittyKopper
8a55d8a468 upd: add a download button to videos and audio
this only works for media from the same origin due to annoying browser
restrictions, but then the same applies to every other download button
in misskey (e.g. the one in drive) and there's basically nothing i can
to do solve it.
2024-02-04 12:51:14 +03:00
Marie
d19812eba9 merge: Catch up to upstream (!402)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/402

Approved-by: Amelia Yukii <amelia.yukii@shourai.de>
Approved-by: dakkar <dakkar@thenautilus.net>
2024-02-04 09:01:54 +00:00
Marie
6b068d2311 upd: add back focus 2024-02-03 21:17:46 +00:00
dakkar
dff7b98ebc add types like upstream did in MkNoteDetailed 2024-02-03 21:14:00 +00:00
Marie
23e9067f57
fix: accidental replacing of noteEl to rootEl 2024-02-03 21:37:51 +01:00
Marie
bd0186296c
fix: scrolling to note 2024-02-03 21:35:55 +01:00
Marie
2a50e6e9e7
upd: apply changes 2024-02-03 21:29:46 +01:00
Marie
e7b86090e1
fix: relashionship to relationship in docker exmaple 2024-02-03 20:22:02 +01:00
Marie
11628e4b6a
merge: upstream 2024-02-03 20:19:44 +01:00
syuilo
66714d94fc
2024.2.0-beta.9 2024-02-02 21:38:15 +09:00
syuilo
580cec33a9
New Crowdin updates (#13090)
* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Korean (Gyeongsang))

* New translations ja-jp.yml (Chinese Simplified)

* New translations ja-jp.yml (Chinese Traditional)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (French)
2024-02-02 21:37:21 +09:00
syuilo
6c67b2e40e fix(frontend): チャートのlegendがクリックに反応しない問題を修正
これにより発生 https://github.com/misskey-dev/misskey/pull/12926
2024-02-02 18:15:25 +09:00
かっこかり
430290c084
fix(frontend): selectUserのパラメータを調整 (#13142)
* fix(frontend): selectUserのパラメータを調整

* ついでに軽微なスタイルの修正
2024-02-02 16:49:09 +09:00
かっこかり
c38f5ee528
fix(frontend): アバターデコレーションのアニメーションが止まらない (#13139)
* fix(frontend): アバターデコレーションのアニメーションが止まらない

* Update Changelog

* i -> index

* key

* revert lint fixes
2024-02-02 15:19:23 +09:00
かっこかり
9e1145df81
enhance(frontend): shiki v1に移行 (#13138)
* enhance(frontend): shiki v1に移行

* optimize chunks, エラーを握りつぶす

* wasmを分離

* バンドルサイズの警告の最小値を650kBに引き上げ

* optimize
2024-02-02 15:05:18 +09:00
zyoshoka
e5876440cb
refactor(frontend): os.popup()propsの型チェックを有効化 (#13140)
* refactor(frontend): `os.popup()`の`props`の型チェックを有効化

* refactor: `ComponentProps`に書き換え

* refacor: `import type`
2024-02-02 15:04:42 +09:00
yukineko
d8bdbd53ed
fix: api-docが開けない問題を修正 (#13132)
* refactor: 自己参照を使用している箇所に`selfRef`を持たせるように

* feat: スキーマ生成時に自己参照を含むかどうかを指定できるように

* fix: api.jsonにselfRefが含まれているのを修正

* refactor: 他の箇所と同様にselfRefの除去を行うように

* remove: 不要なimportを削除
2024-02-02 12:47:07 +09:00
Srgr0
3499814498
Update deploy-test-environment.yml (#13136) 2024-02-02 11:58:43 +09:00
おさむのひと
85809a240e
fix(backend): "誰でも新規登録できるようにする"の初期値をOFFにする (#13130)
* fix(backend): "誰でも新規登録できるようにする"の初期値をOFFにする

* fix CHANGELOG.md

* fix
2024-02-02 11:24:45 +09:00
tamaina
07dc99d197
「見たことのあるリノートを省略して表示」が効いていない問題を修正 (#13133)
* fix: 「見たことのあるリノートを省略して表示」が効いていない問題を修正
fix #13131

* add a comment
2024-02-02 11:24:20 +09:00
dependabot[bot]
30b48df9d9
chore(deps): bump peter-evans/slash-command-dispatch from 3 to 4 (#13124)
Bumps [peter-evans/slash-command-dispatch](https://github.com/peter-evans/slash-command-dispatch) from 3 to 4.
- [Release notes](https://github.com/peter-evans/slash-command-dispatch/releases)
- [Commits](https://github.com/peter-evans/slash-command-dispatch/compare/v3...v4)

---
updated-dependencies:
- dependency-name: peter-evans/slash-command-dispatch
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 21:45:44 +09:00
tamaina
b0a38c0cae
fix: Hide reactions of all remote users / feat: moderators can see reactions of all users (#13128)
* fix: Hide reactions of all remote users
https://github.com/misskey-dev/misskey/issues/12964

* feat: Moderators can see reactions of all users
https://github.com/misskey-dev/misskey/issues/13127

* modify CHANGELOG.md

* fix iAmModerator
2024-02-01 20:05:45 +09:00
dependabot[bot]
ada2c69c7d
chore(deps): bump codecov/codecov-action from 3 to 4 (#13125)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 20:04:46 +09:00
syuilo
6915fde1cf enhance(frontend): 🌸 2024-02-01 15:07:29 +09:00
1Step621
0641454c23
Fix(frontend): リバーシで自分自信を招待できるのを修正 & os.selectUser()のincludeSelfが機能していないのを修正 (#13117)
* リバーシで自分自信を招待できるのを修正 & os.selectUser()のincludeSelfが機能していないのを修正

* lint fix
2024-02-01 09:43:23 +09:00
zyoshoka
5079a4b7dd
fix(ci): misskey-js のバージョンチェックをトリガーする条件の修正 (#13116)
* fix(misskey-js): バージョンチェックのトリガー条件を修正

* chore(misskey-js): 2024.2.0-beta.8
2024-02-01 09:42:55 +09:00
おさむのひと
d5860d0685
fix(frontend): styleの指定方法を変更 (#13120) 2024-01-31 20:35:07 +09:00
zyoshoka
2db5b61616
refactor(backend): User関連のスキーマ/型の指定を強くする (#12808)
* refactor(backend): User関連のスキーマ/型の指定を強くする

* refactor(backend): `pack()`の引数にスキーマを指定するように

* chore: fix ci

* fix: 変更漏れ

* fix ci

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2024-01-31 15:45:35 +09:00
syuilo
8aea3603a6 refactor(frontend): global/router -> router 2024-01-30 21:07:34 +09:00
syuilo
eb078d67ab Revert "Revert "Create deploy-test-environment.yml (#13079)""
This reverts commit 4553d6426b.
2024-01-30 21:02:58 +09:00
syuilo
eef46ed3c9 2024.2.0-beta.8 2024-01-30 21:00:17 +09:00
syuilo
e90dea4be9 update deps 2024-01-30 20:59:44 +09:00
かっこかり
6a41afaaee
fix/refactor(reversi): 既存のバグを修正・型定義を強化 (#13105)
* 既存のバグを修正

* fix types

* fix misskey-js autogen

* Update index.d.ts

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2024-01-30 20:54:30 +09:00
yukineko
a6a91fec3a
refactor: frontendのcomponentsの型エラーを改善 (#12926)
* add: safeFloatParserを追加

* fix: 欠けていた型を追加

* refactor: pageBlockTypesをjson-schemaに移植

* refactor: components/global内の型エラーが出ている箇所を修正

* lint: fix null check style

* refactor: fix type error

* refactor: fix some type errors

* fix: 翻訳が抜けていた箇所を修正

* refactor: getJsonSchemaで正しいスキーマが返されるように修正

* fix: MkChartの型エラーとbytesオプションが機能していない問題を修正

* fix(misskey-js): `drive`->`folderUpdated`のpayloadの型が間違っていたのを修正

* refactor: fix some type errors

* change: Captcha読み込み中の文言をLoadingに変更

* refactor(backend/misskey-js): MainEventの型を改善

* refactor: chartjs-plugin-gradientが二重でpluginに登録されていたのを修正

* update: misskey-js.api.md

* refactor: fix some type errors

* fix: backendのtypecheckが落ちていたのを修正

* update: misskey-js.api.md

* add: json-schemaのnoteにpollの型定義を追加

* refactor: noteのjson-schemaの型を改善

* refactor: MkPoll

* refactor: fix some type errors

* change: UserLiteにisLockedを持たせるように

* fix: notificationスキーマにroleが含まれていないのを修正

* Revert "change: UserLiteにisLockedを持たせるように"

This reverts commit 1bb0c8e7a9b19a4e9f21bf7381712b98f27672a5.

* fix: フォロー通知から鍵垢へのフォローを行うと処理中のまま止まってしまう問題を修正

* refactor: noteスキーマのvisibilityにenumを追加

* change: deepCloneのCloneableTypeにundefinedを追加

* refactor: fix some type errors

* refactor: `allowEmpty: false`を使用していた箇所を`minLength: 1`に置き換え

* enhance: API 'retension' のresponseの型を追加

* fix: Chart関連のtooltipが正しい位置に表示されない問題を修正

* refactor: fix some type errors

* fix: 型情報が不足していたのを修正

* enhance: announcementスキーマにenumを追加

* enhance: ロールポリシーの型定義をRoleServiceからjson-schemaに移植

* refactor: policiesを`ref: RolePolicies`に統一

* fix: API `meta` のレスポンスの型にpoliciesが含まれていないのを修正

* refactor: fix some type errors

* fix: backendのlintが落ちているのを修正

* fix: MkFoldableSectionの開閉時のanimationが適用されていない問題を修正

* fix: backendのtypecheckが落ちているのを修正

* update: run build-misskey-js-with-types

* fix: MkDialogのmount時に文字数制限の判定が行われない問題を修正

* update: CHANGELOG.md

* refactor: MkUserSelectDialogの型を改善

* fix: deepCloneでundefinedはcloneしないように (#9207)

* change: frontendのcloneをbackend側にも反映

* update: CHANGELOG.md

* fix: RoleServiceからPackを通して型RolePoliciesに依存させないように

* Update packages/frontend/src/scripts/get-note-summary.ts

* revert RoleService.ts changes

* change:  optional chaining -> non-null assertion

* remove: unused import

* fix: propsで渡されたuserがUserLiteの場合に意図しない動作になってしまうのを修正

* change: fix null check style

* refactor: fix type error

* change: fix null check style

* Update packages/frontend/src/components/MkDrive.vue

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>

* refactor: css moduleでglobalを使わないように

* refactor: roleのiconUrlは必ず存在するものとして扱うように

* enhance: MenuButtonのactiveにcomputedを受け付けられるように

* Update packages/frontend/src/components/MkNotePreview.vue

* Update MkWindow.vue

* refactor: notification.noteは必ず存在するものとして扱うように

* Update packages/frontend/src/components/MkNotification.vue

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>

* fix: MkSignupDialogでdoneのemit時にresを含んでいなかったのを修正

* Update packages/frontend/src/scripts/clone.ts

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>

* refactor: 不要な返り値の型を削除

* refactor: 不要なnullチェックを削除

* update: misskey-js-autogen

* update: clone.ts

* refactor

* Update MkNotification.vue

* Update MkNotification.vue

* ✌️

* Update MkNotification.vue

* Update MkNotification.vue

* Update MkNotification.vue

* Update MkNotifications.vue

* Update MkUserSetupDialog.Profile.vue

* Update MkUserCardMini.vue

* ✌️

* Update MkMenu.vue

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2024-01-30 19:53:53 +09:00
tamaina
9ac2c36d76
iOSで大きな画像を変換してアップロードできない問題を修正 (#13109)
Fix https://github.com/misskey-dev/misskey/issues/12026
2024-01-30 15:01:24 +09:00
Kagami Sascha Rosylight
e21cecefa1
test(frontend): load default config to start vite (#12867)
Co-authored-by: おさむのひと <46447427+samunohito@users.noreply.github.com>
2024-01-29 21:39:34 +09:00
かっこかり
4535f9b41b
fix(i18n): ストック情報とフロー情報の文言をわかりやすく変更 (#13085)
* fix(i18n): ストック情報とフロー情報をわかりやすく書き直す

* Update ja-JP.yml

* Update ja-JP.yml
2024-01-29 21:33:42 +09:00
かっこかり
b62d9f3920
feat(frontend/nirax): リダイレクトを設定できるように (#13030)
* feat(frontend/nirax): リダイレクトを設定できるように

* revert demonstrative changes

* fix

* revert unrelated changes

* リダイレクトの際にパスが変わらない問題を修正

* リダイレクトが必要なrouteを設定

* fix lint

* router向けe2eテストの追加

* fix

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Co-authored-by: samunohito <46447427+samunohito@users.noreply.github.com>
2024-01-28 19:22:38 +09:00
syuilo
fe7036a1a8
Update CHANGELOG.md 2024-01-28 15:09:32 +09:00
woxtu
cdac3988b5
fix(backend): Fix typos in job configurations (#13086)
* Fix typos

* Update CHANGELOG
2024-01-28 15:08:45 +09:00
かっこかり
9753cce4aa
enhance(frontend): リモートのユーザーはメニューから直接リモートで表示できるように (#13087)
* enhance(frontend): リモートのユーザーはメニューから直接リモートで表示できるように

* change changelog

* Apply suggestions from code review

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2024-01-27 18:25:15 +09:00
かっこかり
30f4023c36
refactor(frontend/MediaPlayer): cssの重複を削除 (#13094)
* Update MkMediaAudio.vue

* Update MkMediaVideo.vue
2024-01-27 16:33:30 +09:00
かっこかり
15727088be
fix misskey-js version 2024-01-27 10:34:07 +09:00
おさむのひと
b7270c6238
fix(dev): pnpm devで依存関係更新が一部反映されない (#13091) 2024-01-27 09:18:09 +09:00
syuilo
bb330533c1
New Crowdin updates (#13082)
* New translations ja-jp.yml (Japanese, Kansai)

* New translations ja-jp.yml (Chinese Simplified)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Japanese, Kansai)
2024-01-26 14:24:38 +09:00
295 changed files with 3566 additions and 2063 deletions

View file

@ -148,14 +148,14 @@ id: 'aidx'
# Job concurrency per worker # Job concurrency per worker
# deliverJobConcurrency: 128 # deliverJobConcurrency: 128
# inboxJobConcurrency: 16 # inboxJobConcurrency: 16
# relashionshipJobConcurrency: 16 # relationshipJobConcurrency: 16
# What's relashionshipJob?: # What's relationshipJob?:
# Follow, unfollow, block and unblock(ings) while following-imports, etc. or account migrations. # Follow, unfollow, block and unblock(ings) while following-imports, etc. or account migrations.
# Job rate limiter # Job rate limiter
# deliverJobPerSec: 128 # deliverJobPerSec: 128
# inboxJobPerSec: 32 # inboxJobPerSec: 32
# relashionshipJobPerSec: 64 # relationshipJobPerSec: 64
# Job attempts # Job attempts
# deliverJobMaxAttempts: 12 # deliverJobMaxAttempts: 12

View file

@ -160,14 +160,14 @@ id: 'aidx'
# Job concurrency per worker # Job concurrency per worker
#deliverJobConcurrency: 128 #deliverJobConcurrency: 128
#inboxJobConcurrency: 16 #inboxJobConcurrency: 16
#relashionshipJobConcurrency: 16 #relationshipJobConcurrency: 16
# What's relashionshipJob?: # What's relationshipJob?:
# Follow, unfollow, block and unblock(ings) while following-imports, etc. or account migrations. # Follow, unfollow, block and unblock(ings) while following-imports, etc. or account migrations.
# Job rate limiter # Job rate limiter
#deliverJobPerSec: 128 #deliverJobPerSec: 128
#inboxJobPerSec: 32 #inboxJobPerSec: 32
#relashionshipJobPerSec: 64 #relationshipJobPerSec: 64
# Job attempts # Job attempts
#deliverJobMaxAttempts: 12 #deliverJobMaxAttempts: 12

View file

@ -21,6 +21,9 @@
- Feat: [mCaptcha](https://github.com/mCaptcha/mCaptcha)のサポートを追加 - Feat: [mCaptcha](https://github.com/mCaptcha/mCaptcha)のサポートを追加
- Fix: リストライムラインの「リノートを表示」が正しく機能しない問題を修正 - Fix: リストライムラインの「リノートを表示」が正しく機能しない問題を修正
- Feat: Add support for TrueMail - Feat: Add support for TrueMail
- Fix: リモートユーザーのリアクション一覧がすべて見えてしまうのを修正
* すべてのリモートユーザーのリアクション一覧を見えないようにします
- Enhance: モデレーターはすべてのユーザーのリアクション一覧を見られるように
### Client ### Client
- Feat: 新しいゲームを追加 - Feat: 新しいゲームを追加
@ -45,11 +48,20 @@
- Enhance: ノート作成画面のファイル添付メニューから直接ファイルを削除できるように - Enhance: ノート作成画面のファイル添付メニューから直接ファイルを削除できるように
- Enhance: MFMの属性でオートコンプリートが使用できるように #12735 - Enhance: MFMの属性でオートコンプリートが使用できるように #12735
- Enhance: 絵文字編集ダイアログをモーダルではなくウィンドウで表示するように - Enhance: 絵文字編集ダイアログをモーダルではなくウィンドウで表示するように
- Enhance: リモートのユーザーはメニューから直接リモートで表示できるように
- Fix: ネイティブモードの絵文字がモノクロにならないように - Fix: ネイティブモードの絵文字がモノクロにならないように
- Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正 - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正
- Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正 - Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正
- Fix: v2023.12.1で追加された`$[clickable ...]`および`onClickEv`が正しく機能していないのを修正 - Fix: v2023.12.1で追加された`$[clickable ...]`および`onClickEv`が正しく機能していないのを修正
- Fix: Renoteのキーボードショートカットが機能していなかった問題を修正
- Fix: 投稿フォームでアンケートの日時指定をした状態で再読み込みをすると期日が復元されない問題を修正
- Fix: アンケートを設定したノートを「削除して編集」をするとアンケートの期日が引き継がれず、リセットされてしまう問題を修正
- Fix: デッキのプロファイル作成時に名前を空にできる問題を修正
- Fix: テーマ作成時に名称が空欄でも作成できてしまう問題を修正
- Fix: プラグインで`Plugin:register_note_post_interruptor`を使用すると、ノートが投稿できなくなる問題を修正
- Enhance: ページ遷移時にPlayerを閉じるように - Enhance: ページ遷移時にPlayerを閉じるように
- Fix: iOSで大きな画像を変換してアップロードできない問題を修正
- Fix: 「アニメーション画像を再生しない」もしくは「データセーバー(アイコン)」を有効にしていても、アイコンデコレーションのアニメーションが停止されない問題を修正
### Server ### Server
- Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました - Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました
@ -62,6 +74,8 @@
- Fix: `notes/create`で、`text`が空白文字のみで構成されていてかつリノート、ファイルまたは投票を含んでいるリクエストに対するレスポンスの`text`が`""`から`null`になるように変更 - Fix: `notes/create`で、`text`が空白文字のみで構成されていてかつリノート、ファイルまたは投票を含んでいるリクエストに対するレスポンスの`text`が`""`から`null`になるように変更
- Fix: ipv4とipv6の両方が利用可能な環境でallowedPrivateNetworksが設定されていた場合プライベートipの検証ができていなかった問題を修正 - Fix: ipv4とipv6の両方が利用可能な環境でallowedPrivateNetworksが設定されていた場合プライベートipの検証ができていなかった問題を修正
- Fix: properly handle cc followers - Fix: properly handle cc followers
- Fix: ジョブに関する設定の名前を修正 relashionshipJobPerSec -> relationshipJobPerSec
- Fix: コントロールパネル->モデレーション->「誰でも新規登録できるようにする」の初期値をONからOFFに変更 #13122
### Service Worker ### Service Worker
- Enhance: オフライン表示のデザインを改善・多言語対応 - Enhance: オフライン表示のデザインを改善・多言語対応

30
cypress/e2e/router.cy.js Normal file
View file

@ -0,0 +1,30 @@
describe('Router transition', () => {
describe('Redirect', () => {
// サーバの初期化。ルートのテストに関しては各describeごとに1度だけ実行で十分だと思う使いまわした方が早い
before(() => {
cy.resetState();
// インスタンス初期セットアップ
cy.registerUser('admin', 'pass', true);
// ユーザー作成
cy.registerUser('alice', 'alice1234');
cy.login('alice', 'alice1234');
// アカウント初期設定ウィザード
// 表示に時間がかかるのでデフォルト秒数だとタイムアウトする
cy.get('[data-cy-user-setup] [data-cy-modal-window-close]', { timeout: 12000 }).click();
cy.wait(500);
cy.get('[data-cy-modal-dialog-ok]').click();
});
it('redirect to user profile', () => {
// テストのためだけに用意されたリダイレクト用ルートに飛ぶ
cy.visit('/redirect-test');
// プロフィールページのURLであることを確認する
cy.url().should('include', '/@alice')
});
});
});

View file

@ -130,6 +130,7 @@ overwriteFromPinnedEmojis: "Sobreescriu des dels emojis fixats"
reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem \"+\" per afegir." reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem \"+\" per afegir."
rememberNoteVisibility: "Recorda la configuració de visibilitat de les notes" rememberNoteVisibility: "Recorda la configuració de visibilitat de les notes"
attachCancel: "Eliminar el fitxer adjunt" attachCancel: "Eliminar el fitxer adjunt"
deleteFile: "Esborrar l'arxiu "
markAsSensitive: "Marcar com a NSFW" markAsSensitive: "Marcar com a NSFW"
unmarkAsSensitive: "Deixar de marcar com a sensible" unmarkAsSensitive: "Deixar de marcar com a sensible"
enterFileName: "Defineix nom del fitxer" enterFileName: "Defineix nom del fitxer"
@ -1039,6 +1040,12 @@ rolesAssignedToMe: "Rols assignats "
resetPasswordConfirm: "Vols canviar la teva contrasenya?" resetPasswordConfirm: "Vols canviar la teva contrasenya?"
sensitiveWords: "Paraules sensibles" sensitiveWords: "Paraules sensibles"
sensitiveWordsDescription: "La visibilitat de totes les notes que continguin qualsevol de les paraules configurades seran, automàticament, afegides a \"Inici\". Pots llistar diferents paraules separant les per línies noves." sensitiveWordsDescription: "La visibilitat de totes les notes que continguin qualsevol de les paraules configurades seran, automàticament, afegides a \"Inici\". Pots llistar diferents paraules separant les per línies noves."
sensitiveWordsDescription2: "Fent servir espais crearà expressions AND si l'expressió s'envolta amb barres inclinades es converteix en una expressió regular."
hiddenTags: "Etiquetes ocultes"
hiddenTagsDescription: "La visibilitat de totes les notes que continguin qualsevol de les paraules configurades seran, automàticament, afegides a \"Inici\". Pots llistar diferents paraules separant les per línies noves."
notesSearchNotAvailable: "La cerca de notes no es troba disponible."
license: "Llicència"
unfavoriteConfirm: "Esborrar dels favorits?"
myClips: "Els meus retalls" myClips: "Els meus retalls"
drivecleaner: "Netejador de Disc" drivecleaner: "Netejador de Disc"
retryAllQueuesNow: "Prova de nou d'executar totes les cues" retryAllQueuesNow: "Prova de nou d'executar totes les cues"
@ -1048,6 +1055,14 @@ enableChartsForRemoteUser: "Generar gràfiques d'usuaris remots"
enableChartsForFederatedInstances: "Generar gràfiques d'instàncies remotes" enableChartsForFederatedInstances: "Generar gràfiques d'instàncies remotes"
showClipButtonInNoteFooter: "Afegir \"Retall\" al menú d'acció de la nota" showClipButtonInNoteFooter: "Afegir \"Retall\" al menú d'acció de la nota"
reactionsDisplaySize: "Mida de les reaccions" reactionsDisplaySize: "Mida de les reaccions"
limitWidthOfReaction: "Limitar l'amplada màxima de la reacció i mostrar-les en una mida reduïda "
noteIdOrUrl: "ID o URL de la nota"
video: "Vídeo"
videos: "Vídeos "
audio: "So"
audioFiles: "So"
dataSaver: "Economitzador de dades"
accountMigration: "Migració del compte"
accountMoved: "Aquest usuari té un compte nou:" accountMoved: "Aquest usuari té un compte nou:"
accountMovedShort: "Aquest compte ha sigut migrat" accountMovedShort: "Aquest compte ha sigut migrat"
operationForbidden: "Operació no permesa " operationForbidden: "Operació no permesa "
@ -1098,9 +1113,50 @@ branding: "Marca"
enableServerMachineStats: "Publicar estadístiques del maquinari del servidor" enableServerMachineStats: "Publicar estadístiques del maquinari del servidor"
enableIdenticonGeneration: "Activar la generació d'icones d'identificació " enableIdenticonGeneration: "Activar la generació d'icones d'identificació "
turnOffToImprovePerformance: "Desactivant aquesta opció es pot millorar el rendiment." turnOffToImprovePerformance: "Desactivant aquesta opció es pot millorar el rendiment."
createInviteCode: "Crear codi d'invitació "
createWithOptions: "Crear invitació amb opcions"
createCount: "Comptador d'invitacions "
inviteCodeCreated: "Invitació creada"
inviteLimitExceeded: "Has sobrepassat el límit d'invitacions que pots crear."
createLimitRemaining: "Et queden {limit} invitacions restants"
inviteLimitResetCycle: "Cada {time} {limit} invitacions."
expirationDate: "Data de venciment"
noExpirationDate: "Sense data de venciment"
inviteCodeUsedAt: "Codi d'invitació fet servir el"
registeredUserUsingInviteCode: "Codi d'invitació fet servir per l'usuari "
waitingForMailAuth: "Esperant la verificació per correu electrònic "
inviteCodeCreator: "Invitació creada per"
usedAt: "Utilitzada el"
unused: "Sense utilitzar"
used: "Utilitzada"
expired: "Caducat"
doYouAgree: "Estàs d'acord?"
beSureToReadThisAsItIsImportant: "Llegeix això perquè és molt important."
iHaveReadXCarefullyAndAgree: "He llegit {x} i estic d'acord."
dialog: "Diàleg "
icon: "Icona" icon: "Icona"
forYou: "Per a tu"
currentAnnouncements: "Informes actuals"
pastAnnouncements: "Informes passats"
youHaveUnreadAnnouncements: "Tens informes per llegir."
useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey."
replies: "Respostes" replies: "Respostes"
renotes: "Impulsa" renotes: "Impulsa"
loadReplies: "Mostrar les respostes"
loadConversation: "Mostrar la conversació "
pinnedList: "Llista fixada"
keepScreenOn: "Mantenir la pantalla encesa"
verifiedLink: "La propietat de l'enllaç ha sigut verificada"
notifyNotes: "Notificar quan hi hagi notes noves"
unnotifyNotes: "Deixar de notificar quan hi hagi notes noves"
authentication: "Autenticació "
authenticationRequiredToContinue: "Si us plau autentificat per continuar"
dateAndTime: "Data i hora"
showRenotes: "Mostrar impulsos"
edited: "Editat"
notificationRecieveConfig: "Paràmetres de notificacions"
mutualFollow: "Seguidor mutu"
fileAttachedOnly: "Només notes amb adjunts"
externalServices: "Serveis externs" externalServices: "Serveis externs"
impressum: "Impressum" impressum: "Impressum"
impressumUrl: "Adreça URL impressum" impressumUrl: "Adreça URL impressum"
@ -1153,6 +1209,149 @@ _initialAccountSetting:
privacySetting: "Configuració de seguretat" privacySetting: "Configuració de seguretat"
theseSettingsCanEditLater: "Aquests ajustos es poden canviar més tard." theseSettingsCanEditLater: "Aquests ajustos es poden canviar més tard."
youCanEditMoreSettingsInSettingsPageLater: "A més d'això, es poden fer diferents configuracions a través de la pàgina de configuració. Assegureu-vos de comprovar-ho més tard." youCanEditMoreSettingsInSettingsPageLater: "A més d'això, es poden fer diferents configuracions a través de la pàgina de configuració. Assegureu-vos de comprovar-ho més tard."
_initialTutorial:
_reaction:
letsTryReacting: "Es poden afegir reaccions fent clic al botó '+'. Prova reaccionant a aquesta nota!"
reactToContinue: "Afegeix una reacció per continuar."
reactNotification: "Rebràs notificacions en temps real quan un usuari reaccioni a les teves notes."
reactDone: "Pots desfer una reacció fent clic al botó '-'."
_timeline:
title: "El concepte de les línies de temps"
description1: "Misskey mostra diferents línies de temps basades en l'ús (algunes poden no estar disponibles depenent de la política del servidor)"
home: "Pots veure notes dels comptes que segueixes"
local: "Pots veure les notes dels usuaris del servidor."
social: "Es mostren les notes de les línies de temps d'Inici i Local."
global: "Pots veure les notes de tots els servidors connectats."
description2: "Pots canviar la línia de temps en qualsevol moment fent servir la barra de la pantalla superior."
description3: "A més hi ha línies de temps per llistes i per canals. Si vols saber més {link}."
_postNote:
title: "Configuració de la publicació de les notes"
description1: "Quan públiques una nota a Misskey hi ha diferents opcions disponibles. El formulari de publicació es veu així"
_visibility:
description: "Pots limitar qui pot veure les teves notes."
public: "La teva nota serà visible per a tots els usuaris."
home: "Publicar només a línia de temps d'Inici. La gent que visiti el teu perfil o mitjançant les remotes també la podran veure."
followers: "Només visible per a seguidors. Només els teus seguidors la podran veure i ningú més. Ningú més podrà fer renotes."
direct: "Només visible per a alguns seguidors, el destinatari rebre una notificació. Es pot fer servir com una alternativa als missatges directes."
doNotSendConfidencialOnDirect1: "Tingues cura quan enviïs informació sensible."
doNotSendConfidencialOnDirect2: "Els administradors del servidor poden veure tot el que escrius. Ves compte quan enviïs informació sensible en enviar notes directes a altres usuaris en servidors de poca confiança."
localOnly: "Publicar amb aquesta opció activada farà que la nota no federi amb altres servidors. Els usuaris d'altres servidors no podran veure la nota directament, sense importar les opcions de visualització."
_cw:
title: "Avís de Contingut (CW)"
description: "En comptes del cos de la nota es mostrarà el que s'escrigui al camp de 'comentaris'. Fent clic a 'Llegir més' es mostrarà el cos."
_exampleNote:
cw: "Això et farà venir gana!"
note: "Acabo de menjar-me un donut de xocolata 🍩😋"
useCases: "Això es fa servir per seguir normes del servidor sobre certes notes o per ocultar contingut sensible O revelador."
_howToMakeAttachmentsSensitive:
title: "Com marcar adjunts com a contingut sensible?"
description: "Per adjunts que sigui requerit per les normes del servidor o que puguin contenir material sensible, s'ha d'afegir l'opció 'sensible'."
tryThisFile: "Prova de marcar la imatge adjunta en aquest formulari com a sensible!"
_exampleNote:
note: "Oops! L'he fet bona en obrir la tapa de Nocilla..."
method: "Per marcar un adjunt com a sensible, fes clic a la miniatura de l'adjunt, obre el menú i fes clic a 'Marcar com a sensible'."
sensitiveSucceeded: "Quan adjuntis fitxers si us plau marca la sensibilitat seguint les normes del servidor."
doItToContinue: "Marca el fitxer adjunt com a sensible per poder continuar."
_done:
title: "Has completat el tutorial 🎉"
description: "Les funcions explicades aquí és una petita mostra. Per una explicació més detallada de com fer servir MissKey consulta {link}."
_timelineDescription:
home: "A la línia de temps d'Inici pots veure les notes dels usuaris que segueixes."
local: "A la línia de temps Local pots veure les notes de tots els usuaris d'aquest servidor."
social: "La línia de temps Social mostren les notes de les línies de temps d'Inici i Local."
global: "A la línia de temps Global pots veure les notes de tots els servidors connectats."
_serverRules:
description: "Un conjunt de regles que seran mostrades abans de registrar-se. Es recomanable configurar un resum dels termes d'ús."
_serverSettings:
iconUrl: "URL de la icona"
appIconDescription: "Especifica la icona que es mostrarà quan el {host} es mostri en una aplicació."
appIconUsageExample: "Per exemple com a PWA, o quan es mostri com un favorit a la pàgina d'inici del telèfon mòbil"
appIconStyleRecommendation: "Com la icona pot ser retallada com un cercle o un quadrat, es recomana fer servir una icona amb un marge acolorit que l'envolti."
appIconResolutionMustBe: "La resolució mínima és {resolution}."
manifestJsonOverride: "Sobreescriure manifest.json"
shortName: "Nom curt"
shortNameDescription: "Una abreviatura del nom de la instància que es poguí mostrar en cas que el nom oficial sigui massa llarg"
fanoutTimelineDescription: "Quan es troba activat millora bastant el rendiment quan es recuperen les línies de temps i redueix la carrega de la base de dades. Com a contrapunt, l'ús de memòria de Redis es veurà incrementada. Considera d'estabilitat aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes de inestabilitat."
_achievements:
_types:
_notes100000:
flavor: "Segur que tens moltes coses a dir?"
_login3:
title: "Principiant I"
description: "Vas iniciar sessió fa tres dies"
flavor: "Des d'avui diguem Misskist"
_login7:
title: "Principiant II"
description: "Vas iniciar sessió fa set dies"
flavor: "Ja saps com va funcionant tot?"
_login15:
title: "Principiant III"
description: "Vas iniciar sessió fa quinze dies"
_login30:
title: "Misskist I"
description: "Vas iniciar sessió fa trenta dies"
_login60:
title: "Misskist II"
description: "Vas iniciar sessió fa seixanta dies"
_login100:
title: "Misskist III"
description: "Vas iniciar sessió fa cent dies"
flavor: "Misskist violent"
_login200:
title: "Regular I"
description: "Vas iniciar sessió fa dos-cents dies"
_login300:
title: "Regular II"
description: "Vas iniciar sessió fa tres-cents dies"
_login400:
title: "Regular III"
description: "Vas iniciar sessió fa quatre-cents dies"
_login500:
title: "Expert I"
description: "Vas iniciar sessió fa cinc-cents dies"
flavor: "Amics, he dit massa vegades que soc un amant de les notes"
_login600:
title: "Expert II"
description: "Vas iniciar sessió fa sis-cents dies"
_login700:
title: "Expert III"
description: "Vas iniciar sessió fa set-cents dies"
_login800:
title: "Mestre de les Notes I"
description: "Vas iniciar sessió fa vuit-cents dies "
_login900:
title: "Mestre de les Notes II"
description: "Vas iniciar sessió fa nou-cents dies"
_login1000:
title: "Mestre de les Notes III"
description: "Vas iniciar sessió fa mil dies"
flavor: "Gràcies per fer servir MissKey!"
_noteClipped1:
title: "He de retallar-te!"
description: "Retalla la teva primera nota"
_noteFavorited1:
title: "Quan miro les estrelles"
description: "La primera vegada que vaig registrar el meu favorit"
_myNoteFavorited1:
title: "Vull una estrella"
description: "La meva nota va ser registrada com favorita per una de les altres persones"
_profileFilled:
title: "Estic a punt"
description: "Vaig fer la configuració de perfil"
_markedAsCat:
title: "Soc un gat"
description: "He establert el meu compte com si fos un Gat"
flavor: "Encara no tinc nom"
_following1:
title: "És el meu primer seguiment"
description: "És la primera vegada que et segueixo"
_following10:
title: "Segueix-me... Segueix-me..."
_open3windows:
title: "Multi finestres"
description: "I va obrir més de tres finestres"
_driveFolderCircularReference:
title: "Consulteu la secció de bucle"
_role: _role:
assignTarget: "Assignar " assignTarget: "Assignar "
priority: "Prioritat" priority: "Prioritat"
@ -1274,6 +1473,7 @@ _webhookSettings:
_moderationLogTypes: _moderationLogTypes:
suspend: "Suspèn" suspend: "Suspèn"
resetPassword: "Restableix la contrasenya" resetPassword: "Restableix la contrasenya"
createInvitation: "Crear codi d'invitació "
_reversi: _reversi:
total: "Total" total: "Total"

View file

@ -122,7 +122,10 @@ add: "Hinzufügen"
reaction: "Reaktionen" reaction: "Reaktionen"
reactions: "Reaktionen" reactions: "Reaktionen"
emojiPicker: "Emoji auswählen" emojiPicker: "Emoji auswählen"
pinnedEmojisForReactionSettingDescription: "Wähle die Emojis aus, um sie an zu pinnen" pinnedEmojisForReactionSettingDescription: "Lege Emojis fest, die angepinnt werden sollen, um sie beim Reagieren als Erstes anzuzeigen."
pinnedEmojisSettingDescription: "Lege Emojis fest, die angepinnt werden sollen, um sie in der Emoji-Auswahl als Erstes anzuzeigen"
overwriteFromPinnedEmojisForReaction: "Überschreiben mit den Reaktions-Einstellungen"
overwriteFromPinnedEmojis: "Überschreiben mit den allgemeinen Einstellungen"
reactionSettingDescription2: "Ziehe um Anzuordnen, klicke um zu löschen, drücke „+“ um hinzuzufügen" reactionSettingDescription2: "Ziehe um Anzuordnen, klicke um zu löschen, drücke „+“ um hinzuzufügen"
rememberNoteVisibility: "Notizsichtbarkeit merken" rememberNoteVisibility: "Notizsichtbarkeit merken"
attachCancel: "Anhang entfernen" attachCancel: "Anhang entfernen"
@ -181,7 +184,7 @@ searchWith: "Suchen: {q}"
youHaveNoLists: "Du hast keine Listen" youHaveNoLists: "Du hast keine Listen"
followConfirm: "Möchtest du {name} wirklich folgen?" followConfirm: "Möchtest du {name} wirklich folgen?"
proxyAccount: "Proxy-Benutzerkonto" proxyAccount: "Proxy-Benutzerkonto"
proxyAccountDescription: "Ein Proxy-Benutzerkonto ist ein Benutzerkonto, das sich für Nutzer unter bestimmten Konditionen wie ein Follower aus einer fremden Instanz verhält. Zum Beispiel wird die Aktivität eines Nutzers aus einer fremden Instanz nicht an diese Instanz übermittelt, falls es keinen Benutzer dieser Instanz gibt, der diesem Nutzer aus fremder Instanz folgt. In diesem Fall folgt stattdessen das Proxy-Benutzerkonto." proxyAccountDescription: "Ein Proxy-Konto ist ein Benutzerkonto, das unter bestimmten Bedingungen als Follower für Benutzer fremder Instanzen fungiert. Wenn zum Beispiel ein Benutzer einen Benutzer einer fremden Instanz zu einer Liste hinzufügt, werden die Aktivitäten des entfernten Benutzers nicht an die Instanz übermittelt, wenn kein lokaler Benutzer diesem Benutzer folgt; stattdessen folgt das Proxy-Konto."
host: "Hostname" host: "Hostname"
selectUser: "Benutzer auswählen" selectUser: "Benutzer auswählen"
recipient: "Empfänger" recipient: "Empfänger"
@ -263,6 +266,7 @@ removed: "Erfolgreich gelöscht"
removeAreYouSure: "Möchtest du „{x}“ wirklich entfernen?" removeAreYouSure: "Möchtest du „{x}“ wirklich entfernen?"
deleteAreYouSure: "Möchtest du „{x}“ wirklich löschen?" deleteAreYouSure: "Möchtest du „{x}“ wirklich löschen?"
resetAreYouSure: "Wirklich zurücksetzen?" resetAreYouSure: "Wirklich zurücksetzen?"
areYouSure: "Bist du sicher?"
saved: "Erfolgreich gespeichert" saved: "Erfolgreich gespeichert"
messaging: "Chat" messaging: "Chat"
upload: "Hochladen" upload: "Hochladen"
@ -357,7 +361,7 @@ enableLocalTimeline: "Lokale Chronik aktivieren"
enableGlobalTimeline: "Globale Chronik aktivieren" enableGlobalTimeline: "Globale Chronik aktivieren"
disablingTimelinesInfo: "Administratoren und Moderatoren haben immer Zugriff auf alle Chroniken, auch wenn diese deaktiviert sind." disablingTimelinesInfo: "Administratoren und Moderatoren haben immer Zugriff auf alle Chroniken, auch wenn diese deaktiviert sind."
registration: "Registrieren" registration: "Registrieren"
enableRegistration: "Registration neuer Benutzer erlauben" enableRegistration: "Registrierung neuer Benutzer erlauben"
invite: "Einladen" invite: "Einladen"
driveCapacityPerLocalAccount: "Drive-Kapazität pro lokalem Benutzerkonto" driveCapacityPerLocalAccount: "Drive-Kapazität pro lokalem Benutzerkonto"
driveCapacityPerRemoteAccount: "Drive-Kapazität pro Benutzer fremder Instanzen" driveCapacityPerRemoteAccount: "Drive-Kapazität pro Benutzer fremder Instanzen"
@ -375,8 +379,11 @@ hcaptcha: "hCaptcha"
enableHcaptcha: "hCaptcha aktivieren" enableHcaptcha: "hCaptcha aktivieren"
hcaptchaSiteKey: "Site key" hcaptchaSiteKey: "Site key"
hcaptchaSecretKey: "Secret key" hcaptchaSecretKey: "Secret key"
mcaptcha: "mCaptcha"
enableMcaptcha: "mCaptcha aktivieren"
mcaptchaSiteKey: "Site key" mcaptchaSiteKey: "Site key"
mcaptchaSecretKey: "Secret key" mcaptchaSecretKey: "Secret key"
mcaptchaInstanceUrl: "mCaptcha Instanz-URL"
recaptcha: "reCAPTCHA" recaptcha: "reCAPTCHA"
enableRecaptcha: "reCAPTCHA aktivieren" enableRecaptcha: "reCAPTCHA aktivieren"
recaptchaSiteKey: "Site key" recaptchaSiteKey: "Site key"
@ -434,7 +441,7 @@ lastUsed: "Zuletzt benutzt"
lastUsedAt: "Zuletzt verwendet: {t}" lastUsedAt: "Zuletzt verwendet: {t}"
unregister: "Deaktivieren" unregister: "Deaktivieren"
passwordLessLogin: "Passwortloses Anmelden" passwordLessLogin: "Passwortloses Anmelden"
passwordLessLoginDescription: "Ermöglicht passwortfreies Einloggen, nur via Security-Token oder Passkey" passwordLessLoginDescription: "Ermöglicht passwortloses Einloggen mit einem Security-Token oder Passkey"
resetPassword: "Passwort zurücksetzen" resetPassword: "Passwort zurücksetzen"
newPasswordIs: "Das neue Passwort ist „{password}“" newPasswordIs: "Das neue Passwort ist „{password}“"
reduceUiAnimation: "Animationen der Benutzeroberfläche reduzieren" reduceUiAnimation: "Animationen der Benutzeroberfläche reduzieren"
@ -1174,6 +1181,9 @@ signupPendingError: "Beim Überprüfen der Mailadresse ist etwas schiefgelaufen.
cwNotationRequired: "Ist \"Inhaltswarnung verwenden\" aktiviert, muss eine Beschreibung gegeben werden." cwNotationRequired: "Ist \"Inhaltswarnung verwenden\" aktiviert, muss eine Beschreibung gegeben werden."
doReaction: "Reagieren" doReaction: "Reagieren"
code: "Code" code: "Code"
decorate: "Dekorieren"
addMfmFunction: "MFM hinzufügen"
sfx: "Soundeffekte"
lastNDays: "Letzten {n} Tage" lastNDays: "Letzten {n} Tage"
_announcement: _announcement:
forExistingUsers: "Nur für existierende Nutzer" forExistingUsers: "Nur für existierende Nutzer"
@ -1184,6 +1194,7 @@ _announcement:
tooManyActiveAnnouncementDescription: "Zu viele aktive Ankündigungen können die Benutzerfreundlichkeit verschlechtern. Es wird empfohlen, veraltete Ankündigungen zu archivieren." tooManyActiveAnnouncementDescription: "Zu viele aktive Ankündigungen können die Benutzerfreundlichkeit verschlechtern. Es wird empfohlen, veraltete Ankündigungen zu archivieren."
readConfirmTitle: "Als gelesen markieren?" readConfirmTitle: "Als gelesen markieren?"
readConfirmText: "Dies markiert den Inhalt von \"{title}\" als gelesen." readConfirmText: "Dies markiert den Inhalt von \"{title}\" als gelesen."
shouldNotBeUsedToPresentPermanentInfo: "Es wird empfohlen, Ankündigungen für aktuelle und zeitlich begrenzte Neuigkeiten zu nutzen, statt für Informationen, die langfristig relevant sind."
dialogAnnouncementUxWarn: "Bei der Verwendung von mehr als zwei Meldungen im Dialog-Format wird um Vorsicht geboten, da dies negative Auswirkungen auf die UX haben kann." dialogAnnouncementUxWarn: "Bei der Verwendung von mehr als zwei Meldungen im Dialog-Format wird um Vorsicht geboten, da dies negative Auswirkungen auf die UX haben kann."
silence: "Keine Benachrichtigung" silence: "Keine Benachrichtigung"
silenceDescription: "Wenn aktiviert, gibt diese Meldung keine Nachricht aus und muss nicht als \"gelesen\" markiert werden." silenceDescription: "Wenn aktiviert, gibt diese Meldung keine Nachricht aus und muss nicht als \"gelesen\" markiert werden."
@ -1213,6 +1224,24 @@ _initialTutorial:
description: "Hier kannst du sehen, wie Misskey funktioniert" description: "Hier kannst du sehen, wie Misskey funktioniert"
_note: _note:
title: "Was sind Notizen?" title: "Was sind Notizen?"
description: "Beiträge auf Misskey heißen \"Notizen\". Notizen werden chronologisch in der Chronik angeordnet und in Echtzeit aktualisiert."
reply: "Klicke auf diesen Button, um auf eine Nachricht zu antworten. Es ist auch möglich, auf Antworten zu antworten und die Unterhaltung wie einen Thread fortzusetzen."
_reaction:
title: "Was sind Reaktionen?"
reactToContinue: "Füge eine Reaktion hinzu, um fortzufahren."
reactNotification: "Du erhältst Echtzeit-Benachrichtigungen, wenn jemand auf deine Notiz reagiert."
_postNote:
_visibility:
description: "Du kannst einschränken, wer deine Notiz sehen kann."
public: "Deine Notiz wird für alle Nutzer sichtbar sein."
doNotSendConfidencialOnDirect1: "Sei vorsichtig, wenn du sensible Informationen verschickst!"
_cw:
title: "Inhaltswarnung"
_done:
title: "Du hast das Tutorial abgeschlossen! 🎉"
_timelineDescription:
local: "In der lokalen Chronik siehst du Notizen von allen Benutzern auf diesem Server."
global: "In der globalen Chronik siehst du Notizen von allen föderierten Servern."
_serverRules: _serverRules:
description: "Eine Reihe von Regeln, die vor der Registrierung angezeigt werden. Eine Zusammenfassung der Nutzungsbedingungen anzuzeigen ist empfohlen." description: "Eine Reihe von Regeln, die vor der Registrierung angezeigt werden. Eine Zusammenfassung der Nutzungsbedingungen anzuzeigen ist empfohlen."
_serverSettings: _serverSettings:
@ -1225,6 +1254,8 @@ _serverSettings:
shortName: "Abkürzung" shortName: "Abkürzung"
shortNameDescription: "Ein Kürzel für den Namen der Instanz, der angezeigt werden kann, falls der volle Instanzname lang ist." shortNameDescription: "Ein Kürzel für den Namen der Instanz, der angezeigt werden kann, falls der volle Instanzname lang ist."
fanoutTimelineDescription: "Ist diese Option aktiviert, kann eine erhebliche Verbesserung im Abrufen von Chroniken und eine Reduzierung der Datenbankbelastung erzielt werden, im Gegenzug zu einer Steigerung in der Speichernutzung von Redis. Bei geringem Serverspeicher oder Serverinstabilität kann diese Option deaktiviert werden." fanoutTimelineDescription: "Ist diese Option aktiviert, kann eine erhebliche Verbesserung im Abrufen von Chroniken und eine Reduzierung der Datenbankbelastung erzielt werden, im Gegenzug zu einer Steigerung in der Speichernutzung von Redis. Bei geringem Serverspeicher oder Serverinstabilität kann diese Option deaktiviert werden."
fanoutTimelineDbFallback: "Auf die Datenbank zurückfallen"
fanoutTimelineDbFallbackDescription: "Ist diese Option aktiviert, wird die Chronik auf zusätzliche Abfragen in der Datenbank zurückgreifen, wenn sich die Chronik nicht im Cache befindet. Eine Deaktivierung führt zu geringerer Serverlast, aber schränkt den Zeitraum der abrufbaren Chronik ein. "
_accountMigration: _accountMigration:
moveFrom: "Von einem anderen Konto zu diesem migrieren" moveFrom: "Von einem anderen Konto zu diesem migrieren"
moveFromSub: "Alias für ein anderes Konto erstellen" moveFromSub: "Alias für ein anderes Konto erstellen"
@ -1482,6 +1513,8 @@ _achievements:
_smashTestNotificationButton: _smashTestNotificationButton:
title: "Testüberfluss" title: "Testüberfluss"
description: "Betätige den Benachrichtigungstest mehrfach innerhalb einer extrem kurzen Zeitspanne" description: "Betätige den Benachrichtigungstest mehrfach innerhalb einer extrem kurzen Zeitspanne"
_tutorialCompleted:
description: "Tutorial abgeschlossen"
_role: _role:
new: "Rolle erstellen" new: "Rolle erstellen"
edit: "Rolle bearbeiten" edit: "Rolle bearbeiten"
@ -1492,7 +1525,9 @@ _role:
assignTarget: "Zuweisungsart" assignTarget: "Zuweisungsart"
descriptionOfAssignTarget: "<b>Manuell</b> bedeutet, dass die Liste der Benutzer einer Rolle manuell verwaltet wird.\n<b>Konditional</b> bedeutet, dass die Liste der Benutzer einer Rolle durch eine Bedingung automatisch verwaltet wird." descriptionOfAssignTarget: "<b>Manuell</b> bedeutet, dass die Liste der Benutzer einer Rolle manuell verwaltet wird.\n<b>Konditional</b> bedeutet, dass die Liste der Benutzer einer Rolle durch eine Bedingung automatisch verwaltet wird."
manual: "Manuell" manual: "Manuell"
manualRoles: "Manuelle Rollen"
conditional: "Konditional" conditional: "Konditional"
conditionalRoles: "Bedingte Rolle"
condition: "Bedingung" condition: "Bedingung"
isConditionalRole: "Dies ist eine konditionale Rolle." isConditionalRole: "Dies ist eine konditionale Rolle."
isPublic: "Öffentliche Rolle" isPublic: "Öffentliche Rolle"
@ -1534,13 +1569,14 @@ _role:
webhookMax: "Maximale Anzahl an Webhooks" webhookMax: "Maximale Anzahl an Webhooks"
clipMax: "Maximale Anzahl an Clips" clipMax: "Maximale Anzahl an Clips"
noteEachClipsMax: "Maximale Anzahl an Notizen innerhalb eines Clips" noteEachClipsMax: "Maximale Anzahl an Notizen innerhalb eines Clips"
userListMax: "Maximale Anzahl an Benutzern in einer Benutzerliste" userListMax: "Maximale Anzahl an Benutzerlisten"
userEachUserListsMax: "Maximale Anzahl an Benutzerlisten" userEachUserListsMax: "Maximale Anzahl an Benutzern in einer Benutzerliste"
rateLimitFactor: "Versuchsanzahl" rateLimitFactor: "Versuchsanzahl"
descriptionOfRateLimitFactor: "Je niedriger desto weniger restriktiv, je höher destro restriktiver." descriptionOfRateLimitFactor: "Je niedriger desto weniger restriktiv, je höher destro restriktiver."
canHideAds: "Kann Werbung ausblenden" canHideAds: "Kann Werbung ausblenden"
canSearchNotes: "Nutzung der Notizsuchfunktion" canSearchNotes: "Nutzung der Notizsuchfunktion"
canUseTranslator: "Verwendung des Übersetzers" canUseTranslator: "Verwendung des Übersetzers"
avatarDecorationLimit: "Maximale Anzahl an Profilbilddekorationen, die angebracht werden können"
_condition: _condition:
isLocal: "Lokaler Benutzer" isLocal: "Lokaler Benutzer"
isRemote: "Benutzer fremder Instanz" isRemote: "Benutzer fremder Instanz"
@ -1569,6 +1605,7 @@ _emailUnavailable:
disposable: "Wegwerf-Email-Adressen können nicht verwendet werden" disposable: "Wegwerf-Email-Adressen können nicht verwendet werden"
mx: "Dieser Email-Server ist ungültig" mx: "Dieser Email-Server ist ungültig"
smtp: "Dieser Email-Server antwortet nicht" smtp: "Dieser Email-Server antwortet nicht"
banned: "Du kannst dich mit dieser E-Mail-Adresse nicht registrieren"
_ffVisibility: _ffVisibility:
public: "Öffentlich" public: "Öffentlich"
followers: "Nur für Follower sichtbar" followers: "Nur für Follower sichtbar"
@ -1897,6 +1934,7 @@ _widgets:
_userList: _userList:
chooseList: "Liste auswählen" chooseList: "Liste auswählen"
clicker: "Klickzähler" clicker: "Klickzähler"
birthdayFollowings: "Nutzer, die heute Geburtstag haben"
_cw: _cw:
hide: "Inhalt verbergen" hide: "Inhalt verbergen"
show: "Inhalt anzeigen" show: "Inhalt anzeigen"
@ -2246,5 +2284,9 @@ _externalResourceInstaller:
title: "Das Farbschema konnte nicht installiert werden" title: "Das Farbschema konnte nicht installiert werden"
description: "Während der Installation des Farbschemas ist ein Problem aufgetreten. Bitte versuche es erneut. Detaillierte Fehlerinformationen können über die Javascript-Konsole abgerufen werden." description: "Während der Installation des Farbschemas ist ein Problem aufgetreten. Bitte versuche es erneut. Detaillierte Fehlerinformationen können über die Javascript-Konsole abgerufen werden."
_reversi: _reversi:
blackOrWhite: "Schwarz/Weiß"
rules: "Regeln"
black: "Schwarz"
white: "Weiß"
total: "Gesamt" total: "Gesamt"

View file

@ -391,8 +391,11 @@ hcaptcha: "hCaptcha"
enableHcaptcha: "Enable hCaptcha" enableHcaptcha: "Enable hCaptcha"
hcaptchaSiteKey: "Site key" hcaptchaSiteKey: "Site key"
hcaptchaSecretKey: "Secret key" hcaptchaSecretKey: "Secret key"
mcaptcha: "mCaptcha"
enableMcaptcha: "Enable mCaptcha"
mcaptchaSiteKey: "Site key" mcaptchaSiteKey: "Site key"
mcaptchaSecretKey: "Secret key" mcaptchaSecretKey: "Secret key"
mcaptchaInstanceUrl: "mCaptcha instance URL"
recaptcha: "reCAPTCHA" recaptcha: "reCAPTCHA"
enableRecaptcha: "Enable reCAPTCHA" enableRecaptcha: "Enable reCAPTCHA"
recaptchaSiteKey: "Site key" recaptchaSiteKey: "Site key"
@ -645,6 +648,7 @@ medium: "Medium"
small: "Small" small: "Small"
generateAccessToken: "Generate access token" generateAccessToken: "Generate access token"
permission: "Permissions" permission: "Permissions"
adminPermission: "Admin Permissions"
enableAll: "Enable all" enableAll: "Enable all"
disableAll: "Disable all" disableAll: "Disable all"
tokenRequested: "Grant access to account" tokenRequested: "Grant access to account"
@ -661,7 +665,7 @@ smtpHost: "Host"
smtpPort: "Port" smtpPort: "Port"
smtpUser: "Username" smtpUser: "Username"
smtpPass: "Password" smtpPass: "Password"
emptyToDisableSmtpAuth: "Leave username and password empty to disable SMTP verification" emptyToDisableSmtpAuth: "Leave username and password empty to disable SMTP authentication"
smtpSecure: "Use implicit SSL/TLS for SMTP connections" smtpSecure: "Use implicit SSL/TLS for SMTP connections"
smtpSecureInfo: "Turn this off when using STARTTLS" smtpSecureInfo: "Turn this off when using STARTTLS"
testEmail: "Test email delivery" testEmail: "Test email delivery"
@ -688,6 +692,7 @@ useGlobalSettingDesc: "If turned on, your account's notification settings will b
other: "Other" other: "Other"
regenerateLoginToken: "Regenerate login token" regenerateLoginToken: "Regenerate login token"
regenerateLoginTokenDescription: "Regenerates the token used internally during login. Normally this action is not necessary. If regenerated, all devices will be logged out." regenerateLoginTokenDescription: "Regenerates the token used internally during login. Normally this action is not necessary. If regenerated, all devices will be logged out."
theKeywordWhenSearchingForCustomEmoji: "This is the keyword when searching for custom emojis."
setMultipleBySeparatingWithSpace: "Separate multiple entries with spaces." setMultipleBySeparatingWithSpace: "Separate multiple entries with spaces."
fileIdOrUrl: "File ID or URL" fileIdOrUrl: "File ID or URL"
behavior: "Behavior" behavior: "Behavior"
@ -1091,6 +1096,8 @@ limitWidthOfReaction: "Limits the maximum width of reactions and display them in
noteIdOrUrl: "Note ID or URL" noteIdOrUrl: "Note ID or URL"
video: "Video" video: "Video"
videos: "Videos" videos: "Videos"
audio: "Audio"
audioFiles: "Audio"
dataSaver: "Data Saver" dataSaver: "Data Saver"
accountMigration: "Account Migration" accountMigration: "Account Migration"
accountMoved: "This user has moved to a new account:" accountMoved: "This user has moved to a new account:"
@ -1212,7 +1219,7 @@ donationUrl: "Donation URL"
avatarDecorations: "Avatar decorations" avatarDecorations: "Avatar decorations"
attach: "Attach" attach: "Attach"
detach: "Remove" detach: "Remove"
detachAll: "Remove all" detachAll: "Remove All"
angle: "Angle" angle: "Angle"
flip: "Flip" flip: "Flip"
showAvatarDecorations: "Show avatar decorations" showAvatarDecorations: "Show avatar decorations"
@ -1234,8 +1241,23 @@ addMfmFunction: "Add MFM"
enableQuickAddMfmFunction: "Show advanced MFM picker" enableQuickAddMfmFunction: "Show advanced MFM picker"
bubbleGame: "Bubble Game" bubbleGame: "Bubble Game"
sfx: "Sound Effects" sfx: "Sound Effects"
soundWillBePlayed: "Sound will be played"
showReplay: "View Replay"
replay: "Replay" replay: "Replay"
replaying: "Showing replay"
ranking: "Ranking"
lastNDays: "Last {n} days" lastNDays: "Last {n} days"
backToTitle: "Go back to title"
hemisphere: "Where are you located"
withSensitive: "Include notes with sensitive files"
userSaysSomethingSensitive: "Post by {name} contains sensitive content"
enableHorizontalSwipe: "Swipe to switch tabs"
_bubbleGame:
howToPlay: "How to play"
_howToPlay:
section1: "Adjust the position and drop the object into the box."
section2: "When two objects of the same type touch each other, they will change into a different object and you score points."
section3: "The game is over when objects overflow from the box. Aim for a high score by fusing objects together while you avoid overflowing the box!"
_announcement: _announcement:
forExistingUsers: "Existing users only" forExistingUsers: "Existing users only"
forExistingUsersDescription: "This announcement will only be shown to users existing at the point of publishment if enabled. If disabled, those newly signing up after it has been posted will also see it." forExistingUsersDescription: "This announcement will only be shown to users existing at the point of publishment if enabled. If disabled, those newly signing up after it has been posted will also see it."
@ -1245,7 +1267,7 @@ _announcement:
tooManyActiveAnnouncementDescription: "Having too many active announcements may worsen the user experience. Please consider archiving announcements that have become obsolete." tooManyActiveAnnouncementDescription: "Having too many active announcements may worsen the user experience. Please consider archiving announcements that have become obsolete."
readConfirmTitle: "Mark as read?" readConfirmTitle: "Mark as read?"
readConfirmText: "This will mark the contents of \"{title}\" as read." readConfirmText: "This will mark the contents of \"{title}\" as read."
shouldNotBeUsedToPresentPermanentInfo: "As it may significantly impact the user experience for new users, it is recommended to use notifications in the flow information rather than stock information." shouldNotBeUsedToPresentPermanentInfo: "It's best to use announcements to publish fresh and time-bound information, not for information that will be relevant in the long term."
dialogAnnouncementUxWarn: "Having two or more dialog-style notifications simultaneously can significantly impact the user experience, so please use them carefully." dialogAnnouncementUxWarn: "Having two or more dialog-style notifications simultaneously can significantly impact the user experience, so please use them carefully."
silence: "No notification" silence: "No notification"
silenceDescription: "Turning this on will skip the notification of this announcement and the user won't need to read it." silenceDescription: "Turning this on will skip the notification of this announcement and the user won't need to read it."
@ -1608,8 +1630,11 @@ _achievements:
description: "Tutorial completed" description: "Tutorial completed"
_bubbleGameExplodingHead: _bubbleGameExplodingHead:
title: "🤯" title: "🤯"
description: "The biggest object in the bubble game"
_bubbleGameDoubleExplodingHead: _bubbleGameDoubleExplodingHead:
title: "Double🤯" title: "Double🤯"
description: "Two of the biggest objects in the bubble game at the same time"
flavor: "You can fill a lunch box like this 🤯 🤯 a bit."
_role: _role:
new: "New role" new: "New role"
edit: "Edit role" edit: "Edit role"
@ -2562,6 +2587,53 @@ _dataSaver:
_code: _code:
title: "Code highlighting" title: "Code highlighting"
description: "If code highlighting notations are used in MFM, etc., they will not load until tapped. Syntax highlighting requires downloading the highlight definition files for each programming language. Therefore, disabling the automatic loading of these files is expected to reduce the amount of communication data." description: "If code highlighting notations are used in MFM, etc., they will not load until tapped. Syntax highlighting requires downloading the highlight definition files for each programming language. Therefore, disabling the automatic loading of these files is expected to reduce the amount of communication data."
_hemisphere:
N: "Northern Hemisphere"
S: "Southern Hemisphere"
caption: "Used in some client settings to determine season."
_reversi: _reversi:
reversi: "Reversi"
gameSettings: "Game settings"
chooseBoard: "Choose a board"
blackOrWhite: "Black/White"
blackIs: "{name} is playing Black"
rules: "Rules"
thisGameIsStartedSoon: "The game will begin shortly"
waitingForOther: "Waiting for opponent's turn"
waitingForMe: "Waiting for your turn"
waitingBoth: "Get ready"
ready: "Ready"
cancelReady: "Not ready"
opponentTurn: "Opponent's turn"
myTurn: "Your turn"
turnOf: "It's {name}'s turn"
pastTurnOf: "{name}'s turn"
surrender: "Surrender"
surrendered: "Surrendered"
timeout: "Out of time"
drawn: "Draw"
won: "{name} wins"
black: "Black"
white: "White"
total: "Total" total: "Total"
turnCount: "Turn {count}"
myGames: "My rounds"
allGames: "All rounds"
ended: "Ended"
playing: "Currently playing"
isLlotheo: "The one with fewer stones wins (Llotheo)"
loopedMap: "Looping map"
canPutEverywhere: "Tiles are placeable everywhere"
timeLimitForEachTurn: "Time limit for turn"
freeMatch: "Free Match"
lookingForPlayer: "Finding opponent..."
gameCanceled: "The game has been cancelled."
shareToTlTheGameWhenStart: "Share Game to timeline when started"
iStartedAGame: "The game has begun! #MisskeyReversi"
opponentHasSettingsChanged: "The opponent has changed their settings."
allowIrregularRules: "Irregular rules (completely free)"
disallowIrregularRules: "No irregular rules"
_offlineScreen:
title: "Offline - cannot connect to the server"
header: "Unable to connect to the server"

View file

@ -399,7 +399,7 @@ antennaKeywords: "Mots clés à recevoir"
antennaExcludeKeywords: "Mots clés à exclure" antennaExcludeKeywords: "Mots clés à exclure"
antennaKeywordsDescription: "Séparer avec des espaces pour la condition AND. Séparer avec un saut de ligne pour une condition OR." antennaKeywordsDescription: "Séparer avec des espaces pour la condition AND. Séparer avec un saut de ligne pour une condition OR."
notifyAntenna: "Me notifier pour les nouvelles notes" notifyAntenna: "Me notifier pour les nouvelles notes"
withFileAntenna: "Notes ayant des attachements uniquement" withFileAntenna: "Notes ayant des fichiers joints uniquement"
enableServiceworker: "Activer ServiceWorker" enableServiceworker: "Activer ServiceWorker"
antennaUsersDescription: "Saisissez un seul nom dutilisateur·rice par ligne" antennaUsersDescription: "Saisissez un seul nom dutilisateur·rice par ligne"
caseSensitive: "Sensible à la casse" caseSensitive: "Sensible à la casse"

2
locales/index.d.ts vendored
View file

@ -5079,7 +5079,7 @@ export interface Locale extends ILocale {
*/ */
"readConfirmText": ParameterizedString<"title">; "readConfirmText": ParameterizedString<"title">;
/** /**
* UXを損ねる可能性が高いため使 * UXを損ねる可能性が高いため使
*/ */
"shouldNotBeUsedToPresentPermanentInfo": string; "shouldNotBeUsedToPresentPermanentInfo": string;
/** /**

View file

@ -1269,7 +1269,7 @@ _announcement:
tooManyActiveAnnouncementDescription: "アクティブなお知らせが多いため、UXが低下する可能性があります。終了したお知らせはアーカイブすることを検討してください。" tooManyActiveAnnouncementDescription: "アクティブなお知らせが多いため、UXが低下する可能性があります。終了したお知らせはアーカイブすることを検討してください。"
readConfirmTitle: "既読にしますか?" readConfirmTitle: "既読にしますか?"
readConfirmText: "「{title}」の内容を読み、既読にします。" readConfirmText: "「{title}」の内容を読み、既読にします。"
shouldNotBeUsedToPresentPermanentInfo: "特に新規ユーザーのUXを損ねる可能性が高いため、ストック情報ではなくフロー情報の掲示にお知らせを使用することを推奨します。" shouldNotBeUsedToPresentPermanentInfo: "特に新規ユーザーのUXを損ねる可能性が高いため、常時掲示するための情報ではなく、即時性が求められる情報の掲示のためにお知らせを使用することを推奨します。"
dialogAnnouncementUxWarn: "ダイアログ形式のお知らせが同時に2つ以上ある場合、UXに悪影響を及ぼす可能性が非常に高いため、使用は慎重に行うことを推奨します。" dialogAnnouncementUxWarn: "ダイアログ形式のお知らせが同時に2つ以上ある場合、UXに悪影響を及ぼす可能性が非常に高いため、使用は慎重に行うことを推奨します。"
silence: "非通知" silence: "非通知"
silenceDescription: "オンにすると、このお知らせは通知されず、既読にする必要もなくなります。" silenceDescription: "オンにすると、このお知らせは通知されず、既読にする必要もなくなります。"

View file

@ -2035,9 +2035,9 @@ _auth:
_antennaSources: _antennaSources:
all: "みんなのノート" all: "みんなのノート"
homeTimeline: "フォローしとるユーザーのノート" homeTimeline: "フォローしとるユーザーのノート"
users: "選んだ一人か複数のユーザーのノート" users: "選んだ一人か複数のユーザーのノート"
userList: "選んだリストのユーザーのノート" userList: "選んだリストのユーザーのノート"
userBlacklist: "選んだ1人か複数のユーザーのノート" userBlacklist: "選んだ一人か複数のユーザーを除いた全てのノート"
_weekday: _weekday:
sunday: "日曜日" sunday: "日曜日"
monday: "月曜日" monday: "月曜日"
@ -2487,6 +2487,8 @@ _reversi:
shareToTlTheGameWhenStart: "初めの時に対局をタイムラインに投稿するで" shareToTlTheGameWhenStart: "初めの時に対局をタイムラインに投稿するで"
iStartedAGame: "対局し始めたで! #MisskeyReversi" iStartedAGame: "対局し始めたで! #MisskeyReversi"
opponentHasSettingsChanged: "相手が設定変えたで" opponentHasSettingsChanged: "相手が設定変えたで"
allowIrregularRules: "変則許可 (完全フリー)"
disallowIrregularRules: "変則なし"
_offlineScreen: _offlineScreen:
title: "オフライン - サーバーに接続できひんで" title: "オフライン - サーバーに接続できひんで"
header: "サーバーに接続できへんわ" header: "サーバーに接続できへんわ"

View file

@ -515,7 +515,7 @@ objectStoragePrefixDesc: "요 Prefix 디렉토리 안에다가 파일이 들어
objectStorageEndpoint: "Endpoint" objectStorageEndpoint: "Endpoint"
objectStorageEndpointDesc: "AWS S3을 쓸라멘 요는 비워두고, 아이멘은 그 서비스 가이드에 맞게 endpoint를 넣어 주이소. '<host>' 내지 '<host>:<port>'처럼 넣십니다." objectStorageEndpointDesc: "AWS S3을 쓸라멘 요는 비워두고, 아이멘은 그 서비스 가이드에 맞게 endpoint를 넣어 주이소. '<host>' 내지 '<host>:<port>'처럼 넣십니다."
objectStorageRegion: "Region" objectStorageRegion: "Region"
objectStorageRegionDesc: "'xx-east-1' 같은 region 이름을 옇어 주이소. 써먹을 서비스에 region 개념 같은 게 읎다! 카면은 대신에 'us-east-1'을 옇어 놓으이소. AWS 설정 파일이나 환경 변수를 갖다 끌어다 쓸 거면은 요는 비워 두이소." objectStorageRegionDesc: "'xx-east-1' 같은 region 이름을 옇어 주이소. 만약에 내 서비스엔 region 같은 개념이 읎다, 카면은 대신에 'us-east-1'라고 해 두이소. AWS 설정 파일이나 환경 변수를 끌어다 쓰겠다믄 요는 비워 두이소."
objectStorageUseSSL: "SSL 쓰기" objectStorageUseSSL: "SSL 쓰기"
objectStorageUseSSLDesc: "API 호출할 때 HTTPS 안 쓸거면은 꺼 두이소" objectStorageUseSSLDesc: "API 호출할 때 HTTPS 안 쓸거면은 꺼 두이소"
objectStorageUseProxy: "연결에 프락시 사용" objectStorageUseProxy: "연결에 프락시 사용"
@ -538,7 +538,7 @@ volume: "음량"
masterVolume: "대빵 음량" masterVolume: "대빵 음량"
notUseSound: "음소거하기" notUseSound: "음소거하기"
useSoundOnlyWhenActive: "Misskey가 활성화되어 있을 때만 소리 내기" useSoundOnlyWhenActive: "Misskey가 활성화되어 있을 때만 소리 내기"
details: "좀 더" details: "자세히"
chooseEmoji: "이모지 선택" chooseEmoji: "이모지 선택"
unableToProcess: "작업 다 몬 했십니다" unableToProcess: "작업 다 몬 했십니다"
recentUsed: "최근 쓴 놈" recentUsed: "최근 쓴 놈"

View file

@ -2482,6 +2482,11 @@ _reversi:
freeMatch: "프리매치" freeMatch: "프리매치"
lookingForPlayer: "상대를 찾고 있습니다" lookingForPlayer: "상대를 찾고 있습니다"
gameCanceled: "대국이 취소되었습니다" gameCanceled: "대국이 취소되었습니다"
shareToTlTheGameWhenStart: "대국 시작 시 타임라인에 대국을 게시"
iStartedAGame: "대국이 시작되었습니다! #MisskeyReversi"
opponentHasSettingsChanged: "상대방이 설정을 변경했습니다"
allowIrregularRules: "규칙변경 허가 (완전 자유)"
disallowIrregularRules: "규칙변경 없음"
_offlineScreen: _offlineScreen:
title: "오프라인 - 서버에 접속할 수 없습니다" title: "오프라인 - 서버에 접속할 수 없습니다"
header: "서버에 접속할 수 없습니다" header: "서버에 접속할 수 없습니다"

View file

@ -1185,6 +1185,7 @@ useGroupedNotifications: "分组显示通知"
signupPendingError: "确认电子邮件时出现错误。链接可能已过期。" signupPendingError: "确认电子邮件时出现错误。链接可能已过期。"
cwNotationRequired: "在启用「隐藏内容」时必须输入注释" cwNotationRequired: "在启用「隐藏内容」时必须输入注释"
doReaction: "回应" doReaction: "回应"
code: "代码"
reloadRequiredToApplySettings: "需要重新载入来使设置生效" reloadRequiredToApplySettings: "需要重新载入来使设置生效"
remainingN: "剩余:{n}" remainingN: "剩余:{n}"
overwriteContentConfirm: "将覆盖现有内容。确定吗?" overwriteContentConfirm: "将覆盖现有内容。确定吗?"
@ -1202,9 +1203,14 @@ lastNDays: "最近 {n} 天"
backToTitle: "返回标题" backToTitle: "返回标题"
hemisphere: "居住地区" hemisphere: "居住地区"
withSensitive: "显示包含敏感媒体的帖子" withSensitive: "显示包含敏感媒体的帖子"
userSaysSomethingSensitive: "含 {name} 敏感文件的帖子"
enableHorizontalSwipe: "滑动切换标签页" enableHorizontalSwipe: "滑动切换标签页"
_bubbleGame: _bubbleGame:
howToPlay: "游戏说明" howToPlay: "游戏说明"
_howToPlay:
section1: "对准位置将Emoji投入盒子。"
section2: "相同的Emoji相互接触合成后会得到新的Emoji以此获得分数。"
section3: "如果Emoji从箱子中溢出游戏将会结束。在防止Emoji溢出的同时不断合成新的Emoji来获取更高的分数吧"
_announcement: _announcement:
forExistingUsers: "仅限现有用户" forExistingUsers: "仅限现有用户"
forExistingUsersDescription: "若启用,该公告将仅对创建此公告时存在的用户可见。 如果禁用,则在创建此公告后注册的用户也可以看到该公告。" forExistingUsersDescription: "若启用,该公告将仅对创建此公告时存在的用户可见。 如果禁用,则在创建此公告后注册的用户也可以看到该公告。"
@ -1577,6 +1583,10 @@ _achievements:
description: "完成了教学" description: "完成了教学"
_bubbleGameExplodingHead: _bubbleGameExplodingHead:
title: "🤯" title: "🤯"
description: "你合成出了游戏里最大的Emoji"
_bubbleGameDoubleExplodingHead:
title: "两个🤯"
description: "你合成出了2个游戏里最大的Emoji"
_role: _role:
new: "创建角色" new: "创建角色"
edit: "编辑角色" edit: "编辑角色"
@ -2435,7 +2445,41 @@ _hemisphere:
caption: "在某些客户端设置中用来确定季节" caption: "在某些客户端设置中用来确定季节"
_reversi: _reversi:
reversi: "黑白棋" reversi: "黑白棋"
gameSettings: "对局设置"
blackOrWhite: "先手/后手"
blackIs: "{name}执黑(先手)"
rules: "规则"
thisGameIsStartedSoon: "对局即将开始"
waitingForOther: "等待对手准备"
waitingForMe: "等待你的准备"
waitingBoth: "请准备"
ready: "准备就绪"
cancelReady: "重新准备"
opponentTurn: "对手的回合"
myTurn: "你的回合"
turnOf: "{name}的回合"
pastTurnOf: "{name}的回合"
timeout: "超时"
drawn: "平局"
won: "{name}获胜"
black: "黑"
white: "白"
total: "总计" total: "总计"
turnCount: "第{count}回合"
myGames: "我的对局"
allGames: "所有对局"
ended: "结束"
playing: "对局中"
canPutEverywhere: "无限制放置模式"
timeLimitForEachTurn: "1回合的时间限制"
freeMatch: "自由匹配"
lookingForPlayer: "正在寻找对手"
gameCanceled: "对局被取消了"
shareToTlTheGameWhenStart: "开始时在时间线发布对局"
iStartedAGame: "对局开始!#MisskeyReversi"
opponentHasSettingsChanged: "对手更改了设定"
allowIrregularRules: "允许非常规规则(完全自由)"
disallowIrregularRules: "禁止非常规规则"
_offlineScreen: _offlineScreen:
title: "离线——无法连接到服务器" title: "离线——无法连接到服务器"
header: "无法连接到服务器" header: "无法连接到服务器"

View file

@ -1221,7 +1221,7 @@ _announcement:
tooManyActiveAnnouncementDescription: "有過多公告可能會影響使用者體驗。請考慮歸檔已結束的公告。" tooManyActiveAnnouncementDescription: "有過多公告可能會影響使用者體驗。請考慮歸檔已結束的公告。"
readConfirmTitle: "標記為已讀嗎?" readConfirmTitle: "標記為已讀嗎?"
readConfirmText: "閱讀「{title}」的內容並標記為已讀。" readConfirmText: "閱讀「{title}」的內容並標記為已讀。"
shouldNotBeUsedToPresentPermanentInfo: "由於可能會破壞使用者體驗,尤其是對於新使用者而言,我們建議使用公告來發布有時效性的資訊而不是固定不變的資訊。" shouldNotBeUsedToPresentPermanentInfo: "為了避免損害新用戶的使用體驗,建議使用公告來發布即時性的訊息,而不是用於固定不變的資訊。"
dialogAnnouncementUxWarn: "如果同時有 2 個以上對話方塊形式的公告存在,對於使用者體驗很可能會有不良的影響,因此建議謹慎使用。" dialogAnnouncementUxWarn: "如果同時有 2 個以上對話方塊形式的公告存在,對於使用者體驗很可能會有不良的影響,因此建議謹慎使用。"
silence: "不發送通知" silence: "不發送通知"
silenceDescription: "啟用此選項後,將不會發送此公告的通知,並且無需將其標記為已讀。" silenceDescription: "啟用此選項後,將不會發送此公告的通知,並且無需將其標記為已讀。"

View file

@ -1,6 +1,6 @@
{ {
"name": "sharkey", "name": "sharkey",
"version": "2024.2.0-beta2", "version": "2024.2.0-beta.9",
"codename": "shonk", "codename": "shonk",
"repository": { "repository": {
"type": "git", "type": "git",

View file

@ -3,6 +3,6 @@ import { genOpenapiSpec } from './built/server/api/openapi/gen-spec.js'
import { writeFileSync } from "node:fs"; import { writeFileSync } from "node:fs";
const config = loadConfig(); const config = loadConfig();
const spec = genOpenapiSpec(config); const spec = genOpenapiSpec(config, true);
writeFileSync('./built/api.json', JSON.stringify(spec), 'utf-8'); writeFileSync('./built/api.json', JSON.stringify(spec), 'utf-8');

View file

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class FixMetaDisableRegistration1706791962000 {
name = 'FixMetaDisableRegistration1706791962000'
async up(queryRunner) {
await queryRunner.query(`alter table meta alter column "disableRegistration" set default true;`);
}
async down(queryRunner) {
await queryRunner.query(`alter table meta alter column "disableRegistration" set default false;`);
}
}

View file

@ -65,9 +65,9 @@
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "3.412.0", "@aws-sdk/client-s3": "3.412.0",
"@aws-sdk/lib-storage": "3.412.0", "@aws-sdk/lib-storage": "3.412.0",
"@bull-board/api": "5.10.2", "@bull-board/api": "5.14.0",
"@bull-board/fastify": "5.10.2", "@bull-board/fastify": "5.14.0",
"@bull-board/ui": "5.10.2", "@bull-board/ui": "5.14.0",
"@discordapp/twemoji": "15.0.2", "@discordapp/twemoji": "15.0.2",
"@fastify/accepts": "4.3.0", "@fastify/accepts": "4.3.0",
"@fastify/cookie": "9.3.1", "@fastify/cookie": "9.3.1",
@ -84,11 +84,11 @@
"@nestjs/testing": "10.2.10", "@nestjs/testing": "10.2.10",
"@peertube/http-signature": "1.7.0", "@peertube/http-signature": "1.7.0",
"@transfem-org/sfm-js": "0.24.4", "@transfem-org/sfm-js": "0.24.4",
"@simplewebauthn/server": "9.0.0", "@simplewebauthn/server": "9.0.1",
"@sinonjs/fake-timers": "11.2.2", "@sinonjs/fake-timers": "11.2.2",
"@smithy/node-http-handler": "2.1.10", "@smithy/node-http-handler": "2.1.10",
"@swc/cli": "0.1.63", "@swc/cli": "0.1.63",
"@swc/core": "1.3.105", "@swc/core": "1.3.107",
"@twemoji/parser": "15.0.0", "@twemoji/parser": "15.0.0",
"accepts": "1.3.8", "accepts": "1.3.8",
"ajv": "8.12.0", "ajv": "8.12.0",
@ -98,7 +98,7 @@
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "2.0.5", "blurhash": "2.0.5",
"body-parser": "1.20.2", "body-parser": "1.20.2",
"bullmq": "5.1.4", "bullmq": "5.1.5",
"cacheable-lookup": "7.0.0", "cacheable-lookup": "7.0.0",
"cbor": "9.0.1", "cbor": "9.0.1",
"chalk": "5.3.0", "chalk": "5.3.0",
@ -117,7 +117,7 @@
"fluent-ffmpeg": "2.1.2", "fluent-ffmpeg": "2.1.2",
"form-data": "4.0.0", "form-data": "4.0.0",
"glob": "10.3.10", "glob": "10.3.10",
"got": "14.0.0", "got": "14.1.0",
"happy-dom": "10.0.3", "happy-dom": "10.0.3",
"hpagent": "1.2.0", "hpagent": "1.2.0",
"http-link-header": "1.1.1", "http-link-header": "1.1.1",
@ -148,7 +148,7 @@
"otpauth": "9.2.2", "otpauth": "9.2.2",
"parse5": "7.1.2", "parse5": "7.1.2",
"pg": "8.11.3", "pg": "8.11.3",
"pkce-challenge": "4.0.1", "pkce-challenge": "4.1.0",
"probe-image-size": "7.2.3", "probe-image-size": "7.2.3",
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
"pug": "3.0.2", "pug": "3.0.2",
@ -169,12 +169,12 @@
"slacc": "0.0.10", "slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0", "stringz": "2.1.0",
"systeminformation": "5.21.23", "systeminformation": "5.21.24",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tmp": "0.2.1", "tmp": "0.2.1",
"tsc-alias": "1.8.8", "tsc-alias": "1.8.8",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typeorm": "0.3.19", "typeorm": "0.3.20",
"typescript": "5.3.3", "typescript": "5.3.3",
"ulid": "2.3.0", "ulid": "2.3.0",
"uuid": "^9.0.1", "uuid": "^9.0.1",
@ -186,7 +186,7 @@
"devDependencies": { "devDependencies": {
"@jest/globals": "29.7.0", "@jest/globals": "29.7.0",
"@misskey-dev/eslint-plugin": "1.0.0", "@misskey-dev/eslint-plugin": "1.0.0",
"@nestjs/platform-express": "10.3.0", "@nestjs/platform-express": "10.3.1",
"@simplewebauthn/typescript-types": "8.3.4", "@simplewebauthn/typescript-types": "8.3.4",
"@swc/jest": "0.2.31", "@swc/jest": "0.2.31",
"@types/accepts": "1.3.7", "@types/accepts": "1.3.7",
@ -205,13 +205,13 @@
"@types/jsrsasign": "10.5.12", "@types/jsrsasign": "10.5.12",
"@types/mime-types": "2.1.4", "@types/mime-types": "2.1.4",
"@types/ms": "0.7.34", "@types/ms": "0.7.34",
"@types/node": "20.11.5", "@types/node": "20.11.10",
"@types/node-fetch": "3.0.3", "@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.14", "@types/nodemailer": "6.4.14",
"@types/oauth": "0.9.4", "@types/oauth": "0.9.4",
"@types/oauth2orize": "1.11.3", "@types/oauth2orize": "1.11.3",
"@types/oauth2orize-pkce": "0.1.2", "@types/oauth2orize-pkce": "0.1.2",
"@types/pg": "8.10.9", "@types/pg": "8.11.0",
"@types/pug": "2.0.10", "@types/pug": "2.0.10",
"@types/punycode": "2.1.3", "@types/punycode": "2.1.3",
"@types/qrcode": "1.5.5", "@types/qrcode": "1.5.5",

View file

@ -76,10 +76,10 @@ type Source = {
deliverJobConcurrency?: number; deliverJobConcurrency?: number;
inboxJobConcurrency?: number; inboxJobConcurrency?: number;
relashionshipJobConcurrency?: number; relationshipJobConcurrency?: number;
deliverJobPerSec?: number; deliverJobPerSec?: number;
inboxJobPerSec?: number; inboxJobPerSec?: number;
relashionshipJobPerSec?: number; relationshipJobPerSec?: number;
deliverJobMaxAttempts?: number; deliverJobMaxAttempts?: number;
inboxJobMaxAttempts?: number; inboxJobMaxAttempts?: number;
@ -141,10 +141,10 @@ export type Config = {
outgoingAddressFamily: 'ipv4' | 'ipv6' | 'dual' | undefined; outgoingAddressFamily: 'ipv4' | 'ipv6' | 'dual' | undefined;
deliverJobConcurrency: number | undefined; deliverJobConcurrency: number | undefined;
inboxJobConcurrency: number | undefined; inboxJobConcurrency: number | undefined;
relashionshipJobConcurrency: number | undefined; relationshipJobConcurrency: number | undefined;
deliverJobPerSec: number | undefined; deliverJobPerSec: number | undefined;
inboxJobPerSec: number | undefined; inboxJobPerSec: number | undefined;
relashionshipJobPerSec: number | undefined; relationshipJobPerSec: number | undefined;
deliverJobMaxAttempts: number | undefined; deliverJobMaxAttempts: number | undefined;
inboxJobMaxAttempts: number | undefined; inboxJobMaxAttempts: number | undefined;
proxyRemoteFiles: boolean | undefined; proxyRemoteFiles: boolean | undefined;
@ -257,10 +257,10 @@ export function loadConfig(): Config {
outgoingAddressFamily: config.outgoingAddressFamily, outgoingAddressFamily: config.outgoingAddressFamily,
deliverJobConcurrency: config.deliverJobConcurrency, deliverJobConcurrency: config.deliverJobConcurrency,
inboxJobConcurrency: config.inboxJobConcurrency, inboxJobConcurrency: config.inboxJobConcurrency,
relashionshipJobConcurrency: config.relashionshipJobConcurrency, relationshipJobConcurrency: config.relationshipJobConcurrency,
deliverJobPerSec: config.deliverJobPerSec, deliverJobPerSec: config.deliverJobPerSec,
inboxJobPerSec: config.inboxJobPerSec, inboxJobPerSec: config.inboxJobPerSec,
relashionshipJobPerSec: config.relashionshipJobPerSec, relationshipJobPerSec: config.relationshipJobPerSec,
deliverJobMaxAttempts: config.deliverJobMaxAttempts, deliverJobMaxAttempts: config.deliverJobMaxAttempts,
inboxJobMaxAttempts: config.inboxJobMaxAttempts, inboxJobMaxAttempts: config.inboxJobMaxAttempts,
proxyRemoteFiles: config.proxyRemoteFiles, proxyRemoteFiles: config.proxyRemoteFiles,

View file

@ -96,7 +96,7 @@ export class AccountMoveService {
await this.apDeliverManagerService.deliverToFollowers(src, moveAct); await this.apDeliverManagerService.deliverToFollowers(src, moveAct);
// Publish meUpdated event // Publish meUpdated event
const iObj = await this.userEntityService.pack<true, true>(src.id, src, { detail: true, includeSecrets: true }); const iObj = await this.userEntityService.pack(src.id, src, { schema: 'MeDetailed', includeSecrets: true });
this.globalEventService.publishMainStream(src.id, 'meUpdated', iObj); this.globalEventService.publishMainStream(src.id, 'meUpdated', iObj);
// Unfollow after 24 hours // Unfollow after 24 hours

View file

@ -54,15 +54,15 @@ export interface MainEventTypes {
reply: Packed<'Note'>; reply: Packed<'Note'>;
renote: Packed<'Note'>; renote: Packed<'Note'>;
follow: Packed<'UserDetailedNotMe'>; follow: Packed<'UserDetailedNotMe'>;
followed: Packed<'User'>; followed: Packed<'UserLite'>;
unfollow: Packed<'User'>; unfollow: Packed<'UserDetailedNotMe'>;
meUpdated: Packed<'User'>; meUpdated: Packed<'MeDetailed'>;
pageEvent: { pageEvent: {
pageId: MiPage['id']; pageId: MiPage['id'];
event: string; event: string;
var: any; var: any;
userId: MiUser['id']; userId: MiUser['id'];
user: Packed<'User'>; user: Packed<'UserDetailed'>;
}; };
urlUploadFinished: { urlUploadFinished: {
marker?: string | null; marker?: string | null;
@ -92,7 +92,7 @@ export interface MainEventTypes {
}; };
driveFileCreated: Packed<'DriveFile'>; driveFileCreated: Packed<'DriveFile'>;
readAntenna: MiAntenna; readAntenna: MiAntenna;
receiveFollowRequest: Packed<'User'>; receiveFollowRequest: Packed<'UserLite'>;
announcementCreated: { announcementCreated: {
announcement: Packed<'Announcement'>; announcement: Packed<'Announcement'>;
}; };
@ -143,8 +143,8 @@ type NoteStreamEventTypes = {
}; };
export interface UserListEventTypes { export interface UserListEventTypes {
userAdded: Packed<'User'>; userAdded: Packed<'UserLite'>;
userRemoved: Packed<'User'>; userRemoved: Packed<'UserLite'>;
} }
export interface AntennaEventTypes { export interface AntennaEventTypes {

View file

@ -109,13 +109,13 @@ export class UserBlockingService implements OnModuleInit {
if (this.userEntityService.isLocalUser(followee)) { if (this.userEntityService.isLocalUser(followee)) {
this.userEntityService.pack(followee, followee, { this.userEntityService.pack(followee, followee, {
detail: true, schema: 'MeDetailed',
}).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed)); }).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed));
} }
if (this.userEntityService.isLocalUser(follower) && !silent) { if (this.userEntityService.isLocalUser(follower) && !silent) {
this.userEntityService.pack(followee, follower, { this.userEntityService.pack(followee, follower, {
detail: true, schema: 'UserDetailedNotMe',
}).then(async packed => { }).then(async packed => {
this.globalEventService.publishMainStream(follower.id, 'unfollow', packed); this.globalEventService.publishMainStream(follower.id, 'unfollow', packed);

View file

@ -293,9 +293,9 @@ export class UserFollowingService implements OnModuleInit {
if (this.userEntityService.isLocalUser(follower) && !silent) { if (this.userEntityService.isLocalUser(follower) && !silent) {
// Publish follow event // Publish follow event
this.userEntityService.pack(followee.id, follower, { this.userEntityService.pack(followee.id, follower, {
detail: true, schema: 'UserDetailedNotMe',
}).then(async packed => { }).then(async packed => {
this.globalEventService.publishMainStream(follower.id, 'follow', packed as Packed<'UserDetailedNotMe'>); this.globalEventService.publishMainStream(follower.id, 'follow', packed);
const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('follow')); const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('follow'));
for (const webhook of webhooks) { for (const webhook of webhooks) {
@ -360,7 +360,7 @@ export class UserFollowingService implements OnModuleInit {
if (!silent && this.userEntityService.isLocalUser(follower)) { if (!silent && this.userEntityService.isLocalUser(follower)) {
// Publish unfollow event // Publish unfollow event
this.userEntityService.pack(followee.id, follower, { this.userEntityService.pack(followee.id, follower, {
detail: true, schema: 'UserDetailedNotMe',
}).then(async packed => { }).then(async packed => {
this.globalEventService.publishMainStream(follower.id, 'unfollow', packed); this.globalEventService.publishMainStream(follower.id, 'unfollow', packed);
@ -500,7 +500,7 @@ export class UserFollowingService implements OnModuleInit {
this.userEntityService.pack(follower.id, followee).then(packed => this.globalEventService.publishMainStream(followee.id, 'receiveFollowRequest', packed)); this.userEntityService.pack(follower.id, followee).then(packed => this.globalEventService.publishMainStream(followee.id, 'receiveFollowRequest', packed));
this.userEntityService.pack(followee.id, followee, { this.userEntityService.pack(followee.id, followee, {
detail: true, schema: 'MeDetailed',
}).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed)); }).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed));
// 通知を作成 // 通知を作成
@ -548,7 +548,7 @@ export class UserFollowingService implements OnModuleInit {
}); });
this.userEntityService.pack(followee.id, followee, { this.userEntityService.pack(followee.id, followee, {
detail: true, schema: 'MeDetailed',
}).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed)); }).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed));
} }
@ -576,7 +576,7 @@ export class UserFollowingService implements OnModuleInit {
} }
this.userEntityService.pack(followee.id, followee, { this.userEntityService.pack(followee.id, followee, {
detail: true, schema: 'MeDetailed',
}).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed)); }).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed));
} }
@ -696,7 +696,7 @@ export class UserFollowingService implements OnModuleInit {
@bindThis @bindThis
private async publishUnfollow(followee: Both, follower: Local): Promise<void> { private async publishUnfollow(followee: Both, follower: Local): Promise<void> {
const packedFollowee = await this.userEntityService.pack(followee.id, follower, { const packedFollowee = await this.userEntityService.pack(followee.id, follower, {
detail: true, schema: 'UserDetailedNotMe',
}); });
this.globalEventService.publishMainStream(follower.id, 'unfollow', packedFollowee); this.globalEventService.publishMainStream(follower.id, 'unfollow', packedFollowee);

View file

@ -94,6 +94,29 @@ type ToJsonSchema<S> = {
}; };
export function getJsonSchema<S extends Schema>(schema: S): ToJsonSchema<Unflatten<ChartResult<S>>> { export function getJsonSchema<S extends Schema>(schema: S): ToJsonSchema<Unflatten<ChartResult<S>>> {
const unflatten = (str: string, parent: Record<string, any>) => {
const keys = str.split('.');
const key = keys.shift();
const nextKey = keys[0];
if (key == null) return;
if (parent.properties[key] == null) {
parent.properties[key] = nextKey ? {
type: 'object',
properties: {},
required: [],
} : {
type: 'array',
items: {
type: 'number',
},
};
}
if (nextKey) unflatten(keys.join('.'), parent.properties[key] as Record<string, any>);
};
const jsonSchema = { const jsonSchema = {
type: 'object', type: 'object',
properties: {} as Record<string, unknown>, properties: {} as Record<string, unknown>,
@ -101,10 +124,7 @@ export function getJsonSchema<S extends Schema>(schema: S): ToJsonSchema<Unflatt
}; };
for (const k in schema) { for (const k in schema) {
jsonSchema.properties[k] = { unflatten(k, jsonSchema);
type: 'array',
items: { type: 'number' },
};
} }
return jsonSchema as ToJsonSchema<Unflatten<ChartResult<S>>>; return jsonSchema as ToJsonSchema<Unflatten<ChartResult<S>>>;

View file

@ -38,13 +38,13 @@ export class AbuseUserReportEntityService {
targetUserId: report.targetUserId, targetUserId: report.targetUserId,
assigneeId: report.assigneeId, assigneeId: report.assigneeId,
reporter: this.userEntityService.pack(report.reporter ?? report.reporterId, null, { reporter: this.userEntityService.pack(report.reporter ?? report.reporterId, null, {
detail: true, schema: 'UserDetailedNotMe',
}), }),
targetUser: this.userEntityService.pack(report.targetUser ?? report.targetUserId, null, { targetUser: this.userEntityService.pack(report.targetUser ?? report.targetUserId, null, {
detail: true, schema: 'UserDetailedNotMe',
}), }),
assignee: report.assigneeId ? this.userEntityService.pack(report.assignee ?? report.assigneeId, null, { assignee: report.assigneeId ? this.userEntityService.pack(report.assignee ?? report.assigneeId, null, {
detail: true, schema: 'UserDetailedNotMe',
}) : null, }) : null,
forwarded: report.forwarded, forwarded: report.forwarded,
}); });

View file

@ -37,7 +37,7 @@ export class BlockingEntityService {
createdAt: this.idService.parse(blocking.id).date.toISOString(), createdAt: this.idService.parse(blocking.id).date.toISOString(),
blockeeId: blocking.blockeeId, blockeeId: blocking.blockeeId,
blockee: this.userEntityService.pack(blocking.blockeeId, me, { blockee: this.userEntityService.pack(blocking.blockeeId, me, {
detail: true, schema: 'UserDetailedNotMe',
}), }),
}); });
} }

View file

@ -42,7 +42,7 @@ export class FlashEntityService {
createdAt: this.idService.parse(flash.id).date.toISOString(), createdAt: this.idService.parse(flash.id).date.toISOString(),
updatedAt: flash.updatedAt.toISOString(), updatedAt: flash.updatedAt.toISOString(),
userId: flash.userId, userId: flash.userId,
user: this.userEntityService.pack(flash.user ?? flash.userId, me), // { detail: true } すると無限ループするので注意 user: this.userEntityService.pack(flash.user ?? flash.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意
title: flash.title, title: flash.title,
summary: flash.summary, summary: flash.summary,
script: flash.script, script: flash.script,

View file

@ -89,10 +89,10 @@ export class FollowingEntityService {
followeeId: following.followeeId, followeeId: following.followeeId,
followerId: following.followerId, followerId: following.followerId,
followee: opts.populateFollowee ? this.userEntityService.pack(following.followee ?? following.followeeId, me, { followee: opts.populateFollowee ? this.userEntityService.pack(following.followee ?? following.followeeId, me, {
detail: true, schema: 'UserDetailedNotMe',
}) : undefined, }) : undefined,
follower: opts.populateFollower ? this.userEntityService.pack(following.follower ?? following.followerId, me, { follower: opts.populateFollower ? this.userEntityService.pack(following.follower ?? following.followerId, me, {
detail: true, schema: 'UserDetailedNotMe',
}) : undefined, }) : undefined,
}); });
} }

View file

@ -37,7 +37,7 @@ export class ModerationLogEntityService {
info: log.info, info: log.info,
userId: log.userId, userId: log.userId,
user: this.userEntityService.pack(log.user ?? log.userId, null, { user: this.userEntityService.pack(log.user ?? log.userId, null, {
detail: true, schema: 'UserDetailedNotMe',
}), }),
}); });
} }

View file

@ -39,7 +39,7 @@ export class MutingEntityService {
expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null, expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null,
muteeId: muting.muteeId, muteeId: muting.muteeId,
mutee: this.userEntityService.pack(muting.muteeId, me, { mutee: this.userEntityService.pack(muting.muteeId, me, {
detail: true, schema: 'UserDetailedNotMe',
}), }),
}); });
} }

View file

@ -181,7 +181,7 @@ export class NoteEntityService implements OnModuleInit {
return { return {
multiple: poll.multiple, multiple: poll.multiple,
expiresAt: poll.expiresAt, expiresAt: poll.expiresAt?.toISOString() ?? null,
choices, choices,
}; };
} }
@ -342,9 +342,7 @@ export class NoteEntityService implements OnModuleInit {
createdAt: this.idService.parse(note.id).date.toISOString(), createdAt: this.idService.parse(note.id).date.toISOString(),
updatedAt: note.updatedAt ? note.updatedAt.toISOString() : undefined, updatedAt: note.updatedAt ? note.updatedAt.toISOString() : undefined,
userId: note.userId, userId: note.userId,
user: this.userEntityService.pack(note.user ?? note.userId, me, { user: this.userEntityService.pack(note.user ?? note.userId, me),
detail: false,
}),
text: text, text: text,
cw: note.cw, cw: note.cw,
visibility: note.visibility, visibility: note.visibility,

View file

@ -62,7 +62,7 @@ export class NotificationEntityService implements OnModuleInit {
}, },
hint?: { hint?: {
packedNotes: Map<MiNote['id'], Packed<'Note'>>; packedNotes: Map<MiNote['id'], Packed<'Note'>>;
packedUsers: Map<MiUser['id'], Packed<'User'>>; packedUsers: Map<MiUser['id'], Packed<'UserLite'>>;
}, },
): Promise<Packed<'Notification'>> { ): Promise<Packed<'Notification'>> {
const notification = src; const notification = src;
@ -76,9 +76,7 @@ export class NotificationEntityService implements OnModuleInit {
const userIfNeed = 'notifierId' in notification ? ( const userIfNeed = 'notifierId' in notification ? (
hint?.packedUsers != null hint?.packedUsers != null
? hint.packedUsers.get(notification.notifierId) ? hint.packedUsers.get(notification.notifierId)
: this.userEntityService.pack(notification.notifierId, { id: meId }, { : this.userEntityService.pack(notification.notifierId, { id: meId })
detail: false,
})
) : undefined; ) : undefined;
const role = notification.type === 'roleAssigned' ? await this.roleEntityService.pack(notification.roleId) : undefined; const role = notification.type === 'roleAssigned' ? await this.roleEntityService.pack(notification.roleId) : undefined;
@ -131,9 +129,7 @@ export class NotificationEntityService implements OnModuleInit {
const users = userIds.length > 0 ? await this.usersRepository.find({ const users = userIds.length > 0 ? await this.usersRepository.find({
where: { id: In(userIds) }, where: { id: In(userIds) },
}) : []; }) : [];
const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }, { const packedUsersArray = await this.userEntityService.packMany(users, { id: meId });
detail: false,
});
const packedUsers = new Map(packedUsersArray.map(p => [p.id, p])); const packedUsers = new Map(packedUsersArray.map(p => [p.id, p]));
// 既に解決されたフォローリクエストの通知を除外 // 既に解決されたフォローリクエストの通知を除外
@ -161,7 +157,7 @@ export class NotificationEntityService implements OnModuleInit {
}, },
hint?: { hint?: {
packedNotes: Map<MiNote['id'], Packed<'Note'>>; packedNotes: Map<MiNote['id'], Packed<'Note'>>;
packedUsers: Map<MiUser['id'], Packed<'User'>>; packedUsers: Map<MiUser['id'], Packed<'UserLite'>>;
}, },
): Promise<Packed<'Notification'>> { ): Promise<Packed<'Notification'>> {
const notification = src; const notification = src;
@ -175,18 +171,14 @@ export class NotificationEntityService implements OnModuleInit {
const userIfNeed = 'notifierId' in notification ? ( const userIfNeed = 'notifierId' in notification ? (
hint?.packedUsers != null hint?.packedUsers != null
? hint.packedUsers.get(notification.notifierId) ? hint.packedUsers.get(notification.notifierId)
: this.userEntityService.pack(notification.notifierId, { id: meId }, { : this.userEntityService.pack(notification.notifierId, { id: meId })
detail: false,
})
) : undefined; ) : undefined;
if (notification.type === 'reaction:grouped') { if (notification.type === 'reaction:grouped') {
const reactions = await Promise.all(notification.reactions.map(async reaction => { const reactions = await Promise.all(notification.reactions.map(async reaction => {
const user = hint?.packedUsers != null const user = hint?.packedUsers != null
? hint.packedUsers.get(reaction.userId)! ? hint.packedUsers.get(reaction.userId)!
: await this.userEntityService.pack(reaction.userId, { id: meId }, { : await this.userEntityService.pack(reaction.userId, { id: meId });
detail: false,
});
return { return {
user, user,
reaction: reaction.reaction, reaction: reaction.reaction,
@ -206,9 +198,7 @@ export class NotificationEntityService implements OnModuleInit {
return packedUser; return packedUser;
} }
return this.userEntityService.pack(userId, { id: meId }, { return this.userEntityService.pack(userId, { id: meId });
detail: false,
});
})); }));
return await awaitAll({ return await awaitAll({
id: notification.id, id: notification.id,
@ -275,9 +265,7 @@ export class NotificationEntityService implements OnModuleInit {
const users = userIds.length > 0 ? await this.usersRepository.find({ const users = userIds.length > 0 ? await this.usersRepository.find({
where: { id: In(userIds) }, where: { id: In(userIds) },
}) : []; }) : [];
const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }, { const packedUsersArray = await this.userEntityService.packMany(users, { id: meId });
detail: false,
});
const packedUsers = new Map(packedUsersArray.map(p => [p.id, p])); const packedUsers = new Map(packedUsersArray.map(p => [p.id, p]));
// 既に解決されたフォローリクエストの通知を除外 // 既に解決されたフォローリクエストの通知を除外

View file

@ -90,7 +90,7 @@ export class PageEntityService {
createdAt: this.idService.parse(page.id).date.toISOString(), createdAt: this.idService.parse(page.id).date.toISOString(),
updatedAt: page.updatedAt.toISOString(), updatedAt: page.updatedAt.toISOString(),
userId: page.userId, userId: page.userId,
user: this.userEntityService.pack(page.user ?? page.userId, me), // { detail: true } すると無限ループするので注意 user: this.userEntityService.pack(page.user ?? page.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意
content: page.content, content: page.content,
variables: page.variables, variables: page.variables,
title: page.title, title: page.title,

View file

@ -38,7 +38,7 @@ export class RenoteMutingEntityService {
createdAt: this.idService.parse(muting.id).date.toISOString(), createdAt: this.idService.parse(muting.id).date.toISOString(),
muteeId: muting.muteeId, muteeId: muting.muteeId,
mutee: this.userEntityService.pack(muting.muteeId, me, { mutee: this.userEntityService.pack(muting.muteeId, me, {
detail: true, schema: 'UserDetailedNotMe',
}), }),
}); });
} }

View file

@ -30,14 +30,6 @@ import type { NoteEntityService } from './NoteEntityService.js';
import type { DriveFileEntityService } from './DriveFileEntityService.js'; import type { DriveFileEntityService } from './DriveFileEntityService.js';
import type { PageEntityService } from './PageEntityService.js'; import type { PageEntityService } from './PageEntityService.js';
type IsUserDetailed<Detailed extends boolean> = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>;
type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends boolean> =
Detailed extends true ?
ExpectsMe extends true ? Packed<'MeDetailed'> :
ExpectsMe extends false ? Packed<'UserDetailedNotMe'> :
Packed<'UserDetailed'> :
Packed<'UserLite'>;
const Ajv = _Ajv.default; const Ajv = _Ajv.default;
const ajv = new Ajv(); const ajv = new Ajv();
@ -304,17 +296,17 @@ export class UserEntityService implements OnModuleInit {
return `${this.config.url}/users/${userId}`; return `${this.config.url}/users/${userId}`;
} }
public async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>( public async pack<S extends 'MeDetailed' | 'UserDetailedNotMe' | 'UserDetailed' | 'UserLite' = 'UserLite'>(
src: MiUser['id'] | MiUser, src: MiUser['id'] | MiUser,
me?: { id: MiUser['id']; } | null | undefined, me?: { id: MiUser['id']; } | null | undefined,
options?: { options?: {
detail?: D, schema?: S,
includeSecrets?: boolean, includeSecrets?: boolean,
userProfile?: MiUserProfile, userProfile?: MiUserProfile,
}, },
): Promise<IsMeAndIsUserDetailed<ExpectsMe, D>> { ): Promise<Packed<S>> {
const opts = Object.assign({ const opts = Object.assign({
detail: false, schema: 'UserLite',
includeSecrets: false, includeSecrets: false,
}, options); }, options);
@ -346,19 +338,20 @@ export class UserEntityService implements OnModuleInit {
}); });
} }
const isDetailed = opts.schema !== 'UserLite';
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 MiUser) : false; const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false;
const relation = meId && !isMe && opts.detail ? await this.getRelation(meId, user.id) : null; const relation = meId && !isMe && isDetailed ? await this.getRelation(meId, user.id) : null;
const pins = opts.detail ? await this.userNotePiningsRepository.createQueryBuilder('pin') const pins = isDetailed ? await this.userNotePiningsRepository.createQueryBuilder('pin')
.where('pin.userId = :userId', { userId: user.id }) .where('pin.userId = :userId', { userId: user.id })
.innerJoinAndSelect('pin.note', 'note') .innerJoinAndSelect('pin.note', 'note')
.orderBy('pin.id', 'DESC') .orderBy('pin.id', 'DESC')
.getMany() : []; .getMany() : [];
const profile = opts.detail ? (opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id })) : null; const profile = isDetailed ? (opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id })) : null;
const mastoapi = !opts.detail ? opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id }) : null; const mastoapi = !isDetailed ? opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id }) : null;
const followingCount = profile == null ? null : const followingCount = profile == null ? null :
(profile.followingVisibility === 'public') || isMe ? user.followingCount : (profile.followingVisibility === 'public') || isMe ? user.followingCount :
@ -370,17 +363,16 @@ export class UserEntityService implements OnModuleInit {
(profile.followersVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount : (profile.followersVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount :
null; null;
const isModerator = isMe && opts.detail ? this.roleService.isModerator(user) : null; const isModerator = isMe && isDetailed ? this.roleService.isModerator(user) : null;
const isAdmin = isMe && opts.detail ? this.roleService.isAdministrator(user) : null; const isAdmin = isMe && isDetailed ? this.roleService.isAdministrator(user) : null;
const unreadAnnouncements = isMe && opts.detail ? const unreadAnnouncements = isMe && isDetailed ?
(await this.announcementService.getUnreadAnnouncements(user)).map((announcement) => ({ (await this.announcementService.getUnreadAnnouncements(user)).map((announcement) => ({
createdAt: this.idService.parse(announcement.id).date.toISOString(), createdAt: this.idService.parse(announcement.id).date.toISOString(),
...announcement, ...announcement,
})) : null; })) : null;
const checkHost = user.host == null ? this.config.host : user.host; const checkHost = user.host == null ? this.config.host : user.host;
const notificationsInfo = isMe && isDetailed ? await this.getNotificationsInfo(user.id) : null;
const notificationsInfo = isMe && opts.detail ? await this.getNotificationsInfo(user.id) : null;
const packed = { const packed = {
id: user.id, id: user.id,
@ -425,7 +417,7 @@ export class UserEntityService implements OnModuleInit {
displayOrder: r.displayOrder, displayOrder: r.displayOrder,
}))) : undefined, }))) : undefined,
...(opts.detail ? { ...(isDetailed ? {
url: profile!.url, url: profile!.url,
uri: user.uri, uri: user.uri,
movedTo: user.movedToUri ? this.apPersonService.resolvePerson(user.movedToUri).then(user => user.id).catch(() => null) : null, movedTo: user.movedToUri ? this.apPersonService.resolvePerson(user.movedToUri).then(user => user.id).catch(() => null) : null,
@ -453,7 +445,7 @@ export class UserEntityService implements OnModuleInit {
}), }),
pinnedPageId: profile!.pinnedPageId, pinnedPageId: profile!.pinnedPageId,
pinnedPage: profile!.pinnedPageId ? this.pageEntityService.pack(profile!.pinnedPageId, me) : null, pinnedPage: profile!.pinnedPageId ? this.pageEntityService.pack(profile!.pinnedPageId, me) : null,
publicReactions: profile!.publicReactions, publicReactions: this.isLocalUser(user) ? profile!.publicReactions : false, // https://github.com/misskey-dev/misskey/issues/12964
followersVisibility: profile!.followersVisibility, followersVisibility: profile!.followersVisibility,
followingVisibility: profile!.followingVisibility, followingVisibility: profile!.followingVisibility,
twoFactorEnabled: profile!.twoFactorEnabled, twoFactorEnabled: profile!.twoFactorEnabled,
@ -480,7 +472,7 @@ export class UserEntityService implements OnModuleInit {
moderationNote: iAmModerator ? (profile!.moderationNote ?? '') : undefined, moderationNote: iAmModerator ? (profile!.moderationNote ?? '') : undefined,
} : {}), } : {}),
...(opts.detail && isMe ? { ...(isDetailed && isMe ? {
avatarId: user.avatarId, avatarId: user.avatarId,
bannerId: user.bannerId, bannerId: user.bannerId,
backgroundId: user.backgroundId, backgroundId: user.backgroundId,
@ -554,19 +546,19 @@ export class UserEntityService implements OnModuleInit {
notify: relation.following?.notify ?? 'none', notify: relation.following?.notify ?? 'none',
withReplies: relation.following?.withReplies ?? false, withReplies: relation.following?.withReplies ?? false,
} : {}), } : {}),
} as Promiseable<Packed<'User'>> as Promiseable<IsMeAndIsUserDetailed<ExpectsMe, D>>; } as Promiseable<Packed<S>>;
return await awaitAll(packed); return await awaitAll(packed);
} }
public packMany<D extends boolean = false>( public packMany<S extends 'MeDetailed' | 'UserDetailedNotMe' | 'UserDetailed' | 'UserLite' = 'UserLite'>(
users: (MiUser['id'] | MiUser)[], users: (MiUser['id'] | MiUser)[],
me?: { id: MiUser['id'] } | null | undefined, me?: { id: MiUser['id'] } | null | undefined,
options?: { options?: {
detail?: D, schema?: S,
includeSecrets?: boolean, includeSecrets?: boolean,
}, },
): Promise<IsUserDetailed<D>[]> { ): Promise<Packed<S>[]> {
return Promise.all(users.map(u => this.pack(u, me, options))); return Promise.all(users.map(u => this.pack(u, me, options)));
} }
} }

View file

@ -6,7 +6,7 @@
// structredCloneが遅いため // structredCloneが遅いため
// SEE: http://var.blog.jp/archives/86038606.html // SEE: http://var.blog.jp/archives/86038606.html
type Cloneable = string | number | boolean | null | { [key: string]: Cloneable } | Cloneable[]; type Cloneable = string | number | boolean | null | undefined | { [key: string]: Cloneable } | Cloneable[];
export function deepClone<T extends Cloneable>(x: T): T { export function deepClone<T extends Cloneable>(x: T): T {
if (typeof x === 'object') { if (typeof x === 'object') {
@ -14,7 +14,7 @@ export function deepClone<T extends Cloneable>(x: T): T {
if (Array.isArray(x)) return x.map(deepClone) as T; if (Array.isArray(x)) return x.map(deepClone) as T;
const obj = {} as Record<string, Cloneable>; const obj = {} as Record<string, Cloneable>;
for (const [k, v] of Object.entries(x)) { for (const [k, v] of Object.entries(x)) {
obj[k] = deepClone(v); obj[k] = v === undefined ? undefined : deepClone(v);
} }
return obj as T; return obj as T;
} else { } else {

View file

@ -25,7 +25,7 @@ import { packedBlockingSchema } from '@/models/json-schema/blocking.js';
import { packedNoteReactionSchema } from '@/models/json-schema/note-reaction.js'; import { packedNoteReactionSchema } from '@/models/json-schema/note-reaction.js';
import { packedHashtagSchema } from '@/models/json-schema/hashtag.js'; import { packedHashtagSchema } from '@/models/json-schema/hashtag.js';
import { packedInviteCodeSchema } from '@/models/json-schema/invite-code.js'; import { packedInviteCodeSchema } from '@/models/json-schema/invite-code.js';
import { packedPageSchema } from '@/models/json-schema/page.js'; import { packedPageSchema, packedPageBlockSchema } from '@/models/json-schema/page.js';
import { packedNoteFavoriteSchema } from '@/models/json-schema/note-favorite.js'; import { packedNoteFavoriteSchema } from '@/models/json-schema/note-favorite.js';
import { packedChannelSchema } from '@/models/json-schema/channel.js'; import { packedChannelSchema } from '@/models/json-schema/channel.js';
import { packedAntennaSchema } from '@/models/json-schema/antenna.js'; import { packedAntennaSchema } from '@/models/json-schema/antenna.js';
@ -37,7 +37,7 @@ import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/jso
import { packedFlashSchema } from '@/models/json-schema/flash.js'; import { packedFlashSchema } from '@/models/json-schema/flash.js';
import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js'; import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
import { packedSigninSchema } from '@/models/json-schema/signin.js'; import { packedSigninSchema } from '@/models/json-schema/signin.js';
import { packedRoleLiteSchema, packedRoleSchema } from '@/models/json-schema/role.js'; import { packedRoleLiteSchema, packedRoleSchema, packedRolePoliciesSchema } from '@/models/json-schema/role.js';
import { packedAdSchema } from '@/models/json-schema/ad.js'; import { packedAdSchema } from '@/models/json-schema/ad.js';
import { packedReversiGameLiteSchema, packedReversiGameDetailedSchema } from '@/models/json-schema/reversi-game.js'; import { packedReversiGameLiteSchema, packedReversiGameDetailedSchema } from '@/models/json-schema/reversi-game.js';
@ -67,6 +67,7 @@ export const refs = {
Hashtag: packedHashtagSchema, Hashtag: packedHashtagSchema,
InviteCode: packedInviteCodeSchema, InviteCode: packedInviteCodeSchema,
Page: packedPageSchema, Page: packedPageSchema,
PageBlock: packedPageBlockSchema,
Channel: packedChannelSchema, Channel: packedChannelSchema,
QueueCount: packedQueueCountSchema, QueueCount: packedQueueCountSchema,
Antenna: packedAntennaSchema, Antenna: packedAntennaSchema,
@ -79,12 +80,16 @@ export const refs = {
Signin: packedSigninSchema, Signin: packedSigninSchema,
RoleLite: packedRoleLiteSchema, RoleLite: packedRoleLiteSchema,
Role: packedRoleSchema, Role: packedRoleSchema,
RolePolicies: packedRolePoliciesSchema,
ReversiGameLite: packedReversiGameLiteSchema, ReversiGameLite: packedReversiGameLiteSchema,
ReversiGameDetailed: packedReversiGameDetailedSchema, ReversiGameDetailed: packedReversiGameDetailedSchema,
}; };
export type Packed<x extends keyof typeof refs> = SchemaType<typeof refs[x]>; export type Packed<x extends keyof typeof refs> = SchemaType<typeof refs[x]>;
export type KeyOf<x extends keyof typeof refs> = PropertiesToUnion<typeof refs[x]>;
type PropertiesToUnion<p extends Schema> = p['properties'] extends NonNullable<Obj> ? keyof p['properties'] : never;
type TypeStringef = 'null' | 'boolean' | 'integer' | 'number' | 'string' | 'array' | 'object' | 'any'; type TypeStringef = 'null' | 'boolean' | 'integer' | 'number' | 'string' | 'array' | 'object' | 'any';
type StringDefToType<T extends TypeStringef> = type StringDefToType<T extends TypeStringef> =
T extends 'null' ? null : T extends 'null' ? null :
@ -114,6 +119,7 @@ export interface Schema extends OfSchema {
readonly example?: any; readonly example?: any;
readonly format?: string; readonly format?: string;
readonly ref?: keyof typeof refs; readonly ref?: keyof typeof refs;
readonly selfRef?: boolean;
readonly enum?: ReadonlyArray<string | null>; readonly enum?: ReadonlyArray<string | null>;
readonly default?: (this['type'] extends TypeStringef ? StringDefToType<this['type']> : any) | null; readonly default?: (this['type'] extends TypeStringef ? StringDefToType<this['type']> : any) | null;
readonly maxLength?: number; readonly maxLength?: number;

View file

@ -38,7 +38,7 @@ export class MiAnnouncement {
length: 256, nullable: false, length: 256, nullable: false,
default: 'info', default: 'info',
}) })
public icon: string; public icon: 'info' | 'warning' | 'error' | 'success';
// normal ... お知らせページ掲載 // normal ... お知らせページ掲載
// banner ... お知らせページ掲載 + バナー表示 // banner ... お知らせページ掲載 + バナー表示
@ -47,7 +47,7 @@ export class MiAnnouncement {
length: 256, nullable: false, length: 256, nullable: false,
default: 'normal', default: 'normal',
}) })
public display: string; public display: 'normal' | 'banner' | 'dialog';
@Column('boolean', { @Column('boolean', {
default: false, default: false,

View file

@ -37,10 +37,12 @@ export const packedAnnouncementSchema = {
icon: { icon: {
type: 'string', type: 'string',
optional: false, nullable: false, optional: false, nullable: false,
enum: ['info', 'warning', 'error', 'success'],
}, },
display: { display: {
type: 'string', type: 'string',
optional: false, nullable: false, optional: false, nullable: false,
enum: ['dialog', 'normal', 'banner'],
}, },
needConfirmationToRead: { needConfirmationToRead: {
type: 'boolean', type: 'boolean',

View file

@ -25,7 +25,7 @@ export const packedBlockingSchema = {
blockee: { blockee: {
type: 'object', type: 'object',
optional: false, nullable: false, optional: false, nullable: false,
ref: 'UserDetailed', ref: 'UserDetailedNotMe',
}, },
}, },
} as const; } as const;

View file

@ -30,12 +30,12 @@ export const packedFollowingSchema = {
followee: { followee: {
type: 'object', type: 'object',
optional: true, nullable: false, optional: true, nullable: false,
ref: 'UserDetailed', ref: 'UserDetailedNotMe',
}, },
follower: { follower: {
type: 'object', type: 'object',
optional: true, nullable: false, optional: true, nullable: false,
ref: 'UserDetailed', ref: 'UserDetailedNotMe',
}, },
}, },
} as const; } as const;

View file

@ -30,7 +30,7 @@ export const packedMutingSchema = {
mutee: { mutee: {
type: 'object', type: 'object',
optional: false, nullable: false, optional: false, nullable: false,
ref: 'UserDetailed', ref: 'UserDetailedNotMe',
}, },
}, },
} as const; } as const;

View file

@ -69,6 +69,7 @@ export const packedNoteSchema = {
visibility: { visibility: {
type: 'string', type: 'string',
optional: false, nullable: false, optional: false, nullable: false,
enum: ['public', 'home', 'followers', 'specified'],
}, },
mentions: { mentions: {
type: 'array', type: 'array',
@ -117,6 +118,48 @@ export const packedNoteSchema = {
poll: { poll: {
type: 'object', type: 'object',
optional: true, nullable: true, optional: true, nullable: true,
properties: {
expiresAt: {
type: 'string',
optional: true, nullable: true,
format: 'date-time',
},
multiple: {
type: 'boolean',
optional: false, nullable: false,
},
choices: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
properties: {
isVoted: {
type: 'boolean',
optional: false, nullable: false,
},
text: {
type: 'string',
optional: false, nullable: false,
},
votes: {
type: 'number',
optional: false, nullable: false,
},
},
},
},
},
},
emojis: {
type: 'object',
optional: true, nullable: false,
additionalProperties: {
anyOf: [{
type: 'string',
}],
},
}, },
channelId: { channelId: {
type: 'string', type: 'string',
@ -162,9 +205,23 @@ export const packedNoteSchema = {
type: 'string', type: 'string',
optional: false, nullable: true, optional: false, nullable: true,
}, },
reactionEmojis: {
type: 'object',
optional: false, nullable: false,
additionalProperties: {
anyOf: [{
type: 'string',
}],
},
},
reactions: { reactions: {
type: 'object', type: 'object',
optional: false, nullable: false, optional: false, nullable: false,
additionalProperties: {
anyOf: [{
type: 'number',
}],
},
}, },
renoteCount: { renoteCount: {
type: 'number', type: 'number',
@ -196,7 +253,7 @@ export const packedNoteSchema = {
}, },
myReaction: { myReaction: {
type: 'object', type: 'string',
optional: true, nullable: true, optional: true, nullable: true,
}, },
}, },

View file

@ -5,7 +5,7 @@
import { notificationTypes } from '@/types.js'; import { notificationTypes } from '@/types.js';
export const packedNotificationSchema = { const baseSchema = {
type: 'object', type: 'object',
properties: { properties: {
id: { id: {
@ -23,68 +23,368 @@ export const packedNotificationSchema = {
optional: false, nullable: false, optional: false, nullable: false,
enum: [...notificationTypes, 'reaction:grouped', 'renote:grouped'], enum: [...notificationTypes, 'reaction:grouped', 'renote:grouped'],
}, },
user: { },
type: 'object', } as const;
ref: 'UserLite',
optional: true, nullable: true, export const packedNotificationSchema = {
}, type: 'object',
userId: { oneOf: [{
type: 'string', type: 'object',
optional: true, nullable: true, properties: {
format: 'id', ...baseSchema.properties,
}, type: {
note: { type: 'string',
type: 'object', optional: false, nullable: false,
ref: 'Note', enum: ['note'],
optional: true, nullable: true,
},
reaction: {
type: 'string',
optional: true, nullable: true,
},
achievement: {
type: 'string',
optional: true, nullable: false,
},
body: {
type: 'string',
optional: true, nullable: true,
},
header: {
type: 'string',
optional: true, nullable: true,
},
icon: {
type: 'string',
optional: true, nullable: true,
},
reactions: {
type: 'array',
optional: true, nullable: true,
items: {
type: 'object',
properties: {
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
reaction: {
type: 'string',
optional: false, nullable: false,
},
},
required: ['user', 'reaction'],
}, },
}, user: {
users: {
type: 'array',
optional: true, nullable: true,
items: {
type: 'object', type: 'object',
ref: 'UserLite', ref: 'UserLite',
optional: false, nullable: false, optional: false, nullable: false,
}, },
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
note: {
type: 'object',
ref: 'Note',
optional: false, nullable: false,
},
}, },
}, }, {
type: 'object',
properties: {
...baseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['mention'],
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
note: {
type: 'object',
ref: 'Note',
optional: false, nullable: false,
},
},
}, {
type: 'object',
properties: {
...baseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['reply'],
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
note: {
type: 'object',
ref: 'Note',
optional: false, nullable: false,
},
},
}, {
type: 'object',
properties: {
...baseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['renote'],
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
note: {
type: 'object',
ref: 'Note',
optional: false, nullable: false,
},
},
}, {
type: 'object',
properties: {
...baseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['quote'],
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
note: {
type: 'object',
ref: 'Note',
optional: false, nullable: false,
},
},
}, {
type: 'object',
properties: {
...baseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['reaction'],
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
note: {
type: 'object',
ref: 'Note',
optional: false, nullable: false,
},
reaction: {
type: 'string',
optional: false, nullable: false,
},
},
}, {
type: 'object',
properties: {
...baseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['pollEnded'],
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
note: {
type: 'object',
ref: 'Note',
optional: false, nullable: false,
},
},
}, {
type: 'object',
properties: {
...baseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['follow'],
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
}, {
type: 'object',
properties: {
...baseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['receiveFollowRequest'],
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
}, {
type: 'object',
properties: {
...baseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['followRequestAccepted'],
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
}, {
type: 'object',
properties: {
...baseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['roleAssigned'],
},
role: {
type: 'object',
ref: 'Role',
optional: false, nullable: false,
},
},
}, {
type: 'object',
properties: {
...baseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['achievementEarned'],
},
achievement: {
type: 'string',
optional: false, nullable: false,
},
},
}, {
type: 'object',
properties: {
...baseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['app'],
},
body: {
type: 'string',
optional: false, nullable: false,
},
header: {
type: 'string',
optional: false, nullable: false,
},
icon: {
type: 'string',
optional: false, nullable: false,
},
},
}, {
type: 'object',
properties: {
...baseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['reaction:grouped'],
},
note: {
type: 'object',
ref: 'Note',
optional: false, nullable: false,
},
reactions: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
properties: {
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
reaction: {
type: 'string',
optional: false, nullable: false,
},
},
required: ['user', 'reaction'],
},
},
},
}, {
type: 'object',
properties: {
...baseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['renote:grouped'],
},
note: {
type: 'object',
ref: 'Note',
optional: false, nullable: false,
},
users: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
},
},
}, {
type: 'object',
properties: {
...baseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['test'],
},
},
}],
} as const; } as const;

View file

@ -3,6 +3,108 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
const blockBaseSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
},
type: {
type: 'string',
optional: false, nullable: false,
},
},
} as const;
const textBlockSchema = {
type: 'object',
properties: {
...blockBaseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['text'],
},
text: {
type: 'string',
optional: false, nullable: false,
},
},
} as const;
const sectionBlockSchema = {
type: 'object',
properties: {
...blockBaseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['section'],
},
title: {
type: 'string',
optional: false, nullable: false,
},
children: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'PageBlock',
selfRef: true,
},
},
},
} as const;
const imageBlockSchema = {
type: 'object',
properties: {
...blockBaseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['image'],
},
fileId: {
type: 'string',
optional: false, nullable: true,
},
},
} as const;
const noteBlockSchema = {
type: 'object',
properties: {
...blockBaseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['note'],
},
detailed: {
type: 'boolean',
optional: false, nullable: false,
},
note: {
type: 'string',
optional: false, nullable: true,
},
},
} as const;
export const packedPageBlockSchema = {
type: 'object',
oneOf: [
textBlockSchema,
sectionBlockSchema,
imageBlockSchema,
noteBlockSchema,
],
} as const;
export const packedPageSchema = { export const packedPageSchema = {
type: 'object', type: 'object',
properties: { properties: {
@ -38,6 +140,7 @@ export const packedPageSchema = {
items: { items: {
type: 'object', type: 'object',
optional: false, nullable: false, optional: false, nullable: false,
ref: 'PageBlock',
}, },
}, },
variables: { variables: {

View file

@ -25,7 +25,7 @@ export const packedRenoteMutingSchema = {
mutee: { mutee: {
type: 'object', type: 'object',
optional: false, nullable: false, optional: false, nullable: false,
ref: 'UserDetailed', ref: 'UserDetailedNotMe',
}, },
}, },
} as const; } as const;

View file

@ -1,26 +1,103 @@
const rolePolicyValue = { export const packedRolePoliciesSchema = {
type: 'object', type: 'object',
optional: false, nullable: false,
properties: { properties: {
value: { gtlAvailable: {
oneOf: [ type: 'boolean',
{ optional: false, nullable: false,
type: 'integer',
optional: false, nullable: false,
},
{
type: 'boolean',
optional: false, nullable: false,
},
],
}, },
priority: { ltlAvailable: {
type: 'boolean',
optional: false, nullable: false,
},
canPublicNote: {
type: 'boolean',
optional: false, nullable: false,
},
canInvite: {
type: 'boolean',
optional: false, nullable: false,
},
inviteLimit: {
type: 'integer', type: 'integer',
optional: false, nullable: false, optional: false, nullable: false,
}, },
useDefault: { inviteLimitCycle: {
type: 'integer',
optional: false, nullable: false,
},
inviteExpirationTime: {
type: 'integer',
optional: false, nullable: false,
},
canManageCustomEmojis: {
type: 'boolean', type: 'boolean',
optional: false, nullable: false, optional: false, nullable: false,
}, },
canManageAvatarDecorations: {
type: 'boolean',
optional: false, nullable: false,
},
canSearchNotes: {
type: 'boolean',
optional: false, nullable: false,
},
canUseTranslator: {
type: 'boolean',
optional: false, nullable: false,
},
canHideAds: {
type: 'boolean',
optional: false, nullable: false,
},
driveCapacityMb: {
type: 'integer',
optional: false, nullable: false,
},
alwaysMarkNsfw: {
type: 'boolean',
optional: false, nullable: false,
},
pinLimit: {
type: 'integer',
optional: false, nullable: false,
},
antennaLimit: {
type: 'integer',
optional: false, nullable: false,
},
wordMuteLimit: {
type: 'integer',
optional: false, nullable: false,
},
webhookLimit: {
type: 'integer',
optional: false, nullable: false,
},
clipLimit: {
type: 'integer',
optional: false, nullable: false,
},
noteEachClipsLimit: {
type: 'integer',
optional: false, nullable: false,
},
userListLimit: {
type: 'integer',
optional: false, nullable: false,
},
userEachUserListsLimit: {
type: 'integer',
optional: false, nullable: false,
},
rateLimitFactor: {
type: 'integer',
optional: false, nullable: false,
},
avatarDecorationLimit: {
type: 'integer',
optional: false, nullable: false,
},
}, },
} as const; } as const;
@ -121,31 +198,28 @@ export const packedRoleSchema = {
policies: { policies: {
type: 'object', type: 'object',
optional: false, nullable: false, optional: false, nullable: false,
properties: { additionalProperties: {
pinLimit: rolePolicyValue, anyOf: [{
canInvite: rolePolicyValue, type: 'object',
clipLimit: rolePolicyValue, properties: {
canHideAds: rolePolicyValue, value: {
inviteLimit: rolePolicyValue, oneOf: [
antennaLimit: rolePolicyValue, {
gtlAvailable: rolePolicyValue, type: 'integer',
ltlAvailable: rolePolicyValue, },
webhookLimit: rolePolicyValue, {
canPublicNote: rolePolicyValue, type: 'boolean',
userListLimit: rolePolicyValue, },
wordMuteLimit: rolePolicyValue, ],
alwaysMarkNsfw: rolePolicyValue, },
canSearchNotes: rolePolicyValue, priority: {
driveCapacityMb: rolePolicyValue, type: 'integer',
rateLimitFactor: rolePolicyValue, },
inviteLimitCycle: rolePolicyValue, useDefault: {
noteEachClipsLimit: rolePolicyValue, type: 'boolean',
inviteExpirationTime: rolePolicyValue, },
canManageCustomEmojis: rolePolicyValue, },
userEachUserListsLimit: rolePolicyValue, }],
canManageAvatarDecorations: rolePolicyValue,
canUseTranslator: rolePolicyValue,
avatarDecorationLimit: rolePolicyValue,
}, },
}, },
usersCount: { usersCount: {

View file

@ -628,104 +628,7 @@ export const packedMeDetailedOnlySchema = {
policies: { policies: {
type: 'object', type: 'object',
nullable: false, optional: false, nullable: false, optional: false,
properties: { ref: 'RolePolicies',
gtlAvailable: {
type: 'boolean',
nullable: false, optional: false,
},
ltlAvailable: {
type: 'boolean',
nullable: false, optional: false,
},
canPublicNote: {
type: 'boolean',
nullable: false, optional: false,
},
canInvite: {
type: 'boolean',
nullable: false, optional: false,
},
inviteLimit: {
type: 'number',
nullable: false, optional: false,
},
inviteLimitCycle: {
type: 'number',
nullable: false, optional: false,
},
inviteExpirationTime: {
type: 'number',
nullable: false, optional: false,
},
canManageCustomEmojis: {
type: 'boolean',
nullable: false, optional: false,
},
canManageAvatarDecorations: {
type: 'boolean',
nullable: false, optional: false,
},
canSearchNotes: {
type: 'boolean',
nullable: false, optional: false,
},
canUseTranslator: {
type: 'boolean',
nullable: false, optional: false,
},
canHideAds: {
type: 'boolean',
nullable: false, optional: false,
},
driveCapacityMb: {
type: 'number',
nullable: false, optional: false,
},
alwaysMarkNsfw: {
type: 'boolean',
nullable: false, optional: false,
},
pinLimit: {
type: 'number',
nullable: false, optional: false,
},
antennaLimit: {
type: 'number',
nullable: false, optional: false,
},
wordMuteLimit: {
type: 'number',
nullable: false, optional: false,
},
webhookLimit: {
type: 'number',
nullable: false, optional: false,
},
clipLimit: {
type: 'number',
nullable: false, optional: false,
},
noteEachClipsLimit: {
type: 'number',
nullable: false, optional: false,
},
userListLimit: {
type: 'number',
nullable: false, optional: false,
},
userEachUserListsLimit: {
type: 'number',
nullable: false, optional: false,
},
rateLimitFactor: {
type: 'number',
nullable: false, optional: false,
},
avatarDecorationLimit: {
type: 'number',
nullable: false, optional: false,
},
},
}, },
//#region secrets //#region secrets
email: { email: {
@ -820,13 +723,5 @@ export const packedUserSchema = {
type: 'object', type: 'object',
ref: 'UserDetailed', ref: 'UserDetailed',
}, },
{
type: 'object',
ref: 'UserDetailedNotMe',
},
{
type: 'object',
ref: 'MeDetailed',
},
], ],
} as const; } as const;

View file

@ -295,9 +295,9 @@ export class QueueProcessorService implements OnApplicationShutdown {
}, { }, {
...baseQueueOptions(this.config, QUEUE.RELATIONSHIP), ...baseQueueOptions(this.config, QUEUE.RELATIONSHIP),
autorun: false, autorun: false,
concurrency: this.config.relashionshipJobConcurrency ?? 16, concurrency: this.config.relationshipJobConcurrency ?? 16,
limiter: { limiter: {
max: this.config.relashionshipJobPerSec ?? 64, max: this.config.relationshipJobPerSec ?? 64,
duration: 1000, duration: 1000,
}, },
}); });

View file

@ -206,7 +206,7 @@ export class ServerService implements OnApplicationShutdown {
}); });
this.globalEventService.publishMainStream(profile.userId, 'meUpdated', await this.userEntityService.pack(profile.userId, { id: profile.userId }, { this.globalEventService.publishMainStream(profile.userId, 'meUpdated', await this.userEntityService.pack(profile.userId, { id: profile.userId }, {
detail: true, schema: 'MeDetailed',
includeSecrets: true, includeSecrets: true,
})); }));

View file

@ -157,7 +157,7 @@ export class ApiServerService {
return { return {
ok: true, ok: true,
token: token.token, token: token.token,
user: await this.userEntityService.pack(token.userId, null, { detail: true }), user: await this.userEntityService.pack(token.userId, null, { schema: 'UserDetailedNotMe' }),
}; };
} else { } else {
return { return {

View file

@ -260,7 +260,7 @@ export class SignupApiService {
}); });
const res = await this.userEntityService.pack(account, account, { const res = await this.userEntityService.pack(account, account, {
detail: true, schema: 'MeDetailed',
includeSecrets: true, includeSecrets: true,
}); });

View file

@ -4,8 +4,7 @@
*/ */
import { permissions } from 'misskey-js'; import { permissions } from 'misskey-js';
import type { Schema } from '@/misc/json-schema.js'; import type { KeyOf, Schema } from '@/misc/json-schema.js';
import { RolePolicies } from '@/core/RoleService.js';
import * as ep___admin_meta from './endpoints/admin/meta.js'; import * as ep___admin_meta from './endpoints/admin/meta.js';
import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js'; import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
@ -802,7 +801,7 @@ interface IEndpointMetaBase {
*/ */
readonly requireAdmin?: boolean; readonly requireAdmin?: boolean;
readonly requireRolePolicy?: keyof RolePolicies; readonly requireRolePolicy?: KeyOf<'RolePolicies'>;
/** /**
* *

View file

@ -62,17 +62,17 @@ export const meta = {
reporter: { reporter: {
type: 'object', type: 'object',
nullable: false, optional: false, nullable: false, optional: false,
ref: 'User', ref: 'UserDetailedNotMe',
}, },
targetUser: { targetUser: {
type: 'object', type: 'object',
nullable: false, optional: false, nullable: false, optional: false,
ref: 'User', ref: 'UserDetailedNotMe',
}, },
assignee: { assignee: {
type: 'object', type: 'object',
nullable: true, optional: true, nullable: true, optional: true,
ref: 'User', ref: 'UserDetailedNotMe',
}, },
}, },
}, },

View file

@ -11,6 +11,7 @@ import { SignupService } from '@/core/SignupService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { localUsernameSchema, passwordSchema } from '@/models/User.js'; import { localUsernameSchema, passwordSchema } from '@/models/User.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { Packed } from '@/misc/json-schema.js';
export const meta = { export const meta = {
tags: ['admin'], tags: ['admin'],
@ -18,7 +19,7 @@ export const meta = {
res: { res: {
type: 'object', type: 'object',
optional: false, nullable: false, optional: false, nullable: false,
ref: 'User', ref: 'MeDetailed',
properties: { properties: {
token: { token: {
type: 'string', type: 'string',
@ -60,11 +61,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}); });
const res = await this.userEntityService.pack(account, account, { const res = await this.userEntityService.pack(account, account, {
detail: true, schema: 'MeDetailed',
includeSecrets: true, includeSecrets: true,
}); }) as Packed<'MeDetailed'> & { token: string };
(res as any).token = secret; res.token = secret;
return res; return res;
}); });

View file

@ -27,7 +27,7 @@ export const meta = {
res: { res: {
type: 'object', type: 'object',
optional: false, nullable: false, optional: false, nullable: false,
ref: 'User', ref: 'UserDetailedNotMe',
}, },
} as const; } as const;
@ -58,7 +58,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
} }
const res = await this.userEntityService.pack(profile.user!, null, { const res = await this.userEntityService.pack(profile.user!, null, {
detail: true, schema: 'UserDetailedNotMe',
}); });
return res; return res;

View file

@ -40,7 +40,7 @@ export const meta = {
}, },
required: ['id', 'createdAt', 'user'], required: ['id', 'createdAt', 'user'],
}, },
} },
} as const; } as const;
export const paramDef = { export const paramDef = {
@ -92,7 +92,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
return await Promise.all(assigns.map(async assign => ({ return await Promise.all(assigns.map(async assign => ({
id: assign.id, id: assign.id,
createdAt: this.idService.parse(assign.id).date.toISOString(), createdAt: this.idService.parse(assign.id).date.toISOString(),
user: await this.userEntityService.pack(assign.user!, me, { detail: true }), user: await this.userEntityService.pack(assign.user!, me, { schema: 'UserDetailed' }),
expiresAt: assign.expiresAt?.toISOString() ?? null, expiresAt: assign.expiresAt?.toISOString() ?? null,
}))); })));
}); });

View file

@ -50,7 +50,7 @@ export const meta = {
user: { user: {
type: 'object', type: 'object',
optional: false, nullable: false, optional: false, nullable: false,
ref: 'UserDetailed', ref: 'UserDetailedNotMe',
}, },
}, },
}, },

View file

@ -115,7 +115,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const users = await query.getMany(); const users = await query.getMany();
return await this.userEntityService.packMany(users, me, { detail: true }); return await this.userEntityService.packMany(users, me, { schema: 'UserDetailed' });
}); });
} }
} }

View file

@ -148,7 +148,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (user != null) { if (user != null) {
return { return {
type: 'User', type: 'User',
object: await this.userEntityService.pack(user, me, { detail: true }), object: await this.userEntityService.pack(user, me, { schema: 'UserDetailedNotMe' }),
}; };
} else if (note != null) { } else if (note != null) {
try { try {

View file

@ -112,7 +112,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
return { return {
accessToken: accessToken.token, accessToken: accessToken.token,
user: await this.userEntityService.pack(session.userId, null, { user: await this.userEntityService.pack(session.userId, null, {
detail: true, schema: 'UserDetailedNotMe',
}), }),
}; };
}); });

View file

@ -102,7 +102,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
await this.userBlockingService.block(blocker, blockee); await this.userBlockingService.block(blocker, blockee);
return await this.userEntityService.pack(blockee.id, blocker, { return await this.userEntityService.pack(blockee.id, blocker, {
detail: true, schema: 'UserDetailedNotMe',
}); });
}); });
} }

View file

@ -103,7 +103,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
await this.userBlockingService.unblock(blocker, blockee); await this.userBlockingService.unblock(blocker, blockee);
return await this.userEntityService.pack(blockee.id, blocker, { return await this.userEntityService.pack(blockee.id, blocker, {
detail: true, schema: 'UserDetailedNotMe',
}); });
}); });
} }

View file

@ -61,7 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
relations: ['user'], relations: ['user'],
}); });
const users = await this.userEntityService.packMany(records.map(r => r.user!), null, { detail: false }); const users = await this.userEntityService.packMany(records.map(r => r.user!), null);
return records.map(r => ({ return records.map(r => ({
id: r.id, id: r.id,

View file

@ -54,7 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.limit(ps.limit) .limit(ps.limit)
.getMany(); .getMany();
return await this.userEntityService.packMany(users, me, { detail: true }); return await this.userEntityService.packMany(users, me, { schema: 'UserDetailedNotMe' });
}); });
} }
} }

View file

@ -76,7 +76,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const users = await query.limit(ps.limit).getMany(); const users = await query.limit(ps.limit).getMany();
return await this.userEntityService.packMany(users, me, { detail: true }); return await this.userEntityService.packMany(users, me, { schema: 'UserDetailed' });
}); });
} }
} }

View file

@ -71,8 +71,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
userProfile.loggedInDates = [...userProfile.loggedInDates, today]; userProfile.loggedInDates = [...userProfile.loggedInDates, today];
} }
return await this.userEntityService.pack<true, true>(userProfile.user!, userProfile.user!, { return await this.userEntityService.pack(userProfile.user!, userProfile.user!, {
detail: true, schema: 'MeDetailed',
includeSecrets: isSecure, includeSecrets: isSecure,
userProfile, userProfile,
}); });

View file

@ -64,7 +64,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
// Publish meUpdated event // Publish meUpdated event
this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
detail: true, schema: 'MeDetailed',
includeSecrets: true, includeSecrets: true,
})); }));

View file

@ -112,7 +112,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
// Publish meUpdated event // Publish meUpdated event
this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
detail: true, schema: 'MeDetailed',
includeSecrets: true, includeSecrets: true,
})); }));

View file

@ -74,7 +74,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
// Publish meUpdated event // Publish meUpdated event
this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
detail: true, schema: 'MeDetailed',
includeSecrets: true, includeSecrets: true,
})); }));
}); });

View file

@ -99,7 +99,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
// Publish meUpdated event // Publish meUpdated event
this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
detail: true, schema: 'MeDetailed',
includeSecrets: true, includeSecrets: true,
})); }));

View file

@ -77,7 +77,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
// Publish meUpdated event // Publish meUpdated event
this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
detail: true, schema: 'MeDetailed',
includeSecrets: true, includeSecrets: true,
})); }));
}); });

View file

@ -69,7 +69,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
// Publish meUpdated event // Publish meUpdated event
this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
detail: true, schema: 'MeDetailed',
includeSecrets: true, includeSecrets: true,
})); }));

View file

@ -66,8 +66,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw err; throw err;
}); });
return await this.userEntityService.pack<true, true>(me.id, me, { return await this.userEntityService.pack(me.id, me, {
detail: true, schema: 'MeDetailed',
}); });
}); });
} }

View file

@ -51,8 +51,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw err; throw err;
}); });
return await this.userEntityService.pack<true, true>(me.id, me, { return await this.userEntityService.pack(me.id, me, {
detail: true, schema: 'MeDetailed',
}); });
}); });
} }

View file

@ -44,7 +44,7 @@ export const meta = {
res: { res: {
type: 'object', type: 'object',
ref: 'UserDetailed', ref: 'MeDetailed',
}, },
} as const; } as const;
@ -107,7 +107,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}); });
const iObj = await this.userEntityService.pack(me.id, me, { const iObj = await this.userEntityService.pack(me.id, me, {
detail: true, schema: 'MeDetailed',
includeSecrets: true, includeSecrets: true,
}); });

View file

@ -478,8 +478,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
verifiedLinks: [], verifiedLinks: [],
}); });
const iObj = await this.userEntityService.pack<true, true>(user.id, user, { const iObj = await this.userEntityService.pack(user.id, user, {
detail: true, schema: 'MeDetailed',
includeSecrets: isSecure, includeSecrets: isSecure,
}); });

View file

@ -314,6 +314,11 @@ export const meta = {
type: 'string', type: 'string',
optional: false, nullable: true, optional: false, nullable: true,
}, },
policies: {
type: 'object',
optional: false, nullable: false,
ref: 'RolePolicies',
},
}, },
}, },
} as const; } as const;

View file

@ -55,7 +55,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
var: ps.var, var: ps.var,
userId: me.id, userId: me.id,
user: await this.userEntityService.pack(me.id, { id: page.userId }, { user: await this.userEntityService.pack(me.id, { id: page.userId }, {
detail: true, schema: 'UserDetailed',
}), }),
}); });
}); });

View file

@ -52,7 +52,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
host: acct.host ?? IsNull(), host: acct.host ?? IsNull(),
}))); })));
return await this.userEntityService.packMany(users.filter(x => x !== null) as MiUser[], me, { detail: true }); return await this.userEntityService.packMany(users.filter(x => x !== null) as MiUser[], me, { schema: 'UserDetailed' });
}); });
} }
} }

View file

@ -14,6 +14,32 @@ export const meta = {
requireCredential: false, requireCredential: false,
res: { res: {
type: 'array',
items: {
type: 'object',
properties: {
createdAt: {
type: 'string',
format: 'date-time',
},
users: {
type: 'number',
},
data: {
type: 'object',
additionalProperties: {
anyOf: [{
type: 'number',
}],
},
},
},
required: [
'createdAt',
'users',
'data',
],
},
}, },
allowGet: true, allowGet: true,

View file

@ -33,11 +33,11 @@ export const meta = {
properties: { properties: {
id: { id: {
type: 'string', type: 'string',
format: 'misskey:id' format: 'misskey:id',
}, },
user: { user: {
type: 'object', type: 'object',
ref: 'User' ref: 'UserDetailed',
}, },
}, },
required: ['id', 'user'], required: ['id', 'user'],
@ -94,7 +94,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
return await Promise.all(assigns.map(async assign => ({ return await Promise.all(assigns.map(async assign => ({
id: assign.id, id: assign.id,
user: await this.userEntityService.pack(assign.user!, me, { detail: true }), user: await this.userEntityService.pack(assign.user!, me, { schema: 'UserDetailed' }),
}))); })));
}); });
} }

View file

@ -89,7 +89,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const users = await query.getMany(); const users = await query.getMany();
return await this.userEntityService.packMany(users, me, { detail: true }); return await this.userEntityService.packMany(users, me, { schema: 'UserDetailed' });
}); });
} }
} }

View file

@ -122,7 +122,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
// Make replies object (includes weights) // Make replies object (includes weights)
const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({ const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({
user: await this.userEntityService.pack(user, me, { detail: true }), user: await this.userEntityService.pack(user, me, { schema: 'UserDetailed' }),
weight: repliedUsers[user] / peak, weight: repliedUsers[user] / peak,
}))); })));

View file

@ -46,7 +46,7 @@ export const meta = {
}, },
user: { user: {
type: 'object', type: 'object',
ref: 'User', ref: 'UserLite',
}, },
withReplies: { withReplies: {
type: 'boolean', type: 'boolean',

View file

@ -9,6 +9,9 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import { QueryService } from '@/core/QueryService.js'; import { QueryService } from '@/core/QueryService.js';
import { NoteReactionEntityService } from '@/core/entities/NoteReactionEntityService.js'; import { NoteReactionEntityService } from '@/core/entities/NoteReactionEntityService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { CacheService } from '@/core/CacheService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { RoleService } from '@/core/RoleService.js';
import { ApiError } from '../../error.js'; import { ApiError } from '../../error.js';
export const meta = { export const meta = {
@ -34,6 +37,11 @@ export const meta = {
code: 'REACTIONS_NOT_PUBLIC', code: 'REACTIONS_NOT_PUBLIC',
id: '673a7dd2-6924-1093-e0c0-e68456ceae5c', id: '673a7dd2-6924-1093-e0c0-e68456ceae5c',
}, },
isRemoteUser: {
message: 'Currently unavailable to display reactions of remote users. See https://github.com/misskey-dev/misskey/issues/12964',
code: 'IS_REMOTE_USER',
id: '6b95fa98-8cf9-2350-e284-f0ffdb54a805',
},
}, },
} as const; } as const;
@ -59,14 +67,24 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.noteReactionsRepository) @Inject(DI.noteReactionsRepository)
private noteReactionsRepository: NoteReactionsRepository, private noteReactionsRepository: NoteReactionsRepository,
private cacheService: CacheService,
private userEntityService: UserEntityService,
private noteReactionEntityService: NoteReactionEntityService, private noteReactionEntityService: NoteReactionEntityService,
private queryService: QueryService, private queryService: QueryService,
private roleService: RoleService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: ps.userId }); const iAmModerator = me ? await this.roleService.isModerator(me) : false; // Moderators can see reactions of all users
if (!iAmModerator) {
const user = await this.cacheService.findUserById(ps.userId);
if (this.userEntityService.isRemoteUser(user)) {
throw new ApiError(meta.errors.isRemoteUser);
}
if ((me == null || me.id !== ps.userId) && !profile.publicReactions) { const profile = await this.userProfilesRepository.findOneByOrFail({ userId: ps.userId });
throw new ApiError(meta.errors.reactionsNotPublic); if ((me == null || me.id !== ps.userId) && !profile.publicReactions) {
throw new ApiError(meta.errors.reactionsNotPublic);
}
} }
const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'), const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'),

View file

@ -76,7 +76,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const users = await query.limit(ps.limit).offset(ps.offset).getMany(); const users = await query.limit(ps.limit).offset(ps.offset).getMany();
return await this.userEntityService.packMany(users, me, { detail: true }); return await this.userEntityService.packMany(users, me, { schema: 'UserDetailed' });
}); });
} }
} }

View file

@ -131,7 +131,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.getMany(); .getMany();
} }
return await this.userEntityService.packMany(users, me, { detail: !!ps.detail }); return await this.userEntityService.packMany(users, me, { schema: ps.detail ? 'UserDetailed' : 'UserLite' });
}); });
} }
} }

View file

@ -141,7 +141,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
} }
} }
return await this.userEntityService.packMany(users, me, { detail: ps.detail }); return await this.userEntityService.packMany(users, me, { schema: ps.detail ? 'UserDetailed' : 'UserLite' });
}); });
} }
} }

View file

@ -116,7 +116,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
} }
return await Promise.all(_users.map(u => this.userEntityService.pack(u, me, { return await Promise.all(_users.map(u => this.userEntityService.pack(u, me, {
detail: true, schema: 'UserDetailed',
}))); })));
} else { } else {
// Lookup user // Lookup user
@ -146,7 +146,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
} }
return await this.userEntityService.pack(user, me, { return await this.userEntityService.pack(user, me, {
detail: true, schema: 'UserDetailed',
}); });
} }
}); });

View file

@ -6,9 +6,9 @@
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import endpoints, { IEndpoint } from '../endpoints.js'; import endpoints, { IEndpoint } from '../endpoints.js';
import { errors as basicErrors } from './errors.js'; import { errors as basicErrors } from './errors.js';
import { schemas, convertSchemaToOpenApiSchema } from './schemas.js'; import { getSchemas, convertSchemaToOpenApiSchema } from './schemas.js';
export function genOpenapiSpec(config: Config) { export function genOpenapiSpec(config: Config, includeSelfRef = false) {
const spec = { const spec = {
openapi: '3.1.0', openapi: '3.1.0',
@ -30,7 +30,7 @@ export function genOpenapiSpec(config: Config) {
paths: {} as any, paths: {} as any,
components: { components: {
schemas: schemas, schemas: getSchemas(includeSelfRef),
securitySchemes: { securitySchemes: {
bearerAuth: { bearerAuth: {
@ -56,7 +56,7 @@ export function genOpenapiSpec(config: Config) {
} }
} }
const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res, 'res') : {}; const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res, 'res', includeSelfRef) : {};
let desc = (endpoint.meta.description ? endpoint.meta.description : 'No description provided.') + '\n\n'; let desc = (endpoint.meta.description ? endpoint.meta.description : 'No description provided.') + '\n\n';
@ -71,7 +71,7 @@ export function genOpenapiSpec(config: Config) {
} }
const requestType = endpoint.meta.requireFile ? 'multipart/form-data' : 'application/json'; const requestType = endpoint.meta.requireFile ? 'multipart/form-data' : 'application/json';
const schema = { ...convertSchemaToOpenApiSchema(endpoint.params, 'param') }; const schema = { ...convertSchemaToOpenApiSchema(endpoint.params, 'param', false) };
if (endpoint.meta.requireFile) { if (endpoint.meta.requireFile) {
schema.properties = { schema.properties = {

View file

@ -6,10 +6,10 @@
import type { Schema } from '@/misc/json-schema.js'; import type { Schema } from '@/misc/json-schema.js';
import { refs } from '@/misc/json-schema.js'; import { refs } from '@/misc/json-schema.js';
export function convertSchemaToOpenApiSchema(schema: Schema, type: 'param' | 'res') { export function convertSchemaToOpenApiSchema(schema: Schema, type: 'param' | 'res', includeSelfRef: boolean): any {
// optional, nullable, refはスキーマ定義に含まれないので分離しておく // optional, nullable, refはスキーマ定義に含まれないので分離しておく
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const { optional, nullable, ref, ...res }: any = schema; const { optional, nullable, ref, selfRef, ...res }: any = schema;
if (schema.type === 'object' && schema.properties) { if (schema.type === 'object' && schema.properties) {
if (type === 'res') { if (type === 'res') {
@ -21,20 +21,20 @@ export function convertSchemaToOpenApiSchema(schema: Schema, type: 'param' | 're
} }
for (const k of Object.keys(schema.properties)) { for (const k of Object.keys(schema.properties)) {
res.properties[k] = convertSchemaToOpenApiSchema(schema.properties[k], type); res.properties[k] = convertSchemaToOpenApiSchema(schema.properties[k], type, includeSelfRef);
} }
} }
if (schema.type === 'array' && schema.items) { if (schema.type === 'array' && schema.items) {
res.items = convertSchemaToOpenApiSchema(schema.items, type); res.items = convertSchemaToOpenApiSchema(schema.items, type, includeSelfRef);
} }
for (const o of ['anyOf', 'oneOf', 'allOf'] as const) { for (const o of ['anyOf', 'oneOf', 'allOf'] as const) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (o in schema) res[o] = schema[o]!.map(schema => convertSchemaToOpenApiSchema(schema, type)); if (o in schema) res[o] = schema[o]!.map(schema => convertSchemaToOpenApiSchema(schema, type, includeSelfRef));
} }
if (type === 'res' && schema.ref) { if (type === 'res' && schema.ref && (!schema.selfRef || includeSelfRef)) {
const $ref = `#/components/schemas/${schema.ref}`; const $ref = `#/components/schemas/${schema.ref}`;
if (schema.nullable || schema.optional) { if (schema.nullable || schema.optional) {
res.allOf = [{ $ref }]; res.allOf = [{ $ref }];
@ -54,35 +54,37 @@ export function convertSchemaToOpenApiSchema(schema: Schema, type: 'param' | 're
return res; return res;
} }
export const schemas = { export function getSchemas(includeSelfRef: boolean) {
Error: { return {
type: 'object', Error: {
properties: { type: 'object',
error: { properties: {
type: 'object', error: {
description: 'An error object.', type: 'object',
properties: { description: 'An error object.',
code: { properties: {
type: 'string', code: {
description: 'An error code. Unique within the endpoint.', type: 'string',
}, description: 'An error code. Unique within the endpoint.',
message: { },
type: 'string', message: {
description: 'An error message.', type: 'string',
}, description: 'An error message.',
id: { },
type: 'string', id: {
format: 'uuid', type: 'string',
description: 'An error ID. This ID is static.', format: 'uuid',
description: 'An error ID. This ID is static.',
},
}, },
required: ['code', 'id', 'message'],
}, },
required: ['code', 'id', 'message'],
}, },
required: ['error'],
}, },
required: ['error'],
},
...Object.fromEntries( ...Object.fromEntries(
Object.entries(refs).map(([key, schema]) => [key, convertSchemaToOpenApiSchema(schema, 'res')]), Object.entries(refs).map(([key, schema]) => [key, convertSchemaToOpenApiSchema(schema, 'res', includeSelfRef)]),
), ),
}; };
}

View file

@ -16,3 +16,8 @@ declare const _DATA_TRANSFER_DECK_COLUMN_: string;
// for dev-mode // for dev-mode
declare const _LANGS_FULL_: string[][]; declare const _LANGS_FULL_: string[][];
// TagCanvas
interface Window {
TagCanvas: any;
}

View file

@ -20,7 +20,7 @@
"@discordapp/twemoji": "15.0.2", "@discordapp/twemoji": "15.0.2",
"@github/webauthn-json": "2.1.1", "@github/webauthn-json": "2.1.1",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@misskey-dev/browser-image-resizer": "2.2.1-misskey.10", "@misskey-dev/browser-image-resizer": "2024.1.0",
"@rollup/plugin-json": "6.1.0", "@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "5.0.5", "@rollup/plugin-replace": "5.0.5",
"@rollup/pluginutils": "5.1.0", "@rollup/pluginutils": "5.1.0",
@ -28,8 +28,8 @@
"@syuilo/aiscript": "0.17.0", "@syuilo/aiscript": "0.17.0",
"@phosphor-icons/web": "^2.0.3", "@phosphor-icons/web": "^2.0.3",
"@twemoji/parser": "15.0.0", "@twemoji/parser": "15.0.0",
"@vitejs/plugin-vue": "5.0.2", "@vitejs/plugin-vue": "5.0.3",
"@vue/compiler-sfc": "3.4.3", "@vue/compiler-sfc": "3.4.15",
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.0.6", "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.0.6",
"astring": "1.8.6", "astring": "1.8.6",
"broadcast-channel": "7.0.0", "broadcast-channel": "7.0.0",
@ -40,7 +40,7 @@
"chartjs-chart-matrix": "2.0.1", "chartjs-chart-matrix": "2.0.1",
"chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-gradient": "0.6.1",
"chartjs-plugin-zoom": "2.0.1", "chartjs-plugin-zoom": "2.0.1",
"chromatic": "10.3.1", "chromatic": "10.6.1",
"compare-versions": "6.1.0", "compare-versions": "6.1.0",
"cropperjs": "2.0.0-beta.4", "cropperjs": "2.0.0-beta.4",
"date-fns": "2.30.0", "date-fns": "2.30.0",
@ -61,10 +61,10 @@
"rollup": "4.9.6", "rollup": "4.9.6",
"sanitize-html": "2.11.0", "sanitize-html": "2.11.0",
"sass": "1.70.0", "sass": "1.70.0",
"shiki": "0.14.7", "shiki": "1.0.0-beta.3",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.160.0", "three": "0.160.1",
"throttle-debounce": "5.0.0", "throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tsc-alias": "1.8.8", "tsc-alias": "1.8.8",
@ -77,8 +77,8 @@
"vuedraggable": "next" "vuedraggable": "next"
}, },
"devDependencies": { "devDependencies": {
"@misskey-dev/eslint-plugin": "^1.0.0", "@misskey-dev/eslint-plugin": "1.0.0",
"@misskey-dev/summaly": "^5.0.3", "@misskey-dev/summaly": "5.0.3",
"@storybook/addon-actions": "7.6.10", "@storybook/addon-actions": "7.6.10",
"@storybook/addon-essentials": "7.6.10", "@storybook/addon-essentials": "7.6.10",
"@storybook/addon-interactions": "7.6.10", "@storybook/addon-interactions": "7.6.10",
@ -102,12 +102,12 @@
"@types/estree": "1.0.5", "@types/estree": "1.0.5",
"@types/matter-js": "0.19.6", "@types/matter-js": "0.19.6",
"@types/micromatch": "4.0.6", "@types/micromatch": "4.0.6",
"@types/node": "20.11.5", "@types/node": "20.11.10",
"@types/punycode": "2.1.3", "@types/punycode": "2.1.3",
"@types/sanitize-html": "2.9.5", "@types/sanitize-html": "2.9.5",
"@types/throttle-debounce": "5.0.2", "@types/throttle-debounce": "5.0.2",
"@types/tinycolor2": "1.4.6", "@types/tinycolor2": "1.4.6",
"@types/uuid": "9.0.7", "@types/uuid": "9.0.8",
"@types/ws": "8.5.10", "@types/ws": "8.5.10",
"@typescript-eslint/eslint-plugin": "6.18.1", "@typescript-eslint/eslint-plugin": "6.18.1",
"@typescript-eslint/parser": "6.18.1", "@typescript-eslint/parser": "6.18.1",
@ -135,7 +135,8 @@
"vite-plugin-turbosnap": "1.0.3", "vite-plugin-turbosnap": "1.0.3",
"vitest": "0.34.6", "vitest": "0.34.6",
"vitest-fetch-mock": "0.2.2", "vitest-fetch-mock": "0.2.2",
"vue-eslint-parser": "9.4.0", "vue-component-type-helpers": "^1.8.27",
"vue-eslint-parser": "9.4.2",
"vue-tsc": "1.8.27" "vue-tsc": "1.8.27"
} }
} }

View file

@ -22,7 +22,7 @@ import { getAccountFromId } from '@/scripts/get-account-from-id.js';
import { deckStore } from '@/ui/deck/deck-store.js'; import { deckStore } from '@/ui/deck/deck-store.js';
import { miLocalStorage } from '@/local-storage.js'; import { miLocalStorage } from '@/local-storage.js';
import { fetchCustomEmojis } from '@/custom-emojis.js'; import { fetchCustomEmojis } from '@/custom-emojis.js';
import { setupRouter } from '@/global/router/definition.js'; import { setupRouter } from '@/router/definition.js';
export async function common(createVue: () => App<Element>) { export async function common(createVue: () => App<Element>) {
console.info(`Sharkey v${version}`); console.info(`Sharkey v${version}`);

View file

@ -19,7 +19,7 @@ import { claimAchievement, claimedAchievements } from '@/scripts/achievements.js
import { initializeSw } from '@/scripts/initialize-sw.js'; import { initializeSw } from '@/scripts/initialize-sw.js';
import { deckStore } from '@/ui/deck/deck-store.js'; import { deckStore } from '@/ui/deck/deck-store.js';
import { emojiPicker } from '@/scripts/emoji-picker.js'; import { emojiPicker } from '@/scripts/emoji-picker.js';
import { mainRouter } from '@/global/router/main.js'; import { mainRouter } from '@/router/main.js';
export async function mainBoot() { export async function mainBoot() {
const { isClientUpdated } = await common(() => createApp( const { isClientUpdated } = await common(() => createApp(
@ -79,13 +79,18 @@ export async function mainBoot() {
// ▼南半球 // ▼南半球
if (month === 7 || month === 8) { if (month === 7 || month === 8) {
const SnowfallEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect; const SnowfallEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect;
new SnowfallEffect().render(); new SnowfallEffect({}).render();
} }
} else { } else {
// ▼北半球 // ▼北半球
if (month === 12 || month === 1) { if (month === 12 || month === 1) {
const SnowfallEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect; const SnowfallEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect;
new SnowfallEffect().render(); new SnowfallEffect({}).render();
} else if (month === 3 || month === 4) {
const SakuraEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect;
new SakuraEffect({
sakura: true,
}).render();
} }
} }
} }

View file

@ -39,7 +39,7 @@ import * as os from '@/os.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
const props = defineProps<{ const props = defineProps<{
user: Misskey.entities.User; user: Misskey.entities.UserDetailed;
initialComment?: string; initialComment?: string;
}>(); }>();

View file

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div> <div>
<div v-if="achievements" :class="$style.root"> <div v-if="achievements" :class="$style.root">
<div v-for="achievement in achievements" :key="achievement" :class="$style.achievement" class="_panel"> <div v-for="achievement in achievements" :key="achievement.name" :class="$style.achievement" class="_panel">
<div :class="$style.icon"> <div :class="$style.icon">
<div <div
:class="[$style.iconFrame, { :class="[$style.iconFrame, {

View file

@ -49,7 +49,7 @@ async function ok() {
if (confirm.canceled) return; if (confirm.canceled) return;
} }
modal.value.close(); modal.value?.close();
misskeyApi('i/read-announcement', { announcementId: props.announcement.id }); misskeyApi('i/read-announcement', { announcementId: props.announcement.id });
updateAccount({ updateAccount({
unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== props.announcement.id), unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== props.announcement.id),
@ -57,7 +57,7 @@ async function ok() {
} }
function onBgClick() { function onBgClick() {
rootEl.value.animate([{ rootEl.value?.animate([{
offset: 0, offset: 0,
transform: 'scale(1)', transform: 'scale(1)',
}, { }, {

Some files were not shown because too many files have changed in this diff Show more