Compare commits

...

213 Commits

Author SHA1 Message Date
syuilo
8dc2f1f49d wip 2024-01-19 20:49:17 +09:00
syuilo
353098f576 wip 2024-01-19 18:35:53 +09:00
syuilo
4c43ee4b53 Merge branch 'develop' into reversi 2024-01-19 11:11:57 +09:00
syuilo
037a1daa79 wip 2024-01-19 10:59:55 +09:00
Acid Chicken (硫酸鶏)
43401210c3 refactor: fully typed locales (#13033)
* refactor: fully typed locales

* refactor: hide parameterized locale strings from type data in ts access

* refactor: missing assertions

* docs: annotation
2024-01-19 07:58:07 +09:00
syuilo
1259fabd7f wip 2024-01-18 20:59:21 +09:00
syuilo
58f4d5d790 wip 2024-01-18 20:03:15 +09:00
かっこかり
c1019a006b feat(frontend): 横スワイプでタブを切り替える機能 (#13011)
* (add) 横スワイプでタブを切り替える機能

* Change Changelog

* y方向の移動が一定量を超えたらスワイプを中断するように

* Update swipe distance thresholds

* Remove console.log

* adjust threshold

* rename, use v-model

* fix

* Update MkHorizontalSwipe.vue

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

* use css module

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2024-01-18 18:21:33 +09:00
syuilo
7ca93893ca wip 2024-01-18 18:16:01 +09:00
syuilo
fbad40bb9b wip 2024-01-18 17:50:24 +09:00
syuilo
7794e7ae2f wip 2024-01-18 17:43:59 +09:00
syuilo
a78013d0e5 wip 2024-01-18 17:38:24 +09:00
syuilo
768d0bdc00 wip 2024-01-18 16:10:28 +09:00
syuilo
36450d7fac wip 2024-01-18 16:02:24 +09:00
syuilo
4e1fb618b8 wip 2024-01-18 15:46:14 +09:00
syuilo
1a25401452 wip 2024-01-18 15:28:39 +09:00
かっこかり
67a41c09ae fix(frontend/MediaVideo): 再生シークバーの当たり判定を調整 (#13027)
* fix(frontend/MediaVideo): 再生シークバーの当たり判定を調整

* fix
2024-01-18 14:45:11 +09:00
FineArchs
fc7cd636a3 refactor: MkCodeをブロックとインラインで別コンポーネント化する (#13026)
* Create MkCodeInline.vue

* Update MkCode.vue

* Update MkMisskeyFlavoredMarkdown.ts

* Update flash.vue

* Update MkCodeInline.vue
2024-01-18 12:16:12 +09:00
syuilo
8ef70283fa wip 2024-01-18 11:59:47 +09:00
syuilo
22bb79b3fd wip 2024-01-18 11:47:21 +09:00
syuilo
38e7054f06 Merge branch 'develop' into reversi 2024-01-18 11:20:10 +09:00
syuilo
1e237a7df5 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2024-01-18 11:19:45 +09:00
syuilo
1dcf25c24f chore(drop-and-fusion): bump version 2024-01-18 11:19:43 +09:00
FineArchs
9a40b366a3 MkCodeにコピーボタンを追加 (#12999)
* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update CHANGELOG.md

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2024-01-18 10:53:29 +09:00
syuilo
d6c8e520f8 wip 2024-01-17 20:44:02 +09:00
syuilo
1b5ad63672 Merge branch 'develop' into reversi 2024-01-17 20:34:50 +09:00
syuilo
945d6a2b09 enhance(drop-and-fusion): ゲームバランスの調整など 2024-01-17 20:11:32 +09:00
a
0283142d0e Fix: properly handle cc followers (#13009)
* Fix: properly handle cc followers

Fix #13001

* Update CHANGELOG.md

* Fix syntax error
2024-01-17 18:19:20 +09:00
FineArchs
bcb4560f0d $[border ...]にクリッピング機能を追加 (#13002)
* Update MkMisskeyFlavoredMarkdown.ts

* Update MkMisskeyFlavoredMarkdown.ts

* Update CHANGELOG.md

* Set clipping as default
2024-01-17 17:42:19 +09:00
syuilo
5bcdd6e849 wip 2024-01-17 16:16:16 +09:00
かっこかり
8db800ed98 enhance(frontend): チャンネルノートの場合はその前後を見れるように (#13019)
* チャンネルノートの場合はその前後を見れるように

* Update Changelog
2024-01-17 15:22:07 +09:00
1Step621
acab9ccb81 Enhance(frontend): MkCustomEmojiDetailedDialogを調整 (#13015)
* MkEmojiDetailedDialogを調整

* 絵文字ライセンスでMFMを使えるように

* <a> -> <MkLink>

* 入力ボックスでmfmのオートコンプリートを効かせる
2024-01-17 14:29:24 +09:00
ikasoba
bc5aebe956 enhance(frontend): ページ遷移時にPlayerを閉じるように (#13013)
* なんかできた

* update changelog.md

* onDeactivatedを使うように
2024-01-17 13:26:36 +09:00
かっこかり
c971edd2dd (style) sticky系フッターのデザイン調整 (#13005) 2024-01-17 09:32:52 +09:00
かっこかり
8b0fdfcd69 enhance: 動画・音声周りのUIと動作改良 (#12925)
* wip

* (fix) `/files` をバイトレンジリクエストに対応させる

* video

* audio

* fix

* fix

* spdx

* fix (rangeRequest)

* fix

* Update CHANGELOG.md

* (add) ボリュームを保存できるように

* (fix) ミュート復帰時に音量が固定される

* named export

* tweak design

* Add sensitive class for audio component

* Refactor seekbar styles

* Refactor hms

* Revert "(add) ボリュームを保存できるように"

This reverts commit 6271f9493b.

* Revert "(fix) ミュート復帰時に音量が固定される"

This reverts commit a65002b56e.

* revert revert changes

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2024-01-15 18:17:01 +09:00
syuilo
3d83c1aaba enhance(frontend): dedicated games page 2024-01-15 13:51:59 +09:00
YS
d92aaf81c4 refactor: noteテーブルのインデックス整理と配列カラムへのクエリでインデックスを使うように (#12993)
* Optimize note model index

* enhance(backend): ANY()をやめる (MisskeyIO#239)

* add small e2e test drive endpoint

---------

Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com>
2024-01-15 08:19:27 +09:00
かっこかり
0ea8e0c25c enhance(frontend) 日本語の拡張絵文字辞書を追加 (#12855)
* Create ja-JP.json

* Update general.vue

* Update ja-JP.json

* Update ja-JP.json

* Update ja-JP.json

* fix

* fix design

* (Add) ひらがな [wip]

* fix lint

* Apply suggestions from code review

Co-authored-by: 1Step621 <86859447+1STEP621@users.noreply.github.com>

* (add) ja-JP_hira

Co-authored-by: 1Step621 <86859447+1STEP621@users.noreply.github.com>

* (enhance) 言語名をちゃんと表示するように

---------

Co-authored-by: 1Step621 <86859447+1STEP621@users.noreply.github.com>
2024-01-14 16:04:48 +09:00
GrapeApple0
ec4e57bb67 fix: isPrivateIpで検証時にipバージョンが一致するかを確認するように (#12988)
* fix: isPrivateIpで検証時にipバージョンが一致するかを確認するように

* Update CHANGELOG.md

* Update CHANGELOG.md
2024-01-14 15:57:26 +09:00
かっこかり
12142a221a enhance(frontend): Playの説明欄にMFMを使えるように (#12899)
* (enhance) Playの説明欄にMFMを使えるように

* Update Changelog

* use class for mfm component

* Update packages/frontend/src/pages/flash/flash-edit.vue

Co-authored-by: 1Step621 <86859447+1STEP621@users.noreply.github.com>

* Update flash.vue

* Update CHANGELOG.md

---------

Co-authored-by: 1Step621 <86859447+1STEP621@users.noreply.github.com>
2024-01-14 15:31:11 +09:00
ikasoba
79a9defa0c 完成 (#12980) 2024-01-14 15:30:21 +09:00
syuilo
fc4c868269 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2024-01-14 13:00:00 +09:00
syuilo
27dc0d3530 enhance(drop-and-fusion): sweets mode 2024-01-14 12:59:58 +09:00
おさむのひと
57017f2747 feat(CI): CHANGELOG.mdの追記個所をチェックするCIを追加 (#12963)
* feat(CI): CHANGELOG.mdの追記個所をチェックするCIを追加

* fix

* remove strategy

* fix

* fix
2024-01-13 21:17:30 +09:00
FineArchs
bc8a741e14 feat: 枠線をつけるMFMを追加 (#12981)
* Update MkMisskeyFlavoredMarkdown.ts

* Update const.ts

* Update MkMisskeyFlavoredMarkdown.ts

* Update MkMisskeyFlavoredMarkdown.ts

* Update CHANGELOG.md
2024-01-13 21:17:00 +09:00
syuilo
4846ab077b enhance(drop-and-fusion): refactor and new mode(wip) 2024-01-13 18:03:31 +09:00
syuilo
920888ed2a Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2024-01-13 17:06:43 +09:00
syuilo
c26c01c7a0 fix type 2024-01-13 17:06:41 +09:00
zyoshoka
d792f4f348 fix(backend): 虚無ノートを投稿できる問題の修正と api.json の OpenAPI Specification 3.1.0 への対応 (#12969)
* fix(backend): `text: null`だけのノートは投稿できないように

* add test

* Update CHANGELOG.md

* chore: bump OpenAPI Specification from 3.0.0 to 3.1.0

* chore: テストがすでにコメントで記述されていたのでそっちを使うことにする

* fix test

* fix(backend): prohibit posting whitespace-only notes

* Update CHANGELOG.md

* fix(backend): `renoteId`または`fileIds`(`mediaIds`)または`poll`が`null`でない場合に、`text  が空白文字のみで構成されたリクエストになることを許可して、結果は`text: null`を返すように

* test(backend): 引用renoteで空白文字のみで構成されたtextにするとレスポンスが`text: null`になることをチェックするテストを追加

* fix(frontend): `text`が`null`であって`renoteId`と`replyId`が`null`でないようなノートは引用リノートとして表示するように

* fix(misskey-js): OpenAPI 3.1に対応

* fix(misskey-js): 型生成をOpenAPI Specification 3.1.0に対応

* fix(ci): `validate-api.json`をOpenAPI Specification 3.1.0に対応

* fix(ci): スキーマ書き換えの際のミスを修正

* Revert "fix(frontend): `text`が`null`であって`renoteId`と`replyId`が`null`でないようなノートは引用リノートとして表示するように"

This reverts commit a9ca55343d.

* fix(misskey-js): `build-misskey-js-with-types`時は`api.json`のGETをスキップするように

* Revert "fix(misskey-js): `build-misskey-js-with-types`時は`api.json`のGETをスキップするように"

This reverts commit 865458989f.

* fix(misskey-js): `openapi-parser`で`validate`のかわりに`parse`を用いるように

* Update CHANGELOG.md
2024-01-13 16:54:25 +09:00
syuilo
79370aaee2 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2024-01-13 15:45:25 +09:00
syuilo
18a5e377f4 tweak 2024-01-13 15:45:23 +09:00
1Step621
19fe32bd7e Feat(frontend): リアクション・ノート内絵文字・/about#emojisで絵文字詳細が見られるように (#12984)
* リアクション・ノート内絵文字・/about#emojisで絵文字詳細が見られるように

* update CHANGELOG.md

* fix locale & type errors

* fix locale etc

* fix

* fix type

* lint fixes

* lint fixes(2)
2024-01-13 15:25:11 +09:00
syuilo
d246f6c360 enhance(drop-and-fusion): some tweaks 2024-01-13 14:57:06 +09:00
syuilo
5503ad9d1a clean up 2024-01-13 12:01:18 +09:00
syuilo
7b0f5b50fc refactor(drop-and-fusion): some refactors 2024-01-13 12:00:12 +09:00
syuilo
6177fcb2f5 perf(drop-and-fusion): remove root Transition component for improve performance 2024-01-13 11:49:47 +09:00
syuilo
c33f56e3ed refactor(drop-and-fusion): レンダリングや効果音に関する関心をエンジンから分離 2024-01-13 11:43:13 +09:00
syuilo
b13410df02 enhance(drop-and-fusion): some tweaks 2024-01-13 09:08:44 +09:00
syuilo
87ffc672dd enhance(drop-and-fusion): yenモードで生涯で稼いだ額を記録するように 2024-01-12 21:01:38 +09:00
syuilo
5672ead957 Update 10000yen.png 2024-01-12 20:45:57 +09:00
syuilo
0aefebf02a enhance(drop-and-fusion): some tweaks 2024-01-12 20:38:04 +09:00
syuilo
271407312e chore(drop-and-fusion): tweak sounds 2024-01-12 17:55:27 +09:00
syuilo
c2a9a7b69e enhance(drop-and-fusion): tweak sounds 2024-01-12 17:34:24 +09:00
syuilo
b920435068 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2024-01-12 15:29:22 +09:00
zyoshoka
1aeede97f5 refactor(frontend): activity.heatmap.vueをコンポーネントに置換 (#12967) 2024-01-12 15:29:06 +09:00
syuilo
a5ea7c976b chore(drop-and-fusion): bump version 2024-01-12 15:28:41 +09:00
syuilo
d2063df78d enhance(drop-and-fusion): add new mode, some tweaks 2024-01-12 14:48:44 +09:00
かっこかり
be57ff4985 run pnpm build-misskey-js-with-types (#12972) 2024-01-11 23:41:22 +09:00
syuilo
cf54c2ba47 feat: ranking system of bubble game
Resolve #12961
2024-01-11 18:13:39 +09:00
syuilo
762fa6a8d8 enhance(drop-and-fusion): make game engine headless for server-side running 2024-01-11 12:34:03 +09:00
syuilo
36fd7d17cf enhance(drop-and-fusion): some tweaks 2024-01-10 19:54:59 +09:00
syuilo
5c786cace8 enhance(drop-and-fusion): add game description 2024-01-10 17:31:59 +09:00
1Step621
c1c363bf08 Enhance(frontend): 絵文字ピッカー/オートコンプリートで完全一致の絵文字を優先するように (#12928)
* 絵文字ピッカー/オートコンプリートで完全一致の絵文字を優先するように

* update CHANGELOG.md

* improve performance
2024-01-10 15:06:04 +09:00
syuilo
4bd9f664d7 enhance(drop-and-fusion): some tweaks 2024-01-10 13:44:00 +09:00
syuilo
3d9e42efca enhance(drop-and-fusion): リプレイの倍速再生対応 2024-01-10 11:38:49 +09:00
まっちゃとーにゅ
138a248a6c fix(drop-and-fusion): バブルゲームのリトライボタンでリトライができない問題を修正 (#12957)
ゲーム中なら諦める、ゲームオーバー画面の表示中はリスタートになるように
2024-01-10 10:40:09 +09:00
FineArchs
6bae440f39 bump aiscript version to 0.17.0 (#12955)
* bump aiscript version to 0.17.0

* Update CHANGELOG.md
2024-01-10 09:47:47 +09:00
syuilo
f5b864df7b fix(frontend): fix game replay 2024-01-10 07:26:16 +09:00
FineArchs
7e52ea4818 Update CHANGELOG.md (#12953) 2024-01-10 00:44:13 +09:00
Camilla Ett
358dc6289b Enhance(frontend): 管理者の場合はAPI tokenの発行画面で管理機能に関する権限を付与できるように (#12944)
* Enhance(frontend): 管理者の場合はAPI tokenの発行画面で管理機能に関する権限を付与できるように

* update CHANGELOG.md

* tweak style

* (refactor) remove unnecessary imports

* fix lint

---------

Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
Co-authored-by: kakkokari-gtyih <daisho7308+f@gmail.com>
2024-01-09 21:18:09 +09:00
syuilo
1063d39de8 enhnace(frontend): tweak game 2024-01-09 21:15:56 +09:00
syuilo
14aedc17ae update sound 2024-01-09 16:06:22 +09:00
かっこかり
0d7f9308cc enhance(frontend): バブルゲームの諸々を修正・改良2 (#12948)
* (fix) ゲームが正常に終了するように

* (enhance) 効果音の音量を設定可能に

* (add) store

* (add) スクショにロゴの透かしを入れる

* Update packages/frontend/src/pages/drop-and-fusion.vue

Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com>

* tweak

* tweak

* tweak

* tweak

* Update drop-and-fusion.vue

* tweak

* tweak

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com>
2024-01-09 13:25:33 +09:00
おさむのひと
34088ecd27 feat(ci): api.jsonのバリデーションチェックCIを追加 (#12950)
* feat(ci): api.jsonのバリデーションチェックCIを追加

* fix name
2024-01-09 08:34:23 +09:00
おさむのひと
0f9e3bccef refactor(CI): 修正範囲と関係ないActionsが走るのを抑止する (#12918)
* refactor?: 修正範囲と関係ないActionsが走るのを抑止する

* fix

* バックエンドの対象にmisskey-jsを追加&フロントエンドの対象にmisskey-jsとbackendを追加
2024-01-08 23:51:31 +09:00
FineArchs
64de87438e Update CHANGELOG.md (#12949) 2024-01-08 18:51:08 +09:00
かっこかり
5dcd8c827b Update CHANGELOG.md (項目の順番の修正) 2024-01-08 17:56:41 +09:00
おさむのひと
35ec41fc1e enhance(backend): テストの高速化 (#12939)
* enhance(backend): テストの高速化

* add ls

* 自動的にマージされるようなので不要

* 起動方法を揃える

* fix test
2024-01-08 17:43:52 +09:00
zyoshoka
618e2ba1d2 fix(backend): drive/files/updateにおけるファイル名のバリデーションが機能していない問題を修正 (#12923)
* fix(backend): `drive/files/update`におけるファイル名のバリデーションが機能していない問題を修正

* Update CHANGELOG.md

* refactor: `!== undefined` -> `!= null`

* add test
2024-01-08 17:40:37 +09:00
おさむのひと
04f9147db6 refactor(frontend): router.ts解きほぐし (#12907)
* refactor(frontend): router.ts解きほぐし

* add debug hmr option

* fix comment

* fix not working

* add comment

* fix name

* Update definition.ts

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2024-01-08 14:44:43 +09:00
syuilo
0ed2a220f4 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2024-01-08 12:46:23 +09:00
syuilo
e9c3fe1228 enhance(frontend): add game bgm and refactor sound system 2024-01-08 12:46:20 +09:00
Kagami Sascha Rosylight
0c2118e963 refactor: make sure promises are settled before app shutdown (#12942)
👍
2024-01-08 12:28:13 +09:00
syuilo
145d28a8e4 refactor(frontend): extract game engine from vue component 2024-01-08 11:13:20 +09:00
かっこかり
6a02dfdd3b enhance(frontend): バブルゲームの諸々を修正・改良 (#12938)
* enhance(frontend): バブルゲームのテクスチャをゲーム開始時にキャッシュするように

* (fix) カーソルが枠線内を動くように

* (add) 最大コンボ数を表示するように

* (add) 実績を追加

* Update ja-JP.yml

* tweak

* tweak flavor

* perf tweak

* refactor

* perf tweak

* lint

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2024-01-08 11:02:05 +09:00
syuilo
831131864f Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2024-01-08 10:23:05 +09:00
syuilo
1bd7693416 Update logo.png 2024-01-08 10:23:03 +09:00
かっこかり
5251cd3aad (refactor) api呼び出し関数のレスポンス型を必要に応じてオーバーライドできるように (#12936) 2024-01-08 08:13:36 +09:00
zyoshoka
0e536bdd86 refactor(frontend): widgets/server-metric内の型エラーを除去 (#12937) 2024-01-07 23:56:46 +09:00
syuilo
fd519f5def update game logo 2024-01-07 20:26:37 +09:00
syuilo
0d830d720a enhance(frontend): tweak ui 2024-01-07 16:32:52 +09:00
syuilo
0d49e94982 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2024-01-07 16:03:36 +09:00
syuilo
e6022c0d51 enhance(frontend): tweak game 2024-01-07 16:03:23 +09:00
Kagami Sascha Rosylight
5e71418d5c fix(frontend/emoji) restore U+FE0F for simple emojis (#12866)
* fix(frontend/emoji) restore U+FE0F for simple emojis

* Update CHANGELOG.md

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2024-01-07 16:02:53 +09:00
syuilo
c6a4caa8be refactor 2024-01-07 14:32:57 +09:00
syuilo
1d1780081e enhance(frontend): ゲームのシェア機能 2024-01-07 14:21:19 +09:00
FineArchs
622a09f8ed Fix: Mk:C:mfmonClickEvが正常に呼び出されない問題を修正 (#12831)
* fix clickable api

* Update CHANGELOG.md

* revert CHANGELOG.md

* Update CHANGELOG.md
2024-01-07 13:29:17 +09:00
syuilo
00e195f50b tweak game 2024-01-07 13:19:10 +09:00
syuilo
8bf6d31334 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2024-01-07 10:36:08 +09:00
Kagami Sascha Rosylight
2a9db983fc feat: export clips (#12931)
* feat: export clips

* Update CHANGELOG.md
2024-01-07 10:35:58 +09:00
syuilo
4ea030d669 tweak game 2024-01-07 10:35:39 +09:00
_
f2dee7b25e Fix: リストライムラインの「リノートを表示」が正しく機能しない問題を修正 (#12932)
* fix: list timeline withRenotes

* add CHANGELOG
2024-01-07 09:57:01 +09:00
syuilo
a0976772b3 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2024-01-07 09:24:07 +09:00
syuilo
0815a5235d tweak game 2024-01-07 09:24:04 +09:00
Kagami Sascha Rosylight
9eae82de1d chore(dependabot) open-pull-requests-limit=10?
Somehow it's not opening any PR, so try higher count
2024-01-06 13:33:56 +01:00
syuilo
746367004e feat(frontend): add new game 2024-01-06 20:15:28 +09:00
Chocolate Pie
072f67d6e7 feat: Add support for mCaptcha (#12905)
* feat: Add support for mCaptcha

* fix: Fix docker compose configuration

* chore(frontend/docs): update changelog & fix eslint errors

* `@mcaptcha/vanilla-glue`をダイナミックインポートするように

* chore: Add missing prefix to CHANGELOG

* refactor(backend): 適当につけた変数の名前を変更
2024-01-06 20:14:33 +09:00
zyoshoka
b55a6a80e1 refactor(frontend): scripts/form.tsの型定義を修正してTS2344/TS2345エラーを削減 (#12913) 2024-01-06 18:43:28 +09:00
riku6460
24645e3d3d enhance(backend): ActivityPub 周りで連合先から HTTP 429 Too Many Requests を受け取った際にジョブをリトライするように (#12917)
* enhance(backend): ActivityPub 周りで HTTP 429 Too Many Requests を受け取った際にリトライするように

* add to changelog

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2024-01-06 09:40:08 +09:00
MeiMei
d415fd29a3 enhance(backend): ActivityPub Deliver queueでBodyを事前処理するように (#12916)
* Pre-processing deliver body

* CHANGELOG

* ループ内で計算されると意味がないので

* 同じ処理を同じ形に

---------

Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com>
2024-01-06 09:07:48 +09:00
syuilo
7768385be2 refactor(frontend): reduce type errors 2024-01-05 15:25:26 +09:00
syuilo
2177792a3c refactor(frontend): reduce type errors 2024-01-05 12:52:24 +09:00
syuilo
9e20065496 refactor(frontend): reduce type errors 2024-01-05 12:38:06 +09:00
syuilo
2cd32b2248 refactor(frontend): reduce type errors 2024-01-05 12:33:47 +09:00
おさむのひと
fa9c4a19b9 refactor(frontend): os.tsに引き込んだscripts/api.tsの再exportをやめる (#12694)
* refactor(frontend): os.tsに引き込んだscripts/api.tsの再exportをやめる

* fix

* fix

* renate to "misskeyApi"

* rename file
2024-01-04 18:32:46 +09:00
syuilo
ea41cc6ec0 refactor(frontend): reduce type errors 2024-01-04 15:30:40 +09:00
syuilo
9716ea0324 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2024-01-04 15:20:25 +09:00
syuilo
02978d0247 lint 2024-01-04 15:20:23 +09:00
MeiMei
6598d320d6 enhance: Use SI prefixes for job queue widget, extends bytes (#12896)
* Use SI prefixes for job queue widget

* a

* bytes

* lint
2024-01-04 13:04:00 +09:00
FineArchs
f8d5a46dbf Fix: AiScriptのreadlineの修正をPlay以外にも適用 (#12841)
* add AiScriptReadline() in api.ts

* apply AiScriptReadline on flash.vue

* AiScriptReadline → aiScriptReadline

* Update flash.vue

* Update scratchpad.vue

* Update WidgetAiscript.vue

* Update WidgetAiscriptApp.vue

* Update WidgetButton.vue

* Update plugin.ts
2024-01-04 12:26:57 +09:00
syuilo
da154c8209 Update ROADMAP.md 2024-01-04 08:44:38 +09:00
Camilla Ett
b46f431a2e fix(frontend): モデレーターがユーザーのアバターバナーを未設定状態に出来る機能が表示されていなかった問題を修正 (#12889)
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2024-01-03 16:41:38 +09:00
かっこかり
30c3f6a222 (fix) MkFormDialogにせっていできる項目がない場合はその旨を表示するように (#12837) 2024-01-03 13:42:09 +09:00
おさむのひと
30311aca18 fix(misskey-js): /signupと/signinの定義を作成してフロントの型エラーを抑制する (#12846)
* fix(misskey-js): /signupと/signinの定義を復活してフロントの型エラーを抑制する

* fix ci

* fix ci

* fix

* fix

---------

Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
2024-01-03 13:41:28 +09:00
かっこかり
a9127e3ecd enhance(frontend): チャンネルノートのピン留めをノートメニューからできるように (#12887)
* enhance(frontend): チャンネルノートのピン留めをノートメニューからできるように

* Update Changelog
2024-01-03 13:35:40 +09:00
Camilla Ett
58469c0a69 enhance(frontend): カスタム絵文字追加画面の「タグ」の説明を追加 (#12888) 2024-01-03 08:07:04 +09:00
かっこかり
9c5559a570 (fix) MkButtonがリンクのときホバー時にunderlineが出る問題を修正 (#12849) 2024-01-02 17:48:11 +09:00
かっこかり
3187c6b28d refactor(frontend): MkNumberのアニメーションを内製してgsapを削除 (#12859)
* (refactor) MkNumberのアニメーションを内製

* 秒数調整

* fix

* fix pnpm-lock

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

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

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2024-01-02 16:55:02 +09:00
Kagami Sascha Rosylight
09aba4cf16 chore(backend/logger): log data for every level if exists (#12863) 2024-01-02 16:52:51 +09:00
かっこかり
5498ec57d0 fix(frontend): MkCodeEditorのデータバインディングを修正 (#12885)
* (fix) MkCodeEditorの双方向データバインディング

* fix
2024-01-02 14:53:28 +09:00
Kagami Sascha Rosylight
4893cce43c chore(dependabot): try enabling again 2023-12-31 19:48:27 +01:00
syuilo
a40ededf6b 2024 2024-01-01 00:30:56 +09:00
syuilo
379079ee42 chore(frontend): update vue to 3.4 2023-12-31 17:01:56 +09:00
tamaina
1d5a0d0777 chore: use @misskey-dev/eslint-plugin (#12860)
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-12-31 15:26:57 +09:00
tamaina
2a33981811 chore: use summaly, browser-image-resizer, and sharp-read-bmp on registry.npmjs.org instead of git (#12856)
* chore: use @misskey-dev/summaly on registry.npmjs.org instead of git

* fix backend dependency

* fic backend dependency

* @misskey-dev/sharp-read-bmp

* fix

* use @misskey-dev/browser-image-resizer
2023-12-31 09:45:35 +09:00
woxtu
c0466d1585 Convert symbols to strings explicitly (#12844) 2023-12-31 07:51:58 +09:00
woxtu
30594dde18 Fix a typo (#12853) 2023-12-29 22:50:03 +09:00
MomentQYC
7948018e6a feat: Add support for TrueMail (#12850)
Co-authored-by: MarryDream <2190758465@qq.com>
2023-12-29 18:23:29 +09:00
かっこかり
8fb8d7c10c enhance(frontend): ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように (#12851)
* (enhance) ハッシュタグ入力時に、本文の末尾の行に何も書かれていないならスペースを追記しない

* Updahe Changelog
2023-12-29 18:22:40 +09:00
zyoshoka
7ca0af9e7e chore(misskey-js): build-misskey-js-with-types時にapi-extractorを走らせるように (#12830) 2023-12-28 13:40:57 +09:00
かっこかり
ac2bace764 Update CHANGELOG.md 2023-12-28 10:27:12 +09:00
syuilo
d97924890d 2023.12.2 2023-12-28 08:05:35 +09:00
Korange
6b4f57781a enhance(frontend): 検索画面においてEnterキー押下で検索できるように (#12752)
* enhance: 検索画面においてEnterキー押下で検索できるように

* enterイベントを使用するように
2023-12-28 07:58:32 +09:00
syuilo
c525394989 New Crowdin updates (#12820)
* New translations ja-jp.yml (Korean)

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

* New translations ja-jp.yml (Korean)

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

* New translations ja-jp.yml (Indonesian)

* New translations ja-jp.yml (Bengali)
2023-12-28 07:58:15 +09:00
anatawa12
8753f9ef06 fix: running from docker is broken (#12824)
* fix: running from docker is broken

* fix: dependencies of misskey-js not found from backend

* docs(changelog): Dockerでサーバーを起動できない問題を修正

* Update CHANGELOG.md

Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>

---------

Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
2023-12-28 07:52:08 +09:00
anatawa12
35fd0a7fc2 Update CHANGELOG.md (#12826) 2023-12-28 00:04:22 +09:00
syuilo
f8261a1957 2023.12.1 2023-12-27 21:28:15 +09:00
YAVIIGI
47558a6648 feat(frontend): 投稿ウインドウにMFM要素を追加するボタンの追加 (#12788)
* functionPicker の追加

* Update CHANGELOG.md

* fix lint errors

* Add addMfmFunction

* add enableQuickAddMfmFunction setting

* Update CHANGELOG.md

issue 番号を追加

* Update index.d.ts

* change 'functionPicker' to 'mfmFunctionPicker'

* Change indent from 4 space to 1 tab

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-12-27 20:57:43 +09:00
1Step621
2a5c9e6002 Fix(frontend): MFMでfgとbgに長い単語を使うと改行されない問題を修正 (#12819)
* MFMでfgとbgに長い単語を使うと改行されない問題を修正

* update CHANGELOG.md
2023-12-27 20:41:01 +09:00
syuilo
9d5fc4ca17 refactor 2023-12-27 20:35:52 +09:00
Chocolate Pie
a598baaf01 fix(test): CIが落ちている問題を修正 (#12816)
* fix(test): CIが落ちているのを修正

* fix(ci)?: CIの`typecheck`が落ちる問題を修正

* fix(ci): コンフィグファイルのタイポを修正
2023-12-27 17:36:38 +09:00
MomentQYC
e0040f5da3 Add a prompt for Tor Browser users (#12776)
* perf: Add a prompt for Tor Browser users

* typo
2023-12-27 15:55:56 +09:00
syuilo
cc659721fb New Crowdin updates (#12789)
* New translations ja-jp.yml (French)

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

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

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

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

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Russian)
2023-12-27 15:55:27 +09:00
GrapeApple0
6439c7b64b Revert "refactor: paginationの型を明示する (#12809)" (#12810)
This reverts commit 6855079811.
2023-12-27 15:55:09 +09:00
syuilo
8904e0a12b 🎨 2023-12-27 15:15:08 +09:00
syuilo
9410bc046b Update CHANGELOG.md 2023-12-27 15:12:43 +09:00
Kagami Sascha Rosylight
ad346b6f36 feat(backend/oauth): allow CORS for token endpoint (#12814)
* feat(backend/oauth): allow CORS for token endpoint

* no need to explicitly set origin to `*`

* Update CHANGELOG.md
2023-12-27 15:10:24 +09:00
Chocolate Pie
c96bc36fed Merge pull request from GHSA-7pxq-6xx9-xpgm
* fix: fix improper authorization when accessing with third-party application

* refactor: refactor type definitions

* fix: get rid of unnecessary access limitation

* enhance: サードパーティアプリケーションがWebsocket APIを使えるように

* fix: add missing parentheses

* Revert "fix(backend): add missing kind definition for admin endpoints to improve security"

This reverts commit 5150053275.

* frontend: 翻訳の抜けを訂正, read:adminとwrite:adminはアクセス発行トークンのデフォルトでは非表示にする

* enhance(test): misskey-ghsa-7pxq-6xx9-xpgmに関するテストを追加

* enhance(test): Websocket APIに対するテストも追加

* enhance(refactor): `@/misc/api-permissions.ts`を`misskey-js/permissions`に統合

* fix(frontend): アクセストークン発行UIで全ての権限を有効にした際、管理者用APIへのアクセスも許可してしまう問題を修正

* enhance(backend): Websocketの接続に最低限必要な権限を変更

* fix(backend): `/api/admin/meta`をサードパーティアプリケーションからはアクセスできないように

* fix(backend): エンドポイントにアクセスするために必要な権限を変更

* fix(frontend/locale): Add missing type declaration

* chore: update `misskey-js/src/autogen`

---------

Co-authored-by: tamaina <tamaina@hotmail.co.jp>
2023-12-27 15:08:59 +09:00
syuilo
d87fecda7f chore(frontend): update team members 2023-12-27 14:21:34 +09:00
GrapeApple0
6855079811 refactor: paginationの型を明示する (#12809)
* refactor: paginationの型を明示する

* asではなくsatisfiesを使うように
2023-12-26 21:40:27 +09:00
shiosyakeyakini
9022b05fea fix(backend): 非センシティブのみ(リモートはいいねのみ)が昨日していない問題を修正 (#12801) (#12802)
Co-authored-by: sorairo <sorairo@shiosyakeyakini.info>
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-12-26 18:42:37 +09:00
zyoshoka
75034d9240 refactor(frontend): Reactivityで型を明示するように (#12791)
* refactor(frontend): Reactivityで型を明示するように

* fix: プロパティの参照が誤っているのを修正

* fix: 初期化の値を空配列に書き換えていた部分をnullに置き換え
2023-12-26 14:19:35 +09:00
かっこかり
a9b42765f9 (dev) Issue Templateに、自分で実装してPRを出したいかの意思表明を追加 (#12799)
* Update 01_bug-report.yml

* Update 02_feature-request.yml
2023-12-26 11:40:55 +09:00
Soli
eb23798c9f fix(frontend): ロールアサイン時の通知で,ロールアイコンが縮小されずに表示される問題を修正 (misskey-dev#12805) (#12806) 2023-12-26 11:40:31 +09:00
FineArchs
4f247a0784 Feat: クリックイベントを発生させるMFM構文を追加 (#12798)
* Update MkMisskeyFlavoredMarkdown.ts

* fix MkMisskeyFlavoredMarkdown.ts

* Update MkAsUi.vue

* Update ui.ts

* Fix MkMisskeyFlavoredMarkdown.ts

* Update CHANGELOG.md

* fix ui.ts

* revert CHANGELOG.md

* Update CHANGELOG.md
2023-12-25 18:03:06 +09:00
syuilo
95547da5a5 Update SECURITY.md 2023-12-25 16:50:41 +09:00
syuilo
b0799089cd Update SECURITY.md 2023-12-25 16:47:26 +09:00
Sayamame-beans
8ed7c7486c fix(frontend): モデログ表示の"logYellow"が機能していない問題を修正 (#12794)
* fix: logYellow of moderation log was not working

* docs(changelog): Fix: 一部のモデログ(logYellowでの表示対象)について、表示の色が変わらない問題を修正
2023-12-25 14:49:06 +09:00
anatawa12
fd040c50b1 fix: 自分のdirect noteがuser list timelineに追加されない (#12782)
* fix: 自分のdirect noteがuser list timelineに追加されない

* docs(changelog): Fix: 自分のdirect noteがuser list timelineに追加されない
2023-12-25 11:56:00 +09:00
anatawa12
237fe242ad chore(misskey-js): update misskey-js with api.json (#12778)
pnpm build && pnpm build-misskey-js-with-types && pnpm --filter misskey-js api
2023-12-24 17:54:00 +09:00
zyoshoka
0009aa332b refactor(frontend): import宣言周りのエラーを修正 (#12773) 2023-12-24 16:16:58 +09:00
syuilo
bf45c23098 Update CHANGELOG.md 2023-12-24 15:38:03 +09:00
syuilo
7167bb397e Update CHANGELOG.md 2023-12-24 15:31:48 +09:00
syuilo
0393d8f53c New Crowdin updates (#12759)
* New translations ja-jp.yml (Spanish)

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

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Korean)
2023-12-24 15:25:13 +09:00
Nya Candy
cae40e68e4 fix: lint (#12761) 2023-12-24 15:24:51 +09:00
おさむのひと
36701f8a7c fix(backend): 1702718871541-ffVisibility.jsのdownが壊れている (#12767) 2023-12-24 15:24:26 +09:00
syuilo
6fce36374d enhance(backend): センシティブワードの設定がハッシュタグトレンドにも適用されるように 2023-12-24 15:23:56 +09:00
anatawa12
316ffcea54 ci: Get api.json from Misskeyでupload-artifact@v4で同名artifactでエラーになるのを修正 (#12770)
* ci: upload-artifact@v4で同名artifactでエラーになるのを修正

Co-authored-by: おさむのひと <46447427+samunohito@users.noreply.github.com>

* report-api-diff.ymlの最中にエラーが発生したときに分かりづらいので、PRにコメントを残すようにする

* 古いget-api-diffを使ってるactionとの互換性をもたせる

---------

Co-authored-by: おさむのひと <46447427+samunohito@users.noreply.github.com>
2023-12-24 14:20:43 +09:00
syuilo
471c8ec050 Update CHANGELOG.md 2023-12-23 19:59:27 +09:00
anatawa12
2f425aa03f fix: ブロックされててもpure RNできる (#12758)
なぜかわからないけど元々Quoteはできるようなのでそれに戻しました
2023-12-23 17:55:34 +09:00
syuilo
e852f4b60d Revert "fix(backend): renote判定がおかしい"
This reverts commit f43599552f.
2023-12-23 17:55:27 +09:00
syuilo
f43599552f fix(backend): renote判定がおかしい 2023-12-23 17:54:29 +09:00
syuilo
8caf2b0a4a New Crowdin updates (#12748)
* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (Chinese Traditional)
2023-12-23 16:43:06 +09:00
syuilo
59b47b8623 Update CHANGELOG.md 2023-12-23 16:40:31 +09:00
皐月なふ (Nafu Satsuki)
30cf5c3ab0 chore(frontend): API設定項目の名前をきちんとサービスの名前で表記する (#12753) 2023-12-23 15:32:53 +09:00
おさむのひと
1716c6562c fix: .npmrcによりpackage.json記載のnodeバージョンに満たない場合はビルドに失敗するようにする (#12755) 2023-12-23 15:32:31 +09:00
syuilo
6e4894c165 lint 2023-12-23 14:39:01 +09:00
おさむのひと
98734af9a7 fix: 2023.12.0のNoteの一部文言を修正 (#12754) 2023-12-23 14:30:39 +09:00
syuilo
2c7d07bca6 Update CHANGELOG.md 2023-12-23 12:15:10 +09:00
GrapeApple0
5b5a537f56 feat: 登録を拒否するメールアドレスのドメインを手動で設定できるように (#12740)
* feat: 使い捨てアドレスのドメインを手動で設定できるように

* Update CHANGELOG.md

* disposableEmailDomains -> bannedEmailDomains

* isBlockedHostを使うように
2023-12-23 12:06:22 +09:00
Nya Candy
b3c4f7eddc fix: email verify enable logic (#12743) 2023-12-23 11:00:14 +09:00
syuilo
6254954957 Update CHANGELOG.md 2023-12-22 21:50:40 +09:00
syuilo
9c04749359 2023.12.0 2023-12-22 20:59:28 +09:00
syuilo
6b7a810b8e Update CHANGELOG.md 2023-12-22 20:57:59 +09:00
ikasoba
5eb944ecde enhance: チャンネルに新規の投稿がある場合にバッジを表示させる (#12690)
* 多分できたかも

* 不要なpropsを削除

* 不要なimportを削除

* 縁を付けた

* 枠線の位置を端に寄せた

* やっぱり内側へ寄せることにした

* できたかも

* 修正

* 修正

* クラスにまとめた

* 微調整

* 直せたかも

* importを付け足し

* 多分できたかも

* Update channel.vue

* Update MkMenu.vue

* Update channel.vue

* Update CHANGELOG.md

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-12-22 20:41:42 +09:00
かっこかり
3d4af18327 [Hub Next] Misskey Hubのリンクを変更 (#12699)
* misskey hub のリンクを変更

* misskey-hub.net -> assets.misskey-hub.net
Related to misskey-dev/misskey-hub-next#57
2023-12-22 20:16:31 +09:00
syuilo
179cb1d813 fix type 2023-12-22 20:14:20 +09:00
syuilo
6d4aa316ac New Crowdin updates (#12732)
* New translations ja-jp.yml (Chinese Traditional)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (English)
2023-12-22 18:05:41 +09:00
zyoshoka
d68214bd46 fix(i18n): ロールが付与された際の通知のローカライゼーションが一部欠けているのを修正 (#12745) 2023-12-22 15:38:27 +09:00
anatawa12
52b94dbc4a fix: 引用RNがpure RNとして連合され、pure RNが引用RNとして連合される (#12744)
* fix: quote notes are rendered as pure renote

* fix: filesが指定されてて空配列のときにQuote扱いされる

* chore: isQuoteの仕様をmisc/is-quote.tsと揃える

* docs: is-quote.tsの方にNoteCreateService.isQuoteのことを書いて更新忘れを防ぐ
2023-12-22 14:03:39 +09:00
おさむのひと
433d46e57f fix(backend): テストが死んでいるのを修正 (#12738)
* fix test

* fix test

* fix test

* fix test

* fix test
2023-12-22 09:06:13 +09:00
GrapeApple0
79ca93cefb enhance: api.jsonのレスポンスの内容を実際の内容に合わせる (#12723)
* Create packedAdSchema

* admin/emoji/add

* admin/get-user-ips

* admin/roles/users

* admin/get-index-stats

* admin/accounts/find-by-email

* fix type of admin/ad/list

* federation/stats

* endpoints

* get-online-users-count

* i/2fa/register-key

* i/2fa/key-done

* i/2fa/register

* i/apps

* i/authorized-apps

* i/registry/get-all

* i/registry/get

* i/registry/get-detail

* i/registry/key-with-type

* i/registry/scopes-with-domain

* i/update-email

* i/move

* i/webhooks/create

* fix miss type

* i/webhooks/show

* i/webhooks/list

* flash/create

* roles/users

* server-info

* test

* users/lists/get-memberships

* users/achievements

* fetch-rss

* fetch-external-resources
2023-12-21 16:57:05 +09:00
syuilo
07d4632cd7 Update CHANGELOG.md
Co-authored-by: zyoshoka <107108195+zyoshoka@users.noreply.github.com>
2023-12-21 14:45:39 +09:00
790 changed files with 28414 additions and 4572 deletions

View File

@@ -2,3 +2,4 @@
POSTGRES_PASSWORD=example-misskey-pass
POSTGRES_USER=example-misskey-user
POSTGRES_DB=misskey
DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}"

View File

@@ -89,3 +89,9 @@ body:
render: markdown
validations:
required: false
- type: checkboxes
attributes:
label: Do you want to address this bug yourself?
options:
- label: Yes, I will patch the bug myself and send a pull request

View File

@@ -14,4 +14,9 @@ body:
label: Purpose
description: Describe the specific problem or need you think this feature will solve, and who it will help.
validations:
required: true
required: true
- type: checkboxes
attributes:
label: Do you want to implement this feature yourself?
options:
- label: Yes, I will implement this by myself and send a pull request

View File

@@ -17,16 +17,32 @@ updates:
directory: "/"
schedule:
interval: daily
# PNPM has an issue with dependabot. See:
# https://github.com/dependabot/dependabot-core/issues/7258
# https://github.com/pnpm/pnpm/issues/6530
# TODO: Restore this when the issue is solved
open-pull-requests-limit: 0
open-pull-requests-limit: 10
# List dependencies required to be updated together, sharing the same version numbers.
# Those who simply have the common owner (e.g. @fastify) don't need to be listed.
groups:
swc:
aws-sdk:
patterns:
- "@swc/*"
- "@aws-sdk/*"
bull-board:
patterns:
- "@bull-board/*"
nestjs:
patterns:
- "@nestjs/*"
slacc:
patterns:
- "slacc-*"
storybook:
patterns:
- "storybook*"
- "@storybook/*"
swc-core:
patterns:
- "@swc/core*"
typescript-eslint:
patterns:
- "@typescript-eslint/*"
tensorflow:
patterns:
- "@tensorflow/*"

View File

@@ -1,6 +1,12 @@
name: API report (misskey.js)
on: [push, pull_request]
on:
push:
paths:
- packages/misskey-js/**
pull_request:
paths:
- packages/misskey-js/**
jobs:
report:

43
.github/workflows/changelog-check.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: Check the description in CHANGELOG.md
on:
pull_request:
branches:
- master
- develop
jobs:
check-changelog:
runs-on: ubuntu-latest
steps:
- name: Checkout head
uses: actions/checkout@v4.1.1
- name: Setup Node.js
uses: actions/setup-node@v4.0.1
with:
node-version-file: '.node-version'
- name: Checkout base
run: |
mkdir _base
cp -r .git _base/.git
cd _base
git fetch --depth 1 origin ${{ github.base_ref }}
git checkout origin/${{ github.base_ref }} CHANGELOG.md
- name: Copy to Checker directory for CHANGELOG-base.md
run: cp _base/CHANGELOG.md scripts/changelog-checker/CHANGELOG-base.md
- name: Copy to Checker directory for CHANGELOG-head.md
run: cp CHANGELOG.md scripts/changelog-checker/CHANGELOG-head.md
- name: diff
continue-on-error: true
run: diff -u CHANGELOG-base.md CHANGELOG-head.md
working-directory: scripts/changelog-checker
- name: Setup Checker
run: npm install
working-directory: scripts/changelog-checker
- name: Run Checker
run: npm run run
working-directory: scripts/changelog-checker

View File

@@ -0,0 +1,127 @@
name: Check Misskey JS autogen
on:
pull_request:
branches:
- master
- develop
paths:
- packages/backend/**
jobs:
check-misskey-js-autogen:
runs-on: ubuntu-latest
permissions:
pull-requests: write
env:
api_json_names: "api-base.json api-head.json"
steps:
- name: checkout
uses: actions/checkout@v4
with:
submodules: true
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: setup node
id: setup-node
uses: actions/setup-node@v4
with:
node-version-file: '.node-version'
cache: pnpm
- name: install dependencies
run: pnpm i --frozen-lockfile
- name: wait get-api-diff
uses: lewagon/wait-on-check-action@v1.3.3
with:
ref: ${{ github.event.pull_request.head.sha }}
check-regexp: get-from-misskey .+
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 30
- name: Download artifact
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const workflows = await github.rest.actions.listWorkflowRunsForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
head_sha: `${{ github.event.pull_request.head.sha }}`
}).then(x => x.data.workflow_runs);
console.log(workflows.map(x => ({name: x.name, title: x.display_title})));
const run_id = workflows.find(x => x.name.includes("Get api.json from Misskey")).id;
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: run_id,
});
let matchArtifacts = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name.startsWith("api-artifact-") || artifact.name == "api-artifact"
});
await Promise.all(matchArtifacts.map(async (artifact) => {
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: artifact.id,
archive_format: 'zip',
});
await fs.promises.writeFile(`${process.env.GITHUB_WORKSPACE}/${artifact.name}.zip`, Buffer.from(download.data));
}));
- name: unzip artifacts
run: |-
find . -mindepth 1 -maxdepth 1 -type f -name '*.zip' -exec unzip {} -d . ';'
ls -la
- name: build autogen
run: |-
for name in $(echo $api_json_names)
do
checksum=$(mktemp)
mv $name packages/misskey-js/generator/api.json
cd packages/misskey-js/generator
pnpm run generate
find built -type f -exec sh -c 'echo $(sed -E "s/^\s+\*\s+generatedAt:.+$//" {} | sha256sum | cut -d" " -f 1) {}' \; > $checksum
cd ../../..
cp $checksum ${name}_checksum
done
- name: check update for type definitions
run: diff $(echo -n ${api_json_names} | awk -v RS=" " '{ printf "%s_checksum ", $0 }')
- name: send message
if: failure()
uses: thollander/actions-comment-pull-request@v2
with:
comment_tag: check-misskey-js-autogen
message: |-
Thank you for sending us a great Pull Request! 👍
Please regenerate misskey-js type definitions! 🙏
example:
```sh
pnpm run build-misskey-js-with-types
```
- name: send message
if: success()
uses: thollander/actions-comment-pull-request@v2
with:
comment_tag: check-misskey-js-autogen
mode: delete
message: "Thank you!"

View File

@@ -56,7 +56,7 @@ jobs:
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: api-artifact
name: api-artifact-${{ matrix.api-json-name }}
path: ${{ matrix.api-json-name }}
save-pr-number:
@@ -69,5 +69,5 @@ jobs:
echo "$PR_NUMBER" > ./pr_number
- uses: actions/upload-artifact@v4
with:
name: api-artifact
name: api-artifact-pr-number
path: pr_number

View File

@@ -5,7 +5,19 @@ on:
branches:
- master
- develop
paths:
- packages/backend/**
- packages/frontend/**
- packages/sw/**
- packages/misskey-js/**
- packages/shared/.eslintrc.js
pull_request:
paths:
- packages/backend/**
- packages/frontend/**
- packages/sw/**
- packages/misskey-js/**
- packages/shared/.eslintrc.js
jobs:
pnpm_install:
@@ -78,4 +90,6 @@ jobs:
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- run: pnpm --filter misskey-js run build
if: ${{ matrix.workspace == 'backend' }}
- run: pnpm --filter ${{ matrix.workspace }} run typecheck

View File

@@ -19,24 +19,28 @@ jobs:
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name == "api-artifact"
})[0];
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
let matchArtifacts = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name.startsWith("api-artifact-") || artifact.name == "api-artifact"
});
let fs = require('fs');
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/api-artifact.zip`, Buffer.from(download.data));
- name: Extract artifact
run: unzip api-artifact.zip -d artifacts
await Promise.all(matchArtifacts.map(async (artifact) => {
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: artifact.id,
archive_format: 'zip',
});
await fs.promises.writeFile(`${process.env.GITHUB_WORKSPACE}/${artifact.name}.zip`, Buffer.from(download.data));
}));
- name: Extract all artifacts
run: |
find . -mindepth 1 -maxdepth 1 -type f -name '*.zip' -exec unzip {} -d artifacts ';'
ls -la
- name: Load PR Number
id: load-pr-num
run: echo "pr-number=$(cat artifacts/pr_number)" >> "$GITHUB_OUTPUT"
@@ -83,3 +87,11 @@ jobs:
pr_number: ${{ steps.load-pr-num.outputs.pr-number }}
comment_tag: show_diff
filePath: ./output.md
- name: Tell error to PR
uses: thollander/actions-comment-pull-request@v2
if: failure() && steps.load-pr-num.outputs.pr-number
with:
pr_number: ${{ steps.load-pr-num.outputs.pr-number }}
comment_tag: show_diff_error
message: |
api.jsonの差分作成中にエラーが発生しました。詳細は[Workflowのログ](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})を確認してください。

View File

@@ -5,10 +5,18 @@ on:
branches:
- master
- develop
paths:
- packages/backend/**
# for permissions
- packages/misskey-js/**
pull_request:
paths:
- packages/backend/**
# for permissions
- packages/misskey-js/**
jobs:
jest:
unit:
runs-on: ubuntu-latest
strategy:
@@ -51,9 +59,59 @@ jobs:
- name: Build
run: pnpm build
- name: Test
run: pnpm jest-and-coverage
- name: Upload Coverage
run: pnpm --filter backend test-and-coverage
- name: Upload to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./packages/backend/coverage/coverage-final.json
e2e:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.10.0]
services:
postgres:
image: postgres:15
ports:
- 54312:5432
env:
POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust
redis:
image: redis:7
ports:
- 56312:6379
steps:
- uses: actions/checkout@v4.1.1
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.1
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .github/misskey/test.yml .config
- name: Build
run: pnpm build
- name: Test
run: pnpm --filter backend test-and-coverage:e2e
- name: Upload to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./packages/backend/coverage/coverage-final.json

View File

@@ -5,7 +5,20 @@ on:
branches:
- master
- develop
paths:
- packages/frontend/**
# for permissions
- packages/misskey-js/**
# for e2e
- packages/backend/**
pull_request:
paths:
- packages/frontend/**
# for permissions
- packages/misskey-js/**
# for e2e
- packages/backend/**
jobs:
vitest:

View File

@@ -6,8 +6,12 @@ name: Test (misskey.js)
on:
push:
branches: [ develop ]
paths:
- packages/misskey-js/**
pull_request:
branches: [ develop ]
paths:
- packages/misskey-js/**
jobs:
test:

47
.github/workflows/validate-api-json.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
name: Test (backend)
on:
push:
branches:
- master
- develop
paths:
- packages/backend/**
pull_request:
paths:
- packages/backend/**
jobs:
validate-api-json:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.10.0]
steps:
- uses: actions/checkout@v4.1.1
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.1
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- name: Install Redocly CLI
run: npm i -g @redocly/cli
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .config/example.yml .config/default.yml
- name: Build and generate
run: pnpm build && pnpm --filter backend generate-api-json
- name: Validation
run: npx @redocly/cli lint --extends=minimal ./packages/backend/built/api.json

1
.gitignore vendored
View File

@@ -41,6 +41,7 @@ docker-compose.yml
# misskey
/build
built
built-test
/data
/.cache-loader
/db

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
engine-strict = true

View File

@@ -5,32 +5,106 @@
-
### Client
- Fix: ページ一覧ページの表示がモバイル環境において崩れているのを修正
- Fix: MFMでルビの中のテキストがnyaizeされない問題を修正
-
### Server
-
-->
## 2023.x.x (unreleased)
## 202x.x.x (Unreleased)
### General
- Feat: [mCaptcha](https://github.com/mCaptcha/mCaptcha)のサポートを追加
- Fix: リストライムラインの「リノートを表示」が正しく機能しない問題を修正
### Client
- Feat: 新しいゲームを追加
- Feat: 音声・映像プレイヤーを追加
- Feat: 絵文字の詳細ダイアログを追加
- Feat: 枠線をつけるMFM`$[border.width=1,style=solid,color=fff,radius=0 ...]`を追加
- デフォルトで枠線からはみ出る部分が隠されるようにしました。初期と同じ挙動にするには`$[border.noclip`が必要です
- Feat: スワイプでタブを切り替えられるように
- Enhance: MFM等のコードブロックに全文コピー用のボタンを追加
- Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように
- Enhance: チャンネルノートのピン留めをノートのメニューからできるように
- Enhance: 管理者の場合はAPI tokenの発行画面で管理機能に関する権限を付与できるように
- Enhance: AiScriptを0.17.0に更新 [CHANGELOG](https://github.com/aiscript-dev/aiscript/blob/bb89d132b633a622d3cb0eff0d0cc7e476c0cfdd/CHANGELOG.md)
- 配列の範囲外・非整数のインデックスへの代入が完全禁止になるので注意
- Enhance: 絵文字ピッカー・オートコンプリートで、完全一致した絵文字を優先的に表示するように
- Enhance: Playの説明欄にMFMを使えるように
- Enhance: チャンネルノートの場合は詳細ページからその前後のノートを見れるように
- Fix: ネイティブモードの絵文字がモノクロにならないように
- Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正
- Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正
- Fix: v2023.12.1で追加された`$[clickable ...]`および`onClickEv`が正しく機能していないのを修正
- Enhance: ページ遷移時にPlayerを閉じるように
### Server
- Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました
- Enhance: ActivityPub Deliver queueでBodyを事前処理するように (#12916)
- Enhance: クリップをエクスポートできるように
- Enhance: `/files`のファイルに対してHTTP Rangeリクエストを行えるように
- Enhance: `api.json`のOpenAPI Specificationを3.1.0に更新
- Fix: `drive/files/update`でファイル名のバリデーションが機能していない問題を修正
- Fix: `notes/create`で、`text`が空白文字のみで構成されているか`null`であって、かつ`text`だけであるリクエストに対するレスポンスが400になるように変更
- Fix: `notes/create`で、`text`が空白文字のみで構成されていてかつリノート、ファイルまたは投票を含んでいるリクエストに対するレスポンスの`text``""`から`null`になるように変更
- Fix: ipv4とipv6の両方が利用可能な環境でallowedPrivateNetworksが設定されていた場合プライベートipの検証ができていなかった問題を修正
- Fix: properly handle cc followers
## 2023.12.2
### General
- v2023.12.1でDockerを利用してサーバーを起動できない問題を修正
### Client
- Enhance: 検索画面においてEnterキー押下で検索できるように
## 2023.12.1
### Note
- Node.js 20.10.0が最小要件になりました
- アクセストークンの権限が再整理されたため、一部のAPIが古いAPIトークンでは動作しなくなりました。\
権限不足になる場合には権限を再設定して再生成してください。
### General
- Enhance: ローカリゼーションの更新
- Fix: 自分のdirect noteがuser list timelineに追加されない
### Client
- Feat: AiScript専用のMFM構文`$[clickable.ev=EVENTNAME ...]`を追加。`Mk:C:mfm`のオプション`onClickEv`に関数を渡すと、クリック時に`EVENTNAME`を引数にして呼び出す
- Enhance: MFM入力補助ボタンを投稿フォームに表示できるように #12787
- Fix: 一部のモデログ(logYellowでの表示対象)について、表示の色が変わらない問題を修正
- Fix: `fg`/`bg`MFMに長い単語を指定すると、オーバーフローされずはみ出る問題を修正
### Server
- Enhance: センシティブワードの設定がハッシュタグトレンドにも適用されるようになりました
- Enhance: `oauth/token`エンドポイントのCORS対応
- Fix: 1702718871541-ffVisibility.jsのdownが壊れている
- Fix:「非センシティブのみ(リモートはいいねのみ)」を設定していても、センシティブに設定されたカスタム絵文字をリアクションできる問題を修正
- Fix: ロールアサイン時の通知で,ロールアイコンが縮小されずに表示される問題を修正
- Fix: サードパーティアプリケーションがWebsocket APIに無条件にアクセスできる問題を修正
- Fix: サードパーティアプリケーションがユーザーの許可なしに非公開の情報を見ることができる問題を修正
## 2023.12.0
### Note
- 依存関係の更新に伴い、Node.js 20.10.0が最小要件になりました
- 絵文字の追加辞書を既にインストールしている場合は、お手数ですが再インストールのほどお願いします
- 絵文字ピッカーにピン留め表示する絵文字設定が「リアクション用」と「絵文字入力用」に分かれました。以前の設定は「リアクション用」として使用されます。
**影響:**
それにより、投稿フォームから表示される絵文字ピッカーのピン留め絵文字がリセットされたように感じるかもしれません(新設された投稿用のピン留め絵文字が使われるため)。
それにより、投稿フォームから表示される絵文字ピッカーのピン留め絵文字がリセットされたように感じるかもしれません(新設された"ピン留め(全般)"の設定が使われるため)。
投稿用のピン留め絵文字をアップデート前の状態にするには、以下の手順で操作します。
1. 「設定」メニューに移動し、「絵文字ピッカー」タブを選択します。
2. 「ピン留 (全般)」のタブを選択します。
3. 「リアクション設定からコピーする」ボタンを押すことで、アップデート前の状態に戻すことができます。
3. 「リアクション設定から上書きする」ボタンを押すことで、アップデート前の状態に戻すことができます。
### General
- Feat: メールアドレスの認証にverifymail.ioを使えるように (cherry-pick from https://github.com/TeamNijimiss/misskey/commit/971ba07a44550f68d2ba31c62066db2d43a0caed)
- Feat: モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能を追加 (cherry-pick from https://github.com/TeamNijimiss/misskey/commit/e0eb5a752f6e5616d6312bb7c9790302f9dbff83)
- Feat: TL上からートが見えなくなるワードミュートであるハードミュートを追加
- Enhance: 指定したドメインのメールアドレスの登録を弾くことができるように
- Enhance: 公開ロールにアサインされたときに通知が作成されるように
- Enhance: アイコンデコレーションを複数設定できるように
- Enhance: アイコンデコレーションの位置を微調整できるように
@@ -62,12 +136,17 @@
- Enhance: ユーザー名、プロフィール、お知らせ、ページの編集画面でMFMや絵文字のオートコンプリートが使用できるように
- Enhance: プロフィール、お知らせの編集画面でMFMのプレビューを表示できるように
- Enhance: 絵文字の詳細ページに記載される情報を追加
- Enhance: リアクションの表示幅制限を設定可能に
- Enhance: Unicode 15.0のサポート
- Enhance: コードブロックのハイライト機能を利用するには言語を明示的に指定させるように
- MFMでコードブロックを利用する際に意図しないハイライトが起こらないようになりました
- 逆に、MFMでコードハイライトを利用したい際は言語を明示的に指定する必要があります
(例: ` ```js ` → Javascript, ` ```ais ` → AiScript
- Enhance: 絵文字などのオートコンプリートでShift+Tabを押すと前の候補を選択できるように
- Enhance: チャンネルに新規の投稿がある場合にバッジを表示させる
- Enhance: サウンド設定に「サウンドを出力しない」と「Misskeyがアクティブな時のみサウンドを出力する」を追加
- Enhance: 設定したタグをトレンドに表示させないようにする項目を管理画面で設定できるように
- Enhance: 絵文字ピッカーのカテゴリに「/」を入れることでフォルダ分け表示できるように
- Fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正
- Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367
- Fix: コードエディタが正しく表示されない問題を修正
@@ -83,10 +162,17 @@
- Fix: 投票のみ/画像のみの引用RNが、通知欄でただのRNとして判定されるバグを修正
- Fix: CWをつけて引用RNしても、普通のRNとして扱われてしまうバグを修正しました。
- Fix: 「画像が1枚のみのメディアリストの高さ」を「デフォルト」以外に設定していると、CWの中などに添付された画像が見られないバグを修正
- Fix: DeepL TranslationのPro accountトグルスイッチが表示されていなかったのを修正
- Fix: twitterの埋め込みカード内リンクからリンク先を開けない問題を修正
- Fix: WebKitブラウザー上でも「デバイスの画面を常にオンにする」機能が効くように
- Fix: ページ一覧ページの表示がモバイル環境において崩れているのを修正
- Fix: MFMでルビの中のテキストがnyaizeされない問題を修正
### Server
- Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように
- Enhance: Meilisearchを有効にした検索で、ユーザーのミュートやブロックを考慮するように
- Enhance: カスタム絵文字のインポート時の動作を改善
- Enhance: json-schema(OpenAPIの戻り値として使用されるスキーマ定義)を出来る限り最新化 #12311
- Fix: 時間経過により無効化されたアンテナを再有効化したとき、サーバ再起動までその状況が反映されないのを修正 #12303
- Fix: ロールタイムラインが保存されない問題を修正
- Fix: api.jsonの生成ロジックを改善 #12402
@@ -103,7 +189,6 @@
- Fix: モデレーションログがモデレーターは閲覧できないように修正
- Fix: ハッシュタグのトレンド除外設定が即時に効果を持つように修正
- Fix: HTTP Digestヘッダのアルゴリズム部分に大文字の"SHA-256"しか使えない
- Fix: 管理者用APIのアクセス権限が適切に設定されていない問題を修正
## 2023.11.1
@@ -114,7 +199,6 @@
- Feat: 管理者がコントロールパネルからメールアドレスの照会を行えるようになりました
- Enhance: ローカリゼーションの更新
- Enhance: 依存関係の更新
- Enhance: json-schema(OpenAPIの戻り値として使用されるスキーマ定義)を出来る限り最新化 #12311
### Client
- Enhance: MFMでルビを振れるように
@@ -123,7 +207,6 @@
- 例: `$[unixtime 1701356400]`
- Enhance: プラグインでエラーが発生した場合のハンドリングを強化
- Enhance: 細かなUIのブラッシュアップ
- Enhance: サウンド設定に「サウンドを出力しない」と「Misskeyがアクティブな時のみサウンドを出力する」を追加
- Fix: 効果音が再生されるとデバイスで再生している動画や音声が停止する問題を修正 #12339
- Fix: デッキに表示されたチャンネルの表示先チャンネルを切り替えた際、即座に反映されない問題を修正 #12236
- Fix: プラグインでノートの表示を書き換えられない問題を修正
@@ -151,7 +234,7 @@
### General
- Feat: アイコンデコレーション機能
- サーバーで用意された画像をアイコンに重ねることができます
- 画像のテンプレートはこちらです: https://misskey-hub.net/avatar-decoration-template.png
- 画像のテンプレートはこちらです: https://misskey-hub.net/brand-assets/
- 最大でも黄色いエリア内にデコレーションを収めることを推奨します。
- 画像は512x512pxを推奨します。
- Feat: チャンネル設定にリノート/引用リノートの可否を設定できる項目を追加
@@ -168,7 +251,7 @@
### Client
- Feat: プラグイン・テーマを外部サイトから直接インストールできるようになりました
- 外部サイトでの実装が必要です。詳細は Misskey Hub をご覧ください
https://misskey-hub.net/docs/advanced/publish-on-your-website.html
https://misskey-hub.net/docs/for-developers/publish-on-your-website/
- Feat: 通知をグルーピングして表示するオプション(オプトアウト)
- Feat: Misskeyの基本的なチュートリアルを実装
- Feat: スワイプしてタイムラインを再読込できるように
@@ -233,7 +316,6 @@
### Client
- Enhance: TLの返信表示オプションを記憶するように
- Enhance: 投稿されてから時間が経過しているノートであることを視覚的に分かりやすく
- Feat: 絵文字ピッカーのカテゴリに「/」を入れることでフォルダ分け表示できるように
### Server
- Enhance: タイムライン取得時のパフォーマンスを向上

View File

@@ -1,5 +1,5 @@
Unless otherwise stated this repository is
Copyright © 2014-2023 syuilo and contributers
Copyright © 2014-2024 syuilo and contributors
And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE.

View File

@@ -51,6 +51,7 @@ WORKDIR /misskey
COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"]
COPY --link ["scripts", "./scripts"]
COPY --link ["packages/backend/package.json", "./packages/backend/"]
COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"]
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
pnpm i --frozen-lockfile --aggregate-output
@@ -77,7 +78,9 @@ WORKDIR /misskey
COPY --chown=misskey:misskey --from=target-builder /misskey/node_modules ./node_modules
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/backend/node_modules ./packages/backend/node_modules
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-js/node_modules ./packages/misskey-js/node_modules
COPY --chown=misskey:misskey --from=native-builder /misskey/built ./built
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-js/built ./packages/misskey-js/built
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/backend/built ./packages/backend/built
COPY --chown=misskey:misskey --from=native-builder /misskey/fluent-emojis /misskey/fluent-emojis
COPY --chown=misskey:misskey . ./

View File

@@ -7,10 +7,10 @@
---
<a href="https://misskey-hub.net/instances.html">
<a href="https://misskey-hub.net/servers/">
<img src="https://custom-icon-badges.herokuapp.com/badge/find_an-instance-acea31?logoColor=acea31&style=for-the-badge&logo=misskey&labelColor=363B40" alt="find an instance"/></a>
<a href="https://misskey-hub.net/docs/install.html">
<a href="https://misskey-hub.net/docs/for-admin/install/guides/">
<img src="https://custom-icon-badges.herokuapp.com/badge/create_an-instance-FBD53C?logoColor=FBD53C&style=for-the-badge&logo=server&labelColor=363B40" alt="create an instance"/></a>
<a href="./CONTRIBUTING.md">
@@ -51,7 +51,7 @@ With Misskey's built in drive, you get cloud storage right in your social media,
## Documentation
Misskey Documentation can be found at [Misskey Hub](https://misskey-hub.net/), some of the links and graphics above also lead to specific portions of it.
Misskey Documentation can be found at [Misskey Hub](https://misskey-hub.net/docs/), some of the links and graphics above also lead to specific portions of it.
## Sponsors

View File

@@ -6,6 +6,7 @@ Also, the later tasks are more indefinite and are subject to change as developme
This is the phase we are at now. We need to make a high-maintenance environment that can withstand future development.
- ~~Make the number of type errors zero (backend)~~ → Done ✔️
- Make the number of type errors zero (frontend)
- Improve CI
- ~~Fix tests~~ → Done ✔️
- Fix random test failures - https://github.com/misskey-dev/misskey/issues/7985 and https://github.com/misskey-dev/misskey/issues/7986

View File

@@ -1,7 +1,6 @@
# Reporting Security Issues
If you discover a security issue in Misskey, please report it by sending an
email to [syuilotan@yahoo.co.jp](mailto:syuilotan@yahoo.co.jp).
If you discover a security issue in Misskey, please report it by **[this form](https://github.com/misskey-dev/misskey/security/advisories/new)**.
This will allow us to assess the risk, and make a fix available before we add a
bug report to the GitHub repository.

View File

@@ -7,6 +7,7 @@ services:
links:
- db
- redis
# - mcaptcha
# - meilisearch
depends_on:
db:
@@ -48,6 +49,36 @@ services:
interval: 5s
retries: 20
# mcaptcha:
# restart: always
# image: mcaptcha/mcaptcha:latest
# networks:
# internal_network:
# external_network:
# aliases:
# - localhost
# ports:
# - 7493:7493
# env_file:
# - .config/docker.env
# environment:
# PORT: 7493
# MCAPTCHA_redis_URL: "redis://mcaptcha_redis/"
# depends_on:
# db:
# condition: service_healthy
# mcaptcha_redis:
# condition: service_healthy
#
# mcaptcha_redis:
# image: mcaptcha/cache:latest
# networks:
# - internal_network
# healthcheck:
# test: "redis-cli ping"
# interval: 5s
# retries: 20
# meilisearch:
# restart: always
# image: getmeili/meilisearch:v1.3.4

View File

@@ -2,6 +2,7 @@
_lang_: "বাংলা"
headlineMisskey: "নোট ব্যাবহার করে সংযুক্ত নেটওয়ার্ক"
introMisskey: "স্বাগতম! মিসকি একটি ওপেন সোর্স, ডিসেন্ট্রালাইজড মাইক্রোব্লগিং পরিষেবা। \n\"নোট\" তৈরির মাধ্যমে যা ঘটছে তা সবার সাথে শেয়ার করুন 📡\n\"রিঅ্যাকশন\" গুলির মাধ্যমে যেকোনো নোট সম্পর্কে আপনার অনুভূতি ব্যাক্ত করতে পারেন 👍\nএকটি নতুন দুনিয়া ঘুরে দেখুন 🚀\n"
poweredByMisskeyDescription: "{name} হল ওপেন সোর্স প্ল্যাটফর্ম <b>Misskey</b>-এর সার্ভারগুলির একটি৷"
monthAndDay: "{day}/{month}"
search: "খুঁজুন"
notifications: "বিজ্ঞপ্তি"
@@ -12,12 +13,14 @@ fetchingAsApObject: "ফেডিভার্স থেকে খবর আন
ok: "ঠিক"
gotIt: "বুঝেছি"
cancel: "বাতিল"
noThankYou: "না, ধন্যবাদ"
enterUsername: "ইউজারনেম লিখুন"
renotedBy: "{user} রিনোট করেছেন"
noNotes: "কোন নোট নেই"
noNotifications: "কোনো বিজ্ঞপ্তি নেই"
instance: "ইন্সট্যান্স"
settings: "সেটিংস"
notificationSettings: "বিজ্ঞপ্তির সেটিংস"
basicSettings: "সাধারণ সেটিংস"
otherSettings: "অন্যান্য সেটিংস"
openInWindow: "নতুন উইন্ডোতে খুলা"
@@ -42,12 +45,20 @@ pin: "পিন করা"
unpin: "পিন সরান"
copyContent: "বিষয়বস্তু কপি করুন"
copyLink: "লিঙ্ক কপি করুন"
copyLinkRenote: "রিনোট লিঙ্ক কপি করুন"
delete: "মুছুন"
deleteAndEdit: "মুছুন এবং সম্পাদনা করুন"
deleteAndEditConfirm: "আপনি কি এই নোটটি মুছে এটি সম্পাদনা করার বিষয়ে নিশ্চিত? আপনি এটির সমস্ত রিঅ্যাকশন, রিনোট এবং জবাব হারাবেন।"
addToList: "লিস্ট এ যোগ করুন"
addToAntenna: "অ্যান্টেনা এ যোগ করুন"
sendMessage: "একটি বার্তা পাঠান"
copyRSS: "RSS কপি করুন"
copyUsername: "ব্যবহারকারীর নাম কপি করুন"
copyUserId: "ব্যবহারকারীর ID কপি করুন"
copyNoteId: "নোটের ID কপি করুন"
copyFileId: "ফাইল ID কপি করুন"
copyFolderId: "ফোল্ডার ID কপি করুন"
copyProfileUrl: "প্রোফাইল URL কপি করুন"
searchUser: "ব্যবহারকারী খুঁজুন..."
reply: "জবাব"
loadMore: "আরও দেখুন"
@@ -100,6 +111,8 @@ renoted: "রিনোট করা হয়েছে"
cantRenote: "এই নোটটি রিনোট করা যাবে না।"
cantReRenote: "রিনোটকে রিনোট করা যাবে না।"
quote: "উদ্ধৃতি"
inChannelRenote: "চ্যানেলে রিনোট"
inChannelQuote: "চ্যানেলে উদ্ধৃতি"
pinnedNote: "পিন করা নোট"
pinned: "পিন করা"
you: "আপনি"
@@ -108,6 +121,10 @@ sensitive: "সংবেদনশীল বিষয়বস্তু"
add: "যুক্ত করুন"
reaction: "প্রতিক্রিয়া"
reactions: "প্রতিক্রিয়া"
emojiPicker: "ইমোজি পিকার"
pinnedEmojisForReactionSettingDescription: "রিঅ্যাকশন দেয়ার সময় আপনি ইমোজিটিকে পিন করা এবং প্রদর্শিত হওয়ার জন্য সেট করতে পারেন।"
pinnedEmojisSettingDescription: "ইমোজি ইনপুট দেয়ার সময় আপনি ইমোজিটিকে পিন করা এবং প্রদর্শিত হওয়ার জন্য সেট করতে পারেন।"
emojiPickerDisplay: "পিকার ডিসপ্লে"
reactionSettingDescription2: "পুনরায় সাজাতে টেনে আনুন, মুছতে ক্লিক করুন, যোগ করতে + টিপুন।"
rememberNoteVisibility: "নোটের দৃশ্যমান্যতার সেটিংস মনে রাখুন"
attachCancel: "অ্যাটাচমেন্ট সরান "
@@ -1034,6 +1051,7 @@ _2fa:
step3: "অ্যাপে প্রদর্শিত টোকেনটি লিখুন এবং আপনার কাজ শেষ।"
step4: "আপনাকে এখন থেকে লগ ইন করার সময়, এইভাবে টোকেন লিখতে হবে।"
securityKeyInfo: "আপনি একটি হার্ডওয়্যার সিকিউরিটি কী ব্যবহার করে লগ ইন করতে পারেন যা FIDO2 বা ডিভাইসের ফিঙ্গারপ্রিন্ট সেন্সর বা পিন সমর্থন করে৷"
renewTOTPCancel: "না, ধন্যবাদ"
_permissions:
"read:account": "অ্যাকাউন্টের তথ্য দেখুন"
"write:account": "অ্যাকাউন্টের তথ্য সম্পাদন করুন"

View File

@@ -121,6 +121,12 @@ sensitive: "NSFW"
add: "Afegir"
reaction: "Reaccions"
reactions: "Reaccions"
emojiPicker: "Selecció d'emojis"
pinnedEmojisForReactionSettingDescription: "Selecciona l'emoji amb el qual reaccionar"
pinnedEmojisSettingDescription: "Selecciona l'emoji amb el qual reaccionar"
emojiPickerDisplay: "Visualitza el selector d'emojis"
overwriteFromPinnedEmojisForReaction: "Reemplaça els emojis de la reacció"
overwriteFromPinnedEmojis: "Sobreescriu des dels emojis fixats"
reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem \"+\" per afegir."
rememberNoteVisibility: "Recorda la configuració de visibilitat de les notes"
attachCancel: "Eliminar el fitxer adjunt"
@@ -213,6 +219,9 @@ clearQueueConfirmText: "Les notes no lliurades que quedin a la cua no es federar
clearCachedFiles: "Esborra la memòria cau"
clearCachedFilesConfirm: "Segur que voleu eliminar tots els fitxers de la memòria cau?"
blockedInstances: "Instàncies bloquejades"
blockedInstancesDescription: "Llista els enllaços d'amfitrió de les instàncies que vols bloquejar separades per un salt de pàgina. Les instàncies llistades no podran comunicar-se amb aquesta instància."
silencedInstances: "Instàncies silenciades"
silencedInstancesDescription: "Llista els enllaços d'amfitrió de les instàncies que vols silenciar. Tots els comptes de les instàncies llistades s'establiran com silenciades i només podran fer sol·licitacions de seguiment, i no podran mencionar als comptes locals si no els segueixen. Això no afectarà les instàncies bloquejades."
muteAndBlock: "Silencia i bloca"
mutedUsers: "Usuaris silenciats"
blockedUsers: "Usuaris bloquejats"
@@ -227,9 +236,12 @@ preview: "Vista prèvia"
default: "Per defecte"
defaultValueIs: "Per defecte: {value}"
noCustomEmojis: "Cap emoji personalitzat"
noJobs: "No hi ha feines"
federating: "Federant"
blocked: "Bloquejat"
suspended: "Suspés"
all: "tot"
subscribing: "Subscrit a"
publishing: "S'està publicant"
notResponding: "Sense resposta"
instanceFollowing: "Seguits del servidor"
@@ -254,11 +266,31 @@ removed: "Eliminat"
removeAreYouSure: "Segur que voleu retirar «{x}»?"
deleteAreYouSure: "Segur que voleu retirar «{x}»?"
resetAreYouSure: "Segur que voleu restablir-ho?"
areYouSure: "Està segur?"
saved: "S'ha desat"
messaging: "Xat"
upload: "Puja"
keepOriginalUploading: "Guarda la imatge original"
keepOriginalUploadingDescription: "Guarda la imatge pujada com hi és. Si està apagat, una versió per a la visualització a la xarxa serà generada quan sigui pujada."
fromDrive: "Des de la unitat"
fromUrl: "Des d'un enllaç"
uploadFromUrl: "Carrega des d'un enllaç"
uploadFromUrlDescription: "Enllaç del fitxer que vols carregar"
uploadFromUrlRequested: "Càrrega sol·licitada"
uploadFromUrlMayTakeTime: "La càrrega des de l'enllaç pot prendre un temps"
explore: "Explora"
messageRead: "Vist"
noMoreHistory: "No hi resta més per veure"
startMessaging: "Començar a xatejar"
nUsersRead: "Vist per {n}"
agreeTo: "Accepto que {0}"
agree: "Hi estic d'acord"
agreeBelow: "Hi estic d'acord amb el següent"
basicNotesBeforeCreateAccount: "Notes importants"
termsOfService: "Condicions d'ús"
start: "Comença"
home: "Inici"
remoteUserCaution: "Ja que aquest usuari resideix a una instància remota, la informació mostrada es podria trobar incompleta."
activity: "Activitat"
images: "Imatges"
image: "Imatges"
@@ -274,16 +306,34 @@ dark: "Fosc"
lightThemes: "Temes clars"
darkThemes: "Temes foscos"
syncDeviceDarkMode: "Sincronitza el mode fosc amb la configuració del dispositiu"
drive: "Unitat"
fileName: "Nom del Fitxer"
selectFile: "Selecciona fitxers"
selectFiles: "Selecciona fitxers"
selectFolder: "Selecció de carpeta"
selectFolders: "Selecció de carpeta"
renameFile: "Canvia el nom del fitxer"
folderName: "Nom de la carpeta"
createFolder: "Crea una carpeta"
renameFolder: "Canvia el nom de la carpeta"
deleteFolder: "Elimina la carpeta"
folder: "Carpeta "
addFile: "Afegeix un fitxer"
emptyDrive: "La teva unitat és buida"
emptyFolder: "La carpeta està buida"
unableToDelete: "No es pot eliminar"
inputNewFileName: "Introduïu el nom de fitxer nou"
inputNewDescription: "Inserta una nova llegenda"
inputNewFolderName: "Introduïu el nom de la carpeta nova"
circularReferenceFolder: "La carpeta destinatària és una subcarpeta de la carpeta a la qual la desitges moure"
hasChildFilesOrFolders: "No és possible esborrar aquesta carpeta ja que no és buida"
copyUrl: "Copia l'URL"
rename: "Canvia el nom"
avatar: "Icona"
banner: "Bàner"
displayOfSensitiveMedia: "Visualització de contingut sensible"
whenServerDisconnected: "Quan es perdi la connexió al servidor"
disconnectedFromServer: "Desconnectat pel servidor"
reload: "Actualitza"
doNothing: "Ignora"
accept: "Accepta"
@@ -353,33 +403,132 @@ notFound: "No s'ha trobat"
markAsReadAllUnreadNotes: "Marca-ho tot com a llegit"
help: "Ajuda"
invites: "Convida"
title: "Títol"
text: "Text"
enable: "Habilita"
next: "Següent"
retype: "Torneu a introduir-la"
noteOf: "Publicació de: {user}"
quoteAttached: "Frase adjunta"
quoteQuestion: "Vols annexar-la com a cita?"
noMessagesYet: "Encara no hi ha missatges"
newMessageExists: "Has rebut un nou missatge"
onlyOneFileCanBeAttached: "Només pots adjuntar un fitxer a un missatge"
signinRequired: "Si us plau, Registra't o inicia la sessió abans de continuar"
invitations: "Convida"
invitationCode: "Codi d'invitació"
checking: "Comprovació en curs..."
available: "Disponible"
unavailable: "No és disponible"
usernameInvalidFormat: "Pots fer servir lletres (majúscules i minúscules), números i barres baixes (\"_\")"
tooShort: "Massa curt"
tooLong: "Massa llarg"
weakPassword: "Contrasenya insegura"
normalPassword: "Bona contrasenya"
strongPassword: "Contrasenya segura"
passwordMatched: "Correcte!"
passwordNotMatched: "No coincideix"
signinWith: "Inicia sessió amb amb {x}"
signinFailed: "Autenticació sense èxit. Intenta-ho un altre cop utilitzant la contrasenya i el nom correctes."
or: "O"
language: "Idioma"
uiLanguage: "Idioma de l'interfície"
aboutX: "Respecte a {x}"
emojiStyle: "Estil d'emoji"
native: "Nadiu"
disableDrawer: "No mostrar els menús en calaixos"
showNoteActionsOnlyHover: "Només mostra accions de la nota en passar amb el cursor"
noHistory: "No hi ha un registre previ"
signinHistory: "Historial d'autenticacions"
enableAdvancedMfm: "Habilitar l'MFM avançat"
enableAnimatedMfm: "Habilitar l'MFM amb moviment"
doing: "Processant..."
category: "Categoria"
tags: "Etiquetes"
docSource: "Font del document"
createAccount: "Crea un compte"
existingAccount: "Compte existent"
regenerate: "Regenera"
fontSize: "Mida del text"
mediaListWithOneImageAppearance: "Altura de la llista de fitxers amb una única imatge"
limitTo: "Limita a {x}"
noFollowRequests: "No tens sol·licituds de seguiment"
openImageInNewTab: "Obre imatges a una nova pestanya"
dashboard: "Panell de control"
local: "Local"
remote: "Remot"
total: "Total"
weekOverWeekChanges: "Canvis l'última setmana"
dayOverDayChanges: "Canvis ahir"
appearance: "Aparença"
clientSettings: "Configuració del client"
accountSettings: "Configuració del compte"
promotion: "Promocionat"
promote: "Promoure"
numberOfDays: "Nombre de dies"
hideThisNote: "Amaga la publicació"
showFeaturedNotesInTimeline: "Mostra publicacions destacades en la línia de temps"
objectStorage: "Emmagatzematge d'objectes\n"
useObjectStorage: "Utilitzar l'emmagatzematge d'objectes"
objectStorageBaseUrl: "Base d'enllaç"
objectStorageBaseUrlDesc: "Prefix d'enllaç utilitzat per a fer referencia als fitxers. Especifica l'enllaç del teu CDN o Proxy si n'estàs utilitzant qualsevol, en cas contrari, especifica l'enllaç al que es pot accedir públicament segons la guia de servei que vosté utilitza.\nPer l'ús d'S3 utilitza 'https://<bucket>.s3.amazonaws.com' I per a GCS o serveis equivalents utilitza 'https://storage.googleapis.com/<bucket>'."
newNoteRecived: "Hi ha publicacions noves"
installedDate: "Data d'instal·lació"
state: "Estat"
sort: "Ordena"
ascendingOrder: "Ascendent"
descendingOrder: "Descendent"
removeAllFollowing: "Deixar de seguir tots els usuaris seguits"
removeAllFollowingDescription: "El fet d'executar això, et farà deixar de seguir a tots els usuaris de {host}. Si us plau, executa això si l'amfitrió, per exemple, ja no existeix."
userSuspended: "Aquest usuari ha sigut suspès"
userSilenced: "Aquest usuari està sent silenciat"
yourAccountSuspendedTitle: "Aquest compte és suspès"
yourAccountSuspendedDescription: "Aquest compte ha sigut suspès a causa de la violació de les condicions d'ús o similars. Contacta l'administrador si en vol saber més. Si us plau, no en faci un altre compte."
tokenRevoked: "Codi de seguretat no vàlid"
tokenRevokedDescription: "La petició més recent ha estat denegada perquè contenia un codi de seguretat no vàlid. Actualitza la pàgina i torna-ho a provar."
accountDeleted: "Compte eliminat amb èxit"
accountDeletedDescription: "Aquest compte ha sigut eliminat"
menu: "Menú"
divider: "Divisor"
addItem: "Afegir element"
rearrange: "Torna a ordenar"
relays: "Relés"
addRelay: "Afegeix relés"
inboxUrl: "Enllaç de la safata d'entrada"
addedRelays: "Relés afegits"
serviceworkerInfo: "És obligatòria l'activació per a obtenir notificacions push"
deletedNote: "Publicacions eliminades"
invisibleNote: "Publicacions amagades"
enableInfiniteScroll: "Carrega més automàticament\n"
visibility: "Visibilitat"
poll: "Enquesta"
useCw: "Amaga el contingut"
enablePlayer: "Obre el reproductor de vídeo"
disablePlayer: "Tanca el reproductor de vídeo"
expandTweet: "Expandir post"
themeEditor: "Editor de temes"
description: "Descripció"
describeFile: "Afegir subtitulació"
enterFileDescription: "Afegeix un títol"
author: "Autor"
leaveConfirm: "Hi ha canvis sense guardar. Els vols descartar?"
manage: "Administració"
plugins: "Extensions"
preferencesBackups: "Configuracions de les Còpies de seguretat"
deck: "Escriptori"
undeck: "Tanca l'escriptori"
useBlurEffectForModal: "Utilitzar l'efecte de difuminació a modals"
useFullReactionPicker: "Utilitza el cercador de reaccions d'escala sencera"
width: "Amplada"
height: "Alçària"
large: "Gran"
medium: "Mitjà"
small: "Petit"
generateAccessToken: "Genera codi d'accés"
permission: "Permisos"
enableAll: "Habilita tot"
disableAll: "Deshabilita tot"
tokenRequested: "Donar accés al compte"
smtpHost: "Amfitrió"
smtpUser: "Nom d'usuari"
smtpPass: "Contrasenya"
@@ -389,12 +538,17 @@ clearCache: "Esborra la memòria cau"
showingPastTimeline: "Estàs veient una línia de temps antiga"
info: "Informació"
user: "Usuaris"
administration: "Administració"
middle: "Mitjà"
global: "Global"
searchByGoogle: "Cercar"
file: "Fitxers"
icon: "Icona"
replies: "Respondre"
renotes: "Impulsa"
_role:
_priority:
middle: "Mitjà"
_options:
antennaMax: "Nombre màxim d'antenes"
_email:
@@ -403,9 +557,11 @@ _email:
_instanceMute:
instanceMuteDescription: "Silencia tots els impulsos dels servidors seleccionats, també els usuaris que responen a altres d'un servidor silenciat."
_theme:
description: "Descripció"
keys:
mention: "Menció"
renote: "Renotar"
divider: "Divisor"
_sfx:
note: "Notes"
notification: "Notificacions"
@@ -447,6 +603,8 @@ _timelines:
local: "Local"
social: "Social"
global: "Global"
_play:
summary: "Descripció"
_pages:
contents: "Contingut"
blocks:

View File

@@ -121,6 +121,8 @@ sensitive: "Sensitive"
add: "Add"
reaction: "Reactions"
reactions: "Reactions"
emojiPicker: "Emoji picker"
emojiPickerDisplay: "Emoji picker display"
reactionSettingDescription2: "Drag to reorder, click to delete, press \"+\" to add."
rememberNoteVisibility: "Remember note visibility settings"
attachCancel: "Remove attachment"
@@ -260,6 +262,7 @@ removed: "Successfully deleted"
removeAreYouSure: "Are you sure that you want to remove \"{x}\"?"
deleteAreYouSure: "Are you sure that you want to delete \"{x}\"?"
resetAreYouSure: "Really reset?"
areYouSure: "Are you sure?"
saved: "Saved"
messaging: "Chat"
upload: "Upload"
@@ -543,7 +546,7 @@ showInPage: "Show in page"
popout: "Pop-out"
volume: "Volume"
masterVolume: "Master volume"
notUseSound: "No sounds output."
notUseSound: "Disable sound"
useSoundOnlyWhenActive: "Output sounds only if Misskey is active."
details: "Details"
chooseEmoji: "Select an emoji"
@@ -874,6 +877,8 @@ makeReactionsPublicDescription: "This will make the list of all your past reacti
classic: "Classic"
muteThread: "Mute thread"
unmuteThread: "Unmute thread"
followingVisibility: "Visibility of follows"
followersVisibility: "Visibility of followers"
continueThread: "View thread continuation"
deleteAccountConfirm: "This will irreversibly delete your account. Proceed?"
incorrectPassword: "Incorrect password."
@@ -1167,6 +1172,7 @@ cwNotationRequired: "If \"Hide content\" is enabled, a description must be provi
doReaction: "Add reaction"
code: "Code"
reloadRequiredToApplySettings: "Reloading is required to apply the settings."
decorate: "Decorate"
_announcement:
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."
@@ -1256,7 +1262,7 @@ _initialTutorial:
sensitiveSucceeded: "When attaching files, please set sensitivities in accordance with the server guidelines."
doItToContinue: "Mark the attachment file as sensitive to proceed."
_done:
title: "The tutorial is complete! 🎉"
title: "You've completed the tutorial! 🎉"
description: "The functions introduced here are just a small part. For a more detailed understanding of using Misskey, please refer to {link}."
_timelineDescription:
home: "In the Home timeline, you can see notes from accounts you follow."
@@ -1971,6 +1977,7 @@ _widgets:
_userList:
chooseList: "Select a list"
clicker: "Clicker"
birthdayFollowings: "Users who celebrate their birthday today"
_cw:
hide: "Hide"
show: "Show content"
@@ -2154,6 +2161,7 @@ _notification:
pollEnded: "Poll results have become available"
newNote: "New note"
unreadAntennaNote: "Antenna {name}"
roleAssigned: "Role given"
emptyPushNotificationMessage: "Push notifications have been updated"
achievementEarned: "Achievement unlocked"
testNotification: "Test notification"
@@ -2175,6 +2183,7 @@ _notification:
pollEnded: "Polls ending"
receiveFollowRequest: "Received follow requests"
followRequestAccepted: "Accepted follow requests"
roleAssigned: "Role given"
achievementEarned: "Achievement unlocked"
app: "Notifications from linked apps"
_actions:
@@ -2326,6 +2335,8 @@ _dataSaver:
_avatar:
title: "Avatar image"
description: "Stop avatar image animation. Animated images can be larger in file size than normal images, potentially leading to further reductions in data traffic."
_urlPreview:
title: "URL preview thumbnails"
_code:
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."

View File

@@ -121,6 +121,12 @@ sensitive: "Marcado como sensible"
add: "Agregar"
reaction: "Reacción"
reactions: "Reacción"
emojiPicker: "Selector de emojis"
pinnedEmojisForReactionSettingDescription: "Puedes seleccionar reacciones para fijarlos en el selector"
pinnedEmojisSettingDescription: "Puedes seleccionar emojis para fijarlos en el selector"
emojiPickerDisplay: "Mostrar el selector de emojis"
overwriteFromPinnedEmojisForReaction: "Sobreescribir las reacciones fijadas"
overwriteFromPinnedEmojis: "Sobreescribir los emojis fijados"
reactionSettingDescription2: "Arrastre para reordenar, click para borrar, apriete la tecla + para añadir."
rememberNoteVisibility: "Recordar visibilidad"
attachCancel: "Quitar adjunto"
@@ -260,6 +266,7 @@ removed: "Borrado"
removeAreYouSure: "¿Desea borrar \"{x}\"?"
deleteAreYouSure: "¿Desea borrar \"{x}\"?"
resetAreYouSure: "¿Desea reestablecer?"
areYouSure: "¿Estás conforme?"
saved: "Guardado"
messaging: "Chat"
upload: "Subir"
@@ -640,6 +647,7 @@ smtpSecure: "Usar SSL/TLS implícito en la conexión SMTP"
smtpSecureInfo: "Apagar cuando se use STARTTLS"
testEmail: "Prueba de envío"
wordMute: "Silenciar palabras"
hardWordMute: "Filtro de palabra fuerte"
regexpError: "Error de la expresión regular"
regexpErrorDescription: "Ocurrió un error en la expresión regular en la linea {line} de las palabras muteadas {tab}"
instanceMute: "Instancias silenciadas"
@@ -873,6 +881,8 @@ makeReactionsPublicDescription: "Todas las reacciones que hayas hecho serán pú
classic: "Clásico"
muteThread: "Silenciar hilo"
unmuteThread: "Mostrar hilo"
followingVisibility: "Visibilidad de seguidos"
followersVisibility: "Visibilidad de seguidores"
continueThread: "Ver la continuación del hilo"
deleteAccountConfirm: "La cuenta será borrada. ¿Está seguro?"
incorrectPassword: "La contraseña es incorrecta"
@@ -1024,6 +1034,7 @@ sensitiveWords: "Palabras sensibles"
sensitiveWordsDescription: "La visibilidad de todas las notas que contienen cualquiera de las palabras configuradas serán puestas en \"Inicio\" automáticamente. Puedes enumerás varias separándolas con saltos de línea"
sensitiveWordsDescription2: "Si se usan espacios se crearán expresiones AND y las palabras subsecuentes con barras inclinadas se convertirán en expresiones regulares."
hiddenTags: "Hashtags ocultos"
hiddenTagsDescription: "Selecciona las etiquetas que no se mostrarán en tendencias. Una etiqueta por línea."
notesSearchNotAvailable: "No se puede buscar una nota"
license: "Licencia"
unfavoriteConfirm: "¿Desea quitar de favoritos?"
@@ -1152,6 +1163,7 @@ tosAndPrivacyPolicy: "Condiciones de Uso y Política de Privacidad"
avatarDecorations: "Decoraciones de avatar"
attach: "Acoplar"
detach: "Quitar"
detachAll: "Quitar todo"
angle: "Ángulo"
flip: "Echar de un capirotazo"
showAvatarDecorations: "Mostrar decoraciones de avatar"
@@ -1165,6 +1177,10 @@ cwNotationRequired: "Si se ha activado \"ocultar contenido\", es necesario propo
doReaction: "Añadir reacción"
code: "Código"
reloadRequiredToApplySettings: "Es necesario recargar para que se aplique la configuración."
remainingN: "Faltan: {n}"
overwriteContentConfirm: "¿Quieres sustituir todo el contenido actual?"
seasonalScreenEffect: "Efectos de pantalla asociados a estaciones"
decorate: "Decorar"
_announcement:
forExistingUsers: "Solo para usuarios registrados"
forExistingUsersDescription: "Este anuncio solo se mostrará a aquellos usuarios registrados en el momento de su publicación. Si se deshabilita esta opción, aquellos usuarios que se registren tras su publicación también lo verán."
@@ -1222,6 +1238,45 @@ _initialTutorial:
home: "Puedes ver los posts de las cuentas que sigues."
local: "Puedes ver los posts de todos los usuarios de este servidor."
social: "Se ven los posts de la línea de tiempo de inicio junto con los de la línea de tiempo local."
global: "Puedes ver notas de todos los servidores conectados."
description2: "Puedes cambiar la línea de tiempo en la parte superior de la pantalla cuando quieras."
description3: "Además, hay listas de líneas de tiempo y listas de canales. Para más detalle, por favor visita este enlace: {link}"
_postNote:
title: "Ajustes de publicación de nota"
description1: "Cuando publicas una nota en Misskey, hay varias opciones disponibles. El formulario tiene este aspecto."
_visibility:
description: "Puedes limitar quién puede ver tu nota."
public: "Tu nota será visible para todos los usuarios."
home: "Publicar solo en la línea de tiempo de Inicio. La nota se verá en tu perfil, la verán tus seguidores y también cuando sea renotada."
followers: "Visible solo para seguidores. Sólo tus seguidores podrán ver la nota, y no podrá ser renotada por otras personas."
direct: "Visible sólo para usuarios específicos, y el destinatario será notificado. Puede usarse como alternativa a la mensajería directa."
doNotSendConfidencialOnDirect1: "¡Ten cuidado cuando vayas a enviar información sensible!"
doNotSendConfidencialOnDirect2: "Los administradores del servidor pueden leer lo que escribes. Ten cuidado cuando envíes información sensible en notas directas en servidores no confiables."
localOnly: "Publicando con esta opción seleccionada, la nota no se federará hacia otros servidores. Los usuarios de otros servidores no podrán ver estas notas directamente, sin importar los ajustes seleccionados más arriba."
_cw:
title: "Alerta de contenido (CW)"
description: "En lugar de mostrarse el contenido de la nota, se mostrará lo que escribas en el campo \"comentarios\". Pulsando en \"leer más\" desplegará el contenido de la nota."
_exampleNote:
cw: "¡Esto te hará tener hambre!"
note: "Acabo de comerme un donut de chocolate glaseado 🍩😋"
useCases: "Esto se usa cuando las normas del servidor lo requieren, o para ocultar spoilers o contenido sensible."
_howToMakeAttachmentsSensitive:
title: "¿Cómo puedo marcar adjuntos como contenido sensible?"
description: "Cuando las normas del servidor lo requieran, o el contenido lo requiera, marca la opción de \"contenido sensible\" para el adjunto."
tryThisFile: "¡Prueba a marcar la imagen adjunta como contenido sensible!"
_exampleNote:
note: "Ups, la he liado al abrir la tapa del natto..."
method: "Para marcar un adjunto como sensible, haz clic en la miniatura, abre el menú, y haz clic en \"Marcar como sensible\"."
sensitiveSucceeded: "Cuando adjuntes archivos, por favor, ten en cuenta las normas del servidor para marcarlos como contenido sensible."
doItToContinue: "Marca el archivo adjunto como sensible para continuar."
_done:
title: "¡Has completado el tutorial! 🎉"
description: "Las funciones que mostramos aquí son sólo una pequeña parte. Para más detalles sobre el funcionamiento de Misskey, pulsa en este enlace: {link}"
_timelineDescription:
home: "En la línea de tiempo de Inicio puedes ver las notas de las cuentas a las que sigues."
local: "En la línea de tiempo Local puedes ver las notas de todos los usuarios del servidor."
social: "En la línea de tiempo Social verás las notas de Inicio y Local a la vez."
global: "En la línea de tiempo Global verás las notas de todos los servidores conectados."
_serverRules:
description: "Un conjunto de reglas que serán mostradas antes del registro. Configurar un sumario de términos de servicio es recomendado."
_serverSettings:
@@ -1233,6 +1288,9 @@ _serverSettings:
manifestJsonOverride: "Sobreescribir manifest.json"
shortName: "Nombre corto"
shortNameDescription: "Forma corta del nombre de la instancia que puede mostrarse si el nombre completo es demasiado largo."
fanoutTimelineDescription: "Incrementa el rendimiento de forma significativa cuando se obtienen las líneas de tiempo y reduce la carga en la base de datos. A cambio, el uso de la memoria en Redis incrementará. Considera desactivar esta opción en caso de que tu servidor tenga poca memoria o detectes inestabilidad."
fanoutTimelineDbFallback: "Cargar desde la base de datos"
fanoutTimelineDbFallbackDescription: "Cuando esta opción está habilitada, la carga de peticiones adicionales de la línea de tiempo se hará desde la base de datos cuando éstas no se encuentren en la caché. Al deshabilitar esta opción se reduce la carga del servidor, pero limita el número de líneas de tiempo que pueden obtenerse."
_accountMigration:
moveFrom: "Trasladar de otra cuenta a ésta"
moveFromSub: "Crear un alias para otra cuenta."
@@ -1490,6 +1548,9 @@ _achievements:
_smashTestNotificationButton:
title: "Sobrecarga de pruebas"
description: "Envía muchas notificaciones de prueba en un corto espacio de tiempo"
_tutorialCompleted:
title: "Diploma del Curso Básico de Misskey"
description: "Tutorial completado"
_role:
new: "Crear rol"
edit: "Editar rol"
@@ -1500,7 +1561,9 @@ _role:
assignTarget: "Asignar objetivo"
descriptionOfAssignTarget: "<b>Manual</b> Para cambiar manualmente lo que se incluye en este rol.\n<b>Condicional</b> configura una condición, y los usuarios que cumplan la condición serán incluídos automáticamente."
manual: "manual"
manualRoles: "Roles manuales"
conditional: "condicional"
conditionalRoles: "Roles condicionales"
condition: "condición"
isConditionalRole: "Esto es un rol condicional"
isPublic: "Publicar rol"
@@ -1549,6 +1612,7 @@ _role:
canHideAds: "Puede ocultar anuncios"
canSearchNotes: "Uso de la búsqueda de notas"
canUseTranslator: "Uso de traductor"
avatarDecorationLimit: "Número máximo de decoraciones de avatar"
_condition:
isLocal: "Usuario local"
isRemote: "Usuario remoto"
@@ -1577,6 +1641,7 @@ _emailUnavailable:
disposable: "No es un correo reutilizable"
mx: "Servidor de correo inválido"
smtp: "Servidor de correo no disponible"
banned: "Email no disponible"
_ffVisibility:
public: "Publicar"
followers: "Visible solo para seguidores"
@@ -1653,6 +1718,7 @@ _aboutMisskey:
donate: "Donar a Misskey"
morePatrons: "Muchas más personas nos apoyan. Muchas gracias🥰"
patrons: "Patrocinadores"
projectMembers: "Miembros del proyecto"
_displayOfSensitiveMedia:
respect: "Esconder medios marcados como sensibles"
ignore: "Mostrar medios marcados como sensibles"
@@ -1677,6 +1743,7 @@ _channel:
notesCount: "{n} notas"
nameAndDescription: "Nombre y descripción"
nameOnly: "Sólo nombre"
allowRenoteToExternal: "Permitir renotas y menciones fuera del canal"
_menuDisplay:
sideFull: "Horizontal"
sideIcon: "Horizontal (ícono)"
@@ -1768,6 +1835,14 @@ _sfx:
notification: "Notificaciones"
antenna: "Antena receptora"
channel: "Notificaciones del canal"
reaction: "Al seleccionar una reacción"
_soundSettings:
driveFile: "Usar un archivo de audio en Drive"
driveFileWarn: "Selecciona un archivo de audio en Drive."
driveFileTypeWarn: "Este archivo es incompatible"
driveFileTypeWarnDescription: "Selecciona un archivo de audio"
driveFileDurationWarn: "La duración del audio es demasiado larga."
driveFileDurationWarnDescription: "Usar un audio de larga duración puede llegar a molestar mientras usas Misskey. ¿Quieres continuar?"
_ago:
future: "Futuro"
justNow: "Justo ahora"
@@ -1780,6 +1855,12 @@ _ago:
yearsAgo: "Hace {n} años"
invalid: "No hay nada que ver aqui"
_timeIn:
seconds: "En {n} segundos"
minutes: "En {n}m"
hours: "En {n}h"
days: "En {n}d"
weeks: "En {n}sem."
months: "En {n}M"
years: "En {n} años"
_time:
second: "Segundos"
@@ -1906,6 +1987,7 @@ _widgets:
_userList:
chooseList: "Seleccione una lista"
clicker: "Cliqueador"
birthdayFollowings: "Hoy cumplen años"
_cw:
hide: "Ocultar"
show: "Ver más"
@@ -1968,6 +2050,7 @@ _profile:
changeAvatar: "Cambiar avatar"
changeBanner: "Cambiar banner"
verifiedLinkDescription: "Introduciendo una URL que contiene un enlace a tu perfil, se puede mostrar un icono de verificación de propiedad al lado del campo."
avatarDecorationMax: "Puedes añadir un máximo de {max} decoraciones de avatar."
_exportOrImport:
allNotes: "Todas las notas"
favoritedNotes: "Notas favoritas"
@@ -2089,6 +2172,7 @@ _notification:
pollEnded: "Estan disponibles los resultados de la encuesta"
newNote: "Nueva nota"
unreadAntennaNote: "Antena {name}"
roleAssigned: "Rol asignado"
emptyPushNotificationMessage: "Se han actualizado las notificaciones push"
achievementEarned: "Logro desbloqueado"
testNotification: "Notificación de prueba"
@@ -2110,6 +2194,7 @@ _notification:
pollEnded: "La encuesta terminó"
receiveFollowRequest: "Recibió una solicitud de seguimiento"
followRequestAccepted: "El seguimiento fue aceptado"
roleAssigned: "Rol asignado"
achievementEarned: "Logro desbloqueado"
app: "Notificaciones desde aplicaciones"
_actions:
@@ -2255,3 +2340,16 @@ _externalResourceInstaller:
_themeInstallFailed:
title: "Instalación de tema fallida"
description: "Ha ocurrido un problema al instalar el tema. Por favor, inténtalo de nuevo. Se pueden ver más detalles del error en la consola de Javascript."
_dataSaver:
_media:
title: "Cargando Multimedia"
description: "Desactiva la carga automática de imágenes y vídeos. Tendrás que tocar en las imágenes y vídeos ocultos para cargarlos."
_avatar:
title: "Avatares animados"
description: "Desactiva la animación de los avatares. Las imágenes animadas pueden llegar a ser de mayor tamaño que las normales, por lo que al desactivarlas puedes reducir el consumo de datos."
_urlPreview:
title: "Vista previa de URLs"
description: "Desactiva la carga de vistas previas de las URLs."
_code:
title: "Resaltar código"
description: "Si se usa resaltado de código en MFM, etc., no se cargará hasta pulsar en ello. El resaltado de sintaxis requiere la descarga de archivos de definición para cada lenguaje de programación. Debido a esto, al deshabilitar la carga automática de estos archivos reducirás el consumo de datos."

View File

@@ -162,6 +162,7 @@ addEmoji: "Ajouter un émoji"
settingGuide: "Configuration proposée"
cacheRemoteFiles: "Mise en cache des fichiers distants"
cacheRemoteFilesDescription: "Lorsque cette option est désactivée, les fichiers distants sont chargés directement depuis linstance distante. La désactiver diminuera certes lutilisation de lespace de stockage local mais augmentera le trafic réseau puisque les miniatures ne seront plus générées."
youCanCleanRemoteFilesCache: "Vous pouvez supprimer tous les caches en cliquant le bouton 🗑️ dans la gestion des fichiers."
cacheRemoteSensitiveFiles: "Mettre en cache les fichiers distants sensibles"
cacheRemoteSensitiveFilesDescription: "Si vous désactivez ce paramètre, les fichiers sensibles distants ne seront pas mis en cache et un lien direct sera utilisé à la place"
flagAsBot: "Ce compte est un robot"
@@ -726,6 +727,7 @@ lockedAccountInfo: "À moins que vous ne définissiez la visibilité de votre no
alwaysMarkSensitive: "Marquer les médias comme contenu sensible par défaut"
loadRawImages: "Affichage complet des images jointes au lieu des vignettes"
disableShowingAnimatedImages: "Désactiver l'animation des images"
highlightSensitiveMedia: "Mettre en évidence les médias sensibles"
verificationEmailSent: "Un e-mail de vérification a été envoyé. Veuillez accéder au lien pour compléter la vérification."
notSet: "Non défini"
emailVerified: "Votre adresse e-mail a été vérifiée."
@@ -979,6 +981,7 @@ show: "Affichage"
neverShow: "Ne plus afficher"
remindMeLater: "Peut-être plus tard"
didYouLikeMisskey: "Avez-vous aimé Misskey ?"
pleaseDonate: "Misskey est le logiciel libre utilisé par {host}. Merci de faire un don pour que nous puissions continuer à le développer !"
roles: "Rôles"
role: "Rôles"
noRole: "Aucun rôle"
@@ -991,8 +994,10 @@ manageCustomEmojis: "Gestion des émojis personnalisés"
manageAvatarDecorations: "Gérer les décorations d'avatar"
youCannotCreateAnymore: "Vous avez atteint la limite de création."
cannotPerformTemporary: "Temporairement indisponible"
cannotPerformTemporaryDescription: "Temporairement indisponible puisque le nombre d'opérations dépasse la limite. Veuillez patienter un peu, puis réessayer."
invalidParamError: "Paramètres invalides"
permissionDeniedError: "Opération refusée"
permissionDeniedErrorDescription: "Ce compte n'a pas la permission d'effectuer cette opération."
preset: "Préréglage"
selectFromPresets: "Sélectionner à partir des préréglages"
achievements: "Accomplissements"
@@ -1021,6 +1026,7 @@ likeOnlyForRemote: "Toutes (mentions j'aime seulement pour les instances distant
nonSensitiveOnly: "Non sensibles seulement"
nonSensitiveOnlyForLocalLikeOnlyForRemote: "Non sensibles seulement (mentions j'aime seulement pour les instances distantes)"
rolesAssignedToMe: "Rôles attribués à moi"
resetPasswordConfirm: "Souhaitez-vous réinitialiser votre mot de passe ?"
sensitiveWords: "Mots sensibles"
hiddenTags: "Hashtags cachés"
hiddenTagsDescription: "Les hashtags définis ne s'afficheront pas dans les tendances. Vous pouvez définir plusieurs hashtags en faisant un saut de ligne."
@@ -1082,6 +1088,7 @@ installed: "Installé"
branding: "Image de marque"
expirationDate: "Date dexpiration"
waitingForMailAuth: "En attente de la vérification de l'adresse courriel"
inviteCodeCreator: "Créateur·rice de ce code d'invitation"
usedAt: "Utilisé le"
unused: "Non-utilisé"
used: "Utilisé"
@@ -1765,6 +1772,7 @@ _visibility:
followersDescription: "Publier à vos abonné·e·s uniquement"
specified: "Direct"
specifiedDescription: "Publier uniquement aux utilisateur·rice·s mentionné·e·s"
disableFederation: "Défédérer"
_postForm:
replyPlaceholder: "Répondre à cette note ..."
quotePlaceholder: "Citez cette note ..."
@@ -1899,6 +1907,7 @@ _notification:
yourFollowRequestAccepted: "Votre demande dabonnement a été accepté"
pollEnded: "Les résultats du sondage sont disponibles"
unreadAntennaNote: "Antenne {name}"
roleAssigned: "Rôle attribué"
emptyPushNotificationMessage: "Les notifications push ont été mises à jour"
achievementEarned: "Accomplissement"
testNotification: "Tester la notification"
@@ -1916,6 +1925,7 @@ _notification:
pollEnded: "Sondages se cloturant"
receiveFollowRequest: "Demande d'abonnement reçue"
followRequestAccepted: "Demande d'abonnement acceptée"
roleAssigned: "Rôle reçu"
achievementEarned: "Accomplissement"
app: "Notifications provenant des apps"
_actions:

View File

@@ -6,54 +6,171 @@ import ts from 'typescript';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const parameterRegExp = /\{(\w+)\}/g;
function createMemberType(item) {
if (typeof item !== 'string') {
return ts.factory.createTypeLiteralNode(createMembers(item));
}
const parameters = Array.from(
item.matchAll(parameterRegExp),
([, parameter]) => parameter,
);
if (!parameters.length) {
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
}
return ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier('ParameterizedString'),
[
ts.factory.createUnionTypeNode(
parameters.map((parameter) =>
ts.factory.createLiteralTypeNode(
ts.factory.createStringLiteral(parameter),
),
),
),
],
);
}
function createMembers(record) {
return Object.entries(record)
.map(([k, v]) => ts.factory.createPropertySignature(
return Object.entries(record).map(([k, v]) =>
ts.factory.createPropertySignature(
undefined,
ts.factory.createStringLiteral(k),
undefined,
typeof v === 'string'
? ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
: ts.factory.createTypeLiteralNode(createMembers(v)),
));
createMemberType(v),
),
);
}
export default function generateDTS() {
const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8'));
const members = createMembers(locale);
const elements = [
ts.factory.createVariableStatement(
[ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
ts.factory.createVariableDeclarationList(
[
ts.factory.createVariableDeclaration(
ts.factory.createIdentifier('kParameters'),
undefined,
ts.factory.createTypeOperatorNode(
ts.SyntaxKind.UniqueKeyword,
ts.factory.createKeywordTypeNode(ts.SyntaxKind.SymbolKeyword),
),
undefined,
),
],
ts.NodeFlags.Const,
),
),
ts.factory.createInterfaceDeclaration(
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
ts.factory.createIdentifier('ParameterizedString'),
[
ts.factory.createTypeParameterDeclaration(
undefined,
ts.factory.createIdentifier('T'),
ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier('string'),
undefined,
),
),
],
undefined,
[
ts.factory.createPropertySignature(
undefined,
ts.factory.createComputedPropertyName(
ts.factory.createIdentifier('kParameters'),
),
undefined,
ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier('T'),
undefined,
),
),
],
),
ts.factory.createInterfaceDeclaration(
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
ts.factory.createIdentifier('ILocale'),
undefined,
undefined,
[
ts.factory.createIndexSignature(
undefined,
[
ts.factory.createParameterDeclaration(
undefined,
undefined,
ts.factory.createIdentifier('_'),
undefined,
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
undefined,
),
],
ts.factory.createUnionTypeNode([
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier('ParameterizedString'),
[ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)],
),
ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier('ILocale'),
undefined,
),
]),
),
],
),
ts.factory.createInterfaceDeclaration(
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
ts.factory.createIdentifier('Locale'),
undefined,
undefined,
[
ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [
ts.factory.createExpressionWithTypeArguments(
ts.factory.createIdentifier('ILocale'),
undefined,
),
]),
],
members,
),
ts.factory.createVariableStatement(
[ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
ts.factory.createVariableDeclarationList(
[ts.factory.createVariableDeclaration(
ts.factory.createIdentifier('locales'),
undefined,
ts.factory.createTypeLiteralNode([ts.factory.createIndexSignature(
[
ts.factory.createVariableDeclaration(
ts.factory.createIdentifier('locales'),
undefined,
[ts.factory.createParameterDeclaration(
undefined,
undefined,
ts.factory.createIdentifier('lang'),
undefined,
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
undefined,
)],
ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier('Locale'),
undefined,
),
)]),
undefined,
)],
ts.NodeFlags.Const | ts.NodeFlags.Ambient | ts.NodeFlags.ContextFlags,
ts.factory.createTypeLiteralNode([
ts.factory.createIndexSignature(
undefined,
[
ts.factory.createParameterDeclaration(
undefined,
undefined,
ts.factory.createIdentifier('lang'),
undefined,
ts.factory.createKeywordTypeNode(
ts.SyntaxKind.StringKeyword,
),
undefined,
),
],
ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier('Locale'),
undefined,
),
),
]),
undefined,
),
],
ts.NodeFlags.Const,
),
),
ts.factory.createFunctionDeclaration(
@@ -70,16 +187,28 @@ export default function generateDTS() {
),
ts.factory.createExportDefault(ts.factory.createIdentifier('locales')),
];
const printed = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed,
}).printList(
ts.ListFormat.MultiLine,
ts.factory.createNodeArray(elements),
ts.createSourceFile('index.d.ts', '', ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS),
);
const printed = ts
.createPrinter({
newLine: ts.NewLineKind.LineFeed,
})
.printList(
ts.ListFormat.MultiLine,
ts.factory.createNodeArray(elements),
ts.createSourceFile(
'index.d.ts',
'',
ts.ScriptTarget.ESNext,
true,
ts.ScriptKind.TS,
),
);
fs.writeFileSync(`${__dirname}/index.d.ts`, `/* eslint-disable */
fs.writeFileSync(
`${__dirname}/index.d.ts`,
`/* eslint-disable */
// This file is generated by locales/generateDTS.js
// Do not edit this file directly.
${printed}`, 'utf-8');
${printed}`,
'utf-8',
);
}

View File

@@ -121,6 +121,10 @@ sensitive: "Konten sensitif"
add: "Tambahkan"
reaction: "Reaksi"
reactions: "Reaksi"
emojiPicker: "Emoji Picker"
pinnedEmojisForReactionSettingDescription: "Atur sematan emoji pada reaksi"
pinnedEmojisSettingDescription: "Atur sematan emoji pada masukan emoji"
emojiPickerDisplay: "Tampilan Emoji Picker"
reactionSettingDescription2: "Geser untuk memindah urutan emoji, klik untuk menghapus, tekan \"+\" untuk menambahkan"
rememberNoteVisibility: "Ingat pengaturan visibilitas catatan"
attachCancel: "Hapus lampiran"
@@ -641,6 +645,7 @@ smtpSecure: "Gunakan SSL/TLS implisit untuk koneksi SMTP"
smtpSecureInfo: "Matikan ini ketika menggunakan STARTTLS"
testEmail: "Tes pengiriman surel"
wordMute: "Bisukan kata"
hardWordMute: "Pembisuan kata keras"
regexpError: "Kesalahan ekspresi reguler"
regexpErrorDescription: "Galat terjadi pada baris {line} ekspresi reguler dari {tab} kata yang dibisukan:"
instanceMute: "Bisukan instansi"
@@ -1154,6 +1159,7 @@ tosAndPrivacyPolicy: "Syarat dan Ketentuan serta Kebijakan Privasi"
avatarDecorations: "Dekorasi avatar"
attach: "Lampirkan"
detach: "Hapus"
detachAll: "Lepas Semua"
angle: "Sudut"
flip: "Balik"
showAvatarDecorations: "Tampilkan dekorasi avatar"
@@ -1168,6 +1174,7 @@ doReaction: "Tambahkan reaksi"
code: "Kode"
reloadRequiredToApplySettings: "Muat ulang diperlukan untuk menerapkan pengaturan."
remainingN: "Sisa : {n}"
decorate: "Dekor"
_announcement:
forExistingUsers: "Hanya pengguna yang telah ada"
forExistingUsersDescription: "Pengumuman ini akan dimunculkan ke pengguna yang sudah ada dari titik waktu publikasi jika dinyalakan. Apabila dimatikan, mereka yang baru mendaftar setelah publikasi ini akan juga melihatnya."
@@ -1215,6 +1222,7 @@ _initialTutorial:
followers: "Perlihatkan ke pengikut saja. Hanya pengikut yang dapat melihat postinganmu dan tidak dapat direnote oleh siapapun."
direct: "Hanya perlihatkan ke pengguna spesifik dan penerima akan diberi tahu. Dapat juga digunakan sebagai alternatif dari pesan langsung."
_cw:
title: "Peringatan Konten (CW)"
_exampleNote:
cw: "Peringatan: Bikin Lapar!"
note: "Baru aja makan donat berlapis coklat 🍩😋"

352
locales/index.d.ts vendored
View File

@@ -1,12 +1,19 @@
/* eslint-disable */
// This file is generated by locales/generateDTS.js
// Do not edit this file directly.
export interface Locale {
declare const kParameters: unique symbol;
export interface ParameterizedString<T extends string> {
[kParameters]: T;
}
export interface ILocale {
[_: string]: string | ParameterizedString<string> | ILocale;
}
export interface Locale extends ILocale {
"_lang_": string;
"headlineMisskey": string;
"introMisskey": string;
"poweredByMisskeyDescription": string;
"monthAndDay": string;
"poweredByMisskeyDescription": ParameterizedString<"name">;
"monthAndDay": ParameterizedString<"month" | "day">;
"search": string;
"notifications": string;
"username": string;
@@ -18,7 +25,7 @@ export interface Locale {
"cancel": string;
"noThankYou": string;
"enterUsername": string;
"renotedBy": string;
"renotedBy": ParameterizedString<"user">;
"noNotes": string;
"noNotifications": string;
"instance": string;
@@ -78,8 +85,8 @@ export interface Locale {
"export": string;
"files": string;
"download": string;
"driveFileDeleteConfirm": string;
"unfollowConfirm": string;
"driveFileDeleteConfirm": ParameterizedString<"name">;
"unfollowConfirm": ParameterizedString<"name">;
"exportRequested": string;
"importRequested": string;
"lists": string;
@@ -183,9 +190,9 @@ export interface Locale {
"wallpaper": string;
"setWallpaper": string;
"removeWallpaper": string;
"searchWith": string;
"searchWith": ParameterizedString<"q">;
"youHaveNoLists": string;
"followConfirm": string;
"followConfirm": ParameterizedString<"name">;
"proxyAccount": string;
"proxyAccountDescription": string;
"host": string;
@@ -208,7 +215,7 @@ export interface Locale {
"software": string;
"version": string;
"metadata": string;
"withNFiles": string;
"withNFiles": ParameterizedString<"n">;
"monitor": string;
"jobQueue": string;
"cpuAndMemory": string;
@@ -237,7 +244,7 @@ export interface Locale {
"processing": string;
"preview": string;
"default": string;
"defaultValueIs": string;
"defaultValueIs": ParameterizedString<"value">;
"noCustomEmojis": string;
"noJobs": string;
"federating": string;
@@ -266,8 +273,8 @@ export interface Locale {
"imageUrl": string;
"remove": string;
"removed": string;
"removeAreYouSure": string;
"deleteAreYouSure": string;
"removeAreYouSure": ParameterizedString<"x">;
"deleteAreYouSure": ParameterizedString<"x">;
"resetAreYouSure": string;
"areYouSure": string;
"saved": string;
@@ -285,8 +292,8 @@ export interface Locale {
"messageRead": string;
"noMoreHistory": string;
"startMessaging": string;
"nUsersRead": string;
"agreeTo": string;
"nUsersRead": ParameterizedString<"n">;
"agreeTo": ParameterizedString<"0">;
"agree": string;
"agreeBelow": string;
"basicNotesBeforeCreateAccount": string;
@@ -298,7 +305,7 @@ export interface Locale {
"images": string;
"image": string;
"birthday": string;
"yearsOld": string;
"yearsOld": ParameterizedString<"age">;
"registeredDate": string;
"location": string;
"theme": string;
@@ -353,9 +360,9 @@ export interface Locale {
"thisYear": string;
"thisMonth": string;
"today": string;
"dayX": string;
"monthX": string;
"yearX": string;
"dayX": ParameterizedString<"day">;
"monthX": ParameterizedString<"month">;
"yearX": ParameterizedString<"year">;
"pages": string;
"integration": string;
"connectService": string;
@@ -382,6 +389,11 @@ export interface Locale {
"enableHcaptcha": string;
"hcaptchaSiteKey": string;
"hcaptchaSecretKey": string;
"mcaptcha": string;
"enableMcaptcha": string;
"mcaptchaSiteKey": string;
"mcaptchaSecretKey": string;
"mcaptchaInstanceUrl": string;
"recaptcha": string;
"enableRecaptcha": string;
"recaptchaSiteKey": string;
@@ -415,7 +427,7 @@ export interface Locale {
"recentlyUpdatedUsers": string;
"recentlyRegisteredUsers": string;
"recentlyDiscoveredUsers": string;
"exploreUsersCount": string;
"exploreUsersCount": ParameterizedString<"count">;
"exploreFediverse": string;
"popularTags": string;
"userList": string;
@@ -432,16 +444,16 @@ export interface Locale {
"moderationNote": string;
"addModerationNote": string;
"moderationLogs": string;
"nUsersMentioned": string;
"nUsersMentioned": ParameterizedString<"n">;
"securityKeyAndPasskey": string;
"securityKey": string;
"lastUsed": string;
"lastUsedAt": string;
"lastUsedAt": ParameterizedString<"t">;
"unregister": string;
"passwordLessLogin": string;
"passwordLessLoginDescription": string;
"resetPassword": string;
"newPasswordIs": string;
"newPasswordIs": ParameterizedString<"password">;
"reduceUiAnimation": string;
"share": string;
"notFound": string;
@@ -461,7 +473,7 @@ export interface Locale {
"enable": string;
"next": string;
"retype": string;
"noteOf": string;
"noteOf": ParameterizedString<"user">;
"quoteAttached": string;
"quoteQuestion": string;
"noMessagesYet": string;
@@ -481,12 +493,12 @@ export interface Locale {
"strongPassword": string;
"passwordMatched": string;
"passwordNotMatched": string;
"signinWith": string;
"signinWith": ParameterizedString<"x">;
"signinFailed": string;
"or": string;
"language": string;
"uiLanguage": string;
"aboutX": string;
"aboutX": ParameterizedString<"x">;
"emojiStyle": string;
"native": string;
"disableDrawer": string;
@@ -504,7 +516,7 @@ export interface Locale {
"regenerate": string;
"fontSize": string;
"mediaListWithOneImageAppearance": string;
"limitTo": string;
"limitTo": ParameterizedString<"x">;
"noFollowRequests": string;
"openImageInNewTab": string;
"dashboard": string;
@@ -582,7 +594,7 @@ export interface Locale {
"deleteAllFiles": string;
"deleteAllFilesConfirm": string;
"removeAllFollowing": string;
"removeAllFollowingDescription": string;
"removeAllFollowingDescription": ParameterizedString<"host">;
"userSuspended": string;
"userSilenced": string;
"yourAccountSuspendedTitle": string;
@@ -629,6 +641,7 @@ export interface Locale {
"small": string;
"generateAccessToken": string;
"permission": string;
"adminPermission": string;
"enableAll": string;
"disableAll": string;
"tokenRequested": string;
@@ -652,9 +665,9 @@ export interface Locale {
"wordMute": string;
"hardWordMute": string;
"regexpError": string;
"regexpErrorDescription": string;
"regexpErrorDescription": ParameterizedString<"tab" | "line">;
"instanceMute": string;
"userSaysSomething": string;
"userSaysSomething": ParameterizedString<"name">;
"makeActive": string;
"display": string;
"copy": string;
@@ -672,6 +685,7 @@ export interface Locale {
"other": string;
"regenerateLoginToken": string;
"regenerateLoginTokenDescription": string;
"theKeywordWhenSearchingForCustomEmoji": string;
"setMultipleBySeparatingWithSpace": string;
"fileIdOrUrl": string;
"behavior": string;
@@ -679,7 +693,7 @@ export interface Locale {
"abuseReports": string;
"reportAbuse": string;
"reportAbuseRenote": string;
"reportAbuseOf": string;
"reportAbuseOf": ParameterizedString<"name">;
"fillAbuseReportDescription": string;
"abuseReported": string;
"reporter": string;
@@ -694,7 +708,7 @@ export interface Locale {
"defaultNavigationBehaviour": string;
"editTheseSettingsMayBreakAccount": string;
"instanceTicker": string;
"waitingFor": string;
"waitingFor": ParameterizedString<"x">;
"random": string;
"system": string;
"switchUi": string;
@@ -704,10 +718,10 @@ export interface Locale {
"optional": string;
"createNewClip": string;
"unclip": string;
"confirmToUnclipAlreadyClippedNote": string;
"confirmToUnclipAlreadyClippedNote": ParameterizedString<"name">;
"public": string;
"private": string;
"i18nInfo": string;
"i18nInfo": ParameterizedString<"link">;
"manageAccessTokens": string;
"accountInfo": string;
"notesCount": string;
@@ -757,9 +771,9 @@ export interface Locale {
"needReloadToApply": string;
"showTitlebar": string;
"clearCache": string;
"onlineUsersCount": string;
"nUsers": string;
"nNotes": string;
"onlineUsersCount": ParameterizedString<"n">;
"nUsers": ParameterizedString<"n">;
"nNotes": ParameterizedString<"n">;
"sendErrorReports": string;
"sendErrorReportsDescription": string;
"myTheme": string;
@@ -791,7 +805,7 @@ export interface Locale {
"publish": string;
"inChannelSearch": string;
"useReactionPickerForContextMenu": string;
"typingUsers": string;
"typingUsers": ParameterizedString<"users">;
"jumpToSpecifiedDate": string;
"showingPastTimeline": string;
"clear": string;
@@ -858,7 +872,7 @@ export interface Locale {
"misskeyUpdated": string;
"whatIsNew": string;
"translate": string;
"translatedFrom": string;
"translatedFrom": ParameterizedString<"x">;
"accountDeletionInProgress": string;
"usernameInfo": string;
"aiChanMode": string;
@@ -889,11 +903,11 @@ export interface Locale {
"continueThread": string;
"deleteAccountConfirm": string;
"incorrectPassword": string;
"voteConfirm": string;
"voteConfirm": ParameterizedString<"choice">;
"hide": string;
"useDrawerReactionPickerForMobile": string;
"welcomeBackWithName": string;
"clickToFinishEmailVerification": string;
"welcomeBackWithName": ParameterizedString<"name">;
"clickToFinishEmailVerification": ParameterizedString<"ok">;
"overridedDeviceKind": string;
"smartphone": string;
"tablet": string;
@@ -921,8 +935,8 @@ export interface Locale {
"cropYes": string;
"cropNo": string;
"file": string;
"recentNHours": string;
"recentNDays": string;
"recentNHours": ParameterizedString<"n">;
"recentNDays": ParameterizedString<"n">;
"noEmailServerWarning": string;
"thereIsUnresolvedAbuseReportWarning": string;
"recommended": string;
@@ -931,7 +945,7 @@ export interface Locale {
"driveCapOverrideCaption": string;
"requireAdminForView": string;
"isSystemAccount": string;
"typeToConfirm": string;
"typeToConfirm": ParameterizedString<"x">;
"deleteAccount": string;
"document": string;
"numberOfPageCache": string;
@@ -985,7 +999,7 @@ export interface Locale {
"neverShow": string;
"remindMeLater": string;
"didYouLikeMisskey": string;
"pleaseDonate": string;
"pleaseDonate": ParameterizedString<"host">;
"roles": string;
"role": string;
"noRole": string;
@@ -1054,6 +1068,8 @@ export interface Locale {
"noteIdOrUrl": string;
"video": string;
"videos": string;
"audio": string;
"audioFiles": string;
"dataSaver": string;
"accountMigration": string;
"accountMoved": string;
@@ -1081,7 +1097,7 @@ export interface Locale {
"preservedUsernamesDescription": string;
"createNoteFromTheFile": string;
"archive": string;
"channelArchiveConfirmTitle": string;
"channelArchiveConfirmTitle": ParameterizedString<"name">;
"channelArchiveConfirmDescription": string;
"thisChannelArchived": string;
"displayOfNote": string;
@@ -1111,8 +1127,8 @@ export interface Locale {
"createCount": string;
"inviteCodeCreated": string;
"inviteLimitExceeded": string;
"createLimitRemaining": string;
"inviteLimitResetCycle": string;
"createLimitRemaining": ParameterizedString<"limit">;
"inviteLimitResetCycle": ParameterizedString<"time" | "limit">;
"expirationDate": string;
"noExpirationDate": string;
"inviteCodeUsedAt": string;
@@ -1125,7 +1141,7 @@ export interface Locale {
"expired": string;
"doYouAgree": string;
"beSureToReadThisAsItIsImportant": string;
"iHaveReadXCarefullyAndAgree": string;
"iHaveReadXCarefullyAndAgree": ParameterizedString<"x">;
"dialog": string;
"icon": string;
"forYou": string;
@@ -1180,10 +1196,30 @@ export interface Locale {
"doReaction": string;
"code": string;
"reloadRequiredToApplySettings": string;
"remainingN": string;
"remainingN": ParameterizedString<"n">;
"overwriteContentConfirm": string;
"seasonalScreenEffect": string;
"decorate": string;
"addMfmFunction": string;
"enableQuickAddMfmFunction": string;
"bubbleGame": string;
"sfx": string;
"soundWillBePlayed": string;
"showReplay": string;
"replay": string;
"replaying": string;
"ranking": string;
"lastNDays": ParameterizedString<"n">;
"backToTitle": string;
"enableHorizontalSwipe": string;
"_bubbleGame": {
"howToPlay": string;
"_howToPlay": {
"section1": string;
"section2": string;
"section3": string;
};
};
"_announcement": {
"forExistingUsers": string;
"forExistingUsersDescription": string;
@@ -1192,7 +1228,7 @@ export interface Locale {
"end": string;
"tooManyActiveAnnouncementDescription": string;
"readConfirmTitle": string;
"readConfirmText": string;
"readConfirmText": ParameterizedString<"title">;
"shouldNotBeUsedToPresentPermanentInfo": string;
"dialogAnnouncementUxWarn": string;
"silence": string;
@@ -1207,10 +1243,10 @@ export interface Locale {
"theseSettingsCanEditLater": string;
"youCanEditMoreSettingsInSettingsPageLater": string;
"followUsers": string;
"pushNotificationDescription": string;
"pushNotificationDescription": ParameterizedString<"name">;
"initialAccountSettingCompleted": string;
"haveFun": string;
"youCanContinueTutorial": string;
"haveFun": ParameterizedString<"name">;
"youCanContinueTutorial": ParameterizedString<"name">;
"startTutorial": string;
"skipAreYouSure": string;
"laterAreYouSure": string;
@@ -1248,7 +1284,7 @@ export interface Locale {
"social": string;
"global": string;
"description2": string;
"description3": string;
"description3": ParameterizedString<"link">;
};
"_postNote": {
"title": string;
@@ -1286,7 +1322,7 @@ export interface Locale {
};
"_done": {
"title": string;
"description": string;
"description": ParameterizedString<"link">;
};
};
"_timelineDescription": {
@@ -1300,10 +1336,10 @@ export interface Locale {
};
"_serverSettings": {
"iconUrl": string;
"appIconDescription": string;
"appIconDescription": ParameterizedString<"host">;
"appIconUsageExample": string;
"appIconStyleRecommendation": string;
"appIconResolutionMustBe": string;
"appIconResolutionMustBe": ParameterizedString<"resolution">;
"manifestJsonOverride": string;
"shortName": string;
"shortNameDescription": string;
@@ -1314,7 +1350,7 @@ export interface Locale {
"_accountMigration": {
"moveFrom": string;
"moveFromSub": string;
"moveFromLabel": string;
"moveFromLabel": ParameterizedString<"n">;
"moveFromDescription": string;
"moveTo": string;
"moveToLabel": string;
@@ -1322,7 +1358,7 @@ export interface Locale {
"moveAccountDescription": string;
"moveAccountHowTo": string;
"startMigration": string;
"migrationConfirm": string;
"migrationConfirm": ParameterizedString<"account">;
"movedAndCannotBeUndone": string;
"postMigrationNote": string;
"movedTo": string;
@@ -1648,6 +1684,15 @@ export interface Locale {
"title": string;
"description": string;
};
"_bubbleGameExplodingHead": {
"title": string;
"description": string;
};
"_bubbleGameDoubleExplodingHead": {
"title": string;
"description": string;
"flavor": string;
};
};
};
"_role": {
@@ -1745,6 +1790,7 @@ export interface Locale {
"disposable": string;
"mx": string;
"smtp": string;
"banned": string;
};
"_ffVisibility": {
"public": string;
@@ -1754,7 +1800,7 @@ export interface Locale {
"_signup": {
"almostThere": string;
"emailAddressInfo": string;
"emailSent": string;
"emailSent": ParameterizedString<"email">;
};
"_accountDelete": {
"accountDelete": string;
@@ -1807,14 +1853,14 @@ export interface Locale {
"save": string;
"inputName": string;
"cannotSave": string;
"nameAlreadyExists": string;
"applyConfirm": string;
"saveConfirm": string;
"deleteConfirm": string;
"renameConfirm": string;
"nameAlreadyExists": ParameterizedString<"name">;
"applyConfirm": ParameterizedString<"name">;
"saveConfirm": ParameterizedString<"name">;
"deleteConfirm": ParameterizedString<"name">;
"renameConfirm": ParameterizedString<"old" | "new">;
"noBackups": string;
"createdAt": string;
"updatedAt": string;
"createdAt": ParameterizedString<"date" | "time">;
"updatedAt": ParameterizedString<"date" | "time">;
"cannotLoad": string;
"invalidFile": string;
};
@@ -1859,8 +1905,8 @@ export interface Locale {
"featured": string;
"owned": string;
"following": string;
"usersCount": string;
"notesCount": string;
"usersCount": ParameterizedString<"n">;
"notesCount": ParameterizedString<"n">;
"nameAndDescription": string;
"nameOnly": string;
"allowRenoteToExternal": string;
@@ -1888,7 +1934,7 @@ export interface Locale {
"manage": string;
"code": string;
"description": string;
"installed": string;
"installed": ParameterizedString<"name">;
"installedThemes": string;
"builtinThemes": string;
"alreadyInstalled": string;
@@ -1911,7 +1957,7 @@ export interface Locale {
"lighten": string;
"inputConstantName": string;
"importInfo": string;
"deleteConstantConfirm": string;
"deleteConstantConfirm": ParameterizedString<"const">;
"keys": {
"accent": string;
"bg": string;
@@ -1974,23 +2020,23 @@ export interface Locale {
"_ago": {
"future": string;
"justNow": string;
"secondsAgo": string;
"minutesAgo": string;
"hoursAgo": string;
"daysAgo": string;
"weeksAgo": string;
"monthsAgo": string;
"yearsAgo": string;
"secondsAgo": ParameterizedString<"n">;
"minutesAgo": ParameterizedString<"n">;
"hoursAgo": ParameterizedString<"n">;
"daysAgo": ParameterizedString<"n">;
"weeksAgo": ParameterizedString<"n">;
"monthsAgo": ParameterizedString<"n">;
"yearsAgo": ParameterizedString<"n">;
"invalid": string;
};
"_timeIn": {
"seconds": string;
"minutes": string;
"hours": string;
"days": string;
"weeks": string;
"months": string;
"years": string;
"seconds": ParameterizedString<"n">;
"minutes": ParameterizedString<"n">;
"hours": ParameterizedString<"n">;
"days": ParameterizedString<"n">;
"weeks": ParameterizedString<"n">;
"months": ParameterizedString<"n">;
"years": ParameterizedString<"n">;
};
"_time": {
"second": string;
@@ -2001,7 +2047,7 @@ export interface Locale {
"_2fa": {
"alreadyRegistered": string;
"registerTOTP": string;
"step1": string;
"step1": ParameterizedString<"a" | "b">;
"step2": string;
"step2Click": string;
"step2Uri": string;
@@ -2016,7 +2062,7 @@ export interface Locale {
"securityKeyName": string;
"tapSecurityKey": string;
"removeKey": string;
"removeKeyConfirm": string;
"removeKeyConfirm": ParameterizedString<"name">;
"whyTOTPOnlyRenew": string;
"renewTOTP": string;
"renewTOTPConfirm": string;
@@ -2065,12 +2111,61 @@ export interface Locale {
"write:flash": string;
"read:flash-likes": string;
"write:flash-likes": string;
"read:admin:abuse-user-reports": string;
"write:admin:delete-account": string;
"write:admin:delete-all-files-of-a-user": string;
"read:admin:index-stats": string;
"read:admin:table-stats": string;
"read:admin:user-ips": string;
"read:admin:meta": string;
"write:admin:reset-password": string;
"write:admin:resolve-abuse-user-report": string;
"write:admin:send-email": string;
"read:admin:server-info": string;
"read:admin:show-moderation-log": string;
"read:admin:show-user": string;
"read:admin:show-users": string;
"write:admin:suspend-user": string;
"write:admin:unset-user-avatar": string;
"write:admin:unset-user-banner": string;
"write:admin:unsuspend-user": string;
"write:admin:meta": string;
"write:admin:user-note": string;
"write:admin:roles": string;
"read:admin:roles": string;
"write:admin:relays": string;
"read:admin:relays": string;
"write:admin:invite-codes": string;
"read:admin:invite-codes": string;
"write:admin:announcements": string;
"read:admin:announcements": string;
"write:admin:avatar-decorations": string;
"read:admin:avatar-decorations": string;
"write:admin:federation": string;
"write:admin:account": string;
"read:admin:account": string;
"write:admin:emoji": string;
"read:admin:emoji": string;
"write:admin:queue": string;
"read:admin:queue": string;
"write:admin:promo": string;
"write:admin:drive": string;
"read:admin:drive": string;
"read:admin:stream": string;
"write:admin:ad": string;
"read:admin:ad": string;
"write:invite-codes": string;
"read:invite-codes": string;
"write:clip-favorite": string;
"read:clip-favorite": string;
"read:federation": string;
"write:report-abuse": string;
};
"_auth": {
"shareAccessTitle": string;
"shareAccess": string;
"shareAccess": ParameterizedString<"name">;
"shareAccessAsk": string;
"permission": string;
"permission": ParameterizedString<"name">;
"permissionAsk": string;
"pleaseGoBack": string;
"callback": string;
@@ -2129,12 +2224,12 @@ export interface Locale {
"_cw": {
"hide": string;
"show": string;
"chars": string;
"files": string;
"chars": ParameterizedString<"count">;
"files": ParameterizedString<"count">;
};
"_poll": {
"noOnlyOneChoice": string;
"choiceN": string;
"choiceN": ParameterizedString<"n">;
"noMore": string;
"canMultipleVote": string;
"expiration": string;
@@ -2144,16 +2239,16 @@ export interface Locale {
"deadlineDate": string;
"deadlineTime": string;
"duration": string;
"votesCount": string;
"totalVotes": string;
"votesCount": ParameterizedString<"n">;
"totalVotes": ParameterizedString<"n">;
"vote": string;
"showResult": string;
"voted": string;
"closed": string;
"remainingDays": string;
"remainingHours": string;
"remainingMinutes": string;
"remainingSeconds": string;
"remainingDays": ParameterizedString<"d" | "h">;
"remainingHours": ParameterizedString<"h" | "m">;
"remainingMinutes": ParameterizedString<"m" | "s">;
"remainingSeconds": ParameterizedString<"s">;
};
"_visibility": {
"public": string;
@@ -2193,11 +2288,12 @@ export interface Locale {
"changeAvatar": string;
"changeBanner": string;
"verifiedLinkDescription": string;
"avatarDecorationMax": string;
"avatarDecorationMax": ParameterizedString<"max">;
};
"_exportOrImport": {
"allNotes": string;
"favoritedNotes": string;
"clips": string;
"followingList": string;
"muteList": string;
"blockingList": string;
@@ -2315,16 +2411,16 @@ export interface Locale {
};
"_notification": {
"fileUploaded": string;
"youGotMention": string;
"youGotReply": string;
"youGotQuote": string;
"youRenoted": string;
"youGotMention": ParameterizedString<"name">;
"youGotReply": ParameterizedString<"name">;
"youGotQuote": ParameterizedString<"name">;
"youRenoted": ParameterizedString<"name">;
"youWereFollowed": string;
"youReceivedFollowRequest": string;
"yourFollowRequestAccepted": string;
"pollEnded": string;
"newNote": string;
"unreadAntennaNote": string;
"unreadAntennaNote": ParameterizedString<"name">;
"roleAssigned": string;
"emptyPushNotificationMessage": string;
"achievementEarned": string;
@@ -2332,9 +2428,9 @@ export interface Locale {
"checkNotificationBehavior": string;
"sendTestNotification": string;
"notificationWillBeDisplayedLikeThis": string;
"reactedBySomeUsers": string;
"renotedBySomeUsers": string;
"followedBySomeUsers": string;
"reactedBySomeUsers": ParameterizedString<"n">;
"renotedBySomeUsers": ParameterizedString<"n">;
"followedBySomeUsers": ParameterizedString<"n">;
"_types": {
"all": string;
"note": string;
@@ -2347,6 +2443,7 @@ export interface Locale {
"pollEnded": string;
"receiveFollowRequest": string;
"followRequestAccepted": string;
"roleAssigned": string;
"achievementEarned": string;
"app": string;
};
@@ -2390,8 +2487,8 @@ export interface Locale {
};
};
"_dialog": {
"charactersExceeded": string;
"charactersBelow": string;
"charactersExceeded": ParameterizedString<"current" | "max">;
"charactersBelow": ParameterizedString<"current" | "min">;
};
"_disabledTimeline": {
"title": string;
@@ -2536,6 +2633,41 @@ export interface Locale {
"description": string;
};
};
"_reversi": {
"reversi": string;
"gameSettings": string;
"chooseBoard": string;
"blackOrWhite": string;
"blackIs": ParameterizedString<"name">;
"rules": string;
"thisGameIsStartedSoon": string;
"waitingForOther": string;
"waitingForMe": string;
"waitingBoth": string;
"ready": string;
"cancelReady": string;
"opponentTurn": string;
"myTurn": string;
"turnOf": ParameterizedString<"name">;
"pastTurnOf": ParameterizedString<"name">;
"surrender": string;
"surrendered": string;
"drawn": string;
"won": ParameterizedString<"name">;
"black": string;
"white": string;
"total": string;
"turnCount": ParameterizedString<"count">;
"myGames": string;
"allGames": string;
"ended": string;
"playing": string;
"isLlotheo": string;
"loopedMap": string;
"canPutEverywhere": string;
"freeMatch": string;
"lookingForPlayer": string;
};
}
declare const locales: {
[lang: string]: Locale;

View File

@@ -379,6 +379,11 @@ hcaptcha: "hCaptcha"
enableHcaptcha: "hCaptchaを有効にする"
hcaptchaSiteKey: "サイトキー"
hcaptchaSecretKey: "シークレットキー"
mcaptcha: "mCaptcha"
enableMcaptcha: "mCaptchaを有効にする"
mcaptchaSiteKey: "サイトキー"
mcaptchaSecretKey: "シークレットキー"
mcaptchaInstanceUrl: "mCaptchaのインスタンスのURL"
recaptcha: "reCAPTCHA"
enableRecaptcha: "reCAPTCHAを有効にする"
recaptchaSiteKey: "サイトキー"
@@ -626,6 +631,7 @@ medium: "中"
small: "小"
generateAccessToken: "アクセストークンの発行"
permission: "権限"
adminPermission: "管理者権限"
enableAll: "全て有効にする"
disableAll: "全て無効にする"
tokenRequested: "アカウントへのアクセス許可"
@@ -669,6 +675,7 @@ useGlobalSettingDesc: "オンにすると、アカウントの通知設定が使
other: "その他"
regenerateLoginToken: "ログイントークンを再生成"
regenerateLoginTokenDescription: "ログインに使用される内部トークンを再生成します。通常この操作を行う必要はありません。再生成すると、全てのデバイスでログアウトされます。"
theKeywordWhenSearchingForCustomEmoji: "カスタム絵文字を検索する時のキーワードになります。"
setMultipleBySeparatingWithSpace: "スペースで区切って複数設定できます。"
fileIdOrUrl: "ファイルIDまたはURL"
behavior: "動作"
@@ -1051,6 +1058,8 @@ limitWidthOfReaction: "リアクションの最大横幅を制限し、縮小し
noteIdOrUrl: "ートIDまたはURL"
video: "動画"
videos: "動画"
audio: "音声"
audioFiles: "音声"
dataSaver: "データセーバー"
accountMigration: "アカウントの移行"
accountMoved: "このユーザーは新しいアカウントに移行しました:"
@@ -1181,6 +1190,25 @@ remainingN: "残り: {n}"
overwriteContentConfirm: "現在の内容に上書きされますがよろしいですか?"
seasonalScreenEffect: "季節に応じた画面の演出"
decorate: "デコる"
addMfmFunction: "装飾を追加"
enableQuickAddMfmFunction: "高度なMFMのピッカーを表示する"
bubbleGame: "バブルゲーム"
sfx: "効果音"
soundWillBePlayed: "サウンドが再生されます"
showReplay: "リプレイを見る"
replay: "リプレイ"
replaying: "リプレイ中"
ranking: "ランキング"
lastNDays: "直近{n}日"
backToTitle: "タイトルへ"
enableHorizontalSwipe: "スワイプしてタブを切り替える"
_bubbleGame:
howToPlay: "遊び方"
_howToPlay:
section1: "位置を調整してハコにモノを落とします。"
section2: "同じ種類のモノがくっつくと別のモノに変化して、スコアが得られます。"
section3: "モノがハコからあふれるとゲームオーバーです。ハコからあふれないようにしつつモノを融合させてハイスコアを目指そう!"
_announcement:
forExistingUsers: "既存ユーザーのみ"
@@ -1559,6 +1587,13 @@ _achievements:
_tutorialCompleted:
title: "Misskey初心者講座 修了証"
description: "チュートリアルを完了した"
_bubbleGameExplodingHead:
title: "🤯"
description: "バブルゲームで最も大きいモノを出した"
_bubbleGameDoubleExplodingHead:
title: "ダブル🤯"
description: "バブルゲームで最も大きいモを2つ同時に出した"
flavor: "これくらいの おべんとばこに 🤯 🤯 ちょっとつめて"
_role:
new: "ロールの作成"
@@ -1652,6 +1687,7 @@ _emailUnavailable:
disposable: "恒久的に使用可能なアドレスではありません"
mx: "正しいメールサーバーではありません"
smtp: "メールサーバーが応答しません"
banned: "このメールアドレスでは登録できません"
_ffVisibility:
public: "公開"
@@ -1970,6 +2006,55 @@ _permissions:
"write:flash": "Playを操作する"
"read:flash-likes": "Playのいいねを見る"
"write:flash-likes": "Playのいいねを操作する"
"read:admin:abuse-user-reports": "ユーザーからの通報を見る"
"write:admin:delete-account": "ユーザーアカウントを削除する"
"write:admin:delete-all-files-of-a-user": "ユーザーのすべてのファイルを削除する"
"read:admin:index-stats": "データベースインデックスに関する情報を見る"
"read:admin:table-stats": "データベーステーブルに関する情報を見る"
"read:admin:user-ips": "ユーザーのIPアドレスを見る"
"read:admin:meta": "インスタンスのメタデータを見る"
"write:admin:reset-password": "ユーザーのパスワードをリセットする"
"write:admin:resolve-abuse-user-report": "ユーザーからの通報を解決する"
"write:admin:send-email": "メールを送る"
"read:admin:server-info": "サーバーの情報を見る"
"read:admin:show-moderation-log": "モデレーションログを見る"
"read:admin:show-user": "ユーザーのプライベートな情報を見る"
"read:admin:show-users": "ユーザーのプライベートな情報を見る"
"write:admin:suspend-user": "ユーザーを凍結する"
"write:admin:unset-user-avatar": "ユーザーのアバターを削除する"
"write:admin:unset-user-banner": "ユーザーのバーナーを削除する"
"write:admin:unsuspend-user": "ユーザーの凍結を解除する"
"write:admin:meta": "インスタンスのメタデータを操作する"
"write:admin:user-note": "モデレーションノートを操作する"
"write:admin:roles": "ロールを操作する"
"read:admin:roles": "ロールを見る"
"write:admin:relays": "リレーを操作する"
"read:admin:relays": "リレーを見る"
"write:admin:invite-codes": "招待コードを操作する"
"read:admin:invite-codes": "招待コードを見る"
"write:admin:announcements": "お知らせを操作する"
"read:admin:announcements": "お知らせを見る"
"write:admin:avatar-decorations": "アバターデコレーションを操作する"
"read:admin:avatar-decorations": "アバターデコレーションを見る"
"write:admin:federation": "連合に関する情報を操作する"
"write:admin:account": "ユーザーアカウントを操作する"
"read:admin:account": "ユーザーに関する情報を見る"
"write:admin:emoji": "絵文字を操作する"
"read:admin:emoji": "絵文字を見る"
"write:admin:queue": "ジョブキューを操作する"
"read:admin:queue": "ジョブキューに関する情報を見る"
"write:admin:promo": "プロモーションノートを操作する"
"write:admin:drive": "ユーザーのドライブを操作する"
"read:admin:drive": "ユーザーのドライブの関する情報を見る"
"read:admin:stream": "管理者用のWebsocket APIを使う"
"write:admin:ad": "広告を操作する"
"read:admin:ad": "広告を見る"
"write:invite-codes": "招待コードを作成する"
"read:invite-codes": "招待コードを取得する"
"write:clip-favorite": "クリップのいいねを操作する"
"read:clip-favorite": "クリップのいいねを見る"
"read:federation": "連合に関する情報を取得する"
"write:report-abuse": "違反を報告する"
_auth:
shareAccessTitle: "アプリへのアクセス許可"
@@ -2101,6 +2186,7 @@ _profile:
_exportOrImport:
allNotes: "全てのノート"
favoritedNotes: "お気に入りにしたノート"
clips: "クリップ"
followingList: "フォロー"
muteList: "ミュート"
blockingList: "ブロック"
@@ -2250,6 +2336,7 @@ _notification:
pollEnded: "アンケートが終了"
receiveFollowRequest: "フォロー申請を受け取った"
followRequestAccepted: "フォローが受理された"
roleAssigned: "ロールが付与された"
achievementEarned: "実績の獲得"
app: "連携アプリからの通知"
@@ -2419,3 +2506,38 @@ _dataSaver:
_code:
title: "コードハイライト"
description: "MFMなどでコードハイライト記法が使われている場合、タップするまで読み込まれなくなります。コードハイライトではハイライトする言語ごとにその定義ファイルを読み込む必要がありますが、それらが自動で読み込まれなくなるため、通信量の削減が見込めます。"
_reversi:
reversi: "リバーシ"
gameSettings: "対局の設定"
chooseBoard: "ボードを選択"
blackOrWhite: "先行/後攻"
blackIs: "{name}が黒(先行)"
rules: "ルール"
thisGameIsStartedSoon: "対局はまもなく開始されます"
waitingForOther: "相手の準備が完了するのを待っています"
waitingForMe: "あなたの準備が完了するのを待っています"
waitingBoth: "準備してください"
ready: "準備完了"
cancelReady: "準備を再開"
opponentTurn: "相手のターンです"
myTurn: "あなたのターンです"
turnOf: "{name}のターンです"
pastTurnOf: "{name}のターン"
surrender: "投了"
surrendered: "投了により"
drawn: "引き分け"
won: "{name}の勝ち"
black: "黒"
white: "白"
total: "合計"
turnCount: "{count}ターン目"
myGames: "自分の対局"
allGames: "みんなの対局"
ended: "終了"
playing: "対局中"
isLlotheo: "石の少ない方が勝ち(ロセオ)"
loopedMap: "ループマップ"
canPutEverywhere: "どこでも置けるモード"
freeMatch: "フリーマッチ"
lookingForPlayer: "対戦相手を探しています"

View File

@@ -260,6 +260,7 @@ removed: "뭉캣십니다"
removeAreYouSure: "{x}(얼)럴 뭉캡니꺼?"
deleteAreYouSure: "{x}(얼)럴 뭉캡니꺼?"
resetAreYouSure: "아시로 데돌립니꺼?"
areYouSure: "갠찮십니꺼?"
saved: "저장햇십니다"
messaging: "대화"
upload: "올리기"
@@ -298,7 +299,7 @@ light: "볽엄"
dark: "어덥엄"
lightThemes: "볽언 테마"
darkThemes: "어덥언 테마"
syncDeviceDarkMode: "드라이브으 어덥엄 모드하고 같구로 마추기"
syncDeviceDarkMode: "디바이스 쪽 어덥엄 모드하고 같구로 마추기"
drive: "드라이브"
fileName: "파일 이럼"
selectFile: "파일 개리기"
@@ -425,20 +426,151 @@ moderationLogs: "중재 일지"
nUsersMentioned: "{n}멩이 이바구하고 잇어예"
securityKeyAndPasskey: "보안키·패스키"
securityKey: "보안키"
lastUsed: "마지막 쓰임"
lastUsedAt: "마지막 쓰임: {t}"
unregister: "맨걸기 무루기"
passwordLessLogin: "비밀번호 없시 로그인"
passwordLessLoginDescription: "비밀번호 말고 보안키나 패스키 같은 것만 써 가 로그인합니다."
resetPassword: "비밀번호 재설정"
newPasswordIs: "새 비밀번호는 \"{password}\" 입니다"
reduceUiAnimation: "화면 움직임 효과들을 수ᇚ후기"
share: "노누기"
notFound: "몬 찾앗십니다"
notFoundDescription: "고런 주소로 들어가는 하멘은 없십니다."
uploadFolder: "기본 업로드 위치"
markAsReadAllNotifications: "모든 알림 이럿다고 표시"
markAsReadAllUnreadNotes: "모든 글 이럿다고 표시"
markAsReadAllTalkMessages: "모든 대화 이럿다고 표시"
help: "도움말"
inputMessageHere: "여따가 메시지를 입력해주이소"
close: "닫기"
invites: "초대하기"
members: "멤버"
transfer: "양도"
title: "제목"
text: "글"
enable: "키기"
next: "다음"
retype: "다시 서기"
noteOf: "{user}님으 노트"
quoteAttached: "따옴"
quoteQuestion: "따와가 작성하겠십니까?"
noMessagesYet: "아직 대화가 없십니다"
newMessageExists: "새 메시지가 있십니다"
onlyOneFileCanBeAttached: "메시지엔 파일 하나까제밖에 몬 넣십니다"
invitations: "초대하기"
invitationCode: "초대장"
checking: "학인하고 잇십니다"
passwordMatched: "맞십니다"
passwordNotMatched: "안 맞십니다"
signinFailed: "로그인 몬 했십니다. 고 이름이랑 비밀번호 제대로 썼는가 확인해 주이소."
or: "아니면"
language: "언어"
uiLanguage: "UI 표시 언어"
aboutX: "{x}에 대해서"
emojiStyle: "이모지 모양"
native: "기본"
disableDrawer: "드로어 메뉴 쓰지 않기"
showNoteActionsOnlyHover: "마우스 올맀을 때만 노트 액션 버턴 보이기"
noHistory: "기록이 없십니다"
signinHistory: "로그인 기록"
enableAdvancedMfm: "복잡한 MFM 키기"
enableAnimatedMfm: "정신사나운 MFM 키기"
doing: "잠만예"
category: "카테고리"
tags: "태그"
docSource: "요 문서의 원본"
createAccount: "게정 맨걸기"
existingAccount: "원래 게정"
regenerate: "엎고 다시 맨걸기"
fontSize: "글자 크기"
mediaListWithOneImageAppearance: "사진 하나짜리 미디어 목록의 높이"
limitTo: "{x}로 제한"
noFollowRequests: "지둘리는 팔로우 요청이 없십니다"
openImageInNewTab: "새 탭서 사진 열기"
dashboard: "대시보드"
local: "로컬"
remote: "웬겍"
total: "합계"
weekOverWeekChanges: "저번주보다"
dayOverDayChanges: "어제보다"
appearance: "모냥"
clientSettings: "클라이언트 설정"
accountSettings: "게정 설정"
promotion: "선전"
promote: "선전하기"
numberOfDays: "며칠동안"
hideThisNote: "요 노트를 수ᇚ후기"
showFeaturedNotesInTimeline: "타임라인에다 추천 노트 보이기"
objectStorage: "오브젝트 스토리지"
useObjectStorage: "오브젝트 스토리지 키기"
objectStorageBaseUrl: "Base URL"
objectStorageBaseUrlDesc: "오브젝트 (미디어) 참조 링크 만들 때 쓰는 URL임다. CDN 내지 프락시를 쓴다 카멘은 그 URL을 갖다 늫고, 아이면 써먹을 서비스네 가이드를 봐봐가 공개적으로 접근할 수 있는 주소를 여 넣어 주이소. 그니께, 내가 AWS S3을 쓴다 카면은 'https://<bucket>.s3.amazonaws.com', GCS를 쓴다 카면 'https://storage.googleapis.com/<bucket>' 처럼 쓰믄 되입니더."
objectStorageBucket: "Bucket"
objectStorageBucketDesc: "써먹을 서비스의 바께쓰 이름을 여 써 주이소."
objectStoragePrefix: "Prefix"
objectStoragePrefixDesc: "요 Prefix 디렉토리 안에다가 파일이 들어감다."
objectStorageEndpoint: "Endpoint"
objectStorageEndpointDesc: "AWS S3을 쓸라멘 요는 비워두고, 아이멘은 그 서비스 가이드에 맞게 endpoint를 넣어 주이소. '<host>' 내지 '<host>:<port>'처럼 넣십니다."
objectStorageRegion: "Region"
objectStorageRegionDesc: "'xx-east-1' 같은 region 이름을 옇어 주이소. 써먹을 서비스에 region 개념 같은 게 읎다! 카면은 대신에 'us-east-1'을 옇어 놓으이소. AWS 설정 파일이나 환경 변수를 갖다 끌어다 쓸 거면은 요는 비워 두이소."
objectStorageUseSSL: "SSL 쓰기"
objectStorageUseSSLDesc: "API 호출할 때 HTTPS 안 쓸거면은 꺼 두이소"
objectStorageUseProxy: "연결에 프락시 사용"
objectStorageUseProxyDesc: "오브젝트 스토리지 API 호출에 프락시 안 쓸 거면 꺼 두이소"
objectStorageSetPublicRead: "업로드할 때 'public-read' 설정하기"
s3ForcePathStyleDesc: "s3ForcePathStyle을 키면, 바께쓰 이름을 URL의 호스트명 말고 경로의 일부로써 취급합니다. 셀프 호스트 Minio 같은 걸 굴릴라믄 켜놔야 될 수도 있십니다."
serverLogs: "서버 로그"
deleteAll: "말캉 뭉캐기"
showFixedPostForm: "타임라인 우에 글 작성 칸 박기"
showFixedPostFormInChannel: "채널 타임라인 우에 글 작성 칸 박기"
withRepliesByDefaultForNewlyFollowed: "팔로우 할 때 기본적으로 답걸도 타임라인에 나오게 하기"
newNoteRecived: "새 노트 있어예"
sounds: "소리"
sound: "소리"
listen: "듣기"
none: "없음"
showInPage: "바닥서 보기"
popout: "새 창 열기"
volume: "음량"
masterVolume: "대빵 음량"
notUseSound: "음소거하기"
useSoundOnlyWhenActive: "Misskey가 활성화되어 있을 때만 소리 내기"
details: "좀 더"
chooseEmoji: "이모지 선택"
unableToProcess: "작업 다 몬 했십니다"
recentUsed: "최근 쓴 놈"
install: "설치"
uninstall: "삭제"
installedApps: "설치된 애플리케이션"
nothing: "뭣도 없어예"
installedDate: "설치한 날"
lastUsedDate: "마지막 사용"
state: "상태"
sort: "정렬하기"
ascendingOrder: "작은 순"
descendingOrder: "큰 순"
scratchpad: "스크래치 패드"
scratchpadDescription: "스크래치 패드는 AiScript를 끼적거리는 창입니더. Misskey랑 갖다 이리저리 상호작용하는 코드를 서가 굴리멘은 그 결과도 바로 확인할 수 있십니다."
output: "출력"
script: "스크립트"
disablePagesScript: "온갖 바닥서 AiScript를 쓰지 않음"
updateRemoteUser: "원겍 사용자 근황 알아오기"
unsetUserAvatar: "아바타 치우기"
unsetUserAvatarConfirm: "아바타 갖다 치울까예?"
unsetUserBanner: "배너 치우기"
unsetUserBannerConfirm: "배너 갖다 치울까예?"
deleteAllFiles: "파일 말캉 뭉캐기"
deleteAllFilesConfirm: "파일을 싸그리 다 뭉캐삐릴까예?"
removeAllFollowing: "팔로잉 말캉 무루기"
removeAllFollowingDescription: "{host} 서버랑 걸어놓은 모든 팔로잉을 무룹니다. 고 서버가 아예 없어지삐맀든가, 그런 경우에 하이소."
userSuspended: "요 게정은... 얼어 있십니다."
userSilenced: "요 게정은... 수ᇚ혀 있십니다."
relays: "릴레이"
addRelay: "릴레이 옇기"
addedRelays: "옇은 릴레이"
enableInfiniteScroll: "알아서 더 보기"
author: "맨던 사람"
manage: "간리"
emailServer: "전자우펜 서버"
email: "전자우펜"
@@ -447,6 +579,8 @@ smtpHost: "호스트 이럼"
smtpPort: "포트"
smtpUser: "사용자 이럼"
smtpPass: "비밀번호"
display: "보기"
create: "맨걸기"
abuseReports: "신고하기"
reportAbuse: "신고하기"
reportAbuseRenote: "리노트 신고하기"
@@ -458,6 +592,7 @@ forwardReport: "웬겍 서버에 신고 보내기"
random: "무작이"
system: "시스템"
clip: "클립 맨걸기"
createNew: "새로 맨걸기"
notesCount: "노트 수"
renotesCount: "리노트한 수"
renotedCount: "리노트덴 수"
@@ -483,6 +618,7 @@ tools: "도구"
like: "좋네예!"
unlike: "좋네예 무루기"
numberOfLikes: "좋네예 수"
show: "보기"
roles: "옉할"
role: "옉할"
noRole: "옉할이 없십니다"
@@ -512,6 +648,8 @@ _gallery:
_email:
_follow:
title: "새 팔로워가 잇십니다"
_serverDisconnectedBehavior:
reload: "알아서 새로곤침"
_channel:
removeBanner: "배너 뭉캐기"
_theme:
@@ -581,4 +719,5 @@ _moderationLogTypes:
suspend: "얼우기"
deleteNote: "노트 뭉캐기"
deleteUserAnnouncement: "사용자 공지 걸 뭉캐기"
resetPassword: "비밀번호 재설정"
resolveAbuseReport: "신고 해겔하기"

View File

@@ -114,7 +114,7 @@ quote: "인용"
inChannelRenote: "채널 내 리노트"
inChannelQuote: "채널 내 인용"
pinnedNote: "고정된 노트"
pinned: "프로필에 고정"
pinned: "고정하기"
you: "나"
clickToShow: "클릭하여 보기"
sensitive: "열람 주의"
@@ -425,9 +425,9 @@ setupOf2fa: "2단계 인증 설정"
totp: "인증 앱"
totpDescription: "인증 앱을 사용하여 일회성 비밀번호 입력"
moderator: "모더레이터"
moderation: "모더레이션"
moderationNote: "모더레이션 노트"
addModerationNote: "모더레이션 노트 추가하기"
moderation: "조정"
moderationNote: "조정 기록"
addModerationNote: "조정 기록 추가하기"
moderationLogs: "모더레이션 로그"
nUsersMentioned: "{n}명이 언급함"
securityKeyAndPasskey: "보안 키 또는 패스 키"
@@ -513,7 +513,7 @@ dayOverDayChanges: "어제보다"
appearance: "모양"
clientSettings: "클라이언트 설정"
accountSettings: "계정 설정"
promotion: "프로모션"
promotion: "홍보"
promote: "프로모션하기"
numberOfDays: "며칠동안"
hideThisNote: "이 노트를 숨기기"
@@ -863,8 +863,8 @@ devMode: "개발자 모드"
keepCw: "CW 유지하기"
pubSub: "Pub/Sub 계정"
lastCommunication: "마지막 통신"
resolved: "해결됨"
unresolved: "해결되지 않음"
resolved: "처리함"
unresolved: "처리되지 않음"
breakFollow: "팔로워 해제"
breakFollowConfirm: "팔로우를 해제하시겠습니까?"
itsOn: "켜져 있습니다"
@@ -1179,8 +1179,10 @@ code: "문자열"
reloadRequiredToApplySettings: "설정을 적용하려면 새로고침을 해야 합니다."
remainingN: "나머지: {n}"
overwriteContentConfirm: "현재 내용을 덮어쓰기 합니다. 계속 진행하시겠습니까?"
seasonalScreenEffect: "철에 맞는 화면으로 꾸미기"
seasonalScreenEffect: "계절에 따른 효과 보이기"
decorate: "장식하기"
addMfmFunction: "장식 추가하기"
enableQuickAddMfmFunction: "상급자용 MFM 선택기 표시하기"
_announcement:
forExistingUsers: "기존 유저에게만 알림"
forExistingUsersDescription: "활성화하면 이 공지사항을 게시한 시점에서 이미 가입한 유저에게만 표시합니다. 비활성화하면 게시 후에 가입한 유저에게도 표시합니다."
@@ -1557,7 +1559,7 @@ _role:
name: "역할 이름"
description: "역할 설명"
permission: "역할 권한"
descriptionOfPermission: "<b>모더레이터</b>는 기본적인 중재와 관련된 작업을 행할 수 있습니다.\n<b>관리자</b>는 서버의 모든 설정을 변경할 수 있습니다."
descriptionOfPermission: "<b>조정자</b>는 기본적인 조정 작업을 행할 수 있습니다.\n<b>관리자</b>는 서버의 모든 설정을 변경할 수 있습니다."
assignTarget: "할당 대상"
descriptionOfAssignTarget: "<b>수동</b>을 선택하면 누가 이 역할에 포함되는지를 수동으로 관리할 수 있습니다.\n<b>조건부</b>를 선택하면 조건을 설정해 일치하는 사용자를 자동으로 포함되게 할 수 있습니다."
manual: "수동"
@@ -1628,7 +1630,7 @@ _role:
or: "다음을 하나라도 만족"
not: "다음을 만족하지 않음"
_sensitiveMediaDetection:
description: "기계학습을 통해 자동으로 민감한 미디어를 탐지하여, 모더레이션에 참고할 수 있도록 합니다. 서버 부하를 약간 증가시킵니다."
description: "기계 학습으로 민감한 미디어를 알아서 찾아내어 조정에 참고하도록 합니다. 서버 부하를 다소 받습니다."
sensitivity: "탐지 민감도"
sensitivityDescription: "민감도가 낮을수록 안전한 미디어가 잘못 탐지될 확률이 줄어들며, 높을수록 민감한 미디어가 탐지되지 않을 확률이 줄어듭니다."
setSensitiveFlagAutomatically: "자동으로 NSFW로 설정하기"
@@ -1641,6 +1643,7 @@ _emailUnavailable:
disposable: "임시 이메일 주소는 사용할 수 없습니다"
mx: "메일 서버가 올바르지 않습니다"
smtp: "메일 서버가 응답하지 않습니다"
banned: "이 메일 주소는 사용할 수 없습니다"
_ffVisibility:
public: "공개"
followers: "팔로워에게만 공개"
@@ -1932,6 +1935,55 @@ _permissions:
"write:flash": "Play를 조작합니다"
"read:flash-likes": "Play의 좋아요를 봅니다"
"write:flash-likes": "Play의 좋아요를 조작합니다"
"read:admin:abuse-user-reports": "사용자 신고 보기"
"write:admin:delete-account": "사용자 계정 삭제하기"
"write:admin:delete-all-files-of-a-user": "모든 사용자 파일 삭제하기"
"read:admin:index-stats": "데이터베이스 색인 정보 보기"
"read:admin:table-stats": "데이터베이스 테이블 정보 보기"
"read:admin:user-ips": "사용자 IP 주소 보기"
"read:admin:meta": "인스턴스 메타데이터 보기"
"write:admin:reset-password": "사용자 비밀번호 재설정하기"
"write:admin:resolve-abuse-user-report": "사용자 신고 처리하기"
"write:admin:send-email": "이메일 보내기"
"read:admin:server-info": "서버 정보 보기"
"read:admin:show-moderation-log": "조정 기록 보기"
"read:admin:show-user": "사용자 개인정보 보기"
"read:admin:show-users": "사용자 개인정보 보기"
"write:admin:suspend-user": "사용자 정지하기"
"write:admin:unset-user-avatar": "사용자 아바타 삭제하기"
"write:admin:unset-user-banner": "사용자 배너 삭제하기"
"write:admin:unsuspend-user": "사용자 정지 해제하기"
"write:admin:meta": "인스턴스 메타데이터 수정하기"
"write:admin:user-note": "조정 기록 수정하기"
"write:admin:roles": "역할 수정하기"
"read:admin:roles": "역할 보기"
"write:admin:relays": "릴레이 수정하기"
"read:admin:relays": "릴레이 보기"
"write:admin:invite-codes": "초대 코드 수정하기"
"read:admin:invite-codes": "초대 코드 보기"
"write:admin:announcements": "공지사항 수정하기"
"read:admin:announcements": "공지사항 보기"
"write:admin:avatar-decorations": "아바타 꾸미기 수정하기"
"read:admin:avatar-decorations": "아바타 꾸미기 보기"
"write:admin:federation": "연합 정보 수정하기"
"write:admin:account": "사용자 계정 수정하기"
"read:admin:account": "사용자 정보 보기"
"write:admin:emoji": "이모지 수정하기"
"read:admin:emoji": "이모지 보기"
"write:admin:queue": "작업 대기열 수정하기"
"read:admin:queue": "작업 대기열 정보 보기"
"write:admin:promo": "홍보 기록 수정하기"
"write:admin:drive": "사용자 드라이브 수정하기"
"read:admin:drive": "사용자 드라이브 정보 보기"
"read:admin:stream": "관리자용 Websocket API 사용하기"
"write:admin:ad": "광고 수정하기"
"read:admin:ad": "광고 보기"
"write:invite-codes": "초대 코드 만들기"
"read:invite-codes": "초대 코드 불러오기"
"write:clip-favorite": "클립의 좋아요 수정하기"
"read:clip-favorite": "클립의 좋아요 보기"
"read:federation": "연합 정보 불러오기"
"write:report-abuse": "위반 내용 신고하기"
_auth:
shareAccessTitle: "어플리케이션의 접근 허가"
shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
@@ -2171,6 +2223,7 @@ _notification:
pollEnded: "투표 결과가 발표되었습니다"
newNote: "새 게시물"
unreadAntennaNote: "안테나 {name}"
roleAssigned: "역할이 부여 되었습니다."
emptyPushNotificationMessage: "푸시 알림이 갱신되었습니다"
achievementEarned: "도전 과제를 달성했습니다"
testNotification: "알림 테스트"
@@ -2192,6 +2245,7 @@ _notification:
pollEnded: "투표가 종료됨"
receiveFollowRequest: "팔로우 요청을 받았을 때"
followRequestAccepted: "팔로우 요청이 승인되었을 때"
roleAssigned: "역할이 부여 됨"
achievementEarned: "도전 과제 획득"
app: "연동된 앱을 통한 알림"
_actions:
@@ -2264,21 +2318,21 @@ _moderationLogTypes:
updateCustomEmoji: "커스텀 이모지 수정"
deleteCustomEmoji: "커스텀 이모지 삭제"
updateServerSettings: "서버 설정 갱신"
updateUserNote: "모더레이션 노트 갱신"
updateUserNote: "조정 기록 갱신"
deleteDriveFile: "파일 삭제"
deleteNote: "노트 삭제"
createGlobalAnnouncement: "전역 공지사항 생성"
createUserAnnouncement: "유저 공지사항 생성"
updateGlobalAnnouncement: "전역 공지사항 수정"
updateUserAnnouncement: "유저 공지사항 수정"
deleteGlobalAnnouncement: "전역 공지사항 삭제"
deleteUserAnnouncement: "유저 공지사항 삭제"
createGlobalAnnouncement: "모든 공지사항 만들기"
createUserAnnouncement: "사용자 공지사항 만들기"
updateGlobalAnnouncement: "모든 공지사항 수정"
updateUserAnnouncement: "사용자 공지사항 수정"
deleteGlobalAnnouncement: "모든 공지사항 삭제"
deleteUserAnnouncement: "사용자 공지사항 삭제"
resetPassword: "비밀번호 재설정"
suspendRemoteInstance: "리모트 서버를 정지"
unsuspendRemoteInstance: "리모트 서버의 정지를 해제"
markSensitiveDriveFile: "파일에 열람주의를 설정"
unmarkSensitiveDriveFile: "파일에 열람주의를 해제"
resolveAbuseReport: "신고 해결"
resolveAbuseReport: "신고 처리"
createInvitation: "초대 코드 생성"
createAd: "광고 생성"
deleteAd: "광고 삭제"

View File

@@ -120,6 +120,12 @@ sensitive: "Содержимое не для всех"
add: "Добавить"
reaction: "Реакции"
reactions: "Реакции"
emojiPicker: "Палитра эмодзи"
pinnedEmojisForReactionSettingDescription: "Здесь можно закрепить эмодзи для реакций"
pinnedEmojisSettingDescription: "Здесь можно закрепить эмодзи в общей палитре"
emojiPickerDisplay: "Внешний вид палитры"
overwriteFromPinnedEmojisForReaction: "Заменить на эмодзи из списка реакций"
overwriteFromPinnedEmojis: "Заменить на эмодзи из общего списка закреплённых"
reactionSettingDescription2: "Расставляйте перетаскиванием, удаляйте нажатием, добавляйте кнопкой «+»."
rememberNoteVisibility: "Запоминать видимость заметок"
attachCancel: "Удалить вложение"
@@ -1053,6 +1059,8 @@ options: "Настройки ролей"
specifyUser: "Указанный пользователь"
failedToPreviewUrl: "Предварительный просмотр недоступен"
update: "Обновить"
rolesThatCanBeUsedThisEmojiAsReaction: "Роли тех, кому можно использовать эти эмодзи как реакцию"
rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Если здесь ничего не указать, в качестве реакции эту эмодзи сможет использовать каждый."
later: "Позже"
goToMisskey: "К Misskey"
additionalEmojiDictionary: "Дополнительные словари эмодзи"

View File

@@ -632,11 +632,11 @@ tokenRequested: "允許存取帳戶"
pluginTokenRequestedDescription: "此外掛將擁有在此設定的權限。"
notificationType: "通知形式"
edit: "編輯"
emailServer: "電伺服器"
enableEmail: "啟用發送電功能"
emailConfigInfo: "用於確認電地址及密碼重置"
emailServer: "電子郵件伺服器"
enableEmail: "啟用發送電子郵件功能"
emailConfigInfo: "用於確認電子郵件地址及密碼重置"
email: "電子郵件"
emailAddress: "電郵地址"
emailAddress: "電子郵件位址"
smtpConfig: "SMTP 伺服器設定"
smtpHost: "主機"
smtpPort: "埠"
@@ -731,7 +731,7 @@ disableShowingAnimatedImages: "不播放動態圖檔"
highlightSensitiveMedia: "強調敏感標記"
verificationEmailSent: "已發送驗證電子郵件。請點擊進入電子郵件中的鏈接完成驗證。"
notSet: "未設定"
emailVerified: "已成功驗證您的電"
emailVerified: "已成功驗證您的電子郵件地址"
noteFavoritesCount: "我的最愛貼文的數目"
pageLikesCount: "頁面被按讚次數"
pageLikedCount: "頁面被按讚次數"
@@ -783,7 +783,7 @@ capacity: "容量"
inUse: "已使用"
editCode: "編輯代碼"
apply: "套用"
receiveAnnouncementFromInstance: "接收由本實例發出的電郵通知"
receiveAnnouncementFromInstance: "接收來自伺服器的通知"
emailNotification: "郵件通知"
publish: "發布"
inChannelSearch: "頻道内搜尋"
@@ -955,7 +955,7 @@ cannotUploadBecauseExceedsFileSizeLimit: "由於超過了檔案大小的限制
beta: "測試版"
enableAutoSensitive: "自動 NSFW 判定"
enableAutoSensitiveDescription: "如果可用,它將使用機器學習技術判斷檔案是否需要標記為敏感。即使關閉此功能,也可能會依實例規則而自動啟用。"
activeEmailValidationDescription: "積極驗證使用者的電地址,以判斷它是否可以通訊。關閉此選項代表只會檢查地址是否符合格式。"
activeEmailValidationDescription: "主動地驗證使用者的電子郵件地址,以確定是否是一次性地址以及是否可以真正與其進行通訊。關閉時,僅檢查格式是否正確。"
navbar: "導覽列"
shuffle: "隨機"
account: "帳戶"
@@ -1181,6 +1181,8 @@ remainingN: "剩餘:{n}"
overwriteContentConfirm: "確定要覆蓋目前的內容嗎?"
seasonalScreenEffect: "隨季節變換畫面的呈現"
decorate: "設置頭像裝飾"
addMfmFunction: "插入MFM功能語法"
enableQuickAddMfmFunction: "顯示高級MFM選擇器"
_announcement:
forExistingUsers: "僅限既有的使用者"
forExistingUsersDescription: "啟用代表僅向現存使用者顯示;停用代表張貼後註冊的新使用者也會看到。"
@@ -1641,6 +1643,7 @@ _emailUnavailable:
disposable: "不是永久可用的地址"
mx: "郵件伺服器不正確"
smtp: "郵件伺服器沒有應答"
banned: "無法使用此電子郵件地址註冊"
_ffVisibility:
public: "公開"
followers: "只有關注您的使用者能看到"
@@ -2171,6 +2174,7 @@ _notification:
pollEnded: "問卷調查已產生結果"
newNote: "新的貼文"
unreadAntennaNote: "天線 {name}"
roleAssigned: "已授予角色"
emptyPushNotificationMessage: "推送通知已更新"
achievementEarned: "獲得成就"
testNotification: "通知測試"
@@ -2192,6 +2196,7 @@ _notification:
pollEnded: "問卷調查結束"
receiveFollowRequest: "已收到追隨請求"
followRequestAccepted: "追隨請求已接受"
roleAssigned: "已授予角色"
achievementEarned: "獲得成就"
app: "應用程式通知"
_actions:

View File

@@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "2023.12.0-beta.6",
"version": "2023.12.2",
"codename": "nasubi",
"repository": {
"type": "git",
@@ -18,7 +18,7 @@
"build-assets": "node ./scripts/build-assets.mjs",
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
"build-storybook": "pnpm --filter frontend build-storybook",
"build-misskey-js-with-types": "pnpm --filter backend build && pnpm --filter backend generate-api-json && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build",
"build-misskey-js-with-types": "pnpm --filter backend build && pnpm --filter backend generate-api-json && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
"init": "pnpm migrate",

View File

@@ -160,7 +160,6 @@ module.exports = {
testMatch: [
"<rootDir>/test/unit/**/*.ts",
"<rootDir>/src/**/*.test.ts",
"<rootDir>/test/e2e/**/*.ts",
],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped

View File

@@ -0,0 +1,15 @@
/*
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/en/configuration.html
*/
const base = require('./jest.config.cjs')
module.exports = {
...base,
globalSetup: "<rootDir>/built-test/entry.js",
setupFilesAfterEnv: ["<rootDir>/test/jest.setup.ts"],
testMatch: [
"<rootDir>/test/e2e/**/*.ts",
],
};

View File

@@ -0,0 +1,14 @@
/*
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/en/configuration.html
*/
const base = require('./jest.config.cjs')
module.exports = {
...base,
testMatch: [
"<rootDir>/test/unit/**/*.ts",
"<rootDir>/src/**/*.test.ts",
],
};

View File

@@ -24,9 +24,11 @@ export class ffVisibility1702718871541 {
async down(queryRunner) {
await queryRunner.query(`CREATE TYPE "public"."user_profile_ffvisibility_enum" AS ENUM('public', 'followers', 'private')`);
await queryRunner.query(`ALTER TABLE "user_profile" ADD "ffVisibility" "public"."user_profile_ffvisibility_enum" NOT NULL DEFAULT 'public'`);
await queryRunner.query(`CREATE CAST ("public"."user_profile_followingvisibility_enum" AS "public"."user_profile_ffvisibility_enum") WITH INOUT AS ASSIGNMENT`);
await queryRunner.query(`UPDATE "user_profile" SET ffVisibility = "user_profile"."followingVisibility"`);
await queryRunner.query(`UPDATE "user_profile" SET "ffVisibility" = "followingVisibility"`);
await queryRunner.query(`DROP CAST ("public"."user_profile_followingvisibility_enum" AS "public"."user_profile_ffvisibility_enum")`);
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "followersVisibility"`);
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "followingVisibility"`);
await queryRunner.query(`DROP TYPE "public"."user_profile_followersVisibility_enum"`);

View File

@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class bannedEmailDomains1703209889304 {
constructor() {
this.name = 'bannedEmailDomains1703209889304';
}
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "bannedEmailDomains" character varying(1024) array NOT NULL DEFAULT '{}'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "bannedEmailDomains"`);
}
}

View File

@@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class SupportTrueMailApi1703658526000 {
name = 'SupportTrueMailApi1703658526000'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "truemailInstance" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "truemailAuthKey" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "enableTruemailApi" boolean NOT NULL DEFAULT false`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableTruemailApi"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "truemailInstance"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "truemailAuthKey"`);
}
}

View File

@@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class SupportMcaptcha1704373210054 {
name = 'SupportMcaptcha1704373210054'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "enableMcaptcha" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaSitekey" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaSecretKey" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaInstanceUrl" character varying(1024)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaInstanceUrl"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaSecretKey"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaSitekey"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableMcaptcha"`);
}
}

View File

@@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class BubbleGameRecord1704959805077 {
name = 'BubbleGameRecord1704959805077'
async up(queryRunner) {
await queryRunner.query(`CREATE TABLE "bubble_game_record" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "seededAt" TIMESTAMP WITH TIME ZONE NOT NULL, "seed" character varying(1024) NOT NULL, "gameVersion" integer NOT NULL, "gameMode" character varying(128) NOT NULL, "score" integer NOT NULL, "logs" jsonb NOT NULL DEFAULT '[]', "isVerified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_a75395fe404b392e2893b50d7ea" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE INDEX "IDX_75276757070d21fdfaf4c05290" ON "bubble_game_record" ("userId") `);
await queryRunner.query(`CREATE INDEX "IDX_4ae7053179014915d1432d3f40" ON "bubble_game_record" ("seededAt") `);
await queryRunner.query(`CREATE INDEX "IDX_26d4ee490b5a487142d35466ee" ON "bubble_game_record" ("score") `);
await queryRunner.query(`ALTER TABLE "bubble_game_record" ADD CONSTRAINT "FK_75276757070d21fdfaf4c052909" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "bubble_game_record" DROP CONSTRAINT "FK_75276757070d21fdfaf4c052909"`);
await queryRunner.query(`DROP INDEX "public"."IDX_26d4ee490b5a487142d35466ee"`);
await queryRunner.query(`DROP INDEX "public"."IDX_4ae7053179014915d1432d3f40"`);
await queryRunner.query(`DROP INDEX "public"."IDX_75276757070d21fdfaf4c05290"`);
await queryRunner.query(`DROP TABLE "bubble_game_record"`);
}
}

View File

@@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class OptimizeNoteIndexForArrayColumns1705222772858 {
name = 'OptimizeNoteIndexForArrayColumns1705222772858'
async up(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_796a8c03959361f97dc2be1d5c"`);
await queryRunner.query(`DROP INDEX "public"."IDX_54ebcb6d27222913b908d56fd8"`);
await queryRunner.query(`DROP INDEX "public"."IDX_88937d94d7443d9a99a76fa5c0"`);
await queryRunner.query(`DROP INDEX "public"."IDX_51c063b6a133a9cb87145450f5"`);
await queryRunner.query(`CREATE INDEX "IDX_NOTE_FILE_IDS" ON "note" using gin ("fileIds")`)
}
async down(queryRunner) {
await queryRunner.query(`DROP INDEX "IDX_NOTE_FILE_IDS"`)
await queryRunner.query(`CREATE INDEX "IDX_51c063b6a133a9cb87145450f5" ON "note" ("fileIds") `);
await queryRunner.query(`CREATE INDEX "IDX_88937d94d7443d9a99a76fa5c0" ON "note" ("tags") `);
await queryRunner.query(`CREATE INDEX "IDX_54ebcb6d27222913b908d56fd8" ON "note" ("mentions") `);
await queryRunner.query(`CREATE INDEX "IDX_796a8c03959361f97dc2be1d5c" ON "note" ("visibleUserIds") `);
}
}

View File

@@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class Reversi1705475608437 {
name = 'Reversi1705475608437'
async up(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_b46ec40746efceac604142be1c"`);
await queryRunner.query(`DROP INDEX "public"."IDX_b604d92d6c7aec38627f6eaf16"`);
await queryRunner.query(`ALTER TABLE "reversi_game" DROP COLUMN "createdAt"`);
await queryRunner.query(`ALTER TABLE "reversi_matching" DROP COLUMN "createdAt"`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "reversi_matching" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`);
await queryRunner.query(`ALTER TABLE "reversi_game" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`);
await queryRunner.query(`CREATE INDEX "IDX_b604d92d6c7aec38627f6eaf16" ON "reversi_matching" ("createdAt") `);
await queryRunner.query(`CREATE INDEX "IDX_b46ec40746efceac604142be1c" ON "reversi_game" ("createdAt") `);
}
}

View File

@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class Reversi21705654039457 {
name = 'Reversi21705654039457'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "reversi_game" RENAME COLUMN "user1Accepted" TO "user1Ready"`);
await queryRunner.query(`ALTER TABLE "reversi_game" RENAME COLUMN "user2Accepted" TO "user2Ready"`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "reversi_game" RENAME COLUMN "user1Ready" TO "user1Accepted"`);
await queryRunner.query(`ALTER TABLE "reversi_game" RENAME COLUMN "user2Ready" TO "user2Accepted"`);
}
}

View File

@@ -13,6 +13,7 @@
"revert": "pnpm typeorm migration:revert -d ormconfig.js",
"check:connect": "node ./check_connect.js",
"build": "swc src -d built -D",
"build:test": "swc test-server -d built-test -D --config-file test-server/.swcrc",
"watch:swc": "swc src -d built -D -w",
"build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
"watch": "node watch.mjs",
@@ -21,11 +22,15 @@
"typecheck": "tsc --noEmit",
"eslint": "eslint --quiet \"src/**/*.ts\"",
"lint": "pnpm typecheck && pnpm eslint",
"jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit",
"jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit",
"jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.unit.cjs",
"jest:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.e2e.cjs",
"jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.unit.cjs",
"jest-and-coverage:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.e2e.cjs",
"jest-clear": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --clearCache",
"test": "pnpm jest",
"test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e",
"test-and-coverage": "pnpm jest-and-coverage",
"test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e",
"generate-api-json": "node ./generate_api_json.js"
},
"optionalDependencies": {
@@ -68,12 +73,14 @@
"@discordapp/twemoji": "15.0.2",
"@fastify/accepts": "4.3.0",
"@fastify/cookie": "9.2.0",
"@fastify/cors": "8.4.2",
"@fastify/cors": "8.5.0",
"@fastify/express": "2.3.0",
"@fastify/http-proxy": "9.3.0",
"@fastify/multipart": "8.0.0",
"@fastify/static": "6.12.0",
"@fastify/view": "8.2.0",
"@misskey-dev/sharp-read-bmp": "^1.1.1",
"@misskey-dev/summaly": "^5.0.3",
"@nestjs/common": "10.2.10",
"@nestjs/core": "10.2.10",
"@nestjs/testing": "10.2.10",
@@ -100,6 +107,7 @@
"cli-highlight": "2.1.11",
"color-convert": "2.0.1",
"content-disposition": "0.5.4",
"crc-32": "^1.2.2",
"date-fns": "2.30.0",
"deep-email-validator": "0.1.21",
"fastify": "4.24.3",
@@ -126,6 +134,7 @@
"microformats-parser": "2.0.2",
"mime-types": "2.1.35",
"misskey-js": "workspace:*",
"misskey-reversi": "workspace:*",
"ms": "3.0.0-canary.1",
"nanoid": "5.0.4",
"nested-property": "4.0.0",
@@ -157,11 +166,9 @@
"sanitize-html": "2.11.0",
"secure-json-parse": "2.7.0",
"sharp": "0.32.6",
"sharp-read-bmp": "github:misskey-dev/sharp-read-bmp",
"slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
"summaly": "github:misskey-dev/summaly",
"systeminformation": "5.21.20",
"tinycolor2": "1.6.0",
"tmp": "0.2.1",
@@ -177,6 +184,8 @@
},
"devDependencies": {
"@jest/globals": "29.7.0",
"@misskey-dev/eslint-plugin": "^1.0.0",
"@nestjs/platform-express": "^10.3.0",
"@simplewebauthn/typescript-types": "8.3.4",
"@swc/jest": "0.2.29",
"@types/accepts": "1.3.7",
@@ -225,9 +234,11 @@
"eslint": "8.56.0",
"eslint-plugin-import": "2.29.1",
"execa": "8.0.1",
"fkill": "^9.0.0",
"jest": "29.7.0",
"jest-mock": "29.7.0",
"nodemon": "3.0.2",
"pid-port": "^1.0.0",
"simple-oauth2": "5.0.0"
}
}

View File

@@ -3,7 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { setTimeout } from 'node:timers/promises';
import { Global, Inject, Module } from '@nestjs/common';
import * as Redis from 'ioredis';
import { DataSource } from 'typeorm';
@@ -12,6 +11,7 @@ import { DI } from './di-symbols.js';
import { Config, loadConfig } from './config.js';
import { createPostgresDataSource } from './postgres.js';
import { RepositoryModule } from './models/RepositoryModule.js';
import { allSettled } from './misc/promise-tracker.js';
import type { Provider, OnApplicationShutdown } from '@nestjs/common';
const $config: Provider = {
@@ -33,7 +33,7 @@ const $meilisearch: Provider = {
useFactory: (config: Config) => {
if (config.meilisearch) {
return new MeiliSearch({
host: `${config.meilisearch.ssl ? 'https' : 'http' }://${config.meilisearch.host}:${config.meilisearch.port}`,
host: `${config.meilisearch.ssl ? 'https' : 'http'}://${config.meilisearch.host}:${config.meilisearch.port}`,
apiKey: config.meilisearch.apiKey,
});
} else {
@@ -91,17 +91,12 @@ export class GlobalModule implements OnApplicationShutdown {
@Inject(DI.redisForPub) private redisForPub: Redis.Redis,
@Inject(DI.redisForSub) private redisForSub: Redis.Redis,
@Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis,
) {}
) { }
public async dispose(): Promise<void> {
if (process.env.NODE_ENV === 'test') {
// XXX:
// Shutting down the existing connections causes errors on Jest as
// Misskey has asynchronous postgres/redis connections that are not
// awaited.
// Let's wait for some random time for them to finish.
await setTimeout(5000);
}
// Wait for all potential DB queries
await allSettled();
// And then disconnect from DB
await Promise.all([
this.db.destroy(),
this.redisClient.disconnect(),

View File

@@ -87,6 +87,8 @@ export const ACHIEVEMENT_TYPES = [
'brainDiver',
'smashTestNotificationButton',
'tutorialCompleted',
'bubbleGameExplodingHead',
'bubbleGameDoubleExplodingHead',
] as const;
@Injectable()

View File

@@ -73,6 +73,37 @@ export class CaptchaService {
}
}
// https://codeberg.org/Gusted/mCaptcha/src/branch/main/mcaptcha.go
@bindThis
public async verifyMcaptcha(secret: string, siteKey: string, instanceHost: string, response: string | null | undefined): Promise<void> {
if (response == null) {
throw new Error('mcaptcha-failed: no response provided');
}
const endpointUrl = new URL('/api/v1/pow/siteverify', instanceHost);
const result = await this.httpRequestService.send(endpointUrl.toString(), {
method: 'POST',
body: JSON.stringify({
key: siteKey,
secret: secret,
token: response,
}),
headers: {
'Content-Type': 'application/json',
},
});
if (result.status !== 200) {
throw new Error('mcaptcha-failed: mcaptcha didn\'t return 200 OK');
}
const resp = (await result.json()) as { valid: boolean };
if (!resp.valid) {
throw new Error('mcaptcha-request-failed');
}
}
@bindThis
public async verifyTurnstile(secret: string, response: string | null | undefined): Promise<void> {
if (response == null) {

View File

@@ -66,6 +66,8 @@ import { FeaturedService } from './FeaturedService.js';
import { FanoutTimelineService } from './FanoutTimelineService.js';
import { ChannelFollowingService } from './ChannelFollowingService.js';
import { RegistryApiService } from './RegistryApiService.js';
import { ReversiService } from './ReversiService.js';
import { ChartLoggerService } from './chart/ChartLoggerService.js';
import FederationChart from './chart/charts/federation.js';
import NotesChart from './chart/charts/notes.js';
@@ -80,6 +82,7 @@ import PerUserFollowingChart from './chart/charts/per-user-following.js';
import PerUserDriveChart from './chart/charts/per-user-drive.js';
import ApRequestChart from './chart/charts/ap-request.js';
import { ChartManagementService } from './chart/ChartManagementService.js';
import { AbuseUserReportEntityService } from './entities/AbuseUserReportEntityService.js';
import { AntennaEntityService } from './entities/AntennaEntityService.js';
import { AppEntityService } from './entities/AppEntityService.js';
@@ -112,6 +115,8 @@ import { UserListEntityService } from './entities/UserListEntityService.js';
import { FlashEntityService } from './entities/FlashEntityService.js';
import { FlashLikeEntityService } from './entities/FlashLikeEntityService.js';
import { RoleEntityService } from './entities/RoleEntityService.js';
import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js';
import { ApAudienceService } from './activitypub/ApAudienceService.js';
import { ApDbResolverService } from './activitypub/ApDbResolverService.js';
import { ApDeliverManagerService } from './activitypub/ApDeliverManagerService.js';
@@ -199,6 +204,7 @@ const $FanoutTimelineService: Provider = { provide: 'FanoutTimelineService', use
const $FanoutTimelineEndpointService: Provider = { provide: 'FanoutTimelineEndpointService', useExisting: FanoutTimelineEndpointService };
const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService', useExisting: ChannelFollowingService };
const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService };
const $ReversiService: Provider = { provide: 'ReversiService', useExisting: ReversiService };
const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService };
const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart };
@@ -247,6 +253,7 @@ const $UserListEntityService: Provider = { provide: 'UserListEntityService', use
const $FlashEntityService: Provider = { provide: 'FlashEntityService', useExisting: FlashEntityService };
const $FlashLikeEntityService: Provider = { provide: 'FlashLikeEntityService', useExisting: FlashLikeEntityService };
const $RoleEntityService: Provider = { provide: 'RoleEntityService', useExisting: RoleEntityService };
const $ReversiGameEntityService: Provider = { provide: 'ReversiGameEntityService', useExisting: ReversiGameEntityService };
const $ApAudienceService: Provider = { provide: 'ApAudienceService', useExisting: ApAudienceService };
const $ApDbResolverService: Provider = { provide: 'ApDbResolverService', useExisting: ApDbResolverService };
@@ -336,6 +343,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
FanoutTimelineEndpointService,
ChannelFollowingService,
RegistryApiService,
ReversiService,
ChartLoggerService,
FederationChart,
NotesChart,
@@ -350,6 +359,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
PerUserDriveChart,
ApRequestChart,
ChartManagementService,
AbuseUserReportEntityService,
AntennaEntityService,
AppEntityService,
@@ -382,6 +392,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
FlashEntityService,
FlashLikeEntityService,
RoleEntityService,
ReversiGameEntityService,
ApAudienceService,
ApDbResolverService,
ApDeliverManagerService,
@@ -466,6 +478,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$FanoutTimelineEndpointService,
$ChannelFollowingService,
$RegistryApiService,
$ReversiService,
$ChartLoggerService,
$FederationChart,
$NotesChart,
@@ -480,6 +494,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$PerUserDriveChart,
$ApRequestChart,
$ChartManagementService,
$AbuseUserReportEntityService,
$AntennaEntityService,
$AppEntityService,
@@ -512,6 +527,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$FlashEntityService,
$FlashLikeEntityService,
$RoleEntityService,
$ReversiGameEntityService,
$ApAudienceService,
$ApDbResolverService,
$ApDeliverManagerService,
@@ -597,6 +614,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
FanoutTimelineEndpointService,
ChannelFollowingService,
RegistryApiService,
ReversiService,
FederationChart,
NotesChart,
UsersChart,
@@ -610,6 +629,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
PerUserDriveChart,
ApRequestChart,
ChartManagementService,
AbuseUserReportEntityService,
AntennaEntityService,
AppEntityService,
@@ -642,6 +662,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
FlashEntityService,
FlashLikeEntityService,
RoleEntityService,
ReversiGameEntityService,
ApAudienceService,
ApDbResolverService,
ApDeliverManagerService,
@@ -726,6 +748,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$FanoutTimelineEndpointService,
$ChannelFollowingService,
$RegistryApiService,
$ReversiService,
$FederationChart,
$NotesChart,
$UsersChart,
@@ -739,6 +763,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$PerUserDriveChart,
$ApRequestChart,
$ChartManagementService,
$AbuseUserReportEntityService,
$AntennaEntityService,
$AppEntityService,
@@ -771,6 +796,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$FlashEntityService,
$FlashLikeEntityService,
$RoleEntityService,
$ReversiGameEntityService,
$ApAudienceService,
$ApDbResolverService,
$ApDeliverManagerService,

View File

@@ -145,7 +145,8 @@ export class DownloadService {
const parsedIp = ipaddr.parse(ip);
for (const net of this.config.allowedPrivateNetworks ?? []) {
if (parsedIp.match(ipaddr.parseCIDR(net))) {
const cidr = ipaddr.parseCIDR(net);
if (cidr[0].kind() === parsedIp.kind() && parsedIp.match(ipaddr.parseCIDR(net))) {
return false;
}
}

View File

@@ -7,7 +7,7 @@ import { randomUUID } from 'node:crypto';
import * as fs from 'node:fs';
import { Inject, Injectable } from '@nestjs/common';
import sharp from 'sharp';
import { sharpBmp } from 'sharp-read-bmp';
import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
import { IsNull } from 'typeorm';
import { DeleteObjectCommandInput, PutObjectCommandInput, NoSuchKey } from '@aws-sdk/client-s3';
import { DI } from '@/di-symbols.js';
@@ -655,7 +655,7 @@ export class DriveService {
public async updateFile(file: MiDriveFile, values: Partial<MiDriveFile>, updater: MiUser) {
const alwaysMarkNsfw = (await this.roleService.getUserPolicies(file.userId)).alwaysMarkNsfw;
if (values.name && !this.driveFileEntityService.validateFileName(file.name)) {
if (values.name != null && !this.driveFileEntityService.validateFileName(values.name)) {
throw new DriveService.InvalidFileNameError();
}

View File

@@ -7,8 +7,8 @@ import { URLSearchParams } from 'node:url';
import * as nodemailer from 'nodemailer';
import { Inject, Injectable } from '@nestjs/common';
import { validate as validateEmail } from 'deep-email-validator';
import { SubOutputFormat } from 'deep-email-validator/dist/output/output.js';
import { MetaService } from '@/core/MetaService.js';
import { UtilityService } from '@/core/UtilityService.js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import type Logger from '@/logger.js';
@@ -30,6 +30,7 @@ export class EmailService {
private metaService: MetaService,
private loggerService: LoggerService,
private utilityService: UtilityService,
private httpRequestService: HttpRequestService,
) {
this.logger = this.loggerService.getLogger('email');
@@ -155,7 +156,7 @@ export class EmailService {
@bindThis
public async validateEmailForAccount(emailAddress: string): Promise<{
available: boolean;
reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp';
reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned' | 'network' | 'blacklist';
}> {
const meta = await this.metaService.fetch();
@@ -164,36 +165,46 @@ export class EmailService {
email: emailAddress,
});
const verifymailApi = meta.enableVerifymailApi && meta.verifymailAuthKey != null;
let validated;
let validated: {
valid: boolean,
reason?: string | null,
};
if (meta.enableActiveEmailValidation && meta.verifymailAuthKey) {
if (verifymailApi) {
if (meta.enableActiveEmailValidation) {
if (meta.enableVerifymailApi && meta.verifymailAuthKey != null) {
validated = await this.verifyMail(emailAddress, meta.verifymailAuthKey);
} else if (meta.enableTruemailApi && meta.truemailInstance && meta.truemailAuthKey != null) {
validated = await this.trueMail(meta.truemailInstance, emailAddress, meta.truemailAuthKey);
} else {
validated = meta.enableActiveEmailValidation ? await validateEmail({
validated = await validateEmail({
email: emailAddress,
validateRegex: true,
validateMx: true,
validateTypo: false, // TLDを見ているみたいだけどclubとか弾かれるので
validateDisposable: true, // 捨てアドかどうかチェック
validateSMTP: false, // 日本だと25ポートが殆どのプロバイダーで塞がれていてタイムアウトになるので
}) : { valid: true, reason: null };
});
}
} else {
validated = { valid: true, reason: null };
}
const available = exist === 0 && validated.valid;
const emailDomain: string = emailAddress.split('@')[1];
const isBanned = this.utilityService.isBlockedHost(meta.bannedEmailDomains, emailDomain);
const available = exist === 0 && validated.valid && !isBanned;
return {
available,
reason: available ? null :
exist !== 0 ? 'used' :
isBanned ? 'banned' :
validated.reason === 'regex' ? 'format' :
validated.reason === 'disposable' ? 'disposable' :
validated.reason === 'mx' ? 'mx' :
validated.reason === 'smtp' ? 'smtp' :
validated.reason === 'network' ? 'network' :
validated.reason === 'blacklist' ? 'blacklist' :
null,
};
}
@@ -258,4 +269,67 @@ export class EmailService {
reason: null,
};
}
private async trueMail<T>(truemailInstance: string, emailAddress: string, truemailAuthKey: string): Promise<{
valid: boolean;
reason: 'used' | 'format' | 'blacklist' | 'mx' | 'smtp' | 'network' | T | null;
}> {
const endpoint = truemailInstance + '?email=' + emailAddress;
try {
const res = await this.httpRequestService.send(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: truemailAuthKey
},
});
const json = (await res.json()) as {
email: string;
success: boolean;
errors?: {
list_match?: string;
regex?: string;
mx?: string;
smtp?: string;
} | null;
};
if (json.email === undefined || (json.email !== undefined && json.errors?.regex)) {
return {
valid: false,
reason: 'format',
};
}
if (json.errors?.smtp) {
return {
valid: false,
reason: 'smtp',
};
}
if (json.errors?.mx) {
return {
valid: false,
reason: 'mx',
};
}
if (!json.success) {
return {
valid: false,
reason: json.errors?.list_match as T || 'blacklist',
};
}
return {
valid: true,
reason: null,
};
} catch (error) {
return {
valid: false,
reason: 'network',
};
}
}
}

View File

@@ -18,7 +18,7 @@ import type { MiSignin } from '@/models/Signin.js';
import type { MiPage } from '@/models/Page.js';
import type { MiWebhook } from '@/models/Webhook.js';
import type { MiMeta } from '@/models/Meta.js';
import { MiAvatarDecoration, MiRole, MiRoleAssignment } from '@/models/_.js';
import { MiAvatarDecoration, MiReversiGame, MiRole, MiRoleAssignment } from '@/models/_.js';
import type { Packed } from '@/misc/json-schema.js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
@@ -159,6 +159,43 @@ export interface AdminEventTypes {
comment: string;
};
}
export interface ReversiEventTypes {
matched: {
game: Packed<'ReversiGameDetailed'>;
};
invited: {
user: Packed<'User'>;
};
}
export interface ReversiGameEventTypes {
changeReadyStates: {
user1: boolean;
user2: boolean;
};
updateSettings: {
userId: MiUser['id'];
key: string;
value: any;
};
putStone: {
at: number;
color: boolean;
pos: number;
next: boolean;
};
syncState: {
crc32: string;
};
started: {
game: Packed<'ReversiGameDetailed'>;
};
ended: {
winnerId: MiUser['id'] | null;
game: Packed<'ReversiGameDetailed'>;
};
}
//#endregion
// 辞書(interface or type)から{ type, body }ユニオンを定義
@@ -249,6 +286,14 @@ export type GlobalEvents = {
name: 'notesStream';
payload: Serialized<Packed<'Note'>>;
};
reversi: {
name: `reversiStream:${MiUser['id']}`;
payload: EventUnionFromDictionary<SerializedAll<ReversiEventTypes>>;
};
reversiGame: {
name: `reversiGameStream:${MiReversiGame['id']}`;
payload: EventUnionFromDictionary<SerializedAll<ReversiGameEventTypes>>;
};
};
// API event definitions
@@ -338,4 +383,14 @@ export class GlobalEventService {
public publishAdminStream<K extends keyof AdminEventTypes>(userId: MiUser['id'], type: K, value?: AdminEventTypes[K]): void {
this.publish(`adminStream:${userId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis
public publishReversiStream<K extends keyof ReversiEventTypes>(userId: MiUser['id'], type: K, value?: ReversiEventTypes[K]): void {
this.publish(`reversiStream:${userId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis
public publishReversiGameStream<K extends keyof ReversiGameEventTypes>(gameId: MiReversiGame['id'], type: K, value?: ReversiGameEventTypes[K]): void {
this.publish(`reversiGameStream:${gameId}`, type, typeof value === 'undefined' ? null : value);
}
}

View File

@@ -15,6 +15,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
import { FeaturedService } from '@/core/FeaturedService.js';
import { MetaService } from '@/core/MetaService.js';
import { UtilityService } from '@/core/UtilityService.js';
@Injectable()
export class HashtagService {
@@ -29,6 +30,7 @@ export class HashtagService {
private featuredService: FeaturedService,
private idService: IdService,
private metaService: MetaService,
private utilityService: UtilityService,
) {
}
@@ -161,6 +163,7 @@ export class HashtagService {
const instance = await this.metaService.fetch();
const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
if (hiddenTags.includes(hashtag)) return;
if (this.utilityService.isSensitiveWordIncluded(hashtag, instance.sensitiveWords)) return;
// YYYYMMDDHHmm (10分間隔)
const now = new Date();

View File

@@ -58,6 +58,7 @@ import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
import { UtilityService } from '@/core/UtilityService.js';
import { UserBlockingService } from '@/core/UserBlockingService.js';
import { isReply } from '@/misc/is-reply.js';
import { trackPromise } from '@/misc/promise-tracker.js';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@@ -253,7 +254,7 @@ export class NoteCreateService implements OnApplicationShutdown {
if (data.visibility === 'public' && data.channel == null) {
const sensitiveWords = meta.sensitiveWords;
if (this.isSensitive(data, sensitiveWords)) {
if (this.utilityService.isSensitiveWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
data.visibility = 'home';
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
data.visibility = 'home';
@@ -293,7 +294,7 @@ export class NoteCreateService implements OnApplicationShutdown {
}
// Check blocking
if (data.renote && this.isQuote(data)) {
if (data.renote && !this.isQuote(data)) {
if (data.renote.userHost === null) {
if (data.renote.userId !== user.id) {
const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id);
@@ -324,6 +325,9 @@ export class NoteCreateService implements OnApplicationShutdown {
data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
}
data.text = data.text.trim();
if (data.text === '') {
data.text = null;
}
} else {
data.text = null;
}
@@ -676,7 +680,7 @@ export class NoteCreateService implements OnApplicationShutdown {
this.relayService.deliverToRelays(user, noteActivity);
}
dm.execute();
trackPromise(dm.execute());
})();
}
//#endregion
@@ -705,33 +709,9 @@ export class NoteCreateService implements OnApplicationShutdown {
}
@bindThis
private isSensitive(note: Option, sensitiveWord: string[]): boolean {
if (sensitiveWord.length > 0) {
const text = note.cw ?? note.text ?? '';
if (text === '') return false;
const matched = sensitiveWord.some(filter => {
// represents RegExp
const regexp = filter.match(/^\/(.+)\/(.*)$/);
// This should never happen due to input sanitisation.
if (!regexp) {
const words = filter.split(' ');
return words.every(keyword => text.includes(keyword));
}
try {
return new RE2(regexp[1], regexp[2]).test(text);
} catch (err) {
// This should never happen due to input sanitisation.
return false;
}
});
if (matched) return true;
}
return false;
}
@bindThis
private isQuote(note: Option): boolean {
return !!note.text || !!note.cw || !!note.files || !!note.poll;
private isQuote(note: Option): note is Option & { renote: MiNote } {
// sync with misc/is-quote.ts
return !!note.renote && (!!note.text || !!note.cw || (!!note.files && !!note.files.length) || !!note.poll);
}
@bindThis
@@ -799,7 +779,7 @@ export class NoteCreateService implements OnApplicationShutdown {
private async renderNoteOrRenoteActivity(data: Option, note: MiNote) {
if (data.localOnly) return null;
const content = data.renote && this.isQuote(data)
const content = data.renote && !this.isQuote(data)
? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note)
: this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note);
@@ -911,6 +891,7 @@ export class NoteCreateService implements OnApplicationShutdown {
// ダイレクトのとき、そのリストが対象外のユーザーの場合
if (
note.visibility === 'specified' &&
note.userId !== userListMembership.userListUserId &&
!note.visibleUserIds.some(v => v === userListMembership.userListUserId)
) continue;

View File

@@ -14,6 +14,7 @@ import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import type { NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import { trackPromise } from '@/misc/promise-tracker.js';
@Injectable()
export class NoteReadService implements OnApplicationShutdown {
@@ -107,7 +108,7 @@ export class NoteReadService implements OnApplicationShutdown {
// TODO: ↓まとめてクエリしたい
this.noteUnreadsRepository.countBy({
trackPromise(this.noteUnreadsRepository.countBy({
userId: userId,
isMentioned: true,
}).then(mentionsCount => {
@@ -115,9 +116,9 @@ export class NoteReadService implements OnApplicationShutdown {
// 全て既読になったイベントを発行
this.globalEventService.publishMainStream(userId, 'readAllUnreadMentions');
}
});
}));
this.noteUnreadsRepository.countBy({
trackPromise(this.noteUnreadsRepository.countBy({
userId: userId,
isSpecified: true,
}).then(specifiedCount => {
@@ -125,7 +126,7 @@ export class NoteReadService implements OnApplicationShutdown {
// 全て既読になったイベントを発行
this.globalEventService.publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
}
});
}));
}
}

View File

@@ -20,6 +20,7 @@ import { CacheService } from '@/core/CacheService.js';
import type { Config } from '@/config.js';
import { UserListService } from '@/core/UserListService.js';
import type { FilterUnionByProperty } from '@/types.js';
import { trackPromise } from '@/misc/promise-tracker.js';
@Injectable()
export class NotificationService implements OnApplicationShutdown {
@@ -74,7 +75,18 @@ export class NotificationService implements OnApplicationShutdown {
}
@bindThis
public async createNotification<T extends MiNotification['type']>(
public createNotification<T extends MiNotification['type']>(
notifieeId: MiUser['id'],
type: T,
data: Omit<FilterUnionByProperty<MiNotification, 'type', T>, 'type' | 'id' | 'createdAt' | 'notifierId'>,
notifierId?: MiUser['id'] | null,
) {
trackPromise(
this.#createNotificationInternal(notifieeId, type, data, notifierId),
);
}
async #createNotificationInternal<T extends MiNotification['type']>(
notifieeId: MiUser['id'],
type: T,
data: Omit<FilterUnionByProperty<MiNotification, 'type', T>, 'type' | 'id' | 'createdAt' | 'notifierId'>,

View File

@@ -212,8 +212,8 @@ export class QueryService {
// または 自分自身
.orWhere('note.userId = :meId')
// または 自分宛て
.orWhere(':meId = ANY(note.visibleUserIds)')
.orWhere(':meId = ANY(note.mentions)')
.orWhere(':meIdAsList <@ note.visibleUserIds')
.orWhere(':meIdAsList <@ note.mentions')
.orWhere(new Brackets(qb => {
qb
// または フォロワー宛ての投稿であり、
@@ -228,7 +228,7 @@ export class QueryService {
}));
}));
q.setParameters({ meId: me.id });
q.setParameters({ meId: me.id, meIdAsList: [me.id] });
}
}

View File

@@ -3,12 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { setTimeout } from 'node:timers/promises';
import { Inject, Module, OnApplicationShutdown } from '@nestjs/common';
import * as Bull from 'bullmq';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { QUEUE, baseQueueOptions } from '@/queue/const.js';
import { allSettled } from '@/misc/promise-tracker.js';
import type { Provider } from '@nestjs/common';
import type { DeliverJobData, InboxJobData, EndedPollNotificationJobData, WebhookDeliverJobData, RelationshipJobData } from '../queue/types.js';
@@ -106,14 +106,9 @@ export class QueueModule implements OnApplicationShutdown {
) {}
public async dispose(): Promise<void> {
if (process.env.NODE_ENV === 'test') {
// XXX:
// Shutting down the existing connections causes errors on Jest as
// Misskey has asynchronous postgres/redis connections that are not
// awaited.
// Let's wait for some random time for them to finish.
await setTimeout(5000);
}
// Wait for all potential queue jobs
await allSettled();
// And then close all queues
await Promise.all([
this.systemQueue.close(),
this.endedPollNotificationQueue.close(),

View File

@@ -16,6 +16,7 @@ import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, Obj
import type { DbJobData, DeliverJobData, RelationshipJobData, ThinUser } from '../queue/types.js';
import type httpSignature from '@peertube/http-signature';
import type * as Bull from 'bullmq';
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
@Injectable()
export class QueueService {
@@ -74,11 +75,15 @@ export class QueueService {
if (content == null) return null;
if (to == null) return null;
const contentBody = JSON.stringify(content);
const digest = ApRequestCreator.createDigest(contentBody);
const data: DeliverJobData = {
user: {
id: user.id,
},
content,
content: contentBody,
digest,
to,
isSharedInbox,
};
@@ -103,6 +108,8 @@ export class QueueService {
@bindThis
public async deliverMany(user: ThinUser, content: IActivity | null, inboxes: Map<string, boolean>) {
if (content == null) return null;
const contentBody = JSON.stringify(content);
const digest = ApRequestCreator.createDigest(contentBody);
const opts = {
attempts: this.config.deliverJobMaxAttempts ?? 12,
@@ -117,7 +124,8 @@ export class QueueService {
name: d[0],
data: {
user,
content,
content: contentBody,
digest,
to: d[0],
isSharedInbox: d[1],
} as DeliverJobData,
@@ -174,6 +182,16 @@ export class QueueService {
});
}
@bindThis
public createExportClipsJob(user: ThinUser) {
return this.dbQueue.add('exportClips', {
user: { id: user.id },
}, {
removeOnComplete: true,
removeOnFail: true,
});
}
@bindThis
public createExportFavoritesJob(user: ThinUser) {
return this.dbQueue.add('exportFavorites', {

View File

@@ -28,6 +28,7 @@ import { UserBlockingService } from '@/core/UserBlockingService.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { RoleService } from '@/core/RoleService.js';
import { FeaturedService } from '@/core/FeaturedService.js';
import { trackPromise } from '@/misc/promise-tracker.js';
const FALLBACK = '❤';
const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
@@ -138,7 +139,7 @@ export class ReactionService {
reaction = reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`;
// センシティブ
if ((note.reactionAcceptance === 'nonSensitiveOnly') && emoji.isSensitive) {
if ((note.reactionAcceptance === 'nonSensitiveOnly' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && emoji.isSensitive) {
reaction = FALLBACK;
}
} else {
@@ -268,7 +269,7 @@ export class ReactionService {
}
}
dm.execute();
trackPromise(dm.execute());
}
//#endregion
}
@@ -316,7 +317,7 @@ export class ReactionService {
dm.addDirectRecipe(reactee as MiRemoteUser);
}
dm.addFollowersRecipe();
dm.execute();
trackPromise(dm.execute());
}
//#endregion
}

View File

@@ -0,0 +1,411 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import CRC32 from 'crc-32';
import { ModuleRef } from '@nestjs/core';
import * as Reversi from 'misskey-reversi';
import { IsNull } from 'typeorm';
import type {
MiReversiGame,
ReversiGamesRepository,
UsersRepository,
} from '@/models/_.js';
import type { MiUser } from '@/models/User.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js';
import { CacheService } from '@/core/CacheService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import type { GlobalEvents } from '@/core/GlobalEventService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { IdService } from '@/core/IdService.js';
import type { Packed } from '@/misc/json-schema.js';
import { NotificationService } from '@/core/NotificationService.js';
import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js';
import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
const MATCHING_TIMEOUT_MS = 1000 * 15; // 15sec
@Injectable()
export class ReversiService implements OnApplicationShutdown, OnModuleInit {
private notificationService: NotificationService;
constructor(
private moduleRef: ModuleRef,
@Inject(DI.redis)
private redisClient: Redis.Redis,
@Inject(DI.reversiGamesRepository)
private reversiGamesRepository: ReversiGamesRepository,
private cacheService: CacheService,
private userEntityService: UserEntityService,
private globalEventService: GlobalEventService,
private reversiGameEntityService: ReversiGameEntityService,
private idService: IdService,
) {
}
async onModuleInit() {
this.notificationService = this.moduleRef.get(NotificationService.name);
}
@bindThis
public async matchSpecificUser(me: MiUser, targetUser: MiUser): Promise<MiReversiGame | null> {
if (targetUser.id === me.id) {
throw new Error('You cannot match yourself.');
}
const invitations = await this.redisClient.zrange(
`reversi:matchSpecific:${me.id}`,
Date.now() - MATCHING_TIMEOUT_MS,
'+inf',
'BYSCORE');
if (invitations.includes(targetUser.id)) {
await this.redisClient.zrem(`reversi:matchSpecific:${me.id}`, targetUser.id);
const game = await this.reversiGamesRepository.insert({
id: this.idService.gen(),
user1Id: targetUser.id,
user2Id: me.id,
user1Ready: false,
user2Ready: false,
isStarted: false,
isEnded: false,
logs: [],
map: Reversi.maps.eighteight.data,
bw: 'random',
isLlotheo: false,
}).then(x => this.reversiGamesRepository.findOneByOrFail(x.identifiers[0]));
const packed = await this.reversiGameEntityService.packDetail(game, { id: targetUser.id });
this.globalEventService.publishReversiStream(targetUser.id, 'matched', { game: packed });
return game;
} else {
this.redisClient.zadd(`reversi:matchSpecific:${targetUser.id}`, Date.now(), me.id);
this.globalEventService.publishReversiStream(targetUser.id, 'invited', {
user: await this.userEntityService.pack(me, targetUser),
});
return null;
}
}
@bindThis
public async matchAnyUser(me: MiUser): Promise<MiReversiGame | null> {
//#region まず自分宛ての招待を探す
const invitations = await this.redisClient.zrange(
`reversi:matchSpecific:${me.id}`,
Date.now() - MATCHING_TIMEOUT_MS,
'+inf',
'BYSCORE');
if (invitations.length > 0) {
const invitorId = invitations[Math.floor(Math.random() * invitations.length)];
await this.redisClient.zrem(`reversi:matchSpecific:${me.id}`, invitorId);
const game = await this.reversiGamesRepository.insert({
id: this.idService.gen(),
user1Id: invitorId,
user2Id: me.id,
user1Ready: false,
user2Ready: false,
isStarted: false,
isEnded: false,
logs: [],
map: Reversi.maps.eighteight.data,
bw: 'random',
isLlotheo: false,
}).then(x => this.reversiGamesRepository.findOneByOrFail(x.identifiers[0]));
const packed = await this.reversiGameEntityService.packDetail(game, { id: invitorId });
this.globalEventService.publishReversiStream(invitorId, 'matched', { game: packed });
return game;
}
//#endregion
const matchings = await this.redisClient.zrange(
'reversi:matchAny',
Date.now() - MATCHING_TIMEOUT_MS,
'+inf',
'BYSCORE');
const userIds = matchings.filter(id => id !== me.id);
if (userIds.length > 0) {
// pick random
const matchedUserId = userIds[Math.floor(Math.random() * userIds.length)];
await this.redisClient.zrem('reversi:matchAny', me.id, matchedUserId);
const game = await this.reversiGamesRepository.insert({
id: this.idService.gen(),
user1Id: matchedUserId,
user2Id: me.id,
user1Ready: false,
user2Ready: false,
isStarted: false,
isEnded: false,
logs: [],
map: Reversi.maps.eighteight.data,
bw: 'random',
isLlotheo: false,
}).then(x => this.reversiGamesRepository.findOneByOrFail(x.identifiers[0]));
const packed = await this.reversiGameEntityService.packDetail(game, { id: matchedUserId });
this.globalEventService.publishReversiStream(matchedUserId, 'matched', { game: packed });
return game;
} else {
await this.redisClient.zadd('reversi:matchAny', Date.now(), me.id);
return null;
}
}
@bindThis
public async matchSpecificUserCancel(user: MiUser, targetUserId: MiUser['id']) {
await this.redisClient.zrem(`reversi:matchSpecific:${targetUserId}`, user.id);
}
@bindThis
public async matchAnyUserCancel(user: MiUser) {
await this.redisClient.zrem('reversi:matchAny', user.id);
}
@bindThis
public async gameReady(game: MiReversiGame, user: MiUser, ready: boolean) {
if (game.isStarted) return;
let isBothReady = false;
if (game.user1Id === user.id) {
await this.reversiGamesRepository.update(game.id, {
user1Ready: ready,
});
this.globalEventService.publishReversiGameStream(game.id, 'changeReadyStates', {
user1: ready,
user2: game.user2Ready,
});
if (ready && game.user2Ready) isBothReady = true;
} else if (game.user2Id === user.id) {
await this.reversiGamesRepository.update(game.id, {
user2Ready: ready,
});
this.globalEventService.publishReversiGameStream(game.id, 'changeReadyStates', {
user1: game.user1Ready,
user2: ready,
});
if (ready && game.user1Ready) isBothReady = true;
} else {
return;
}
if (isBothReady) {
// 3秒後、両者readyならゲーム開始
setTimeout(async () => {
const freshGame = await this.reversiGamesRepository.findOneBy({ id: game.id });
if (freshGame == null || freshGame.isStarted || freshGame.isEnded) return;
if (!freshGame.user1Ready || !freshGame.user2Ready) return;
let bw: number;
if (freshGame.bw === 'random') {
bw = Math.random() > 0.5 ? 1 : 2;
} else {
bw = parseInt(freshGame.bw, 10);
}
function getRandomMap() {
const mapCount = Object.entries(Reversi.maps).length;
const rnd = Math.floor(Math.random() * mapCount);
return Object.values(Reversi.maps)[rnd].data;
}
const map = freshGame.map != null ? freshGame.map : getRandomMap();
await this.reversiGamesRepository.update(game.id, {
startedAt: new Date(),
isStarted: true,
black: bw,
map: map,
});
//#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理
const o = new Reversi.Game(map, {
isLlotheo: freshGame.isLlotheo,
canPutEverywhere: freshGame.canPutEverywhere,
loopedBoard: freshGame.loopedBoard,
});
if (o.isEnded) {
let winner;
if (o.winner === true) {
winner = freshGame.black === 1 ? freshGame.user1Id : freshGame.user2Id;
} else if (o.winner === false) {
winner = freshGame.black === 1 ? freshGame.user2Id : freshGame.user1Id;
} else {
winner = null;
}
await this.reversiGamesRepository.update(game.id, {
isEnded: true,
winnerId: winner,
});
this.globalEventService.publishReversiGameStream(game.id, 'ended', {
winnerId: winner,
game: await this.reversiGameEntityService.packDetail(game.id, user),
});
}
//#endregion
this.globalEventService.publishReversiGameStream(game.id, 'started', {
game: await this.reversiGameEntityService.packDetail(game.id, user),
});
}, 3000);
}
}
@bindThis
public async getInvitations(user: MiUser): Promise<MiUser['id'][]> {
const invitations = await this.redisClient.zrange(
`reversi:matchSpecific:${user.id}`,
Date.now() - MATCHING_TIMEOUT_MS,
'+inf',
'BYSCORE');
return invitations;
}
@bindThis
public async updateSettings(game: MiReversiGame, user: MiUser, key: string, value: any) {
if (game.isStarted) return;
if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) return;
if ((game.user1Id === user.id) && game.user1Ready) return;
if ((game.user2Id === user.id) && game.user2Ready) return;
if (!['map', 'bw', 'isLlotheo', 'canPutEverywhere', 'loopedBoard'].includes(key)) return;
await this.reversiGamesRepository.update(game.id, {
[key]: value,
});
this.globalEventService.publishReversiGameStream(game.id, 'updateSettings', {
userId: user.id,
key: key,
value: value,
});
}
@bindThis
public async putStoneToGame(game: MiReversiGame, user: MiUser, pos: number) {
if (!game.isStarted) return;
if (game.isEnded) return;
if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) return;
const myColor =
((game.user1Id === user.id) && game.black === 1) || ((game.user2Id === user.id) && game.black === 2)
? true
: false;
const o = new Reversi.Game(game.map, {
isLlotheo: game.isLlotheo,
canPutEverywhere: game.canPutEverywhere,
loopedBoard: game.loopedBoard,
});
// 盤面の状態を再生
for (const log of game.logs) {
o.put(log.color, log.pos);
}
if (o.turn !== myColor) return;
if (!o.canPut(myColor, pos)) return;
o.put(myColor, pos);
let winner;
if (o.isEnded) {
if (o.winner === true) {
winner = game.black === 1 ? game.user1Id : game.user2Id;
} else if (o.winner === false) {
winner = game.black === 1 ? game.user2Id : game.user1Id;
} else {
winner = null;
}
}
const log = {
at: Date.now(),
color: myColor,
pos,
};
const crc32 = CRC32.str(game.logs.map(x => x.pos.toString()).join('') + pos.toString()).toString();
game.logs.push(log);
await this.reversiGamesRepository.update(game.id, {
crc32,
isEnded: o.isEnded,
winnerId: winner,
logs: game.logs,
});
this.globalEventService.publishReversiGameStream(game.id, 'putStone', {
...log,
next: o.turn,
});
if (o.isEnded) {
this.globalEventService.publishReversiGameStream(game.id, 'ended', {
winnerId: winner,
game: await this.reversiGameEntityService.packDetail(game.id, user),
});
}
}
@bindThis
public async surrender(game: MiReversiGame, user: MiUser) {
if (game.isEnded) return;
if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) return;
const winnerId = game.user1Id === user.id ? game.user2Id : game.user1Id;
await this.reversiGamesRepository.update(game.id, {
surrendered: user.id,
isEnded: true,
winnerId: winnerId,
});
this.globalEventService.publishReversiGameStream(game.id, 'ended', {
winnerId: winnerId,
game: await this.reversiGameEntityService.packDetail(game.id, user),
});
}
@bindThis
public async get(id: MiReversiGame['id']) {
return this.reversiGamesRepository.findOneBy({ id });
}
@bindThis
public dispose(): void {
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View File

@@ -3,8 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { Inject, Injectable, OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
import * as Redis from 'ioredis';
import { ModuleRef } from '@nestjs/core';
import type { UserListMembershipsRepository } from '@/models/_.js';
import type { MiUser } from '@/models/User.js';
import type { MiUserList } from '@/models/UserList.js';
@@ -21,12 +22,15 @@ import { RedisKVCache } from '@/misc/cache.js';
import { RoleService } from '@/core/RoleService.js';
@Injectable()
export class UserListService implements OnApplicationShutdown {
export class UserListService implements OnApplicationShutdown, OnModuleInit {
public static TooManyUsersError = class extends Error {};
public membersCache: RedisKVCache<Set<string>>;
private roleService: RoleService;
constructor(
private moduleRef: ModuleRef,
@Inject(DI.redis)
private redisClient: Redis.Redis,
@@ -38,7 +42,6 @@ export class UserListService implements OnApplicationShutdown {
private userEntityService: UserEntityService,
private idService: IdService,
private roleService: RoleService,
private globalEventService: GlobalEventService,
private proxyAccountService: ProxyAccountService,
private queueService: QueueService,
@@ -54,6 +57,10 @@ export class UserListService implements OnApplicationShutdown {
this.redisForSub.on('message', this.onMessage);
}
async onModuleInit() {
this.roleService = this.moduleRef.get(RoleService.name);
}
@bindThis
private async onMessage(_: string, data: string): Promise<void> {
const obj = JSON.parse(data);

View File

@@ -6,6 +6,7 @@
import { URL } from 'node:url';
import { toASCII } from 'punycode';
import { Inject, Injectable } from '@nestjs/common';
import RE2 from 're2';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { bindThis } from '@/decorators.js';
@@ -41,6 +42,33 @@ export class UtilityService {
return silencedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`));
}
@bindThis
public isSensitiveWordIncluded(text: string, sensitiveWords: string[]): boolean {
if (sensitiveWords.length === 0) return false;
if (text === '') return false;
const regexpregexp = /^\/(.+)\/(.*)$/;
const matched = sensitiveWords.some(filter => {
// represents RegExp
const regexp = filter.match(regexpregexp);
// This should never happen due to input sanitisation.
if (!regexp) {
const words = filter.split(' ');
return words.every(keyword => text.includes(keyword));
}
try {
// TODO: RE2インスタンスをキャッシュ
return new RE2(regexp[1], regexp[2]).test(text);
} catch (err) {
// This should never happen due to input sanitisation.
return false;
}
});
return matched;
}
@bindThis
public extractDbHost(uri: string): string {
const url = new URL(uri);

View File

@@ -58,7 +58,7 @@ export class ApAudienceService {
};
}
if (toGroups.followers.length > 0) {
if (toGroups.followers.length > 0 || ccGroups.followers.length > 0) {
return {
visibility: 'followers',
mentionedUsers,

View File

@@ -144,7 +144,7 @@ class DeliverManager {
}
// deliver
this.queueService.deliverMany(this.actor, this.activity, inboxes);
await this.queueService.deliverMany(this.actor, this.activity, inboxes);
}
}

View File

@@ -97,6 +97,8 @@ export class ApInboxService {
} catch (err) {
if (err instanceof Error || typeof err === 'string') {
this.logger.error(err);
} else {
throw err;
}
}
}
@@ -256,7 +258,7 @@ export class ApInboxService {
const targetUri = getApId(activity.object);
this.announceNote(actor, activity, targetUri);
await this.announceNote(actor, activity, targetUri);
}
@bindThis
@@ -288,7 +290,7 @@ export class ApInboxService {
} catch (err) {
// 対象が4xxならスキップ
if (err instanceof StatusError) {
if (err.isClientError) {
if (!err.isRetryable) {
this.logger.warn(`Ignored announce target ${targetUri} - ${err.statusCode}`);
return;
}
@@ -373,7 +375,7 @@ export class ApInboxService {
});
if (isPost(object)) {
this.createNote(resolver, actor, object, false, activity);
await this.createNote(resolver, actor, object, false, activity);
} else {
this.logger.warn(`Unknown type: ${getApType(object)}`);
}
@@ -404,7 +406,7 @@ export class ApInboxService {
await this.apNoteService.createNote(note, resolver, silent);
return 'ok';
} catch (err) {
if (err instanceof StatusError && err.isClientError) {
if (err instanceof StatusError && !err.isRetryable) {
return `skip ${err.statusCode}`;
} else {
throw err;

View File

@@ -34,9 +34,9 @@ type PrivateKey = {
};
export class ApRequestCreator {
static createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record<string, string> }): Signed {
static createSignedPost(args: { key: PrivateKey, url: string, body: string, digest?: string, additionalHeaders: Record<string, string> }): Signed {
const u = new URL(args.url);
const digestHeader = `SHA-256=${crypto.createHash('sha256').update(args.body).digest('base64')}`;
const digestHeader = args.digest ?? this.createDigest(args.body);
const request: Request = {
url: u.href,
@@ -59,6 +59,10 @@ export class ApRequestCreator {
};
}
static createDigest(body: string) {
return `SHA-256=${crypto.createHash('sha256').update(body).digest('base64')}`;
}
static createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record<string, string> }): Signed {
const u = new URL(args.url);
@@ -145,8 +149,8 @@ export class ApRequestService {
}
@bindThis
public async signedPost(user: { id: MiUser['id'] }, url: string, object: unknown): Promise<void> {
const body = JSON.stringify(object);
public async signedPost(user: { id: MiUser['id'] }, url: string, object: unknown, digest?: string): Promise<void> {
const body = typeof object === 'string' ? object : JSON.stringify(object);
const keypair = await this.userKeypairService.getUserKeypair(user.id);
@@ -157,6 +161,7 @@ export class ApRequestService {
},
url,
body,
digest,
additionalHeaders: {
},
});

View File

@@ -216,7 +216,7 @@ export class ApNoteService {
return { status: 'ok', res };
} catch (e) {
return {
status: (e instanceof StatusError && e.isClientError) ? 'permerror' : 'temperror',
status: (e instanceof StatusError && !e.isRetryable) ? 'permerror' : 'temperror',
};
}
};

View File

@@ -351,6 +351,7 @@ export class NoteEntityService implements OnModuleInit {
color: channel.color,
isSensitive: channel.isSensitive,
allowRenoteToExternal: channel.allowRenoteToExternal,
userId: channel.userId,
} : undefined,
mentions: note.mentions.length > 0 ? note.mentions : undefined,
uri: note.uri ?? undefined,

View File

@@ -0,0 +1,115 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { ReversiGamesRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/json-schema.js';
import type { } from '@/models/Blocking.js';
import type { MiUser } from '@/models/User.js';
import type { MiReversiGame } from '@/models/ReversiGame.js';
import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
export class ReversiGameEntityService {
constructor(
@Inject(DI.reversiGamesRepository)
private reversiGamesRepository: ReversiGamesRepository,
private userEntityService: UserEntityService,
private idService: IdService,
) {
}
@bindThis
public async packDetail(
src: MiReversiGame['id'] | MiReversiGame,
me?: { id: MiUser['id'] } | null | undefined,
): Promise<Packed<'ReversiGameDetailed'>> {
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
return await awaitAll({
id: game.id,
createdAt: this.idService.parse(game.id).date.toISOString(),
startedAt: game.startedAt && game.startedAt.toISOString(),
isStarted: game.isStarted,
isEnded: game.isEnded,
form1: game.form1,
form2: game.form2,
user1Ready: game.user1Ready,
user2Ready: game.user2Ready,
user1Id: game.user1Id,
user2Id: game.user2Id,
user1: this.userEntityService.pack(game.user1Id, me),
user2: this.userEntityService.pack(game.user2Id, me),
winnerId: game.winnerId,
winner: game.winnerId ? this.userEntityService.pack(game.winnerId, me) : null,
surrendered: game.surrendered,
black: game.black,
bw: game.bw,
isLlotheo: game.isLlotheo,
canPutEverywhere: game.canPutEverywhere,
loopedBoard: game.loopedBoard,
logs: game.logs.map(log => ({
at: log.at,
color: log.color,
pos: log.pos,
})),
map: game.map,
});
}
@bindThis
public packDetailMany(
xs: MiReversiGame[],
me?: { id: MiUser['id'] } | null | undefined,
) {
return Promise.all(xs.map(x => this.packDetail(x, me)));
}
@bindThis
public async packLite(
src: MiReversiGame['id'] | MiReversiGame,
me?: { id: MiUser['id'] } | null | undefined,
): Promise<Packed<'ReversiGameLite'>> {
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
return await awaitAll({
id: game.id,
createdAt: this.idService.parse(game.id).date.toISOString(),
startedAt: game.startedAt && game.startedAt.toISOString(),
isStarted: game.isStarted,
isEnded: game.isEnded,
form1: game.form1,
form2: game.form2,
user1Ready: game.user1Ready,
user2Ready: game.user2Ready,
user1Id: game.user1Id,
user2Id: game.user2Id,
user1: this.userEntityService.pack(game.user1Id, me),
user2: this.userEntityService.pack(game.user2Id, me),
winnerId: game.winnerId,
winner: game.winnerId ? this.userEntityService.pack(game.winnerId, me) : null,
surrendered: game.surrendered,
black: game.black,
bw: game.bw,
isLlotheo: game.isLlotheo,
canPutEverywhere: game.canPutEverywhere,
loopedBoard: game.loopedBoard,
});
}
@bindThis
public packLiteMany(
xs: MiReversiGame[],
me?: { id: MiUser['id'] } | null | undefined,
) {
return Promise.all(xs.map(x => this.packLite(x, me)));
}
}

View File

@@ -37,7 +37,7 @@ export class ServerStatsService implements OnApplicationShutdown {
const log = [] as any[];
ev.on('requestServerStatsLog', x => {
ev.emit(`serverStatsLog:${x.id}`, log.slice(0, x.length ?? 50));
ev.emit(`serverStatsLog:${x.id}`, log.slice(0, x.length));
});
const tick = async () => {

View File

@@ -78,5 +78,7 @@ export const DI = {
flashsRepository: Symbol('flashsRepository'),
flashLikesRepository: Symbol('flashLikesRepository'),
userMemosRepository: Symbol('userMemosRepository'),
bubbleGameRecordsRepository: Symbol('bubbleGameRecordsRepository'),
reversiGamesRepository: Symbol('reversiGamesRepository'),
//#endregion
};

View File

@@ -71,8 +71,11 @@ export default class Logger {
let log = `${l} ${worker}\t[${contexts.join(' ')}]\t${m}`;
if (envOption.withLogTime) log = chalk.gray(time) + ' ' + log;
console.log(important ? chalk.bold(log) : log);
if (level === 'error' && data) console.log(data);
const args: unknown[] = [important ? chalk.bold(log) : log];
if (data != null) {
args.push(data);
}
console.log(...args);
}
@bindThis

View File

@@ -1,40 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export const kinds = [
'read:account',
'write:account',
'read:blocks',
'write:blocks',
'read:drive',
'write:drive',
'read:favorites',
'write:favorites',
'read:following',
'write:following',
'read:messaging',
'write:messaging',
'read:mutes',
'write:mutes',
'write:notes',
'read:notifications',
'write:notifications',
'read:reactions',
'write:reactions',
'write:votes',
'read:pages',
'write:pages',
'write:page-likes',
'read:page-likes',
'read:user-groups',
'write:user-groups',
'read:channels',
'write:channels',
'read:gallery',
'write:gallery',
'read:gallery-likes',
'write:gallery-likes',
];
// IF YOU ADD KINDS(PERMISSIONS), YOU MUST ADD TRANSLATIONS (under _permissions).

View File

@@ -7,5 +7,6 @@ import type { MiNote } from '@/models/Note.js';
// eslint-disable-next-line import/no-default-export
export default function(note: MiNote): boolean {
// sync with NoteCreateService.isQuote
return note.renoteId != null && (note.text != null || note.hasPoll || (note.fileIds != null && note.fileIds.length > 0));
}

View File

@@ -38,6 +38,8 @@ import { packedFlashSchema } from '@/models/json-schema/flash.js';
import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
import { packedSigninSchema } from '@/models/json-schema/signin.js';
import { packedRoleLiteSchema, packedRoleSchema } from '@/models/json-schema/role.js';
import { packedAdSchema } from '@/models/json-schema/ad.js';
import { packedReversiGameLiteSchema, packedReversiGameDetailedSchema } from '@/models/json-schema/reversi-game.js';
export const refs = {
UserLite: packedUserLiteSchema,
@@ -49,6 +51,7 @@ export const refs = {
User: packedUserSchema,
UserList: packedUserListSchema,
Ad: packedAdSchema,
Announcement: packedAnnouncementSchema,
App: packedAppSchema,
Note: packedNoteSchema,
@@ -76,6 +79,8 @@ export const refs = {
Signin: packedSigninSchema,
RoleLite: packedRoleLiteSchema,
Role: packedRoleSchema,
ReversiGameLite: packedReversiGameLiteSchema,
ReversiGameDetailed: packedReversiGameDetailedSchema,
};
export type Packed<x extends keyof typeof refs> = SchemaType<typeof refs[x]>;

View File

@@ -0,0 +1,23 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
const promiseRefs: Set<WeakRef<Promise<unknown>>> = new Set();
/**
* This tracks promises that other modules decided not to wait for,
* and makes sure they are all settled before fully closing down the server.
*/
export function trackPromise(promise: Promise<unknown>) {
if (process.env.NODE_ENV !== 'test') {
return;
}
const ref = new WeakRef(promise);
promiseRefs.add(ref);
promise.finally(() => promiseRefs.delete(ref));
}
export async function allSettled(): Promise<void> {
await Promise.allSettled([...promiseRefs].map(r => r.deref()));
}

View File

@@ -7,6 +7,7 @@ export class StatusError extends Error {
public statusCode: number;
public statusMessage?: string;
public isClientError: boolean;
public isRetryable: boolean;
constructor(message: string, statusCode: number, statusMessage?: string) {
super(message);
@@ -14,5 +15,6 @@ export class StatusError extends Error {
this.statusCode = statusCode;
this.statusMessage = statusMessage;
this.isClientError = typeof this.statusCode === 'number' && this.statusCode >= 400 && this.statusCode < 500;
this.isRetryable = !this.isClientError || this.statusCode === 429;
}
}

View File

@@ -0,0 +1,57 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
import { id } from './util/id.js';
import { MiUser } from './User.js';
@Entity('bubble_game_record')
export class MiBubbleGameRecord {
@PrimaryColumn(id())
public id: string;
@Index()
@Column({
...id(),
})
public userId: MiUser['id'];
@ManyToOne(type => MiUser, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user: MiUser | null;
@Index()
@Column('timestamp with time zone')
public seededAt: Date;
@Column('varchar', {
length: 1024,
})
public seed: string;
@Column('integer')
public gameVersion: number;
@Column('varchar', {
length: 128,
})
public gameMode: string;
@Index()
@Column('integer')
public score: number;
@Column('jsonb', {
default: [],
})
public logs: any[];
@Column('boolean', {
default: false,
})
public isVerified: boolean;
}

View File

@@ -191,6 +191,29 @@ export class MiMeta {
})
public hcaptchaSecretKey: string | null;
@Column('boolean', {
default: false,
})
public enableMcaptcha: boolean;
@Column('varchar', {
length: 1024,
nullable: true,
})
public mcaptchaSitekey: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public mcaptchaSecretKey: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public mcaptchaInstanceUrl: string | null;
@Column('boolean', {
default: false,
})
@@ -457,6 +480,23 @@ export class MiMeta {
})
public verifymailAuthKey: string | null;
@Column('boolean', {
default: false,
})
public enableTruemailApi: boolean;
@Column('varchar', {
length: 1024,
nullable: true,
})
public truemailInstance: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public truemailAuthKey: string | null;
@Column('boolean', {
default: true,
})
@@ -495,6 +535,13 @@ export class MiMeta {
})
public manifestJsonOverride: string;
@Column('varchar', {
length: 1024,
array: true,
default: '{}',
})
public bannedEmailDomains: string[];
@Column('varchar', {
length: 1024, array: true, default: '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }',
})

View File

@@ -11,9 +11,6 @@ import { MiChannel } from './Channel.js';
import type { MiDriveFile } from './DriveFile.js';
@Entity('note')
@Index('IDX_NOTE_TAGS', { synchronize: false })
@Index('IDX_NOTE_MENTIONS', { synchronize: false })
@Index('IDX_NOTE_VISIBLE_USER_IDS', { synchronize: false })
export class MiNote {
@PrimaryColumn(id())
public id: string;
@@ -133,7 +130,7 @@ export class MiNote {
})
public url: string | null;
@Index()
@Index('IDX_NOTE_FILE_IDS', { synchronize: false })
@Column({
...id(),
array: true, default: '{}',
@@ -145,14 +142,14 @@ export class MiNote {
})
public attachedFileTypes: string[];
@Index()
@Index('IDX_NOTE_VISIBLE_USER_IDS', { synchronize: false })
@Column({
...id(),
array: true, default: '{}',
})
public visibleUserIds: MiUser['id'][];
@Index()
@Index('IDX_NOTE_MENTIONS', { synchronize: false })
@Column({
...id(),
array: true, default: '{}',
@@ -174,7 +171,7 @@ export class MiNote {
})
public emojis: string[];
@Index()
@Index('IDX_NOTE_TAGS', { synchronize: false })
@Column('varchar', {
length: 128, array: true, default: '{}',
})

View File

@@ -5,7 +5,7 @@
import { Module } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiAvatarDecoration, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook } from './_.js';
import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiAvatarDecoration, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook, MiBubbleGameRecord, MiReversiGame } from './_.js';
import type { DataSource } from 'typeorm';
import type { Provider } from '@nestjs/common';
@@ -399,6 +399,18 @@ const $userMemosRepository: Provider = {
inject: [DI.db],
};
const $bubbleGameRecordsRepository: Provider = {
provide: DI.bubbleGameRecordsRepository,
useFactory: (db: DataSource) => db.getRepository(MiBubbleGameRecord),
inject: [DI.db],
};
const $reversiGamesRepository: Provider = {
provide: DI.reversiGamesRepository,
useFactory: (db: DataSource) => db.getRepository(MiReversiGame),
inject: [DI.db],
};
@Module({
imports: [
],
@@ -468,6 +480,8 @@ const $userMemosRepository: Provider = {
$flashsRepository,
$flashLikesRepository,
$userMemosRepository,
$bubbleGameRecordsRepository,
$reversiGamesRepository,
],
exports: [
$usersRepository,
@@ -535,6 +549,8 @@ const $userMemosRepository: Provider = {
$flashsRepository,
$flashLikesRepository,
$userMemosRepository,
$bubbleGameRecordsRepository,
$reversiGamesRepository,
],
})
export class RepositoryModule {}

View File

@@ -0,0 +1,127 @@
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
import { id } from './util/id.js';
import { MiUser } from './User.js';
@Entity('reversi_game')
export class MiReversiGame {
@PrimaryColumn(id())
public id: string;
@Column('timestamp with time zone', {
nullable: true,
comment: 'The started date of the ReversiGame.',
})
public startedAt: Date | null;
@Column(id())
public user1Id: MiUser['id'];
@ManyToOne(type => MiUser, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user1: MiUser | null;
@Column(id())
public user2Id: MiUser['id'];
@ManyToOne(type => MiUser, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user2: MiUser | null;
@Column('boolean', {
default: false,
})
public user1Ready: boolean;
@Column('boolean', {
default: false,
})
public user2Ready: boolean;
/**
* どちらのプレイヤーが先行(黒)か
* 1 ... user1
* 2 ... user2
*/
@Column('integer', {
nullable: true,
})
public black: number | null;
@Column('boolean', {
default: false,
})
public isStarted: boolean;
@Column('boolean', {
default: false,
})
public isEnded: boolean;
@Column({
...id(),
nullable: true,
})
public winnerId: MiUser['id'] | null;
@Column({
...id(),
nullable: true,
})
public surrendered: MiUser['id'] | null;
@Column('jsonb', {
default: [],
})
public logs: {
at: number;
color: boolean;
pos: number;
}[];
@Column('varchar', {
array: true, length: 64,
})
public map: string[];
@Column('varchar', {
length: 32,
})
public bw: string;
@Column('boolean', {
default: false,
})
public isLlotheo: boolean;
@Column('boolean', {
default: false,
})
public canPutEverywhere: boolean;
@Column('boolean', {
default: false,
})
public loopedBoard: boolean;
@Column('jsonb', {
nullable: true, default: null,
})
public form1: any | null;
@Column('jsonb', {
nullable: true, default: null,
})
public form2: any | null;
/**
* ログのposを文字列としてすべて連結したもののCRC32値
*/
@Column('varchar', {
length: 32, nullable: true,
})
public crc32: string | null;
}

View File

@@ -68,6 +68,9 @@ import { MiRoleAssignment } from '@/models/RoleAssignment.js';
import { MiFlash } from '@/models/Flash.js';
import { MiFlashLike } from '@/models/FlashLike.js';
import { MiUserListFavorite } from '@/models/UserListFavorite.js';
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
import { MiReversiGame } from '@/models/ReversiGame.js';
import type { Repository } from 'typeorm';
export {
@@ -136,6 +139,8 @@ export {
MiFlash,
MiFlashLike,
MiUserMemo,
MiBubbleGameRecord,
MiReversiGame,
};
export type AbuseUserReportsRepository = Repository<MiAbuseUserReport>;
@@ -203,3 +208,5 @@ export type RoleAssignmentsRepository = Repository<MiRoleAssignment>;
export type FlashsRepository = Repository<MiFlash>;
export type FlashLikesRepository = Repository<MiFlashLike>;
export type UserMemoRepository = Repository<MiUserMemo>;
export type BubbleGameRecordsRepository = Repository<MiBubbleGameRecord>;
export type ReversiGamesRepository = Repository<MiReversiGame>;

View File

@@ -0,0 +1,64 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export const packedAdSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false,
nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
expiresAt: {
type: 'string',
optional: false,
nullable: false,
format: 'date-time',
},
startsAt: {
type: 'string',
optional: false,
nullable: false,
format: 'date-time',
},
place: {
type: 'string',
optional: false,
nullable: false,
},
priority: {
type: 'string',
optional: false,
nullable: false,
},
ratio: {
type: 'number',
optional: false,
nullable: false,
},
url: {
type: 'string',
optional: false,
nullable: false,
},
imageUrl: {
type: 'string',
optional: false,
nullable: false,
},
memo: {
type: 'string',
optional: false,
nullable: false,
},
dayOfWeek: {
type: 'integer',
optional: false,
nullable: false,
},
},
} as const;

View File

@@ -148,6 +148,10 @@ export const packedNoteSchema = {
type: 'boolean',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: true,
},
},
},
localOnly: {

View File

@@ -0,0 +1,234 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export const packedReversiGameLiteSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
startedAt: {
type: 'string',
optional: false, nullable: true,
format: 'date-time',
},
isStarted: {
type: 'boolean',
optional: false, nullable: false,
},
isEnded: {
type: 'boolean',
optional: false, nullable: false,
},
form1: {
type: 'any',
optional: false, nullable: true,
},
form2: {
type: 'any',
optional: false, nullable: true,
},
user1Ready: {
type: 'boolean',
optional: false, nullable: false,
},
user2Ready: {
type: 'boolean',
optional: false, nullable: false,
},
user1Id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user2Id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user1: {
type: 'object',
optional: false, nullable: false,
ref: 'User',
},
user2: {
type: 'object',
optional: false, nullable: false,
ref: 'User',
},
winnerId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
},
winner: {
type: 'object',
optional: false, nullable: true,
ref: 'User',
},
surrendered: {
type: 'string',
optional: false, nullable: true,
format: 'id',
},
black: {
type: 'number',
optional: false, nullable: true,
},
bw: {
type: 'string',
optional: false, nullable: false,
},
isLlotheo: {
type: 'boolean',
optional: false, nullable: false,
},
canPutEverywhere: {
type: 'boolean',
optional: false, nullable: false,
},
loopedBoard: {
type: 'boolean',
optional: false, nullable: false,
},
},
} as const;
export const packedReversiGameDetailedSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
startedAt: {
type: 'string',
optional: false, nullable: true,
format: 'date-time',
},
isStarted: {
type: 'boolean',
optional: false, nullable: false,
},
isEnded: {
type: 'boolean',
optional: false, nullable: false,
},
form1: {
type: 'any',
optional: false, nullable: true,
},
form2: {
type: 'any',
optional: false, nullable: true,
},
user1Ready: {
type: 'boolean',
optional: false, nullable: false,
},
user2Ready: {
type: 'boolean',
optional: false, nullable: false,
},
user1Id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user2Id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user1: {
type: 'object',
optional: false, nullable: false,
ref: 'User',
},
user2: {
type: 'object',
optional: false, nullable: false,
ref: 'User',
},
winnerId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
},
winner: {
type: 'object',
optional: false, nullable: true,
ref: 'User',
},
surrendered: {
type: 'string',
optional: false, nullable: true,
format: 'id',
},
black: {
type: 'number',
optional: false, nullable: true,
},
bw: {
type: 'string',
optional: false, nullable: false,
},
isLlotheo: {
type: 'boolean',
optional: false, nullable: false,
},
canPutEverywhere: {
type: 'boolean',
optional: false, nullable: false,
},
loopedBoard: {
type: 'boolean',
optional: false, nullable: false,
},
logs: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
properties: {
at: {
type: 'number',
optional: false, nullable: false,
},
color: {
type: 'boolean',
optional: false, nullable: false,
},
pos: {
type: 'number',
optional: false, nullable: false,
},
},
},
},
map: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
},
},
},
} as const;

View File

@@ -76,6 +76,8 @@ import { MiRoleAssignment } from '@/models/RoleAssignment.js';
import { MiFlash } from '@/models/Flash.js';
import { MiFlashLike } from '@/models/FlashLike.js';
import { MiUserMemo } from '@/models/UserMemo.js';
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
import { MiReversiGame } from '@/models/ReversiGame.js';
import { Config } from '@/config.js';
import MisskeyLogger from '@/logger.js';
@@ -190,6 +192,8 @@ export const entities = [
MiFlash,
MiFlashLike,
MiUserMemo,
MiBubbleGameRecord,
MiReversiGame,
...charts,
];

View File

@@ -24,6 +24,7 @@ import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmo
import { ExportFollowingProcessorService } from './processors/ExportFollowingProcessorService.js';
import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js';
import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js';
import { ExportClipsProcessorService } from './processors/ExportClipsProcessorService.js';
import { ExportUserListsProcessorService } from './processors/ExportUserListsProcessorService.js';
import { ExportAntennasProcessorService } from './processors/ExportAntennasProcessorService.js';
import { ImportBlockingProcessorService } from './processors/ImportBlockingProcessorService.js';
@@ -53,6 +54,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
DeleteDriveFilesProcessorService,
ExportCustomEmojisProcessorService,
ExportNotesProcessorService,
ExportClipsProcessorService,
ExportFavoritesProcessorService,
ExportFollowingProcessorService,
ExportMutingProcessorService,

View File

@@ -16,6 +16,7 @@ import { InboxProcessorService } from './processors/InboxProcessorService.js';
import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js';
import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmojisProcessorService.js';
import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js';
import { ExportClipsProcessorService } from './processors/ExportClipsProcessorService.js';
import { ExportFollowingProcessorService } from './processors/ExportFollowingProcessorService.js';
import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js';
import { ExportBlockingProcessorService } from './processors/ExportBlockingProcessorService.js';
@@ -91,6 +92,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
private deleteDriveFilesProcessorService: DeleteDriveFilesProcessorService,
private exportCustomEmojisProcessorService: ExportCustomEmojisProcessorService,
private exportNotesProcessorService: ExportNotesProcessorService,
private exportClipsProcessorService: ExportClipsProcessorService,
private exportFavoritesProcessorService: ExportFavoritesProcessorService,
private exportFollowingProcessorService: ExportFollowingProcessorService,
private exportMutingProcessorService: ExportMutingProcessorService,
@@ -164,6 +166,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
case 'deleteDriveFiles': return this.deleteDriveFilesProcessorService.process(job);
case 'exportCustomEmojis': return this.exportCustomEmojisProcessorService.process(job);
case 'exportNotes': return this.exportNotesProcessorService.process(job);
case 'exportClips': return this.exportClipsProcessorService.process(job);
case 'exportFavorites': return this.exportFavoritesProcessorService.process(job);
case 'exportFollowing': return this.exportFollowingProcessorService.process(job);
case 'exportMuting': return this.exportMutingProcessorService.process(job);

View File

@@ -72,7 +72,7 @@ export class DeliverProcessorService {
}
try {
await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content);
await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content, job.data.digest);
// Update stats
this.federatedInstanceService.fetch(host).then(i => {
@@ -111,7 +111,7 @@ export class DeliverProcessorService {
if (res instanceof StatusError) {
// 4xx
if (res.isClientError) {
if (!res.isRetryable) {
// 相手が閉鎖していることを明示しているため、配送停止する
if (job.data.isSharedInbox && res.statusCode === 410) {
this.federatedInstanceService.fetch(host).then(i => {

View File

@@ -0,0 +1,206 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as fs from 'node:fs';
import { Writable } from 'node:stream';
import { Inject, Injectable, StreamableFile } from '@nestjs/common';
import { MoreThan } from 'typeorm';
import { format as dateFormat } from 'date-fns';
import { DI } from '@/di-symbols.js';
import type { ClipNotesRepository, ClipsRepository, MiClip, MiClipNote, MiUser, NotesRepository, PollsRepository, UsersRepository } from '@/models/_.js';
import type Logger from '@/logger.js';
import { DriveService } from '@/core/DriveService.js';
import { createTemp } from '@/misc/create-temp.js';
import type { MiPoll } from '@/models/Poll.js';
import type { MiNote } from '@/models/Note.js';
import { bindThis } from '@/decorators.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import { Packed } from '@/misc/json-schema.js';
import { IdService } from '@/core/IdService.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
import type { DbJobDataWithUser } from '../types.js';
@Injectable()
export class ExportClipsProcessorService {
private logger: Logger;
constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.pollsRepository)
private pollsRepository: PollsRepository,
@Inject(DI.clipsRepository)
private clipsRepository: ClipsRepository,
@Inject(DI.clipNotesRepository)
private clipNotesRepository: ClipNotesRepository,
private driveService: DriveService,
private queueLoggerService: QueueLoggerService,
private idService: IdService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('export-clips');
}
@bindThis
public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> {
this.logger.info(`Exporting clips of ${job.data.user.id} ...`);
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
if (user == null) {
return;
}
// Create temp file
const [path, cleanup] = await createTemp();
this.logger.info(`Temp file is ${path}`);
try {
const stream = Writable.toWeb(fs.createWriteStream(path, { flags: 'a' }));
const writer = stream.getWriter();
writer.closed.catch(this.logger.error);
await writer.write('[');
await this.processClips(writer, user, job);
await writer.write(']');
await writer.close();
this.logger.succ(`Exported to: ${path}`);
const fileName = 'clips-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json';
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
this.logger.succ(`Exported to: ${driveFile.id}`);
} finally {
cleanup();
}
}
async processClips(writer: WritableStreamDefaultWriter, user: MiUser, job: Bull.Job<DbJobDataWithUser>) {
let exportedClipsCount = 0;
let cursor: MiClip['id'] | null = null;
while (true) {
const clips = await this.clipsRepository.find({
where: {
userId: user.id,
...(cursor ? { id: MoreThan(cursor) } : {}),
},
take: 100,
order: {
id: 1,
},
});
if (clips.length === 0) {
job.updateProgress(100);
break;
}
cursor = clips.at(-1)?.id ?? null;
for (const clip of clips) {
// Stringify but remove the last `]}`
const content = JSON.stringify(this.serializeClip(clip)).slice(0, -2);
const isFirst = exportedClipsCount === 0;
await writer.write(isFirst ? content : ',\n' + content);
await this.processClipNotes(writer, clip.id);
await writer.write(']}');
exportedClipsCount++;
}
const total = await this.clipsRepository.countBy({
userId: user.id,
});
job.updateProgress(exportedClipsCount / total);
}
}
async processClipNotes(writer: WritableStreamDefaultWriter, clipId: string): Promise<void> {
let exportedClipNotesCount = 0;
let cursor: MiClipNote['id'] | null = null;
while (true) {
const clipNotes = await this.clipNotesRepository.find({
where: {
clipId,
...(cursor ? { id: MoreThan(cursor) } : {}),
},
take: 100,
order: {
id: 1,
},
relations: ['note', 'note.user'],
}) as (MiClipNote & { note: MiNote & { user: MiUser } })[];
if (clipNotes.length === 0) {
break;
}
cursor = clipNotes.at(-1)?.id ?? null;
for (const clipNote of clipNotes) {
let poll: MiPoll | undefined;
if (clipNote.note.hasPoll) {
poll = await this.pollsRepository.findOneByOrFail({ noteId: clipNote.note.id });
}
const content = JSON.stringify(this.serializeClipNote(clipNote, poll));
const isFirst = exportedClipNotesCount === 0;
await writer.write(isFirst ? content : ',\n' + content);
exportedClipNotesCount++;
}
}
}
private serializeClip(clip: MiClip): Record<string, unknown> {
return {
id: clip.id,
name: clip.name,
description: clip.description,
lastClippedAt: clip.lastClippedAt?.toISOString(),
clipNotes: [],
};
}
private serializeClipNote(clip: MiClipNote & { note: MiNote & { user: MiUser } }, poll: MiPoll | undefined): Record<string, unknown> {
return {
id: clip.id,
createdAt: this.idService.parse(clip.id).date.toISOString(),
note: {
id: clip.note.id,
text: clip.note.text,
createdAt: this.idService.parse(clip.note.id).date.toISOString(),
fileIds: clip.note.fileIds,
replyId: clip.note.replyId,
renoteId: clip.note.renoteId,
poll: poll,
cw: clip.note.cw,
visibility: clip.note.visibility,
visibleUserIds: clip.note.visibleUserIds,
localOnly: clip.note.localOnly,
reactionAcceptance: clip.note.reactionAcceptance,
uri: clip.note.uri,
url: clip.note.url,
user: {
id: clip.note.user.id,
name: clip.note.user.name,
username: clip.note.user.username,
host: clip.note.user.host,
uri: clip.note.user.uri,
},
},
};
}
}

View File

@@ -85,7 +85,7 @@ export class InboxProcessorService {
} catch (err) {
// 対象が4xxならスキップ
if (err instanceof StatusError) {
if (err.isClientError) {
if (!err.isRetryable) {
throw new Bull.UnrecoverableError(`skip: Ignored deleted actors on both ends ${activity.actor} - ${err.statusCode}`);
}
throw new Error(`Error in actor ${activity.actor} - ${err.statusCode}`);

View File

@@ -71,7 +71,7 @@ export class WebhookDeliverProcessorService {
if (res instanceof StatusError) {
// 4xx
if (res.isClientError) {
if (!res.isRetryable) {
throw new Bull.UnrecoverableError(`${res.statusCode} ${res.statusMessage}`);
}

View File

@@ -15,7 +15,9 @@ export type DeliverJobData = {
/** Actor */
user: ThinUser;
/** Activity */
content: unknown;
content: string;
/** Digest header */
digest: string;
/** inbox URL to deliver */
to: string;
/** whether it is sharedInbox */

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