Compare commits

..

218 Commits

Author SHA1 Message Date
syuilo
ff56511638 13.13.0-beta.7 2023-06-01 17:20:25 +09:00
Acid Chicken (硫酸鶏)
337dd97b49 perf(#10923): CSS Modules のクラス名をインライン化する (#10930)
* perf(#10923): unwind css module class name

* perf(#10923): support multiple components

* refactor: clean up

* refactor(#10923): avoid `useCssModule()`

* fix(#10923): allow direct literal class name

* fix(#10923): avoid computed class name

* fix(#10923): allow literal keys

* fix(#10923): typo

* fix(#10923): invalid class names

* chore: test

* revert: test

This reverts commit 5c7ef366ec.

* fix(#10923): hidden tale

* perf(#10923): also unwind scoped css contained components

* perf(#10923): `normalizeClass` AOT compilation

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-06-01 17:19:46 +09:00
syuilo
146e89edd0 Update index.d.ts 2023-06-01 17:19:11 +09:00
syuilo
cd82748889 enhance(frontend): 追加の絵文字用辞書をダウンロードできるように
Resolve #10921
2023-06-01 17:10:53 +09:00
syuilo
2c0b10b0ee refactor 2023-06-01 16:28:24 +09:00
syuilo
6c09361ec6 🎨 2023-06-01 13:50:13 +09:00
SASAGAWA Kiyoshi
3089a86c8e fix: テーマにプロパティ 'fgOnWhite' を追加してフォローボタンのスタイルを調整 (#10931)
* fix: add theme property 'fgOnWhite' and fix styles of follow button.

* fix: add theme property 'fgOnWhite' and fix styles of follow button.
2023-06-01 13:29:44 +09:00
syuilo
ec2f05d4f7 fix(backend): i/notificationsのsinceIdが機能しない問題を修正
Fix #10902
2023-06-01 13:28:43 +09:00
syuilo
31a8129cb9 New translations ja-JP.yml (Korean) (#10933) 2023-06-01 13:20:43 +09:00
syuilo
8cc6c2c864 New Crowdin updates (#10929)
* New translations ja-JP.yml (Korean)

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

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

* New translations ja-JP.yml (Norwegian)
2023-06-01 09:29:29 +09:00
syuilo
a4de927df8 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-06-01 09:20:39 +09:00
syuilo
a2c77a0944 Update style.scss 2023-06-01 09:20:37 +09:00
syuilo
d8a564c6a0 🎨 2023-06-01 08:54:37 +09:00
syuilo
ff94b64c91 🎨 2023-06-01 07:51:02 +09:00
tamaina
1cc616b86c fix(frontend): disconnect ResizeObserver 2023-05-31 16:04:01 +00:00
syuilo
6addf9002c tweak ui 2023-05-31 18:03:43 +09:00
mappi
6dd219b6c7 fix: Firefoxにおける絵文字ピッカーのTabキーフォーカス問題の修正 (#10926)
* fix 10744

* fix 10744

* Update CHANGELOG.md

* add comment
2023-05-31 14:03:54 +09:00
syuilo
a535142e82 13.13.0-beta.6 2023-05-31 13:43:36 +09:00
syuilo
9521519cb8 reafactor 2023-05-31 13:41:38 +09:00
syuilo
e11f82c300 refactor 2023-05-31 13:27:59 +09:00
kabo2468
aba0755880 enhance(client): MFMのx2, scale, positionが含まれていたらノートをたたむようにした (#10165)
* enhance(client): MFMのx2, scale, positionが含まれていたらノートをたたむようにした

* Update CHANGELOG.md

---------

Co-authored-by: tamaina <tamaina@hotmail.co.jp>
2023-05-31 12:57:40 +09:00
syuilo
821bb1c476 perf(frontend): サーバーにカスタム絵文字の種類が多い場合のパフォーマンスの改善
Resolve #10925
2023-05-31 12:42:24 +09:00
syuilo
14da0a65f7 tweak ui 2023-05-31 12:24:00 +09:00
syuilo
8e5d31eb5c New Crowdin updates (#10918)
* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Norwegian)

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

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Norwegian)
2023-05-31 09:06:29 +09:00
tamaina
f8f3304164 fix(frontend/MkNoteDetailed): fix css module 2023-05-30 16:24:45 +00:00
syuilo
d7efdd7123 perf(frontend): minify file names
Resolve #10924
2023-05-30 18:55:22 +09:00
syuilo
4256e20a7f 13.13.0-beta.5 2023-05-30 17:39:37 +09:00
syuilo
f2d9e3105d fix(frontend): Pagesのコンテンツが表示されない・作成や編集ができない問題を修正
Fix #10908
2023-05-30 17:37:38 +09:00
syuilo
a6cd97ca9a fix(backend): 「アクセストークンの管理」画面でアプリの情報が表示されない問題を修正
Fix #10920
2023-05-30 17:27:47 +09:00
syuilo
a4a171781b clean up 2023-05-30 15:23:21 +09:00
syuilo
8e8e3dbe4e refactor 2023-05-30 15:18:08 +09:00
syuilo
de71845b35 🎨 2023-05-30 14:38:13 +09:00
syuilo
a3a47b8d35 🎨 2023-05-30 14:34:55 +09:00
syuilo
f091007882 refactor 2023-05-30 12:16:39 +09:00
syuilo
8f14b42e81 perf: インスタンスデフォルトテーマを予めjson5 -> jsonに変換しておくことでjson5を初期バンドルに含めずに済むように 2023-05-30 11:51:29 +09:00
syuilo
8b33a70fc4 vue-plyrやめるなど
- 問題点が多い
- バンドルサイズ削減
2023-05-30 11:32:29 +09:00
syuilo
dc031b1d07 perf(frontend): emojilistのサイズ削減 2023-05-30 11:18:40 +09:00
syuilo
f551b6d15e Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-05-30 10:21:08 +09:00
syuilo
0a2ac58b82 refactor 2023-05-30 10:20:06 +09:00
tamaina
3c07d3fc08 use pnpm@8.6.0 2023-05-29 13:05:43 +00:00
syuilo
c6ea7f754c New Crowdin updates (#10917)
* New translations ja-JP.yml (German)

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

* New translations ja-JP.yml (English)
2023-05-29 20:08:31 +09:00
syuilo
101e8d7adb refactor 2023-05-29 20:05:46 +09:00
syuilo
19b96ff650 refactor 2023-05-29 19:56:17 +09:00
syuilo
bd66a3f148 🎨 2023-05-29 19:37:03 +09:00
syuilo
7f235275c9 🎨 2023-05-29 19:35:11 +09:00
syuilo
3097bb6c7d refactor 2023-05-29 18:22:15 +09:00
syuilo
f469f555e8 New Crowdin updates (#10916)
* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Russian)

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

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

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (Thai)

* New translations ja-JP.yml (Japanese, Kansai)
2023-05-29 17:53:05 +09:00
syuilo
115167e6da update deps 2023-05-29 17:52:55 +09:00
syuilo
a1200ad886 lint 2023-05-29 17:24:46 +09:00
syuilo
25d1b66ca4 refactor 2023-05-29 17:22:09 +09:00
syuilo
c5f9b1c224 lint 2023-05-29 17:13:45 +09:00
syuilo
8c66fad96b lint 2023-05-29 17:13:12 +09:00
syuilo
afa4cd9112 13.13.0-beta.4 2023-05-29 13:34:55 +09:00
syuilo
f930eaee02 perf(backend): use websockets/ws instead of theturtle32/WebSocket-Node (#10884)
* perf(backend): use websockets/ws instead of theturtle32/WebSocket-Node

Resolve #10883

* refactor

* Update StreamingApiServerService.ts

* Update StreamingApiServerService.ts

* ✌️

* Update StreamingApiServerService.ts

* fix main stream init

* fix timing 2

* setIntervalの重複を避ける(気休め)

* add comment

* ✌️

---------

Co-authored-by: tamaina <tamaina@hotmail.co.jp>
2023-05-29 13:32:19 +09:00
syuilo
b35b9bc27f Update QueueProcessorService.ts 2023-05-29 13:30:57 +09:00
syuilo
4790ddfad6 refactor of QueueProcessorService 2023-05-29 13:30:26 +09:00
syuilo
b6f21b6edb refactor 2023-05-29 13:21:26 +09:00
syuilo
fd7b77c542 enhance(backend): migrate bull to bullmq (#10910)
* wip

* wip

* Update QueueService.ts

* wip

* refactor

* ✌️

* fix

* Update QueueStatsService.ts

* refactor

* Update ApNoteService.ts

* Update mock-resolver.ts

* refactor

* Update mock-resolver.ts
2023-05-29 11:54:49 +09:00
tamaina
7cbd852fe5 pnpm devでCtrl+Cで終了させてもプロセスが完全に殺せないのを修正 (#10914) 2023-05-29 06:37:13 +09:00
tamaina
a80003cde5 fix(frontend): Zen UIで、デッキ設定で直接/以外を表示したときデッキに戻るボタンを表示 (#10909)
* fix(frontend): デッキ設定で直接/以外を表示したときのZen UIでデッキに戻るボタン

* fix style

* ?zenが指定されていた場合はボタンを表示しない
2023-05-28 20:58:39 +09:00
syuilo
fb54c58a66 🎨 2023-05-27 12:09:19 +09:00
syuilo
3a924f3dc6 refactor 2023-05-27 11:44:04 +09:00
syuilo
11d22c7b73 refactor 2023-05-27 11:38:08 +09:00
syuilo
a879607479 refactor 2023-05-27 11:35:26 +09:00
Chocolate Pie
98aef974df enhance: ハッシュタグのノート一覧ページから、そのハッシュタグで投稿するボタンを追加、お知らせの画像URLを空にできない問題を修正 (#10878)
* fix: お知らせの画像URLを空にできない問題を修正 (misskey-dev/misskey#10657)

* ハッシュタグのノート一覧ページからノートできるように(misskey-dev/misskey#10854)

* fix: 色々直した

* location.reloadを使わないように

* CHANGELOGを編集

* tweak

* Update tag.vue

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-05-26 14:47:31 +09:00
NoriDev
cf46816687 feat: アカウント初期設定ウィザードに戻るボタンと、後で進むボタンを追加 (#10893)
* enhance(frontend): アカウント初期設定ウィザードに戻るボタンを追加

* enhance(frontend): アカウント初期設定ウィザードにあとでボタンを追加

* tweak

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-05-26 14:40:44 +09:00
tamaina
eee1e74174 shareページに"Misskeyへ"ボタンを設置
Resolve #10898
2023-05-26 05:18:01 +00:00
tamaina
8050f89d7e Revert "fix(client): /shareをsubBootPathsから外す"
This reverts commit 406e5d297b.
2023-05-26 05:06:52 +00:00
tamaina
406e5d297b fix(client): /shareをsubBootPathsから外す
Resolve #10898
2023-05-26 04:34:34 +00:00
syuilo
10634b3615 refactor 2023-05-26 13:32:42 +09:00
syuilo
fd03e2e1a7 🎨 2023-05-26 13:30:26 +09:00
syuilo
6cc86272f3 🎨 2023-05-26 13:24:31 +09:00
syuilo
06b1250d47 🎨 2023-05-26 11:31:39 +09:00
syuilo
31a7350a10 🎨 2023-05-26 10:48:49 +09:00
tamaina
4129ac157a package.jsonの並び替えを修正 2023-05-25 14:50:14 +00:00
syuilo
30cb791e93 enhance(frontend): フォロー/フォロー解除したときに自動でTLをリロードするのをやめるように
- 不便に感じる場合が多いように思う
- 将来的にTLがpush型になったら無意味になる
2023-05-25 08:17:09 +09:00
syuilo
1c57983bfd refactor 2023-05-24 17:50:15 +09:00
syuilo
bdf08c8a54 refactor 2023-05-24 17:33:31 +09:00
syuilo
0513ff8b4e refactor 2023-05-24 17:29:58 +09:00
Acid Chicken (硫酸鶏)
62fe3bfb54 refactor(#7598): add i18n dynamic typings (#10882)
* refactor: add i18n dynamic typings

* chore: tweak
2023-05-24 17:12:38 +09:00
syuilo
38a1d6693a 🎨 2023-05-24 14:43:53 +09:00
syuilo
d2eec3a9e4 refactor 2023-05-24 14:34:46 +09:00
syuilo
1de774fa3d update deps 2023-05-24 10:16:42 +09:00
syuilo
ed902658a9 refactor 2023-05-24 09:59:30 +09:00
Caipira
acdcd7c623 enhance(frontend): improve signup complete ui (#10876)
* enhance(frontend): improve signup complete ui

* relocation

* tweak

* Update _boot_.ts

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-05-24 09:43:38 +09:00
syuilo
b0344e07c4 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-05-21 20:49:40 +09:00
Acid Chicken (硫酸鶏)
9a6ce1e867 ci: fix head user 2023-05-20 18:52:08 +00:00
Acid Chicken (硫酸鶏)
22a6bd6b22 ci: fix branch name 2023-05-20 14:03:05 +00:00
syuilo
38e6f3f776 🎨 2023-05-20 12:32:51 +09:00
syuilo
ca75afe065 13.13.0-beta.3 2023-05-20 10:35:56 +09:00
Acid Chicken (硫酸鶏)
915ed39715 feat: support summary_large_image (#10862)
* feat: use summary_large_image

* chore: add video

* chore: ignore sensitive image on note
2023-05-20 10:35:23 +09:00
Acid Chicken (硫酸鶏)
81fd94e635 chore: update slacc (#10867)
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-05-20 10:34:31 +09:00
syuilo
05507a4bea enhance(frontend): Deck UIを使用している場合、/以外にアクセスした際にZen UIで表示するように
Resolve #10871
2023-05-20 10:31:53 +09:00
syuilo
d177f97928 refactor 2023-05-20 10:12:18 +09:00
syuilo
30cb03a40d Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-05-20 10:05:39 +09:00
syuilo
c685989e67 refactor 2023-05-20 10:05:35 +09:00
Acid Chicken (硫酸鶏)
ee3f408c7d feat: impl IdlingRenderScheduler (#10547)
* feat: impl IdleRender

* test: pin time on Chromatic

* test: pin time on Chromatic

* fix: typo

* style: rename

* style: rename

* chore: back to setTimeout

* style: linebreak

* refactor: remove unused budget option

* refactor: use raw unix time

* fix: conflict error

* fix: floor

* fix: subtract

* Revert "fix: subtract"

This reverts commit 2ef4afaafc.

* Revert "fix: floor"

This reverts commit bef8ecdf45.

* Revert "refactor: use raw unix time"

This reverts commit 5199e13cb2.
2023-05-20 03:38:07 +09:00
syuilo
1eb35dd5bc delete /docs 2023-05-19 20:59:21 +09:00
Nanashia
15db0b8812 test(backend): Add tests for antennas (#10868) 2023-05-19 20:53:20 +09:00
syuilo
1b78c6a309 refactor 2023-05-19 20:52:15 +09:00
syuilo
c713af8e23 refactor 2023-05-19 18:16:26 +09:00
nenohi
bd6666173a fix:ロールタイムライン無効に関する修正 (#10843)
* 修正

* note visibility change

* public投稿のみ

* update changelog

* RN非表示機能がうごかないところだった
2023-05-19 17:12:22 +09:00
tamaina
02715f5d14 update pnpm to 8.5.1 2023-05-19 07:36:47 +00:00
syuilo
acd5e0b8f6 update deps 2023-05-19 16:34:56 +09:00
syuilo
be2142bb13 refactor 2023-05-19 16:30:39 +09:00
syuilo
4a703d7cf6 refactor 2023-05-19 16:25:48 +09:00
syuilo
95470a40a7 refactor 2023-05-19 16:20:53 +09:00
syuilo
56d4658b36 refactor(frontend): use css module 2023-05-19 14:08:51 +09:00
syuilo
f68008b002 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-05-19 13:58:33 +09:00
syuilo
6a5ef5b6f2 refactor 2023-05-19 13:58:09 +09:00
syuilo
95b9284e79 🎨 2023-05-19 13:35:46 +09:00
まっぴー
8317772436 fix:Firefoxでサインアップする時、メールアドレスがユーザー名として認識される#10523 (#10869)
* fix:Firefoxでサインアップする時、メールアドレスがユーザー名として認識される

* Update CHANGELOG.md

edit CHANGELOG.md
2023-05-19 11:35:46 +09:00
syuilo
0c0ae6ff90 13.13.0-beta.2 2023-05-19 10:07:29 +09:00
syuilo
d63b943116 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-05-19 10:06:33 +09:00
Chocolate Pie
dddbc1c894 feat: 公開リスト (#10842)
* feat: まず公開できるように (misskey-dev/misskey#10447)

* feat: 公開したリストのページを作成 (misskey-dev/misskey#10447)

* feat: いいねできるように

* feat: インポートに対応

* wip

* wip

* CHANGELOGを編集

* add note

* refactor

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-05-19 10:06:12 +09:00
syuilo
f68c743f39 add note 2023-05-19 09:48:48 +09:00
tamaina
59255e11b8 perf: MkImgWithBlurhashとMkMediaImageを最適化 (#10782)
* #10781

* fix tsconfig

* fetch image??

* Revert "fetch image??"

This reverts commit 0925c28d5a.

* wip

* Revert "wip"

This reverts commit be97c6cb88.

* loading="eager"

* loading="eager" 2

* error

* wip

* wip

* wip

* wip

* clean up

* fix

* 生成するworkerを1つにする?

* clean up

* use buraha

* wip

* smaller width, height

* update buraha

* clean up

* fix

* Update MkMediaImage.vue

* Update MkImgWithBlurhash.vue

* Revert "fix(frontend): センシティブ設定された画像を開くとき一瞬レイアウトが崩れる問題を修正"

This reverts commit 41e9aa6f9b.

* Update MkMediaList.vue

* Update MkMediaList.vue

* Update MkMediaList.vue

* Update CHANGELOG.md

* wait for decode

* fix

* ?

* (test) remove container-type: inline-size;

* Revert "(test) remove container-type: inline-size;"

This reverts commit 9448e64228.

* container-name

* Revert "container-name"

This reverts commit 94385d3221.

* width: 100%;

* improve performance

* refactor

* wip

* WIP

* wip

* Revert "wip"

This reverts commit 36e3b75cab.

* Revert "WIP"

This reverts commit 05b729ef91.

* Revert "wip"

This reverts commit 0801e79361.

* #10860

* wip

* no worker

* Revert "no worker"

This reverts commit a9c49e4fb4.

* ✌️

* workerNumber固定は不要

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-05-19 09:44:06 +09:00
syuilo
3804c6e7ad feat: センシティブなカスタム絵文字のリアクションを受け入れない設定を追加 2023-05-19 09:43:38 +09:00
syuilo
527a13b77d enhance(frontend): リアクションの取り消し/変更時に確認ダイアログを出すように 2023-05-19 09:15:24 +09:00
syuilo
a3423bad60 tweak 2023-05-19 09:14:54 +09:00
syuilo
e3f9144608 13.13.0-beta.1 2023-05-18 20:29:13 +09:00
syuilo
231506772a tweak of 7ce569424 2023-05-18 20:17:32 +09:00
syuilo
db1098a180 feat(backend): カスタム絵文字ごとに連合するかどうか設定できるように 2023-05-18 18:48:35 +09:00
syuilo
7ce569424a feat: カスタム絵文字ごとにそれをリアクションとして使えるロールを設定できるように 2023-05-18 18:45:49 +09:00
syuilo
9b5b3a4d1b perf(backend): pre-compile regexp 2023-05-18 18:18:25 +09:00
Acid Chicken (硫酸鶏)
6173cebdca ci: remove deprecated notifications 2023-05-18 08:05:09 +00:00
Acid Chicken (硫酸鶏)
6bb82cda37 ci: use actual base 2023-05-18 14:16:07 +09:00
tamaina
dd60f1a533 fix(frontend/MkUrlPreview): summalyがエラーになった際の挙動を修正 2023-05-17 20:00:28 +00:00
Acid Chicken (硫酸鶏)
45263f4cd0 ci: stop MkGalleryPostPreview snapshot 2023-05-17 07:51:45 +00:00
Acid Chicken (硫酸鶏)
747d323584 ci: fix breaking on foreign repos 2023-05-17 07:39:36 +00:00
syuilo
e707aadbcc update deps 2023-05-17 14:11:32 +09:00
syuilo
7b012967d9 refactor(frontend): MFMコンポーネントのリファクタ & パフォーマンスを改善 2023-05-17 11:50:37 +09:00
syuilo
e126083e10 fix(frontend): コンポーネントのpropを非lowerCamelCaseで渡すと機能しない 2023-05-17 11:42:50 +09:00
syuilo
cbca48846c fix streamin error 2023-05-17 11:10:31 +09:00
syuilo
ca2ed0a59b 🎨 2023-05-17 11:08:46 +09:00
syuilo
5d22e113b2 🎨 2023-05-17 10:52:22 +09:00
syuilo
ed3c137543 Revert "refactor: define _IS_CHROMATIC_"
This reverts commit a35f0d43e4.
2023-05-16 19:37:15 +09:00
syuilo
a35f0d43e4 refactor: define _IS_CHROMATIC_ 2023-05-16 19:24:10 +09:00
syuilo
153eed7d71 fix(frontend/test): MkAnimBgで毎回Chromaticの差分が生成されないように 2023-05-16 18:41:34 +09:00
nenohi
0d8e1c5421 channel favoriteの位置修正 (#10855) 2023-05-16 12:28:59 +09:00
syuilo
d10d5a8d53 enhance: タイムラインにフォロイーの行った他人へのリプライを含めるかどうかの設定をアカウントに保存するのをやめるように
Resolve #10646
2023-05-16 12:16:37 +09:00
syuilo
23f106a0c1 refactor(frontend): boot分割したり副作用減らしたりとか
#10838
2023-05-15 19:08:46 +09:00
syuilo
a7ee4aabcb chore(frontend): 設定画面を整理 2023-05-15 16:40:19 +09:00
syuilo
d867fc00b6 clean up 2023-05-15 16:25:44 +09:00
syuilo
60f504bbe2 fix(frontend): ツールチップが永久にDOMに残ることがある問題を修正
#10805
2023-05-15 14:29:35 +09:00
syuilo
f4e6d73a8a refactor 2023-05-15 13:57:36 +09:00
syuilo
c066013c57 fix #10850 ? 2023-05-15 11:03:18 +09:00
syuilo
86f952e659 fix #10850 ? 2023-05-14 20:32:08 +09:00
syuilo
d36e44bc57 Update pnpm-lock.yaml 2023-05-14 20:22:27 +09:00
syuilo
93629fb29d upgrade aiscript to 0.13.3 2023-05-14 20:08:32 +09:00
syuilo
9ff088a830 ✌️ 2023-05-14 17:55:39 +09:00
syuilo
55190e6b8b Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-05-14 13:23:28 +09:00
syuilo
38391010af Revert "make __VUE_OPTIONS_API__ false"
This reverts commit 636428c72e.
2023-05-14 13:23:25 +09:00
syuilo
3d94dec7a9 Merge pull request #10847 from yupix/feat/client-dev-mode
feat: 開発者モードを追加
2023-05-14 13:02:48 +09:00
syuilo
636428c72e make __VUE_OPTIONS_API__ false 2023-05-14 12:24:21 +09:00
syuilo
0717afc312 refactor(frontend): use composition api 2023-05-14 12:23:39 +09:00
syuilo
3d4a90b08a refactor(frontend): use composition api 2023-05-14 11:43:56 +09:00
yupix
7cc555fd28 Merge branch 'misskey-dev:develop' into feat/client-dev-mode 2023-05-14 10:56:40 +09:00
syuilo
238d0fa667 refactor 2023-05-14 10:50:21 +09:00
yupix
30ff4592cc Merge branch 'misskey-dev:develop' into feat/client-dev-mode 2023-05-14 10:38:52 +09:00
syuilo
a979fb9207 change(frontend): 動的ページのコンポーネントを削除 2023-05-14 10:31:48 +09:00
yupix
f15f60d5b9 feat: 開発者モードを追加 2023-05-14 01:30:46 +00:00
syuilo
8c97c54cfa refactor(frontend): use css modules 2023-05-14 10:21:56 +09:00
syuilo
89a3195dfd 🎨 2023-05-14 09:54:35 +09:00
syuilo
1eaf287b9c 🎨 2023-05-14 09:44:57 +09:00
syuilo
9166a58c5f 🎨 2023-05-13 20:50:21 +09:00
syuilo
eef368abd0 Update about-misskey.vue 2023-05-13 20:50:16 +09:00
syuilo
f983e44d9e fix(frontend): fix retention rate heatmap rendering 2023-05-12 12:15:54 +09:00
syuilo
5be6438bbc update deps 2023-05-12 12:10:56 +09:00
syuilo
d2e6b17a26 fix(frontend): Pageにおいて画像ブロックに画像を設定できない問題を修正
Fix #10837
2023-05-12 11:47:12 +09:00
syuilo
d1c2e4aaa0 refactor 2023-05-12 11:46:13 +09:00
syuilo
6f1994c665 update deps 2023-05-12 11:41:53 +09:00
syuilo
055dc6bb66 enhance(frontend): add retention line chart 2023-05-12 10:29:27 +09:00
syuilo
f06339b970 🎨 2023-05-12 08:26:06 +09:00
syuilo
3370419b0a 🎨 2023-05-12 08:20:36 +09:00
syuilo
9bd4ad0fc1 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-05-12 08:02:25 +09:00
syuilo
23994d8f4e [ci skip] New Crowdin updates (#10834)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (German)

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

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Arabic)
2023-05-12 08:02:15 +09:00
syuilo
bb3c85d3a1 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-05-12 08:01:31 +09:00
syuilo
8328a66104 Update basic.cy.js 2023-05-12 08:01:29 +09:00
Acid Chicken (硫酸鶏)
909ec8c38d ci: fix missing branch 2023-05-11 22:21:48 +09:00
Acid Chicken (硫酸鶏)
b01a1b3c17 ci: notify on changes for push events 2023-05-11 22:20:26 +09:00
Acid Chicken (硫酸鶏)
cbb779f60c ci: skip non-Japanese locale on TurboSnap 2023-05-11 22:06:13 +09:00
syuilo
8ddae83c40 fix MkUserSetupDialog.Privacy.vue 2023-05-11 21:24:27 +09:00
syuilo
1cc106b8de fix(backend): ひとつのMeilisearchサーバーを複数のMisskeyサーバーで使えない問題を修正 2023-05-11 21:09:29 +09:00
syuilo
16eedb86a5 Update CHANGELOG.md 2023-05-11 20:57:34 +09:00
syuilo
2b26a2f5e4 enhance(frontend): アカウント初期設定ウィザードにプライバシー設定を追加 2023-05-11 20:54:47 +09:00
syuilo
a65fa684ce Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-05-11 20:17:48 +09:00
syuilo
0758c56c53 New Crowdin updates (#10824)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (German)

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

* New translations ja-JP.yml (English)

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

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

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

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Japanese, Kansai)

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

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Russian)

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

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (Thai)
2023-05-11 20:17:15 +09:00
rinsuki
3c0dccc8b9 meta: Remove @rinsuki from reviewer-lottery (#10830) 2023-05-11 19:28:17 +09:00
syuilo
e18feb4183 Update about-misskey.vue 2023-05-11 18:22:38 +09:00
syuilo
9e464eaa63 fix(frontend): fix retention chart rendering 2023-05-11 18:20:48 +09:00
syuilo
8b352e4e56 feat(frontend): ユーザー指定ノート検索 2023-05-11 18:10:34 +09:00
syuilo
26d85c8bed enhance(backend): ノートのハッシュタグもMeilisearchに突っ込むように
今後ハッシュタグ検索とか実装するときのため
2023-05-11 16:33:39 +09:00
syuilo
df924d18f1 fix(frontend): チャンネル内の検索ボックスが挙動不審な問題を修正
Fix #10793
2023-05-11 16:29:34 +09:00
syuilo
177359689e fix(frontend): カラーバーがリプライには表示されないのを修正 2023-05-11 16:26:35 +09:00
syuilo
b380dc53e1 fix typo 2023-05-11 16:22:46 +09:00
syuilo
8c30ca50a8 fix(frontend): より明確な説明にしたのとtypo修正 2023-05-11 16:20:03 +09:00
Acid Chicken (硫酸鶏)
69afd0480e ci: fix typo 2023-05-10 10:33:49 +00:00
syuilo
6d4d9f417b New Crowdin updates (#10823)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (German)
2023-05-10 18:26:02 +09:00
syuilo
11ea1a2811 Update CHANGELOG.md 2023-05-10 18:03:16 +09:00
nenohi
c15b75e477 センシティブワードを正規表現、CWにも適用するように (#10688)
* cwにセンシティブが効いてない

* CWが無いときにTextを見るように

* 比較演算子間違えた

* とりあえずチェック

* 正規表現対応

* /test/giにも対応

* matchでしなくてもいいのでは感

* レビュー修正

* Update packages/backend/src/core/NoteCreateService.ts

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>

* Update packages/backend/src/core/NoteCreateService.ts

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>

* 修正

* wipかも

* wordsでスペース区切りのものできたかも

* なんか動いたかも

* test作成

* 文言の修正

* 修正

* note参照

---------

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
2023-05-10 18:02:41 +09:00
syuilo
ea9a95cd98 delete unused component 2023-05-10 17:53:49 +09:00
syuilo
ed634b4b3d refactor(frontend): use css module 2023-05-10 17:53:01 +09:00
syuilo
f9f115b66d refactor(frontend): use css module 2023-05-10 17:49:30 +09:00
syuilo
02059fdee4 bump 2023-05-10 17:45:54 +09:00
syuilo
37c119e1f5 refactor 2023-05-10 17:45:13 +09:00
syuilo
d75a7fd421 New Crowdin updates (#10815)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Korean)

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

* New translations ja-JP.yml (Chinese Traditional)
2023-05-10 16:36:22 +09:00
syuilo
adf66154d2 fix test 2023-05-10 15:59:34 +09:00
syuilo
9557579b67 fix(backend): テスト時は一部のサービスを停止 2023-05-10 15:30:36 +09:00
syuilo
341c42ebb9 enhance(backend): graceful shutdown for job queue and refactor 2023-05-10 15:05:08 +09:00
syuilo
574cfdfda6 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-05-10 12:24:52 +09:00
syuilo
0729678398 fix(frontend): ブラーエフェクトを有効にしている状態で高負荷になる問題を修正 2023-05-10 12:24:50 +09:00
syuilo
b545146bf9 Update ja-JP.yml 2023-05-10 11:43:40 +09:00
syuilo
7ae1d6511a add X-Robots-Tag: noai 2023-05-10 10:57:27 +09:00
syuilo
0c4c4e7202 念のためnoimageaiもつける 2023-05-10 10:54:56 +09:00
syuilo
6a25015a78 enhance(backend): publicReactionsをデフォルトtrueに 2023-05-10 10:53:01 +09:00
syuilo
ae5a72a2df feat: 投稿したコンテンツのAIによる学習を軽減するオプションを追加
Resolve #10819
2023-05-10 10:52:41 +09:00
syuilo
baa45859c1 refactor(frontend): use css modules 2023-05-09 18:47:06 +09:00
606 changed files with 20454 additions and 13336 deletions

View File

@@ -103,6 +103,7 @@ redis:
# port: 7700
# apiKey: ''
# ssl: true
# index: ''
# ┌───────────────┐
#───┘ ID generation └───────────────────────────────────────────

View File

@@ -103,6 +103,7 @@ redis:
# port: 7700
# apiKey: ''
# ssl: true
# index: ''
# ┌───────────────┐
#───┘ ID generation └───────────────────────────────────────────

View File

@@ -103,6 +103,7 @@ redis:
# port: 7700
# apiKey: ''
# ssl: true
# index: ''
# ┌───────────────┐
#───┘ ID generation └───────────────────────────────────────────

View File

@@ -6,5 +6,4 @@ groups:
- syuilo
- acid-chicken
- EbiseLutica
- rinsuki
- tamaina

View File

@@ -16,12 +16,22 @@ jobs:
steps:
- uses: actions/checkout@v3.3.0
if: github.event_name != 'pull_request_target'
with:
fetch-depth: 0
submodules: true
- name: Checkout HEAD
if: github.event_name == 'pull_request'
run: git checkout ${{ github.head_ref }}
- uses: actions/checkout@v3.3.0
if: github.event_name == 'pull_request_target'
with:
fetch-depth: 0
submodules: true
ref: "refs/pull/${{ github.event.number }}/merge"
- name: Checkout actual HEAD
if: github.event_name == 'pull_request_target'
id: rev
run: |
echo "base=$(git rev-list --parents -n1 HEAD | cut -d" " -f2)" >> $GITHUB_OUTPUT
git checkout $(git rev-list --parents -n1 HEAD | cut -d" " -f3)
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
@@ -41,12 +51,12 @@ jobs:
- name: Build storybook
run: pnpm --filter frontend build-storybook
- name: Publish to Chromatic
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master'
if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/master'
run: pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static
env:
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
- name: Publish to Chromatic
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/master'
if: github.event_name != 'pull_request_target' && github.ref != 'refs/heads/master'
id: chromatic_push
run: |
DIFF="${{ github.event.before }} HEAD"
@@ -57,14 +67,18 @@ jobs:
if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
fi
pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static $(echo "$CHROMATIC_PARAMETER")
if pnpm --filter frontend chromatic -d storybook-static $(echo "$CHROMATIC_PARAMETER"); then
echo "success=true" >> $GITHUB_OUTPUT
else
echo "success=false" >> $GITHUB_OUTPUT
fi
env:
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
- name: Publish to Chromatic
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request_target'
id: chromatic_pull_request
run: |
DIFF="${{ github.base_ref }} HEAD"
DIFF="${{ steps.rev.outputs.base }} HEAD"
if [ "$DIFF" = "0000000000000000000000000000000000000000 HEAD" ]; then
DIFF="HEAD"
fi
@@ -72,20 +86,24 @@ jobs:
if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
fi
pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static $(echo "$CHROMATIC_PARAMETER")
BRANCH="${{ github.event.pull_request.head.user.login }}:${{ github.event.pull_request.head.ref }}"
if [ "$BRANCH" = "misskey-dev:${{ github.event.pull_request.head.ref }}" ]; then
BRANCH="${{ github.event.pull_request.head.ref }}"
fi
pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static --branch-name $BRANCH $(echo "$CHROMATIC_PARAMETER")
env:
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
- name: Notify that Chromatic will skip testing
- name: Notify that Chromatic detects changes
uses: actions/github-script@v6.4.0
if: github.event_name == 'pull_request' && steps.chromatic_pull_request.outputs.skip == 'true'
if: github.event_name != 'pull_request_target' && steps.chromatic_push.outputs.success == 'false'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
github.rest.repos.createCommitComment({
owner: context.repo.owner,
repo: context.repo.repo,
body: 'Chromatic will skip testing but you may still have to [review the changes on Chromatic](https://www.chromatic.com/pullrequests?appId=6428f7d7b962f0b79f97d6e4).'
commit_sha: context.sha,
body: 'Chromatic detects changes. Please [review the changes on Chromatic](https://www.chromatic.com/builds?appId=6428f7d7b962f0b79f97d6e4).'
})
- name: Upload Artifacts
uses: actions/upload-artifact@v3

View File

@@ -12,6 +12,66 @@
-->
## 13.13.0
### General
- カスタム絵文字ごとにそれをリアクションとして使えるロールを設定できるように
- カスタム絵文字ごとに連合するかどうか設定できるように
- カスタム絵文字ごとにセンシティブフラグを設定できるように
- センシティブなカスタム絵文字のリアクションを受け入れない設定が可能に
- タイムラインにフォロイーの行った他人へのリプライを含めるかどうかの設定をアカウントに保存するのをやめるように
- 今後はAPI呼び出し時およびストリーミング接続時に設定するようになります
- リストを公開できるようになりました
### Client
- リアクションの取り消し/変更時に確認ダイアログを出すように
- 開発者モードを追加
- AiScriptを0.13.3に更新
- Deck UIを使用している場合、`/`以外にアクセスした際にZen UIで表示するように
- メインカラムを設置していない場合の問題を解決
- ハッシュタグのノート一覧ページから、そのハッシュタグで投稿するボタンを追加
- アカウント初期設定ウィザードに戻るボタンを追加
- アカウントの初期設定ウィザードにあとでボタンを追加
- サーバーにカスタム絵文字の種類が多い場合のパフォーマンスの改善
- Fix: URLプレビューで情報が取得できなかった際の挙動を修正
- Fix: Safari、Firefoxでの新規登録時、パスワードマネージャーにメールアドレスが登録されていた挙動を修正
- Fix: ロールタイムラインが無効でも投稿が流れてしまう問題の修正
- Fix: ロールタイムラインにて全ての投稿が流れてしまう問題の修正
- Fix: 「アクセストークンの管理」画面でアプリの情報が表示されない問題の修正
- Fix: Firefoxにおける絵文字ピッカーのTabキーフォーカス問題の修正
- Fix: フォローボタンがテーマのカラースキームによって視認性が悪くなる問題を修正
- 新しいプロパティ `fgOnWhite` が追加されました
### Server
- bullをbull-mqにアップグレードし、ジョブキューのパフォーマンスを改善
- ストリーミングのパフォーマンスを改善
- Fix: お知らせの画像URLを空にできない問題を修正
- Fix: i/notificationsのsinceIdが機能しない問題を修正
## 13.12.2
## NOTE
Meilisearchの設定に`index`が必要になりました。値はMisskeyサーバーのホスト名にすることをお勧めします(アルファベット、ハイフン、アンダーバーのみ使用可能)。例: `misskey-io`
過去に作成された`notes`インデックスは、`<index名>---notes`にリネームが必要です。例: `misskey-io---notes`
### General
- 投稿したコンテンツのAIによる学習を軽減するオプションを追加
### Client
- ユーザーを指定してのノート検索が可能に
- アカウント初期設定ウィザードにプライバシー設定を追加
- リテンション率チャートに折れ線グラフを追加
- Fix: ブラーエフェクトを有効にしている状態で高負荷になる問題を修正
- Fix: Pageにおいて画像ブロックに画像を設定できない問題を修正
- Fix: カラーバーがリプライには表示されないのを修正
- Fix: チャンネル内の検索ボックスが挙動不審な問題を修正
- Fix: リテンションチャートのレンダリングを修正
- Fix: リアクションエフェクトのレンダリングの問題を修正
### Server
- センシティブワードの登録にAnd、正規表現が使用できるようになりました。
- Fix: ひとつのMeilisearchサーバーを複数のMisskeyサーバーで使えない問題を修正
## 13.12.1
### Client
@@ -63,6 +123,7 @@
* 画像が全て隠れた状態で表示されるようになります
- 閲覧注意設定された画像は表示した状態でもそれが閲覧注意だと分かる表示をするように
- モデレーターはートに添付された画像上から直接NSFW設定できるように
- 1枚だけのメディアリストの画像のアスペクト比を画像に応じて縦長にするように
- プロフィール設定「追加情報」の項目の削除と並び替えができるように
- 新しい実績を追加
- AiScriptを0.13.2に更新
@@ -310,6 +371,7 @@
- アンテナでCWも検索対象にするように
- ノートの操作部をホバー時のみ表示するオプションを追加
- サウンドを追加
- enhance(client): MFMのx2, scale, positionが含まれていたらートをたたむように
- サーバーのパフォーマンスを改善
### Bugfixes

View File

@@ -124,6 +124,7 @@ redis:
# port: 7700
# apiKey: ''
# ssl: true
# index: ''
# ┌───────────────┐
#───┘ ID generation └───────────────────────────────────────────

View File

@@ -169,16 +169,25 @@ describe('After user signed in', () => {
cy.get('[data-cy-user-setup-user-description] textarea').type('ほげ');
// TODO: アイコン設定テスト
cy.get('[data-cy-user-setup-back]').click();
cy.get('[data-cy-user-setup-continue]').click();
// プライバシー設定
cy.get('[data-cy-user-setup-back]').click();
cy.get('[data-cy-user-setup-continue]').click();
// フォローはスキップ
cy.get('[data-cy-user-setup-back]').click();
cy.get('[data-cy-user-setup-continue]').click();
// プッシュ通知設定はスキップ
cy.get('[data-cy-user-setup-back]').click();
cy.get('[data-cy-user-setup-continue]').click();
cy.get('[data-cy-user-setup-back]').click();
cy.get('[data-cy-user-setup-continue]').click();
});
});

View File

@@ -1,25 +0,0 @@
DONATORS
========
The list of people who have sent donation for Misskey.
(In random order, honorific titles are omitted.)
* らふぁ
* 俺様
* なぎうり
* スルメ https://surume.tk/
*
* 音船 https://otofune.me/
* aqz https://misskey.xyz/aqz
* kotodu "虚無創作中"
* Maya Minatsuki
* Knzk https://knzk.me/@Knzk
* ねじりわさび https://knzk.me/@y
* NCLS https://knzk.me/@imncls]
* こじま @skoji@sandbox.skoji.jp
:heart: Thanks for donating, guys!
---
If your name is missing, please contact us!

View File

@@ -2,6 +2,7 @@
_lang_: "العربية"
headlineMisskey: "شبكة مرتبطة بالملاحظات"
introMisskey: "اهلا بك! ميسكي هو منصة تدوين مصغر لا مركزية ومفتوحة المصدر.\nيمكنك مشاركة \"ملاحظات\" عن ما يجري حولك، وإخبار الجميع عن نفسك 📡\nتسمح لك \"الانفعالات\" بتعبير عن شعورك حول ملاحظات الآخرين 👍\nاكتشف عالمًا جديدًا 🚀"
poweredByMisskeyDescription: "{name} هو إحدى الخِدمات التي تستخدم المنصة مفتوحة المصدر <b>ميسكي</b> (يشار إليه كمثيل ميسكي)"
monthAndDay: "{day}/{month}"
search: "البحث"
notifications: "الإشعارات"
@@ -19,6 +20,7 @@ noNotes: "لم يُعثر على أية ملاحظات"
noNotifications: "ليس هناك أية اشعارات"
instance: "مثيل الخادم"
settings: "الاعدادات"
notificationSettings: "إعدادات الإشعارات"
basicSettings: "الاعدادات الأساسية"
otherSettings: "إعدادات أخرى"
openInWindow: "افتح في نافذة جديدة"
@@ -48,6 +50,7 @@ deleteAndEdit: "إزالة وإعادة الصياغة"
deleteAndEditConfirm: "أمتأكد من حذف الملاحظة؟ ستفقد كل مشاركاتها، والتفاعلات، والردود عليها."
addToList: "أضفه إلى قائمة"
sendMessage: "أرسل رسالة"
copyRSS: "انسخ رابط RSS"
copyUsername: "انسخ اسم المستخدم"
searchUser: "ابحث عن مستخدمين"
reply: "رد"
@@ -101,6 +104,8 @@ renoted: "أُعيد نشره"
cantRenote: "لا يمكن إعادة نشر الملاحظة"
cantReRenote: "لا يمكنك إعادة نشر ملاحظة معاد نشرها"
quote: "اقتبس"
inChannelRenote: "إعادة نشر في قناة"
inChannelQuote: "اقتباس في قناة"
pinnedNote: "ملاحظة مدبسة"
pinned: "دبّسها على الصفحة الشخصية"
you: "أنت"
@@ -118,6 +123,8 @@ unmarkAsSensitive: "ألغ تعيينه كمحتوى حساس"
enterFileName: "ادخل اسم الملف"
mute: "اكتم"
unmute: "إلغاء الكتم"
renoteMute: "اكتم إعادة النشر"
renoteUnmute: "ارفع الكتم عن إعادة النشر"
block: "احجب"
unblock: "إلغاء الحجب"
suspend: "علِق"
@@ -127,6 +134,7 @@ unblockConfirm: "أمتأكد من إلغاء حجب هذا الحساب؟"
suspendConfirm: "أمتأكد من تعليق الحساب؟"
unsuspendConfirm: "أمتأكد من إلغاء تعليق؟"
selectList: "اختر قائمة"
selectChannel: "اختر قناة"
selectAntenna: "اختر هوائيًا"
selectWidget: "اختر ودجة"
editWidgets: "عدّل الودجات"
@@ -139,6 +147,7 @@ emojiUrl: "رابط الإيموجي"
addEmoji: "إضافة إيموجي"
settingGuide: "الإعدادات المستحسنة"
cacheRemoteFiles: "خزن مؤقتا الملفات البعيدة"
cacheRemoteFilesDescription: "إذا عُطل هذا الإعداد، ستُحمل الملفات من المثيل البعيد، هذا سيقلل من المساحة المستغلة على القرص لكن سيزيد حجم تدفق البيانات وهذا لأن الصور المصغرة لن تولّد."
flagAsBot: "علّمه كحساب آلي"
flagAsBotDescription: "فعّل هذا الخيار إذا كان هذا الحساب يُدار عبر برمجية. إذا فُعل فسيكون بمثابة علامة للمطورين الآخرين لتجنب سلاسل لا متناهية من التفاعل بين حسابات الآلية وضبط أنظمة ميسكي للتعامل مع هذا الحساب كآلي."
flagAsCat: "علّم هذا الحساب كحساب قط"
@@ -250,6 +259,10 @@ noMoreHistory: "لا يوجد المزيد من التاريخ"
startMessaging: "ابدأ محادثة"
nUsersRead: "قرأه {n}"
agreeTo: "اوافق على {0}"
agree: "أقبل"
agreeBelow: "أقبل ما يلي"
basicNotesBeforeCreateAccount: "ملاحظات مهمة"
termsOfService: "شروط الخدمة"
start: "البداية"
home: "الرئيسي"
remoteUserCaution: "هذه المعلومات قد لا تكون مكتملة بما أن المستخدم من مثيل بعيد."
@@ -357,6 +370,7 @@ antennaExcludeKeywords: "الكلمات المفتاحية المستثناة"
antennaKeywordsDescription: "افصل بينهم بمسافة لاستخدام معامل \"و\" أو بسطر لاستخدام معامل \"أو\""
notifyAntenna: "نبهني بصول ملاحظات جديدة"
withFileAntenna: "ملاحظات تحوي ملفات فقط"
enableServiceworker: "فعّل إرسال الإشعارات للمتصفح"
antennaUsersDescription: "اكتب اسم مستخدم لكل سطر"
caseSensitive: "حساسية حالة الأحرف"
withReplies: "بالردود"
@@ -379,11 +393,14 @@ about: "عن"
aboutMisskey: "عن Misskey"
administrator: "المدير"
token: "الرمز المميز"
2fa: "الاستيثاق بعاملَيْن"
totp: "تطبيق استيثاق"
moderator: "مشرِف"
moderation: "الإشراف"
nUsersMentioned: "{n} مستخدمين أُشير إليهم"
securityKey: "مفتاح الأمان"
lastUsed: "آخر استخدام"
lastUsedAt: "آخر استخدام: {t}"
unregister: "إلغاء التسجيل"
passwordLessLogin: "لِج مِن دون كلمة سرية"
resetPassword: "أعد تعيين كلمتك السرية"
@@ -434,6 +451,7 @@ or: "أو"
language: "اللغة"
uiLanguage: "لغة واجهة المستخدم"
aboutX: "عن {x}"
emojiStyle: "نمط الوجوه التعبيرية"
noHistory: "السجل فارغ"
signinHistory: "تاريخ تسجيل الدخول"
doing: "انتظر لحظة"
@@ -444,6 +462,7 @@ createAccount: "أنشئ حسابًا"
existingAccount: "الحسابات الموجودة"
regenerate: "أعِد التوليد"
fontSize: "حجم الخط"
limitTo: "سقفهُ لـ{x}"
noFollowRequests: "ليس لديك طلبات متابعة معلقة"
openImageInNewTab: "إفتح الصورة بصفحة جديدة"
dashboard: "لوحة التحكم"
@@ -472,6 +491,7 @@ objectStorageUseProxyDesc: "عطل هذا الخيار إذا لم ترد است
serverLogs: "سجلات الخادم"
deleteAll: "حذف الكل"
showFixedPostForm: "أظهر نموذج الكتابة في أعلى الصفحة"
showFixedPostFormInChannel: "أظهر نموذج الكتابة في أعلى الخط الزمني (قنوات)"
newNoteRecived: "هناك ملاحظات جديدة"
sounds: "الرنات"
sound: "الرنات"
@@ -506,6 +526,8 @@ userSuspended: "عُلق هذا المستخدم."
userSilenced: "كُتم هذا المستخدم."
yourAccountSuspendedTitle: "هذا الحساب معلق"
yourAccountSuspendedDescription: "عُلق الحساب بسبب انتهاك شروط خدمة المثيل و ما شابه. إذا أردت معرفة التفصيل تواصل مع مدير المثيل. رجاءً لا تنشئ حساب جديد."
accountDeleted: "حُذف الحساب"
accountDeletedDescription: "حُذف هذا الحساب."
menu: "القائمة"
divider: "فاصل"
addItem: "إضافة عنصر"
@@ -531,6 +553,7 @@ author: "الكاتب"
leaveConfirm: "لديك تغييرات غير محفوظة. أتريد المتابعة دون حفظها؟"
manage: "إدارة "
plugins: "الإضافات"
preferencesBackups: "النُسخ الاحتياطية للإعدادات"
useFullReactionPicker: "استخدم الحجم الكامل لمنتقي التفاعلات"
width: "العرض"
height: "الإرتفاع"
@@ -640,6 +663,7 @@ contact: "التواصل"
useSystemFont: "استخدم الخط الافتراضية للنظام"
clips: "مشابك"
experimentalFeatures: "ميّزات اختبارية"
experimental: "اختباري"
developer: "المطور"
makeExplorable: "أظهر الحساب في صفحة \"استكشاف\""
makeExplorableDescription: "بتعطيل هذا الخيار لن يظهر حسابك في صفحة \"استكشاف\""
@@ -661,6 +685,7 @@ accentColor: "طابع لوني"
textColor: "لون النص"
saveAs: "احفظ كـ..."
advanced: "متقدم"
advancedSettings: "إعدادات متقدمة"
value: "القيمة"
createdAt: "أُنشئ في"
updatedAt: "حُدّث في"
@@ -725,6 +750,7 @@ popularPosts: "المشاركات المتداولة"
shareWithNote: "شاركه في ملاحظة"
ads: "الإعلانات"
expiration: "ينتهي استطلاع الرأي في"
startingperiod: "ابدأ"
memo: "تذكير"
priority: "الأولوية"
high: "عالية"
@@ -755,6 +781,7 @@ lastCommunication: "آخر تواصل"
resolved: "عولج"
unresolved: "لم يعالج"
breakFollow: "إلغاء الاشتراك"
breakFollowConfirm: "أمتأكد من إزالة المتابِع ؟"
itsOn: "مفعّل"
itsOff: "معطّل"
emailRequiredForSignup: "عنوان البريد الإلكتروني إلزامي للتسجيل"
@@ -769,6 +796,7 @@ muteThread: "اكتم النقاش"
unmuteThread: "ارفع الكتم عن النقاش"
ffVisibility: "مرئية المتابِعين/المتابَعين"
ffVisibilityDescription: "يسمح لك بتحديد من يمكنهم رؤية متابِعيك ومتابَعيك."
continueThread: "اعرض بقية النقاش"
deleteAccountConfirm: "سيحذف حسابك نهائيًا، أتريد المتابعة؟"
incorrectPassword: "كلمة السر خاطئة."
voteConfirm: "متيقِّن من تصويتك لـ {choice}؟"
@@ -790,25 +818,127 @@ tenMinutes: "10 دقائق"
oneHour: "ساعة"
oneDay: "يوم"
oneWeek: "أسبوع"
oneMonth: "شهر"
failedToFetchAccountInformation: "تعذر جلب معلومات الحساب"
cropNo: "استخدمها كما هي"
file: "الملفات"
recentNHours: "آخر {n} ساعة"
recentNDays: "آخر {n} أيام"
noEmailServerWarning: "خادم البريد غير مضبوط."
thereIsUnresolvedAbuseReportWarning: "توجد بلاغات غير معالجة."
recommended: "مقترح"
driveCapOverrideLabel: "غيّر حجم قرص التخزين لهذا المستخدم"
driveCapOverrideCaption: "أعد الحجم إلى القيمة الافتراضية بإدخال 0 أو أقل."
requireAdminForView: "لاستعراض هذه الصفحة وجب عليك الولوج كمدير."
typeToConfirm: "أدخل {x} للتأكيد"
deleteAccount: "احذف الحساب"
document: "التوثيق"
numberOfPageCache: "عدد الصفحات المخزنة مؤقتًا"
logoutConfirm: "أتريد الخروج؟"
lastActiveDate: "آخر استخدام"
statusbar: "شريط الحالة"
pleaseSelect: "حدد خيارًا"
reverse: "اقلب"
colored: "ملوّن"
label: "التسمية"
type: "نوع"
speed: "سرعة"
slow: "بطيء"
fast: "سريع"
sensitiveMediaDetection: "التعرف على المحتوى الحساس"
localOnly: "المحلي فقط"
failedToUpload: "فشل الرفع"
cannotUploadBecauseInappropriate: "تعذر رفع الملف لوجود محتوى حساس فيه."
cannotUploadBecauseNoFreeSpace: "تعذر رفع الملف لنقص مساحة التخزين."
cannotUploadBecauseExceedsFileSizeLimit: "تعذر رفع الملف بسبب تجاوز حجمه للحد المسموح"
beta: "بيتا"
navbar: "شريط التنقل"
shuffle: "خلط"
account: "الحسابات"
move: "أنقل"
pushNotification: "إرسال الإشعارات"
subscribePushNotification: "فعّل إرسال الإشعارات"
unsubscribePushNotification: "عطل إرسال الإشعارات"
pushNotificationAlreadySubscribed: "إرسال الإشعارات مفعل سلفًا"
pushNotificationNotSupported: "متصفحك لا يدعم إرسال الإشعارات أو المثيل لا يدعمها."
sendPushNotificationReadMessage: "احذف الإشعارات فور قراءتها"
sendPushNotificationReadMessageCaption: "هذا قد يزيد من معدل استهلاك الطاقة لجهازك."
caption: "التعليق التوضيحي"
tools: "أدوات"
cannotLoad: "تعذر التحميل"
like: "أعجبني"
unlike: "ألغِ الإعجاب"
show: "المظهر"
neverShow: "لا تظهره مجددًا"
didYouLikeMisskey: "هل أعجبك ميسكي؟"
roles: "الأدوار"
role: "الدور"
noRole: "لم يُعثر على دور"
normalUser: "مستخدم عادي"
undefined: "غير معرّف"
color: "اللون"
manageCustomEmojis: "إدارة الإيموجي المخصصة"
cannotPerformTemporary: "غير متاح مؤقتاً"
permissionDeniedError: "رُفضة العملية"
preset: "إعدادات مسبقة"
selectFromPresets: "اختر من الإعدادات المسبقة"
achievements: "الإنجازات"
gotInvalidResponseError: "استجابة غير متوقعة من الخادم"
gotInvalidResponseErrorDescription: "يتعذر الوصول إلى الخادم أوأنه يُصان، رجاءً حاول لاحقًا."
thisPostMayBeAnnoying: "هذا قد يزعج الآخرين."
thisPostMayBeAnnoyingHome: "أنشر في الخط الزمني الرئيس"
thisPostMayBeAnnoyingCancel: "ألغِ"
internalServerError: "خطأ داخلي في الخادم"
internalServerErrorDescription: "واجه الخادم خطأ غي متوقع."
copyErrorInfo: "انسخ تفاصيل الخطأ"
joinThisServer: "سجل في هذا المثيل"
exploreOtherServers: "اعثر على مثيل آخر"
disableFederationOk: "عطّل"
invitationRequiredToRegister: "هذا المثيل للمدعوين فقط. لتسجيل فيه تحتاج رمزًا صالحًا."
postToTheChannel: "انشر في قناة"
cannotBeChangedLater: "لا يمكن تغييره لاحقًا."
reactionAcceptance: "قبول التفاعلات"
rolesAssignedToMe: "الأدوار المسندة إلي"
resetPasswordConfirm: "هل تريد إعادة تعيين كلمة السر؟"
noteIdOrUrl: "معرف الملاحظة أو رابطها"
video: "فيديو"
videos: "فيديوهات"
accountMigration: "ترحيل الحساب"
accountMoved: "نقل هذا المستخدم حسابه:"
accountMovedShort: "رُحل هذا الحساب."
operationForbidden: "عملية ممنوعة"
forceShowAds: "أظهر الإعلانات التجارية دائما"
vertical: "عمودي"
horizontal: "جانبي"
position: "الموضع"
serverRules: "قوانين الخادم"
pleaseConfirmBelowBeforeSignup: "رجاءً وافق على ما يلي قبل التسجيل."
pleaseAgreeAllToContinue: "للمتابعة وافق على الحقول أعلاه."
continue: "متابعة"
preservedUsernames: "أسماء المستخدمين المحجوزة"
preservedUsernamesDescription: "قائمة بأسماء المستخدمين المحجوزة كلٌ في سطر. لن يُقبل التسجيل بهذه الأسماء وستبقى محصورة على التسجيل اليدوي بواسطة المديرين. لن يتأثر المستخدمون الذين يملكون هذه الأسماء سلفًا."
archive: "الأرشيف"
youFollowing: "متابَع"
options: "خيارات"
_role:
new: "دور جديد"
edit: "حرر الأدوار"
name: "اسم الدور"
description: "وصف الدور"
permission: "أذونات الدور"
assignTarget: "نوع الإسناد"
options: "خيارات"
policies: "السياسة العامة"
priority: "الأولوية"
_priority:
low: "منخفضة"
middle: "متوسط"
high: "عالية"
_options:
canManageCustomEmojis: "إدارة الإيموجي المخصصة"
_condition:
isLocal: "مستخدم محلي"
isRemote: "مستخدم بعيد"
_emailUnavailable:
used: "هذا البريد الإلكتروني مستخدم"
format: "صيغة البريد الإلكتروني غير صالحة"
@@ -1056,6 +1186,7 @@ _widgets:
onlineUsers: "المتّصلون"
jobQueue: "قائمة الانتظار"
serverMetric: "إحصائيات الخادم"
userList: "قائمة المستخدمين"
_userList:
chooseList: "اختر قائمة"
_cw:
@@ -1119,6 +1250,7 @@ _profile:
changeBanner: "غيّر اللافتة"
_exportOrImport:
allNotes: "كل الملاحظات"
favoritedNotes: " الملاحظات المفضلة"
followingList: "المتابَعون"
muteList: "المستخدمون المكتومون"
blockingList: "المستخدمون المحجوبون"
@@ -1137,6 +1269,8 @@ _charts:
notesTotal: "إجمالي الملاحظات"
filesIncDec: "تباين عدد الملفات"
filesTotal: "العدد الإجمالي للملفات"
storageUsageIncDec: "التباين في استغلال مساحة التخزين"
storageUsageTotal: "اجمالي مساحة التخزين المستغلة"
_instanceCharts:
requests: "الطلبات"
users: "تباين عدد المستخدمين"
@@ -1256,3 +1390,5 @@ _deck:
_webhookSettings:
name: "الإسم"
active: "مفعّل"
_events:
reaction: "عند تلقي تفاعل"

View File

@@ -52,6 +52,8 @@ addToList: "Zu Liste hinzufügen"
sendMessage: "Nachricht senden"
copyRSS: "RSS kopieren"
copyUsername: "Benutzernamen kopieren"
copyUserId: "Benutzer-ID kopieren"
copyNoteId: "Notiz-ID kopieren"
searchUser: "Nach einem Benutzer suchen"
reply: "Antworten"
loadMore: "Mehr laden"
@@ -790,6 +792,7 @@ noMaintainerInformationWarning: "Betreiberinformationen sind nicht konfiguriert.
noBotProtectionWarning: "Schutz vor Bots ist nicht konfiguriert."
configure: "Konfigurieren"
postToGallery: "Neuen Galeriebeitrag erstellen"
postToHashtag: "Mit diesem Hashtag senden"
gallery: "Galerie"
recentPosts: "Neue Beiträge"
popularPosts: "Beliebte Beiträge"
@@ -823,6 +826,7 @@ translatedFrom: "Aus {x} übersetzt"
accountDeletionInProgress: "Die Löschung deines Benutzerkontos ist momentan in Bearbeitung."
usernameInfo: "Ein Name, durch den dein Benutzerkonto auf diesem Server identifiziert werden kann. Du kannst das Alphabet (a~z, A~Z), Ziffern (0~9) oder Unterstriche (_) verwenden. Benutzernamen können später nicht geändert werden."
aiChanMode: "Ai-Modus"
devMode: "Entwicklermodus"
keepCw: "Inhaltswarnungen beibehalten"
pubSub: "Pub/Sub Benutzerkonten"
lastCommunication: "Letzte Kommunikation"
@@ -832,6 +836,8 @@ breakFollow: "Follower entfernen"
breakFollowConfirm: "Diesen Follower wirklich entfernen?"
itsOn: "Eingeschaltet"
itsOff: "Ausgeschaltet"
on: "An"
off: "Aus"
emailRequiredForSignup: "Angabe einer Email-Adresse als benötigt markieren"
unread: "Ungelesen"
filter: "Filter"
@@ -986,10 +992,13 @@ cannotBeChangedLater: "Kann später nicht mehr geändert werden."
reactionAcceptance: "Reaktionsannahme"
likeOnly: "Nur \"Gefällt mir\""
likeOnlyForRemote: "Nur \"Gefällt mir\" für fremde Instanzen"
nonSensitiveOnly: "Keine Sensitiven"
nonSensitiveOnlyForLocalLikeOnlyForRemote: "Keine Sensitiven (Nur \"Gefällt mir\" von fremden Instanzen)"
rolesAssignedToMe: "Mir zugewiesene Rollen"
resetPasswordConfirm: "Wirklich Passwort zurücksetzen?"
sensitiveWords: "Sensible Wörter"
sensitiveWordsDescription: "Die Notizsichtbarkeit aller Notizen, die diese Wörter enthalten, wird automatisch auf \"Startseite\" gesetzt. Durch Zeilenumbrüche können mehrere konfiguriert werden."
sensitiveWordsDescription2: "Durch die Verwendung von Leerzeichen können AND-Verknüpfungen angegeben werden und durch das Umgeben von Schrägstrichen können reguläre Ausdrücke verwendet werden."
notesSearchNotAvailable: "Die Notizsuche ist nicht verfügbar."
license: "Lizenz"
unfavoriteConfirm: "Wirklich aus Favoriten entfernen?"
@@ -1038,10 +1047,25 @@ thisChannelArchived: "Dieser Kanal wurde archiviert."
displayOfNote: "Anzeige von Notizen"
initialAccountSetting: "Kontoeinrichtung"
youFollowing: "Gefolgt"
preventAiLearning: "Verwendung in machinellem Lernen (Generative bzw. Prediktive AI/KI) ablehnen"
preventAiLearningDescription: "Fordert Crawler auf, gepostetes Text- oder Bildmaterial usw. nicht in Datensätzen für maschinelles Lernen (Generative bzw. Prediktive AI/KI) zu verwenden. Dies wird durch das Hinzufügen einer \"noai\"-Flag in der HTML-Antwort des jeweiligen Inhalts erreicht. Da diese Flag jedoch ignoriert werden kann, ist eine vollständige Verhinderung hierdurch nicht möglich."
options: "Optionen"
specifyUser: "Spezifischer Benutzer"
failedToPreviewUrl: "Vorschau nicht anzeigbar"
update: "Aktualisieren"
rolesThatCanBeUsedThisEmojiAsReaction: "Rollen, die dieses Emoji als Reaktion verwenden können"
rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Sind keine Rollen angegeben, kann jeder dieses Emoji als Reaktion verwenden."
rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Diese Rollen müssen öffentlich sein."
cancelReactionConfirm: "Möchtest du deine Reaktion wirklich löschen?"
changeReactionConfirm: "Möchtest du deine Reaktion wirklich ändern?"
later: "Später"
goToMisskey: "Zu Misskey"
_initialAccountSetting:
accountCreated: "Dein Konto wurde erfolgreich erstellt!"
letsStartAccountSetup: "Lass uns nun dein Konto einrichten."
letsFillYourProfile: "Lass uns zuerst dein Profil einrichten."
profileSetting: "Profileinstellungen"
privacySetting: "Privatsphäreneinstellungen"
theseSettingsCanEditLater: "Diese Einstellungen kannst du jederzeit ändern."
youCanEditMoreSettingsInSettingsPageLater: "In den Einstellungen findest du noch viele weitere Optionen. Schau dort später mal vorbei."
followUsers: "Folge zuerst ein paar Nutzern, um deine Chronik zu füllen."
@@ -1050,6 +1074,7 @@ _initialAccountSetting:
haveFun: "Viel Spaß mit {name}!"
ifYouNeedLearnMore: "Besuche {link}, falls du mehr über {name} (Misskey) lernen möchtest."
skipAreYouSure: "Die Kontoeinrichtung wirklich überspringen?"
laterAreYouSure: "Die Kontoeinrichtung wirklich später erledigen?"
_serverRules:
description: "Eine Reihe von Regeln, die vor der Registrierung angezeigt werden. Eine Zusammenfassung der Nutzungsbedingungen anzuzeigen ist empfohlen."
_accountMigration:

View File

@@ -52,6 +52,8 @@ addToList: "Add to list"
sendMessage: "Send a message"
copyRSS: "Copy RSS"
copyUsername: "Copy username"
copyUserId: "Copy user ID"
copyNoteId: "Copy note ID"
searchUser: "Search for a user"
reply: "Reply"
loadMore: "Load more"
@@ -790,6 +792,7 @@ noMaintainerInformationWarning: "Maintainer information is not configured."
noBotProtectionWarning: "Bot protection is not configured."
configure: "Configure"
postToGallery: "Create new gallery post"
postToHashtag: "Post to this hashtag"
gallery: "Gallery"
recentPosts: "Recent posts"
popularPosts: "Popular posts"
@@ -823,6 +826,7 @@ translatedFrom: "Translated from {x}"
accountDeletionInProgress: "Account deletion is currently in progress"
usernameInfo: "A name that identifies your account from others on this server. You can use the alphabet (a~z, A~Z), digits (0~9) or underscores (_). Usernames cannot be changed later."
aiChanMode: "Ai Mode"
devMode: "Developer mode"
keepCw: "Keep content warnings"
pubSub: "Pub/Sub Accounts"
lastCommunication: "Last communication"
@@ -832,6 +836,8 @@ breakFollow: "Remove follower"
breakFollowConfirm: "Really remove this follower?"
itsOn: "Enabled"
itsOff: "Disabled"
on: "On"
off: "Off"
emailRequiredForSignup: "Require email address for sign-up"
unread: "Unread"
filter: "Filter"
@@ -986,10 +992,13 @@ cannotBeChangedLater: "This cannot be changed later."
reactionAcceptance: "Reaction Acceptance"
likeOnly: "Only likes"
likeOnlyForRemote: "Only likes for remote instances"
nonSensitiveOnly: "Non-sensitive only"
nonSensitiveOnlyForLocalLikeOnlyForRemote: "Non-sensitive only (Only likes from remote)"
rolesAssignedToMe: "Roles assigned to me"
resetPasswordConfirm: "Really reset your password?"
sensitiveWords: "Sensitive words"
sensitiveWordsDescription: "The visibility of all notes containing any of the configured words will be set to \"Home\" automatically. You can list multiple by separating them via line breaks."
sensitiveWordsDescription2: "Using spaces will create AND expressions and surrounding keywords with slashes will turn them into a regular expression."
notesSearchNotAvailable: "Note search is unavailable."
license: "License"
unfavoriteConfirm: "Really remove from favorites?"
@@ -1036,20 +1045,36 @@ channelArchiveConfirmTitle: "Really archive {name}?"
channelArchiveConfirmDescription: "An archived channel won't appear in the channel list or search results anymore. New posts can also not be added to it anymore."
thisChannelArchived: "This channel has been archived."
displayOfNote: "Note display"
initialAccountSetting: "Profile configuration"
initialAccountSetting: "Profile setup"
youFollowing: "Followed"
preventAiLearning: "Reject usage in Machine Learning (Generative AI)"
preventAiLearningDescription: "Requests crawlers to not use posted text or image material etc. in machine learning (Predictive / Generative AI) data sets. This is achieved by adding a \"noai\" HTML-Response flag to the respective content. A complete prevention can however not be achieved through this flag, as it may simply be ignored."
options: "Options"
specifyUser: "Specific user"
failedToPreviewUrl: "Could not preview"
update: "Update"
rolesThatCanBeUsedThisEmojiAsReaction: "Roles that can use this emoji as reaction"
rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "If no roles are specified, anyone can use this emoji as reaction."
rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "These roles must be public."
cancelReactionConfirm: "Really delete your reaction?"
changeReactionConfirm: "Really change your reaction?"
later: "Later"
goToMisskey: "To Misskey"
_initialAccountSetting:
accountCreated: "Your account was successfully created!"
letsStartAccountSetup: "For starters, let's set up your profile."
letsFillYourProfile: "First, let's set up your profile."
profileSetting: "Profile settings"
privacySetting: "Privacy settings"
theseSettingsCanEditLater: "You can always change these settings later."
youCanEditMoreSettingsInSettingsPageLater: "There are many more settings you can configure from the \"Settings\" page. Be sure to visit it later."
followUsers: "Try following some users that interest you to build up your timeline."
pushNotificationDescription: "Enabling push notifications will allow you to receive notifications from {name} directly on your device."
initialAccountSettingCompleted: "Profile configuration complete!"
initialAccountSettingCompleted: "Profile setup complete!"
haveFun: "Enjoy {name}!"
ifYouNeedLearnMore: "If you'd like to learn more about how to use {name} (Misskey), please visit {link}."
skipAreYouSure: "Really skip profile configuration?"
skipAreYouSure: "Really skip profile setup?"
laterAreYouSure: "Really do profile setup later?"
_serverRules:
description: "A set of rules to be displayed before registration. Setting a summary of the Terms of Service is recommended."
_accountMigration:
@@ -1321,7 +1346,7 @@ _role:
isConditionalRole: "This is a conditional role."
isPublic: "Public role"
descriptionOfIsPublic: "Anyone will be able to view a list of users assigned to this role. In addition, this role will be displayed in the profiles of assigned users."
options: "Role options"
options: "Options"
policies: "Policies"
baseRole: "Role template"
useBaseValue: "Use role template value"

View File

@@ -20,6 +20,7 @@ noNotes: "No hay notas"
noNotifications: "No hay notificaciones"
instance: "Instancia"
settings: "Configuración"
notificationSettings: "Configurar las notificaciones"
basicSettings: "Configuración Básica"
otherSettings: "Configuración avanzada"
openInWindow: "Abrir en una ventana"
@@ -262,8 +263,10 @@ noMoreHistory: "El historial se ha acabado"
startMessaging: "Iniciar chat"
nUsersRead: "Leído por {n} personas"
agreeTo: "De acuerdo con {0}"
agree: "De acuerdo."
agreeBelow: "Estoy de acuerdo con lo siguiente"
basicNotesBeforeCreateAccount: "Notas básicas"
termsOfService: "Términos y condiciones"
start: "Comenzar"
home: "Inicio"
remoteUserCaution: "Para el usuario remoto, la información está incompleta"
@@ -473,6 +476,8 @@ createAccount: "Crear cuenta"
existingAccount: "Cuenta existente"
regenerate: "Regenerar"
fontSize: "Tamaño de la letra"
mediaListWithOneImageAppearance: "Altura de la lista de medios con una sola imagen."
limitTo: "{x} hasta un máximo de"
noFollowRequests: "No hay solicitudes de seguimiento"
openImageInNewTab: "Abrir imagen en nueva pestaña"
dashboard: "Panel de control"
@@ -555,6 +560,7 @@ accountDeletedDescription: "Esta cuenta ha sido borrada."
menu: "Menú"
divider: "Divisor"
addItem: "Agregar elemento"
rearrange: "Ordenar"
relays: "Relés"
addRelay: "Agregar relé"
inboxUrl: "Inbox URL"
@@ -698,6 +704,8 @@ contact: "Contacto"
useSystemFont: "Utilizar la tipografía por defecto del sistema"
clips: "Clip"
experimentalFeatures: "Características experimentales"
experimental: "Función experimental"
thisIsExperimentalFeature: "Se trata de una función experimental. Las especificaciones pueden cambiar o puede que no funcione correctamente."
developer: "Desarrolladores"
makeExplorable: "Hacer visible la cuenta en \"Explorar\""
makeExplorableDescription: "Si desactiva esta opción, su cuenta no aparecerá en la sección \"Explorar\"."
@@ -991,8 +999,12 @@ largeNoteReactions: "Agrandar las reacciones de las notas"
noteIdOrUrl: "ID o URL de la nota"
accountMigration: "Migración de cuenta"
accountMoved: "Este usuario se ha mudado a una nueva cuenta:"
accountMovedShort: "Esta cuenta ha sido migrada."
horizontal: "Horizontal"
youFollowing: "Siguiendo"
options: "Opción"
_initialAccountSetting:
accountCreated: "¡La cuenta ha sido creada!"
_accountMigration:
moveFrom: "Trasladar de otra cuenta a ésta"
moveFromLabel: "Cuenta desde la que se realiza el traslado:"
@@ -1173,6 +1185,8 @@ _achievements:
_client30min:
title: "Un descansito"
description: "30 minutos dedicados a Misskey"
_client60min:
title: "Viendo mucho Misskey."
_noteDeletedWithin1min:
title: "Ah... Mejor no..."
description: "Borrar una nota antes que de pase 1 minuto"

View File

@@ -452,6 +452,7 @@ native: "Natif"
disableDrawer: "Les menus ne s'affichent pas dans le tiroir"
noHistory: "Pas d'historique"
signinHistory: "Historique de connexion"
enableAdvancedMfm: "Activer la MFM avancée"
doing: "En cours..."
category: "Catégorie"
tags: "Étiquettes"
@@ -846,6 +847,7 @@ rateLimitExceeded: "Limite de taux dépassée"
cropImage: "Recadrer l'image"
cropImageAsk: "Voulez-vous recadrer cette image ?"
cropYes: "Rogner"
cropNo: "Utiliser en l'état"
file: "Fichiers"
recentNHours: "Dernières {n} heures"
recentNDays: "Derniers {n} jours"
@@ -906,18 +908,22 @@ neverShow: "Ne plus afficher"
remindMeLater: "Peut-être plus tard"
roles: "Rôles"
role: "Rôles"
noRole: "Aucun rôle"
normalUser: "Simple utilisateur·rice"
assign: "Attribuer"
color: "Couleur"
manageCustomEmojis: "Gestion des émojis personnalisés"
preset: "Préréglage"
selectFromPresets: "Sélectionner à partir des préréglages"
thisPostMayBeAnnoying: "Cette note peut gêner d'autres personnes."
thisPostMayBeAnnoyingCancel: "Annuler"
license: "Licence"
video: "Vidéo"
videos: "Vidéos"
dataSaver: "Économiseur de données"
accountMigration: "Migration de compte"
accountMoved: "Cet·te utilisateur·rice a migré son compte vers :"
addMemo: "Ajouter un mémo"
notificationDisplay: "Style des notifications"
leftTop: "En haut à gauche"
rightTop: "En haut à droite"
@@ -925,19 +931,25 @@ leftBottom: "En bas à gauche"
rightBottom: "En bas à droite"
vertical: "Vertical"
horizontal: "Latéral"
serverRules: "Règles du serveur"
youFollowing: "Abonné·e"
_achievements:
_types:
_notes1:
description: "Publiez votre première note"
flavor: "Passez un bon moment avec Misskey !"
_notes100:
title: "Beaucoup de notes"
_notes100000:
title: "ALL YOUR NOTE ARE BELONG TO US"
_login3:
title: "Débutant "
description: "Se connecter pour un total de 3 jours"
_login7:
title: "Débutant Ⅱ"
description: "Se connecter pour un total de 7 jours"
_login15:
title: "Débutant Ⅲ"
description: "Se connecter pour un total de 15 jours"
_login30:
description: "Se connecter pour un total de 30 jours"
@@ -945,6 +957,22 @@ _achievements:
description: "Se connecter pour un total de 60 jours"
_login100:
description: "Se connecter pour un total de 100 jours"
_login200:
description: "Se connecter pour un total de 200 jours"
_login300:
description: "Se connecter pour un total de 300 jours"
_login400:
description: "Se connecter pour un total de 400 jours"
_login500:
description: "Se connecter pour un total de 500 jours"
_login600:
description: "Se connecter pour un total de 600 jours"
_login700:
description: "Se connecter pour un total de 700 jours"
_login800:
description: "Se connecter pour un total de 800 jours"
_login900:
description: "Se connecter pour un total de 900 jours"
_login1000:
flavor: "Merci d'utiliser Misskey !"
_markedAsCat:
@@ -954,10 +982,16 @@ _achievements:
title: "Beaucoup d'amis"
_followers10:
title: "Abonnez-moi !"
_iLoveMisskey:
title: "Jadore Misskey"
_viewInstanceChart:
title: "Analyste"
_loggedInOnBirthday:
title: "Joyeux Anniversaire !"
_loggedInOnNewYearsDay:
title: "Bonne année !"
_cookieClicked:
flavor: "Attendez une minute, vous êtes sur le mauvais site web ?"
_role:
assignTarget: "Attribuer"
priority: "Priorité"

72
locales/generateDTS.js Normal file
View File

@@ -0,0 +1,72 @@
const fs = require('fs');
const yaml = require('js-yaml');
const ts = require('typescript');
function createMembers(record) {
return Object.entries(record)
.map(([k, v]) => ts.factory.createPropertySignature(
undefined,
ts.factory.createStringLiteral(k),
undefined,
typeof v === 'string'
? ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
: ts.factory.createTypeLiteralNode(createMembers(v)),
));
}
module.exports = function generateDTS() {
const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8'));
const members = createMembers(locale);
const elements = [
ts.factory.createInterfaceDeclaration(
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
ts.factory.createIdentifier('Locale'),
undefined,
undefined,
members,
),
ts.factory.createVariableStatement(
[ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
ts.factory.createVariableDeclarationList(
[ts.factory.createVariableDeclaration(
ts.factory.createIdentifier('locales'),
undefined,
ts.factory.createTypeLiteralNode([ts.factory.createIndexSignature(
undefined,
[ts.factory.createParameterDeclaration(
undefined,
undefined,
ts.factory.createIdentifier('lang'),
undefined,
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
undefined,
)],
ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier('Locale'),
undefined,
),
)]),
undefined,
)],
ts.NodeFlags.Const | ts.NodeFlags.Ambient | ts.NodeFlags.ContextFlags,
),
),
ts.factory.createExportAssignment(
undefined,
true,
ts.factory.createIdentifier('locales'),
),
];
const printed = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed,
}).printList(
ts.ListFormat.MultiLine,
ts.factory.createNodeArray(elements),
ts.createSourceFile('index.d.ts', '', ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS),
);
fs.writeFileSync(`${__dirname}/index.d.ts`, `/* eslint-disable */
// This file is generated by locales/generateDTS.js
// Do not edit this file directly.
${printed}`, 'utf-8');
}

View File

@@ -20,6 +20,7 @@ noNotes: "Tidak ada catatan"
noNotifications: "Tidak ada pemberitahuan"
instance: "Instansi"
settings: "Pengaturan"
notificationSettings: "Atur Notifikasi"
basicSettings: "Pengaturan umum"
otherSettings: "Pengaturan lainnya"
openInWindow: "Buka di jendela"
@@ -261,8 +262,10 @@ noMoreHistory: "Tidak ada sejarah lagi"
startMessaging: "Mulai mengirim pesan"
nUsersRead: "Dibaca oleh {n}"
agreeTo: "Saya setuju kepada {0}"
agree: "Setuju"
agreeBelow: "Saya setuju dengan di bawah ini"
basicNotesBeforeCreateAccount: "Catatan penting"
termsOfService: "Syarat dan ketentuan"
start: "Mulai"
home: "Beranda"
remoteUserCaution: "Informasi ini mungkin tidak mutakhir, karena pengguna ini berasal dari instansi luar."
@@ -955,6 +958,7 @@ disableFederationConfirmWarn: "Mematikan federasi tidak membuat kiriman menjadi
disableFederationOk: "Matikan federasi"
horizontal: "Horisontal"
youFollowing: "Mengikuti"
options: "Opsi peran"
_achievements:
earnedAt: "Terbuka pada"
_types:

2150
locales/index.d.ts vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1020,6 +1020,7 @@ pleaseConfirmBelowBeforeSignup: "Ai sensi del regolamento EU 679/2016 GDPR, auto
pleaseAgreeAllToContinue: "Per continuare, occorre selezionare ed essere d'accordo su tutto."
continue: "Continua"
youFollowing: "Seguiti"
options: "Opzioni del ruolo"
_serverRules:
description: "In Europa è necessario mostrare l'informativa sul trattamento dei dati personali, prima della registrazione al servizio."
_accountMigration:

View File

@@ -52,6 +52,8 @@ addToList: "リストに追加"
sendMessage: "メッセージを送信"
copyRSS: "RSSをコピー"
copyUsername: "ユーザー名をコピー"
copyUserId: "ユーザーIDをコピー"
copyNoteId: "ートIDをコピー"
searchUser: "ユーザーを検索"
reply: "返信"
loadMore: "もっと見る"
@@ -689,7 +691,7 @@ no: "いいえ"
driveFilesCount: "ドライブのファイル数"
driveUsage: "ドライブ使用量"
noCrawle: "クローラーによるインデックスを拒否"
noCrawleDescription: "検索エンジンにあなたのユーザーページ、ート、Pagesなどのコンテンツを登録(インデックス)しないよう要します。"
noCrawleDescription: "外部の検索エンジンにあなたのユーザーページ、ート、Pagesなどのコンテンツを登録(インデックス)しないよう要します。"
lockedAccountInfo: "フォローを承認制にしても、ノートの公開範囲を「フォロワー」にしない限り、誰でもあなたのノートを見ることができます。"
alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にする"
loadRawImages: "添付画像のサムネイルをオリジナル画質にする"
@@ -790,6 +792,7 @@ noMaintainerInformationWarning: "管理者情報が設定されていません
noBotProtectionWarning: "Botプロテクションが設定されていません。"
configure: "設定する"
postToGallery: "ギャラリーへ投稿"
postToHashtag: "このハッシュタグで投稿"
gallery: "ギャラリー"
recentPosts: "最近の投稿"
popularPosts: "人気の投稿"
@@ -823,6 +826,7 @@ translatedFrom: "{x}から翻訳"
accountDeletionInProgress: "アカウントの削除が進行中です"
usernameInfo: "サーバー上であなたのアカウントを一意に識別するための名前。アルファベット(a~z, A~Z)、数字(0~9)、およびアンダーバー(_)が使用できます。ユーザー名は後から変更することは出来ません。"
aiChanMode: "藍モード"
devMode: "開発者モード"
keepCw: "CWを維持する"
pubSub: "Pub/Subのアカウント"
lastCommunication: "直近の通信"
@@ -832,6 +836,8 @@ breakFollow: "フォロワーを解除"
breakFollowConfirm: "フォロワー解除しますか?"
itsOn: "オンになっています"
itsOff: "オフになっています"
on: "オン"
off: "オフ"
emailRequiredForSignup: "アカウント登録にメールアドレスを必須にする"
unread: "未読"
filter: "フィルタ"
@@ -985,11 +991,14 @@ postToTheChannel: "チャンネルに投稿"
cannotBeChangedLater: "後から変更できません。"
reactionAcceptance: "リアクションの受け入れ"
likeOnly: "いいねのみ"
likeOnlyForRemote: "リモートからはいいねのみ"
likeOnlyForRemote: "全て (リモートはいいねのみ)"
nonSensitiveOnly: "非センシティブのみ"
nonSensitiveOnlyForLocalLikeOnlyForRemote: "非センシティブのみ (リモートはいいねのみ)"
rolesAssignedToMe: "自分に割り当てられたロール"
resetPasswordConfirm: "パスワードリセットしますか?"
sensitiveWords: "センシティブワード"
sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。"
sensitiveWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
notesSearchNotAvailable: "ノート検索は利用できません。"
license: "ライセンス"
unfavoriteConfirm: "お気に入り解除しますか?"
@@ -1038,12 +1047,27 @@ thisChannelArchived: "このチャンネルはアーカイブされています
displayOfNote: "ノートの表示"
initialAccountSetting: "初期設定"
youFollowing: "フォロー中"
preventAiLearning: "生成AIによる学習を拒否"
preventAiLearningDescription: "外部の文章生成AIや画像生成AIに対して、投稿したートや画像などのコンテンツを学習の対象にしないように要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されますが、この要求に従うかはそのAI次第であるため、学習を完全に防止するものではありません。"
options: "オプション"
specifyUser: "ユーザー指定"
failedToPreviewUrl: "プレビューできません"
update: "更新"
rolesThatCanBeUsedThisEmojiAsReaction: "リアクションとして使えるロール"
rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "ロールの指定が一つもない場合、誰でもリアクションとして使えます。"
rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "ロールは公開ロールである必要があります。"
cancelReactionConfirm: "リアクションを取り消しますか?"
changeReactionConfirm: "リアクションを変更しますか?"
later: "あとで"
goToMisskey: "Misskeyへ"
additionalEmojiDictionary: "絵文字の追加辞書"
_initialAccountSetting:
accountCreated: "アカウントの作成が完了しました!"
letsStartAccountSetup: "アカウントの初期設定を行いましょう。"
letsFillYourProfile: "まずはあなたのプロフィールを設定しましょう。"
profileSetting: "プロフィール設定"
privacySetting: "プライバシー設定"
theseSettingsCanEditLater: "これらの設定は後から変更できます。"
youCanEditMoreSettingsInSettingsPageLater: "この他にも様々な設定を「設定」ページから行えます。ぜひ後で確認してみてください。"
followUsers: "タイムラインを構築するため、気になるユーザーをフォローしてみましょう。"
@@ -1052,6 +1076,7 @@ _initialAccountSetting:
haveFun: "{name}をお楽しみください!"
ifYouNeedLearnMore: "{name}(Misskey)の使い方などを詳しく知るには{link}をご覧ください。"
skipAreYouSure: "初期設定をスキップしますか?"
laterAreYouSure: "初期設定をあとでやり直しますか?"
_serverRules:
description: "新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。"

View File

@@ -8,7 +8,7 @@ search: "探す"
notifications: "通知"
username: "ユーザー名"
password: "パスワード"
forgotPassword: "パスワード忘れてもうた"
forgotPassword: "パスワード忘れたん?"
fetchingAsApObject: "今ちと連合に照会しとるで"
ok: "ええで"
gotIt: "ほい"
@@ -47,11 +47,13 @@ copyContent: "内容をコピー"
copyLink: "リンクをコピー"
delete: "ほかす"
deleteAndEdit: "ほかして直す"
deleteAndEditConfirm: "このノートをほかしてもっかい直す?このノートへのリアクション、Renote、返信も全部消えるんやけどそれでもええん"
deleteAndEditConfirm: "このノートをほかしてもっかい直す?このノートへのツッコミ、Renote、返信も全部消えるんやけどそれでもええん"
addToList: "リストに入れたる"
sendMessage: "メッセージを送る"
copyRSS: "RSSをコピー"
copyUsername: "ユーザー名をコピー"
copyUserId: "ユーザーIDをコピー"
copyNoteId: "ートIDをコピー"
searchUser: "ユーザーを検索"
reply: "返事"
loadMore: "まだまだあるで!"
@@ -689,7 +691,7 @@ no: "あかん"
driveFilesCount: "ドライブのファイル数"
driveUsage: "ドライブ使用量やで"
noCrawle: "クローラーによるインデックスを拒否するで"
noCrawleDescription: "検索エンジンにあんたのユーザーページ、ート、Pagesとかのコンテンツを登録(インデックス)せんように頼むで。邪魔すんねんやったら帰って〜。"
noCrawleDescription: "検索エンジンにあんたのユーザーページ、ート、Pagesとかのコンテンツを登録(インデックス)せぇへんように頼むで。"
lockedAccountInfo: "フォローを承認制にしとっても、ノートの公開範囲を「フォロワー」にせぇへん限り、誰でもあんたのノートを見れるで。"
alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にするで"
loadRawImages: "添付画像のサムネイルをオリジナル画質にするで"
@@ -990,6 +992,7 @@ rolesAssignedToMe: "自分に割り当てられたロール"
resetPasswordConfirm: "パスワード作り直すんでええな?"
sensitiveWords: "けったいな単語"
sensitiveWordsDescription: "設定した単語が入っとるノートの公開範囲をホームにしたるわ。改行で区切ったら複数設定できるで。"
sensitiveWordsDescription2: "スペースで区切るとAND指定、キーワードをスラッシュで囲んだら正規表現や。"
notesSearchNotAvailable: "ノート検索は使われへんで。"
license: "ライセンス"
unfavoriteConfirm: "ほんまに気に入らんの?"
@@ -1038,10 +1041,20 @@ thisChannelArchived: "このチャンネル、アーカイブされとるで。"
displayOfNote: "ノートの表示"
initialAccountSetting: "初期設定"
youFollowing: "フォロー中やで"
preventAiLearning: "生成AIの学習に使わんといて"
preventAiLearningDescription: "他の文章生成AIとか画像生成AIに、投稿したートとか画像なんかを勝手に使わんように頼むで。具体的にはnoaiフラグをHTMLレスポンスに含めるんやけど、これ聞いてくれるんはAIの気分次第やから、使われる可能性もちょっとはあるな。"
options: "オプション"
specifyUser: "ユーザー指定"
rolesThatCanBeUsedThisEmojiAsReaction: "ツッコミとして使えるロール"
rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "ロールが一個も指定されてへんかったら、誰でもツッコミとして使えるで。"
cancelReactionConfirm: "ツッコむんをやっぱやめるか?"
changeReactionConfirm: "ツッコミを別のに変えるか?"
_initialAccountSetting:
accountCreated: "アカウント作り終わったで。"
letsStartAccountSetup: "アカウントの初期設定をしよか。"
letsFillYourProfile: "最初はあんたのプロフィールを設定しよか。"
profileSetting: "プロフィール設定"
privacySetting: "プライバシー設定"
theseSettingsCanEditLater: "この設定はあとから変えれるで。"
youCanEditMoreSettingsInSettingsPageLater: "これ以外にもいろんな設定を「設定」ページからできるで。後で確認してみてな。"
followUsers: "タイムラインを構築するために、気になるユーザーをフォローしてみ。"
@@ -1060,11 +1073,12 @@ _accountMigration:
moveTo: "このアカウントをさらのアカウントに引っ越すで"
moveToLabel: "引っ越し先のアカウント:"
moveCannotBeUndone: "アカウントを移行すると、取り消すことはできへんくなります。"
moveAccountDescription: "この操作は戻されへんで。まず引っ越し先のアカウントでこのアカウントへのエイリアスが作れたか確認してきなはれや。エイリアスができてたら、引っ越し先のアカウントをこんな風に入力してくれへんか?:@person@instance.com"
moveAccountDescription: "おニューのアカウントに移行すんで。\n ・フォロワーがおニューの方を勝手にフォローすんで。\n ・このアカウントからのフォローはまるまる全部解除されんで。\n ・このアカウントでート作れへんようになるで。\n\nフォロワーの移行は勝手にこっちでやっとくけど、フォローの移行は自分でしてや。移行前にこのアカウントでフォローエクスポートして、移行したあとすぐにおニューのところでインポートしてくれな。\nリストとかミュート、あとブロックもおんなじや。自分で移行してな。\n\nこの説明はこのサーバー、つまりMisskey v13.12.0から後の仕様や。Mastodonとか他のActivityPubソフトやとちょっと挙動が違うこともあんで。"
moveAccountHowTo: "アカウントの引っ越しには、まず引っ越し先のアカウントで自分のアカウントに対しエイリアスを作成しなはれや。\nエイリアス作成した後、引っ越し先のアカウントを次のように入力してくれへんか:@username@server.example.com"
startMigration: "引っ越しする"
migrationConfirm: "ほんまにこのアカウントを {account} に引っ越すんか?一回引っ越してもうたら取り消されへんし、二度とこのアカウントを元に戻されへんくなるで。\nそれと、引っ越し先のアカウントでエイリアスが作れたかちゃんと確認しーや"
movedAndCannotBeUndone: "\nアカウントはもう引っ越されてます。\n引っ越しを取り消すことはできまへん。"
postMigrationNote: "このアカウントからのフォロー解除は移行操作から丸一日経ったら実行されんで。\nこのアカウントのフォロー・フォロワー数はどっちも0や。フォローの解除はされへんから、あんたのフォロワーはこのアカウントのフォロワー向けの投稿をこの後も見れるで。"
movedTo: "引っ越し先のアカウント:"
_achievements:
earnedAt: "貰った日ぃ"
@@ -1606,7 +1620,7 @@ _timelineTutorial:
step2_2: "最初のノートは、自己紹介とか「{name}始めてみたんや」とかがええと思うで。"
step3_1: "投稿できた?"
step3_2: "あんたのノートがタイムラインに出てきたら成功や。"
step4_1: "ノートには、「リアクション」を付けれるで。"
step4_1: "ノートには、「ツッコミ」を付けれるで。"
step4_2: "ツッコむんやったら、ノートの「+」マークを押して、好きな絵文字を選ぶで。"
_2fa:
alreadyRegistered: "もう設定終わっとるわ。"

View File

@@ -52,6 +52,8 @@ addToList: "리스트에 추가"
sendMessage: "메시지 보내기"
copyRSS: "RSS 복사"
copyUsername: "유저명 복사"
copyUserId: "유저 ID 복사"
copyNoteId: "노트 ID 복사"
searchUser: "사용자 검색"
reply: "답글"
loadMore: "더 보기"
@@ -505,7 +507,7 @@ objectStoragePrefixDesc: "이 Prefix 의 디렉토리 아래에 파일이 저장
objectStorageEndpoint: "Endpoint"
objectStorageEndpointDesc: "AWS S3의 경우 공란, 다른 서비스의 경우 각 서비스의 가이드에 맞게 endpoint를 설정해주세요. '<host>' 혹은 '<host>:<port>' 와 같이 지정합니다."
objectStorageRegion: "Region"
objectStorageRegionDesc: "'xx-east-1'와 같이 region을 지정해주세요. 사용하는 서비스에 region 개념이 없는 경우, 비워 두거나 'us-east-1'으로 설정해 주세요."
objectStorageRegionDesc: "'xx-east-1'와 같이 region을 지정해 주세요. 사용하는 서비스에 region 개념이 없는 경우 'us-east-1'으로 설정해 주세요. AWS 설정 파일 또는 환경 변수를 참조할 경우에는 비워주세요."
objectStorageUseSSL: "SSL 사용"
objectStorageUseSSLDesc: "API 호출시 HTTPS 를 사용하지 않는 경우 OFF 로 설정해 주세요"
objectStorageUseProxy: "연결에 프록시를 사용"
@@ -790,6 +792,7 @@ noMaintainerInformationWarning: "관리자 정보가 설정되어 있지 않습
noBotProtectionWarning: "Bot 방어가 설정되어 있지 않습니다."
configure: "설정하기"
postToGallery: "갤러리에 업로드"
postToHashtag: "이 해시태그에 게시"
gallery: "갤러리"
recentPosts: "최근 포스트"
popularPosts: "인기 포스트"
@@ -823,6 +826,7 @@ translatedFrom: "{x}에서 번역"
accountDeletionInProgress: "계정 삭제 작업을 진행하고 있습니다"
usernameInfo: "서버상에서 계정을 식별하기 위한 이름. 알파벳(a~z, A~Z), 숫자(0~9) 및 언더바(_)를 사용할 수 있습니다. 사용자명은 나중에 변경할 수 없습니다."
aiChanMode: "아이 모드"
devMode: "개발자 모드"
keepCw: "CW 유지하기"
pubSub: "Pub/Sub 계정"
lastCommunication: "마지막 통신"
@@ -830,8 +834,10 @@ resolved: "해결됨"
unresolved: "해결되지 않음"
breakFollow: "팔로워 해제"
breakFollowConfirm: "팔로우를 해제하시겠습니까?"
itsOn: "켜"
itsOff: "꺼"
itsOn: "켜져 있습니다"
itsOff: "꺼져 있습니다"
on: "켜짐"
off: "꺼짐"
emailRequiredForSignup: "가입할 때 이메일 주소 입력을 필수로 하기"
unread: "읽지 않음"
filter: "필터"
@@ -986,10 +992,13 @@ cannotBeChangedLater: "나중에 변경할 수 없습니다."
reactionAcceptance: "리액션 수신"
likeOnly: "좋아요만 받기"
likeOnlyForRemote: "리모트에서는 좋아요만 받기"
nonSensitiveOnly: "열람 주의로 설정되지 않았을 때만 받기"
nonSensitiveOnlyForLocalLikeOnlyForRemote: "열람 주의로 설정되지 않았을 때만 받기 (리모트에서는 좋아요만 받기)"
rolesAssignedToMe: "나에게 할당된 역할"
resetPasswordConfirm: "비밀번호를 재설정하시겠습니까?"
sensitiveWords: "민감한 단어"
sensitiveWordsDescription: "설정한 단어가 포함된 노트의 공개 범위를 '홈'으로 강제합니다. 개행으로 구분하여 여러 개를 지정할 수 있습니다."
sensitiveWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
notesSearchNotAvailable: "노트 검색을 이용하실 수 없습니다."
license: "라이선스"
unfavoriteConfirm: "즐겨찾기를 해제하시겠습니까?"
@@ -1038,30 +1047,45 @@ thisChannelArchived: "이 채널은 아카이브되었습니다."
displayOfNote: "노트 표시"
initialAccountSetting: "초기 설정"
youFollowing: "팔로잉"
preventAiLearning: "기계학습(생성형 AI)으로의 사용을 거부"
preventAiLearningDescription: "외부의 문장 생성 AI나 이미지 생성 AI에 대해 제출한 노트나 이미지 등의 콘텐츠를 학습의 대상으로 사용하지 않도록 요구합니다. 다만, 이 요구사항을 지킬 의무는 없기 때문에 학습을 완전히 방지하는 것은 아닙니다."
options: "옵션"
specifyUser: "사용자 지정"
failedToPreviewUrl: "미리 볼 수 없음"
update: "업데이트"
rolesThatCanBeUsedThisEmojiAsReaction: "이 이모지를 리액션으로 사용할 수 있는 역할"
rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "역할을 지정하지 않으면, 누구나 이 이모지를 리액션으로 사용할 수 있습니다."
rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "역할은 공개로 설정되어 있어야 합니다."
cancelReactionConfirm: "리액션을 취소하시겠습니까?"
changeReactionConfirm: "리액션을 변경하시겠습니까?"
later: "나중에"
goToMisskey: "Misskey로"
_initialAccountSetting:
accountCreated: "계정 생성이 완료되었습니다!"
letsStartAccountSetup: "계정의 초기 설정을 진행합니다."
letsFillYourProfile: "우선 나의 프로필을 설정해 보아요."
profileSetting: "프로필 설정"
privacySetting: "프라이버시 설정"
theseSettingsCanEditLater: "이 설정들은 나중에도 변경할 수 있습니다."
youCanEditMoreSettingsInSettingsPageLater: "이 외에도 '설정' 페이지에서 다양한 설정을 나의 입맛에 게 조절할 수 있습니다. 꼭 확인해 보세요!"
youCanEditMoreSettingsInSettingsPageLater: "이 외에도 '설정' 페이지에서 다양한 설정을 나의 입맛에 게 조절할 수 있습니다. 꼭 확인해 보세요!"
followUsers: "관심사가 맞는 유저를 팔로우하여 타임라인을 가꾸어 봅시다."
pushNotificationDescription: "푸시 알림을 활성화하면 {name}의 알림을 나의 기기에서 받아볼 수 있게 됩니다."
initialAccountSettingCompleted: "초기 설정을 모두 마쳤습니다!"
haveFun: "{name}와 함께 즐거운 시간 보내세요!"
ifYouNeedLearnMore: "{name}(Misskey)의 사용 방법에 대해 자세히 알아보려면 {link}를 참고해 주세요."
skipAreYouSure: "초기 설정을 넘기시겠습니까?"
skipAreYouSure: "초기 설정을 중단하시겠습니까?"
laterAreYouSure: "초기 설정을 나중에 진행하시겠습니까?"
_serverRules:
description: "회원 가입 이전에 간단하게 표시할 서버 규칙입니다. 이용 약관의 요약으로 구성하는 것을 추천합니다."
_accountMigration:
moveFrom: "다른 계정에서 이 계정으로 이사"
moveFromSub: "다른 계정에 대한 별칭을 생성"
moveFromLabel: "기존 계정:"
moveFromLabel: "기존 계정 #{n}"
moveFromDescription: "다른 계정에서 이 계정으로 팔로워를 가져오려면, 우선 여기에서 별칭을 지정해야 합니다. 반드시 이사하기 전에 지정해야 합니다! 기존 계정을 다음과 같은 형식으로 입력해 주십시오: @person@instance.com"
moveTo: "이 계정에서 다른 계정으로 이사"
moveToLabel: "이사할 계정:"
moveCannotBeUndone: "한 번 이사하면, 두 번 다시 되돌릴 수 없습니다."
moveAccountDescription: "이 작업은 취소할 수 없습니다. 먼저 이사할 계정에서 이 계정에 대한 별칭을 지정하였는지 다시 한 번 확인해 주십시오. 별칭을 지정한 다음, 이사할 계정을 다음과 같은 형식으로 입력해 주십시오: @person@instance.com"
moveAccountDescription: "새 계정으로 이전합니다.\n ・팔로워가 새 계정을 자동으로 팔로우 합니다\n ・이 계정에서 팔로우는 모두 해제됩니다\n ・이 계정으로는 노트 작성 등을 할 수 없게 됩니다\n\n팔로워의 이전은 자동이지만, 팔로우는 수동으로 진행해야 합니다. 이전하기 전에 이 계정에서 팔로우를 내보내고, 이전 후에는 즉시 이전한 계정에서 가져오기를 진행하십시오.\n리스트・뮤트・차단에 대해서도 마찬가지이므로 수동으로 이전해야 합니다.\n\n(이 설명은 이 서버(Misskey v13.12.0 이후)의 사양입니다. Mastodon 등의 다른 ActivityPub 소프트웨어에서는 작동이 다를 수 있습니다.)"
moveAccountHowTo: "계정을 이사하려면 우선 이사갈 계정에서 이 계정에 대한 별칭을 지정해야 합니다.\n별칭을 작성한 다음, 이사갈 계정을 다음과 같이 입력하십시오:\n@username@server.example.com"
startMigration: "이사하기"
migrationConfirm: "정말로 이 계정을 {account} 으로 이전하시겠습니까? 한 번 이전한 다음에는 취소할 수 없으며, 두 번 다시 원래 상태로 복구할 수 없습니다.\n이사할 계정에서 계정 별칭을 지정하였는지 다시 한 번 확인하십시오."
@@ -1240,6 +1264,7 @@ _achievements:
title: "잠깐 쉬어"
description: "클라이언트를 시작하고 30분이 경과하였습니다"
_client60min:
title: "No \"Miss\" in Misskey"
description: "클라이언트를 시작하고 60분이 경과하였습니다"
_noteDeletedWithin1min:
title: "있었는데요 없었습니다"
@@ -1489,7 +1514,7 @@ _menuDisplay:
hide: "숨기기"
_wordMute:
muteWords: "뮤트할 단어"
muteWordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다"
muteWordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다."
muteWordsDescription2: "정규 표현식을 사용하려면 키워드를 빗금표(/)로 감싸 주세요."
softDescription: "지정한 조건의 노트를 타임라인에서 숨깁니다."
hardDescription: "지정한 조건의 노트를 타임라인에 추가하지 않습니다. 타임라인에 추가되지 않은 노트는 조건을 변경해도 표시되지 않습니다."

View File

@@ -1,21 +1,31 @@
---
_lang_: "Norsk Bokmål"
headlineMisskey: "Et nettverk forbundet med Notes"
introMisskey: "Velkommen! Misskey er en desentralisert mikrobloggtjeneste med åpen kildekode.\nOpprett \"Notes\" for å dele tankene dine med alle rundt deg. 📡\nMed \"reaksjoner\" kan du også raskt gi uttrykk for hva du synes om alles Notes. 👍\nLa oss utforske en ny verden! 🚀"
monthAndDay: "{day}-{month}"
search: "Søk"
notifications: "Varsler"
username: "Brukernavn"
password: "Passord"
forgotPassword: "Glemt passord"
fetchingAsApObject: "Henter fra Fediverse..."
ok: "OK"
gotIt: "Skjønner"
cancel: "Avbryt"
noThankYou: "Avbryt"
noThankYou: "Ikke nå"
enterUsername: "Skriv inn brukernavn"
renotedBy: "Renotes av {user}"
noNotes: "Ingen Notes"
noNotifications: "Ingen varsler"
instance: "Server"
settings: "Innstillinger"
notificationSettings: "Varslingsinnstillinger"
basicSettings: "Grunnleggende innstillinger"
otherSettings: "Andre innstillinger"
openInWindow: "Åpne i vindu"
profile: "Profil"
timeline: "Tidslinje"
noAccountDescription: "Denne brukeren har ikke skrevet sin bio ennå."
login: "Logg inn"
loggingIn: "Logget inn"
logout: "Logg ut"
@@ -24,17 +34,21 @@ uploading: "Laster opp"
save: "Lagre"
users: "Brukere"
addUser: "Legg til bruker"
favorite: "Favoritt"
favorite: "Legg til i favoritter"
favorites: "Favoritter"
unfavorite: "Fjern favoritt"
pin: "Fest"
unpin: "Opphev festing"
unfavorite: "Fjern fra favoritter"
favorited: "Lagt til i favoritter."
alreadyFavorited: "Allerede lagt til i favoritter."
cantFavorite: "Kunne ikke legge til i favoritter."
pin: "Fest til profil"
unpin: "Fjern fra profil"
copyContent: "Kopier innhold"
copyLink: "Kopier lenke"
delete: "Slett"
deleteAndEdit: "Slett og rediger"
deleteAndEditConfirm: "Er du sikker på at du vil slette denne Noten og redigere den? Du vil miste alle reaksjoner, Renotes og svar på den."
addToList: "Legg til i liste"
sendMessage: "Send melding"
sendMessage: "Send en melding"
copyRSS: "Kopier RSS"
copyUsername: "Kopier brukernavn"
searchUser: "Søk brukere"
@@ -42,104 +56,200 @@ reply: "Svar"
loadMore: "Vis mer"
showMore: "Vis mer"
showLess: "Lukk"
youGotNewFollower: "fulgte deg"
followRequestAccepted: "Følgeforespørsel akseptert"
importAndExport: "Importer og eksporter"
import: "Importer"
export: "Eksporter"
files: "Filer"
download: "Nedlastinger"
driveFileDeleteConfirm: "Er du sikker på at du vil slette \"{name}\"? Det vil også forsvinne fra alt innhold som bruker det."
unfollowConfirm: "Er du sikker på at du vil slutte å følge {name}?"
importRequested: "Du har bedt om import. Dette kan ta en stund."
lists: "Lister"
noLists: "Ingen lister"
following: "Følg"
note: "Note"
notes: "Notes"
following: "Følger"
followers: "Følgere"
followsYou: "Følger deg"
createList: "Opprett liste"
error: "Feil"
somethingHappened: "En feil har oppstått"
retry: "Prøv igjen"
pageLoadError: "Kunne ikke hente side."
serverIsDead: "Denne serveren svarer ikke. Vennligst vent en stund og prøv igjen."
enterListName: "Skriv inn et navn på listen"
privacy: "Personvern"
follow: "Følg"
followRequest: "Følgeforespørsel"
followRequests: "Følgeforespørsel"
unfollow: "Avfølg"
followRequestPending: "Venter på godkjenning"
enterEmoji: "Skriv inn en emoji"
renote: "Renote"
renoted: "Renotet."
cantRenote: "Dette innlegget kan ikke renotes."
cantReRenote: "En Renote kan ikke renotes."
quote: "Sitat"
pinned: "Fest"
pinnedNote: "Festet Note"
pinned: "Fest til profil"
you: "Du"
clickToShow: "Klikk for å vise"
add: "Legg til"
reaction: "Reaksjon"
reactions: "Reaksjoner"
reactionSetting: "Reaksjoner som vises i reaksjonsvelgeren"
reactionSettingDescription2: "Dra for å endre rekkefølgen, klikk for å slette, trykk \"+\" for å legge til."
rememberNoteVisibility: "Husk innstillingene for synlighet av Notes"
attachCancel: "Fjern vedlegg"
enterFileName: "Skriv inn filnavn"
mute: "Skjul"
unmute: "Vis"
renoteMute: "Skjul Renotes"
renoteUnmute: "Vis Renotes"
block: "Blokker"
unblock: "Opphev blokkering"
blockConfirm: "Blokker?"
selectList: "Velg liste"
selectChannel: "Velg kanal"
suspend: "Suspender"
blockConfirm: "Er du sikker på at du vil blokke denne kontoen?"
unblockConfirm: "Er du sikker på at du vil oppheve blokkeringen av denne kontoen?"
suspendConfirm: "Er du sikker på at du vil suspendere denne kontoen?"
selectList: "Velg en liste"
selectChannel: "Velg en kanal"
selectAntenna: "Velg en antenne"
selectWidget: "Velg en widget"
editWidgets: "Rediger widgeter"
editWidgetsExit: "Ferdig"
emoji: "Emoji"
emojis: "Emojier"
addEmoji: "Legg til emoji"
settingGuide: "Anbefalte innstillinger"
flagAsBot: "Merk denne kontoen som en bot"
flagAsBotDescription: "Aktiver dette alternativet hvis denne kontoen styres av et program. Hvis det er aktivert, vil det fungere som et flagg for andre utviklere for å forhindre endeløse interaksjonskjeder med andre roboter og justere Misskeys interne systemer til å behandle denne kontoen som en bot."
flagAsCat: "Merk denne kontoen som en katt"
flagAsCatDescription: "Aktiver dette alternativet for å merke denne kontoen som en katt."
flagShowTimelineReplies: "Vis svar i tidslinje"
addAccount: "Legg til konto"
selectUser: "Velg bruker"
instances: "Server"
reloadAccountsList: "Last inn kontoliste på nytt"
loginFailed: "Kunne ikke logge inn"
general: "Generelt"
searchWith: "Søk: {q}"
youHaveNoLists: "Du har ingen lister"
followConfirm: "Er du sikker på at du vil følge {name}?"
host: "Vert"
selectUser: "Velg en bruker"
recipient: "Mottaker"
annotation: "Kommentarer"
federation: "Føderasjon"
instances: "Servere"
registeredAt: "Registrerte seg"
latestRequestReceivedAt: "Siste forespørsel mottatt"
latestStatus: "Siste status"
perHour: "Per time"
perDay: "Per dag"
stopActivityDelivery: "Slutt å sende aktiviteter"
blockThisInstance: "Blokker denne serveren"
software: "Programvare"
version: "Versjon"
withNFiles: "{n} fil(er)"
network: "Nettverk"
statistics: "Statistikk"
clearQueue: "Tøm kø"
clearQueueConfirmTitle: "Vil du tømme kø?"
clearQueueConfirmTitle: "Er du sikker på at du vil tømme køen?"
blockedInstances: "Blokkerte severe"
blockedInstancesDescription: "Skriv opp vertsnavnene til serverne du vil blokkere, atskilt med linjeskift. Serverne i listen vil ikke lenger kunne kommunisere med denne serveren."
muteAndBlock: "Skjul og blokker"
mutedUsers: "Skjulte brukere"
blockedUsers: "Blokkerte brukere"
noUsers: "Det er ingen brukere"
editProfile: "Rediger profil"
noteDeleteConfirm: "Er du sikker på at du vil slette denne Noten?"
pinLimitExceeded: "Du kan ikke feste flere."
noCustomEmojis: "Ingen emoji"
intro: "Installasjonen av Misskey er ferdig! Vennligst opprett en administratorkonto."
done: "Ferdig"
noCustomEmojis: "Det er ingen emoji"
noJobs: "Det er ingen jobber"
blocked: "Blokkert"
suspended: "Suspendert"
all: "Alle"
notResponding: "Svarer ikke"
changePassword: "Endre passord"
security: "Sikkerhet"
newPassword: "Nytt passord"
newPasswordRetype: "Nytt passord (gjenta)"
attachFile: "Legg ved filer"
more: "Mer!"
announcements: "Kunngjøringer"
remove: "Slett"
removed: "Slettet"
removed: "Vellykket slettet"
removeAreYouSure: "Er du sikker på at du vil fjerne \"{x}\"?"
deleteAreYouSure: "Er du sikker på at du vil slette \"{x}\"?"
saved: "Lagret"
upload: "Laste opp"
keepOriginalUploading: "Behold originalbildet"
fromUrl: "Fra URL"
explore: "Utforsk"
messageRead: "Lest"
agree: "Jeg godtar"
termsOfService: "Vilkår for bruk"
home: "Hjem"
activity: "Aktivitet"
images: "Bilder"
image: "Bilder"
image: "Bilde"
birthday: "Bursdag"
yearsOld: "{age} år gammel"
light: "Lys"
dark: "Mørk"
fileName: "Filnavn"
selectFile: "Velg fil"
selectFiles: "Velg fil"
selectFolder: "Velg mappe"
selectFolders: "Velg mappe"
selectFile: "Velg en fil"
selectFiles: "Velg filer"
selectFolder: "Velg en mappe"
selectFolders: "Velg mapper"
renameFile: "Endre filnavn"
folderName: "Mappenavn"
createFolder: "Opprett mappe"
createFolder: "Opprett en mappe"
renameFolder: "Endre mappenavn"
deleteFolder: "Slett mappe"
addFile: "Legg til fil"
deleteFolder: "Slett denne mappen"
addFile: "Legg til en fil"
emptyFolder: "Denne mappen er tom"
unableToDelete: "Kan ikke slette"
circularReferenceFolder: "Målmappen er en undermappe til mappen du ønsker å flytte."
hasChildFilesOrFolders: "Siden denne mappen ikke er tom, kan den ikke slettes."
copyUrl: "Kopier URL"
rename: "Endre navn"
doNothing: "Gjør ingenting"
avatar: "Avatar"
banner: "Banner"
doNothing: "Ignorer"
accept: "Tillatt"
reject: "Avslå"
instanceName: "Servernavn"
thisYear: "I år"
instanceDescription: "Serverbeskrivelse"
thisYear: "År"
thisMonth: "Måned"
today: "I dag"
dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "Sider"
pinnedUsers: "Festete brukrere"
pinnedPages: "Festete sider"
integration: "Integrasjon"
enableLocalTimeline: "Aktiver lokal tidslinje"
enableGlobalTimeline: "Aktiver global tidslinje"
disablingTimelinesInfo: "Administratorer og Moderatorer vil alltid ha tilgang til alle tidslinjer, selv om de ikke er aktivert."
registration: "Registrer"
enableRegistration: "Aktiver registrering av nye brukere"
invite: "Inviter"
basicInfo: "Grunnleggende informasjon"
pinnedUsers: "Festede brukrere"
pinnedUsersDescription: "Liste over brukernavn atskilt med linjeskift som skal festes i \"Utforsk\" fanen."
pinnedPages: "Festede sider"
pinnedNotes: "Festet Note"
hcaptcha: "hCaptcha"
enableHcaptcha: "Aktiver hCaptcha"
recaptcha: "reCAPTCHA"
enableRecaptcha: "Aktiver reCAPTCHA"
turnstile: "Turnstile"
enableTurnstile: "Aktiver Turnstile"
antennas: "Antenner"
name: "Navn"
popularUsers: "Populære brukere"
exploreUsersCount: "Det finnes {count} brukere"
@@ -149,33 +259,48 @@ aboutMisskey: "Om Misskey"
share: "Del"
help: "Hjelp"
close: "Lukk"
invites: "Inviter"
members: "Medlemmer"
title: "Tittel"
text: "Tekst"
next: "Neste"
retype: "Gjenta"
invitations: "Inviter"
available: "Tilgjengelig"
unavailable: "Utilgjengelig"
tooShort: "For kort"
tooLong: "For langt"
weakPassword: "Svakt passord"
normalPassword: "Gjennomsnittlig passord"
strongPassword: "Sterkt passord"
signinWith: "Logg inn med {x}"
signinFailed: "Kunne ikke logge inn. Det oppgitte brukernavnet eller passordet er feil."
or: "eller"
language: "Språk"
aboutX: "Om {x}"
category: "Kategorier"
category: "Kategori"
createAccount: "Opprett konto"
openImageInNewTab: "Åpne bilder i ny fane"
clientSettings: "Klientinnstillinger"
accountSettings: "Kontoinnstillinger"
objectStorageRegion: "Region"
objectStorageUseSSL: "Bruk SSL"
objectStorageUseProxy: "Bruk Proxy"
deleteAll: "Slett alt"
newNoteRecived: "Det er nye Notes"
listen: "Lytt"
none: "Ingen"
volume: "Volum"
chooseEmoji: "Velg emoji"
recentUsed: "Sist brukte"
install: "Installer"
uninstall: "Avinstaller"
nothing: "Ingenting"
deleteAllFiles: "Slett alle filer"
deleteAllFilesConfirm: "Vil du slette alle filer?"
deleteAllFilesConfirm: "Er du sikker på at du vil slette alle filer?"
userSuspended: "Denne brukeren har blitt suspendert."
accountDeleted: "Kontoen blir slettet"
accountDeletedDescription: "Denne kontoen blir slettet"
accountDeletedDescription: "Denne kontoen har blitt slettet."
menu: "Meny"
poll: "Avstemning"
description: "Beskrivelse"
@@ -186,6 +311,7 @@ small: "Liten"
notificationType: "Varseltype"
edit: "Rediger"
email: "E-post"
smtpHost: "Vert"
smtpUser: "Brukernavn"
smtpPass: "Passord"
userSaysSomething: "{name} sa noe"
@@ -201,16 +327,25 @@ reportAbuse: "Rappoter"
send: "Send"
openInNewTab: "Åpne i ny fane"
waitingFor: "Venter på {x}"
random: "Tilfeldig"
system: "System"
desktop: "Skrivebord"
i18nInfo: "Misskey oversettes til flere språk av frivillige. Du kan hjelpe til på {link}."
followingCount: "Følger"
followersCount: "Følgere"
yes: "Ja"
no: "Nei"
contact: "Kontakt"
developer: "Utvikler"
makeExplorable: "Gjør konto synlig i \"Utforsk\""
makeExplorableDescription: "Hvis du slår av dette, vises ikke kontoen din i \"Utforsk\" delen."
left: "Venstre"
nNotes: "{n} Notes"
saveAs: "Lagre som"
value: "Verdi"
deleteConfirm: "Vil du slette?"
invalidValue: "Verdien er ugyldig."
closeAccount: "Avslutt konto"
emailNotification: "E-postvarsler"
inChannelSearch: "Søk i kanal"
clear: "Tøm"
@@ -224,16 +359,23 @@ accounts: "Kontoer"
switch: "Bytt"
gallery: "Galleri"
ads: "Annonser"
memo: "Notat"
high: "Høy"
low: "Lav"
sent: "Send"
sent: "Sendt"
received: "Mottatt"
learnMore: "Les mer"
misskeyUpdated: "Misskey har blitt oppdatert!"
translate: "Oversett"
translatedFrom: "Oversatt fra {x}"
unread: "Ulest"
manageAccounts: "Administrer konto"
classic: "Klassisk"
muteThread: "Skjul denne tråden"
unmuteThread: "Vis denne tråden"
continueThread: "Vis fortsettelse av tråden"
hide: "Skjul"
smartphone: "Smarttelefon"
tablet: "Nettbrett"
auto: "Automatisk"
size: "Størrelse"
@@ -249,10 +391,10 @@ check: "Sjekk"
deleteAccount: "Slett konto"
document: "Dokumenter"
logoutConfirm: "Vil du logge ut?"
pleaseSelect: "Vennligst velg"
pleaseSelect: "Velg et alternativ"
type: "Type"
beta: "Beta"
account: "Kontoer"
account: "Konto"
move: "Flytt"
pushNotification: "Push-varsler"
tools: "Verktøy"
@@ -268,6 +410,7 @@ role: "Rolle"
color: "Farge"
youCannotCreateAnymore: "Du kan ikke opprette flere."
cannotPerformTemporary: "Midlertidig utilgjengelig"
achievements: "Prestasjoner"
thisPostMayBeAnnoyingCancel: "Avbryt"
exploreOtherServers: "Utforsk andre severe"
letsLookAtTimeline: "La oss se på tidslinje"
@@ -278,10 +421,31 @@ video: "Video"
videos: "Videoer"
continue: "Fortsett"
youFollowing: "Følger"
options: "Alternativ"
_initialAccountSetting:
theseSettingsCanEditLater: "Du kan endre disse innstillingene senere."
_achievements:
_types:
_notes10:
title: "Noen Notes"
_notes100:
title: "Mange Notes"
_notes500:
title: "Dekket i Notes"
_notes1000:
title: "Et fjell av Notes"
_notes5000:
title: "Overfylte Notes"
_notes10000:
title: "Super Notes"
_notes20000:
title: "Trenger... mer... Notes..."
_notes30000:
title: "Notes Notes Notes!"
_notes40000:
title: "Note fabrikk"
_notes50000:
title: "Planet av Notes"
_notes100000:
flavor: "Du har jammen mye å si."
_noteFavorited1:
@@ -310,11 +474,25 @@ _achievements:
_justPlainLucky:
title: "Rett og slett heldig"
_setNameToSyuilo:
description: "Du har satt navnet ditt til \"syuilo\""
description: "Du satte navnet ditt til \"syuilo\""
_passedSinceAccountCreated1:
title: "Ett års jubileum"
description: "Det har gått ett år siden kontoen din ble opprettet"
_passedSinceAccountCreated2:
title: "To års jubileum"
description: "Det har gått to år siden kontoen din ble opprettet"
_passedSinceAccountCreated3:
title: "Tre års jubileum"
description: "Det har gått tre år siden kontoen din ble opprettet"
_loggedInOnBirthday:
title: "Gratulerer med dagen"
description: "Du logget inn på bursdagen din"
_loggedInOnNewYearsDay:
title: "Godt nytt år"
description: "Du logget inn på årets første dag"
_cookieClicked:
description: "Du klikket på kjeksen"
flavor: "Er du på riktig nettsted?"
_brainDiver:
title: "Brain Diver"
flavor: "Misskey-Misskey La-Tu-Ma"
@@ -332,6 +510,9 @@ _ad:
_gallery:
like: "Liker!"
unlike: "Liker ikke"
_email:
_follow:
title: "fulgte deg"
_preferencesBackups:
saveNew: "Lagre som ny"
cannotSave: "Kunne ikke lagre"
@@ -350,6 +531,8 @@ _channel:
featured: "Populært"
following: "Følger"
nameAndDescription: "Navn og beskrivelse"
_menuDisplay:
hide: "Skjul"
_wordMute:
soft: "Myk"
hard: "Hard"
@@ -359,15 +542,17 @@ _theme:
key: "Nøkkel"
keys:
link: "Lenke"
renote: "Renote"
_sfx:
note: "Notes"
notification: "Varsler"
_ago:
future: "Fremitid"
justNow: "Akkurat nå"
secondsAgo: "{n} sekunder siden"
minutesAgo: "{n} minutter siden"
hoursAgo: "{n} timer siden"
daysAgo: "{n} dager siden"
secondsAgo: "{n}s siden"
minutesAgo: "{n}m siden"
hoursAgo: "{n}t siden"
daysAgo: "{n}d siden"
weeksAgo: "{n} uker siden"
monthsAgo: "{n} måneder siden"
yearsAgo: "{n} år siden"
@@ -396,13 +581,16 @@ _widgets:
calendar: "Kalender"
trends: "Populært"
clock: "Klokke"
activity: "Aktivitet"
photos: "Bilder"
federation: "Føderasjon"
button: "Knapp"
aiscriptApp: "AiScript App"
userList: "Brukerliste"
_userList:
chooseList: "Velg liste"
_cw:
hide: "Skjul"
show: "Vis mer"
_poll:
noOnlyOneChoice: "Trenger minst to valger."
@@ -430,6 +618,7 @@ _exportOrImport:
blockingList: "Blokker"
userLists: "Lister"
_charts:
federation: "Føderasjon"
filesIncDec: "Forskjell på antall filer"
_instanceCharts:
users: "Forskjell på antall brukere"
@@ -441,14 +630,18 @@ _play:
new: "Opprett Play"
edit: "Rediger Play"
featured: "Populært"
title: "Tittel"
summary: "Beskrivelse"
_pages:
invalidNameText: "Pass på at sidetittelen ikke er tom"
like: "Liker"
unlike: "Liker ikke"
my: "Mine sider"
featured: "Populært"
contents: "Innhold"
title: "Tittel"
url: "Side URL"
hideTitleWhenPinned: "Skjul sidetittel når festet til profil"
fontSerif: "Serif"
fontSansSerif: "Sans Serif"
selectType: "Velg type"
@@ -458,13 +651,16 @@ _pages:
image: "Bilde"
button: "Knapp"
_notification:
youWereFollowed: "fulgte deg"
_types:
follow: "Følg"
reply: "Svar"
renote: "Renote"
quote: "Sitat"
reaction: "Reaksjon"
_actions:
reply: "Svar"
renote: "Renote"
_deck:
swapLeft: "Flytt til venstre"
swapRight: "Flytt til høyre"
@@ -476,6 +672,7 @@ _deck:
_columns:
notifications: "Varsler"
tl: "Tidslinje"
antenna: "Antenner"
list: "Lister"
channel: "Kanaler"
direct: "Direkte"

View File

@@ -560,6 +560,7 @@ accountDeletedDescription: "Эта учетная запись удалена"
menu: "Меню"
divider: "Линия-разделитель"
addItem: "Добавить элемент"
rearrange: "Сортировать по"
relays: "Ретрансляторы"
addRelay: "Добавить ретранслятор"
inboxUrl: "URL ящика входящих сообщений"
@@ -989,6 +990,7 @@ rolesAssignedToMe: "Мои роли"
resetPasswordConfirm: "Сбросить пароль?"
sensitiveWords: "Чувствительные слова"
sensitiveWordsDescription: "Установите общедоступный диапазон заметки, содержащей заданное слово, на домашний. Можно сделать несколько настроек, разделив их переносами строк."
sensitiveWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение."
notesSearchNotAvailable: "Поиск заметок недоступен"
license: "Лицензия"
unfavoriteConfirm: "Удалить избранное?"
@@ -1006,6 +1008,7 @@ videos: "Видео"
dataSaver: "Экономия трафика"
horizontal: "Сбоку"
youFollowing: "Подписки"
options: "Настройки ролей"
_achievements:
earnedAt: "Разблокировано в"
_types:

View File

@@ -560,6 +560,7 @@ accountDeletedDescription: "บัญชีนี้ถูกลบไปแล
menu: "เมนู"
divider: "ตัวแบ่ง"
addItem: "เพิ่มรายการ"
rearrange: "จัดใหม่"
relays: "รีเลย์"
addRelay: "เพิ่มรีเลย์"
inboxUrl: "อินบ็อกซ์ URL"
@@ -1030,7 +1031,9 @@ continue: "ดำเนินการต่อ"
preservedUsernames: "ชื่อผู้ใช้ที่สงวนไว้"
preservedUsernamesDescription: "ลิสต์ชื่อผู้ใช้ที่จะสำรองโดยคั่นด้วยการแบ่งบรรทัดนั้น เพราะสิ่งเหล่านี้จะไม่สามารถทำได้ในระหว่างการสร้างบัญชีตามปกติ บัญชีที่มีอยู่แล้วนั้นโดยใช้ชื่อผู้ใช้เหล่านี้จะไม่ได้รับผลกระทบอะไร"
createNoteFromTheFile: "เรียบเรียงโน้ตจากไฟล์นี้"
archive: "เก็บถาวร"
youFollowing: "ติดตามแล้ว"
options: "ตัวเลือกบทบาท"
_serverRules:
description: "ชุดของกฎที่จะแสดงก่อนการลงทะเบียนเราขอแนะนำให้ตั้งค่าสรุปข้อกำหนดในการให้บริการ"
_accountMigration:
@@ -1328,6 +1331,7 @@ _role:
canInvite: "สร้างรหัสเชิญอินสแตนซ์"
canManageCustomEmojis: "จัดการอีโมจิแบบกำหนดเอง"
driveCapacity: "ความจุของไดรฟ์"
alwaysMarkNsfw: "ทำเครื่องหมายไฟล์ว่าเป็น NSFW เสมอ"
pinMax: "จํานวนสูงสุดของโน้ตที่ปักหมุดไว้"
antennaMax: "จำนวนสูงสุดของเสาอากาศ"
wordMuteMax: "จำนวนอักขระสูงสุดที่อนุญาตในการปิดเสียงคำ"
@@ -1579,6 +1583,12 @@ _time:
minute: "นาที"
hour: "ชั่วโมง"
day: "วัน"
_timelineTutorial:
title: "วิธีใช้งาน Misskey"
step3_1: "เสร็จสิ้นการโพสต์โน้ตย่อแรกของคุณแล้วอย่างงั้นหรอ?"
step3_2: "ไชโย! ตอนนี้โน้ตย่อแรกของคุณได้ปรากฏบนไทม์ไลน์ของคุณแล้วนะ"
step4_1: "คุณยังสามารถแนบ \"ปฏิกิริยา\" ไปกับโน้ตได้อีกด้วยนะค่ะ"
step4_2: "หากต้องการแนบการแสดงความรู้สึก ให้กดเครื่องหมาย \"+\" บนโน้ตแล้วเลือกอิโมจิที่คุณต้องการแสดงความรู้สึกที่ตนเองชอบได้เลย"
_2fa:
alreadyRegistered: "คุณได้ลงทะเบียนอุปกรณ์ยืนยันตัวตนแบบ 2 ชั้นแล้ว"
registerTOTP: "ลงทะเบียนแอพตัวตรวจสอบสิทธิ์"

View File

@@ -52,6 +52,8 @@ addToList: "添加至列表"
sendMessage: "发送"
copyRSS: "复制RSS"
copyUsername: "复制用户名"
copyUserId: "复制用户ID"
copyNoteId: "复制帖子ID"
searchUser: "搜索用户"
reply: "回复"
loadMore: "查看更多"
@@ -560,6 +562,7 @@ accountDeletedDescription: "此帐户已经被删除。"
menu: "菜单"
divider: "分割线"
addItem: "添加项目"
rearrange: "排序方式"
relays: "中继"
addRelay: "添加中继"
inboxUrl: "Inbox URL"
@@ -789,6 +792,7 @@ noMaintainerInformationWarning: "管理人员信息未设置。"
noBotProtectionWarning: "Bot保护未设置。"
configure: "设置"
postToGallery: "发送到图库"
postToHashtag: "投稿到这个标签"
gallery: "图库"
recentPosts: "最新发布"
popularPosts: "热门投稿"
@@ -822,6 +826,7 @@ translatedFrom: "从 {x} 翻译"
accountDeletionInProgress: "正在删除账户"
usernameInfo: "在服务器上唯一标识您的帐户的名称。您可以使用字母 (a ~ z, A ~ Z)、数字 (0 ~ 9) 和下划线 (_)。用户名以后不能更改。"
aiChanMode: "小蓝模式"
devMode: "开发者模式"
keepCw: "回复时维持隐藏内容"
pubSub: "Pub/Sub账户"
lastCommunication: "最近通信"
@@ -831,6 +836,8 @@ breakFollow: "移除关注者"
breakFollowConfirm: "你想取消关注吗?"
itsOn: "已开启"
itsOff: "已关闭"
on: "开启"
off: "关闭"
emailRequiredForSignup: "注册账户需要电子邮件地址"
unread: "未读"
filter: "筛选"
@@ -985,10 +992,13 @@ cannotBeChangedLater: "之后不能再更改。"
reactionAcceptance: "接受表情回应"
likeOnly: "仅点赞"
likeOnlyForRemote: "远程仅点赞"
nonSensitiveOnly: "仅限非敏感内容"
nonSensitiveOnlyForLocalLikeOnlyForRemote: "仅限非敏感内容(远程仅点赞)"
rolesAssignedToMe: "指派给自己的角色"
resetPasswordConfirm: "确定重置密码?"
sensitiveWords: "敏感词"
sensitiveWordsDescription: "将包含设置词的帖子的可见范围设置为首页。可以通过用换行符分隔来设置多个。"
sensitiveWordsDescription2: "用空格分割关键词作为AND格式用斜线包裹关键字来构成正则表达式。"
notesSearchNotAvailable: "帖子检索不可用"
license: "许可信息"
unfavoriteConfirm: "确定要取消收藏吗?"
@@ -1028,18 +1038,59 @@ pleaseConfirmBelowBeforeSignup: "在这个服务器上注册账号前,请确
pleaseAgreeAllToContinue: "必须全部勾选「同意」才能够继续。"
continue: "继续"
preservedUsernames: "保留的用户名"
preservedUsernamesDescription: "列出需要保留的用户名,使用换行来作为分割。被指定的用户名在建立账户时无法使用,但由管理员所创建的账户不受该限制。此外,现有的账户也不会受到影响。"
createNoteFromTheFile: "从文件创建帖子"
archive: "归档"
channelArchiveConfirmTitle: "要将{name}归档吗?"
channelArchiveConfirmDescription: "归档后,在频道列表与搜索结果中不会显示,也无法发布新的贴文。"
thisChannelArchived: "该频道已被归档。"
displayOfNote: "显示帖子"
initialAccountSetting: "初始设置"
youFollowing: "正在关注"
preventAiLearning: "拒绝接受生成式AI的学习"
preventAiLearningDescription: "要求文章生成AI或图像生成AI不能够以发布的帖子和图像等内容作为学习对象。这是通过在HTML响应中包含noai标志来实现的这不能完全阻止AI学习你的发布内容并不是所有AI都会遵守这类请求。"
options: "选项"
specifyUser: "用户指定"
failedToPreviewUrl: "无法预览"
update: "更新"
rolesThatCanBeUsedThisEmojiAsReaction: "可以使用表情作为回应的角色"
rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "在没有指定角色的情况下,任何人都可以使用表情作为回应。"
rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "角色必须是公开的。"
cancelReactionConfirm: "要取消回应吗?"
changeReactionConfirm: "要更改回应吗?"
later: "一会再说"
goToMisskey: "去往Misskey"
_initialAccountSetting:
accountCreated: "账户创建完成了!"
letsStartAccountSetup: "来进行帐户的初始设置吧。"
letsFillYourProfile: "首先,来设定你的个人档案吧!"
profileSetting: "个人资料设置"
privacySetting: "隐私设置"
theseSettingsCanEditLater: "也可以在稍后修改这里的设置。"
youCanEditMoreSettingsInSettingsPageLater: "还可以在「设置」页面进行其它各种设置,稍后就来确认一下看看吧。"
followUsers: "为了建立属于你自己的时间线,试着去关注你感兴趣的用户吧。"
pushNotificationDescription: "启用推送通知的话,就可以在设备上受到来自{name}的通知了。"
initialAccountSettingCompleted: "初始设定已经完成了!"
haveFun: "希望{name}在这里玩得开心!"
ifYouNeedLearnMore: "关于{name}(Misskey)的使用方法,详见{link}。"
skipAreYouSure: "要跳过初始设置吗?"
laterAreYouSure: "要稍后再进行初始设定吗?"
_serverRules:
description: "在新用户注册前显示服务器的简单规则。推荐显示服务条款的主要内容。"
_accountMigration:
moveFrom: "从别的账号迁移到此账户"
moveFromSub: "为另一个账户建立别名"
moveFromLabel: "迁移前的账户"
moveFromDescription: "如果迁移时需要继承其他账户的关注者,请在此创造别名。此操作需要在实行迁移之前完成!请如已下输入需要迁移的账户:@person@instance.com"
moveTo: "把这个账户迁移到新的账户"
moveToLabel: "迁移后的账户"
moveCannotBeUndone: "一旦迁移账户,就无法撤销。"
moveAccountDescription: "此操作无法取消。请先确认您已在迁移后的账户上,为此账户创造了别名。创造别名后,请如以下输入您的迁移后的账户:@person@instance.com"
moveAccountHowTo: "要进行账户迁移,请现在目标账户中为此账户建立一个别名。\n建立别名后请像这样输入目标账户@username@server.example.com"
startMigration: "迁移"
migrationConfirm: "确定要把此账户迁移到{account}吗?一旦确定后,此操作无法取消,此账户也无法以原来的状态使用。\n同时请确认迁移后的账户已创造别名。"
movedAndCannotBeUndone: "该账户已被迁移。\n迁移操作无法撤销。"
postMigrationNote: "这个账户的关注会在迁移操作后的24小时后解除。该账户的「关注中」和「关注者」皆会变为0。由于不会解除关注关系你的关注者仍然可以继续查看该账户发补给关注者的帖子。"
movedTo: "迁移后的账户"
_achievements:
earnedAt: "达成时间"
@@ -1321,6 +1372,7 @@ _role:
canInvite: "发放服务器邀请码"
canManageCustomEmojis: "管理自定义表情符号"
driveCapacity: "网盘容量"
alwaysMarkNsfw: "总是将文件标记为NSFW"
pinMax: "帖子置顶数量限制"
antennaMax: "可创建的最大天线数量"
wordMuteMax: "屏蔽词的字数限制"
@@ -1572,6 +1624,16 @@ _time:
minute: "分"
hour: "小时"
day: "日"
_timelineTutorial:
title: "Misskey的使用方法"
step1_1: "这个画面是「时间线」。{name}的投稿会按照帖子的发布时间顺序来显示。"
step1_2: "时间线有许多种类,比如在「首页时间线」中展现的是你关注的人的贴文;而在「本地时间线」中展现的是{name}里全部用户的贴文。"
step2_1: "那么接下来,试着写一些什么东西来发布吧!你可以通过点击屏幕上的铅笔图标来打开投稿页面。"
step2_2: "第一次发布的帖子内容,建议包含自我介绍,以及「开始使用{name}了」。"
step3_1: "将想说的话发出去了吗?"
step3_2: "太棒了!现在你可以在你的时间线中看到刚刚发布的帖子了。"
step4_1: "试着对帖子使用「回应」吧!"
step4_2: "在他人的帖子上按下「+」图标,即可选择想要的表情来进行「回应」。"
_2fa:
alreadyRegistered: "此设备已被注册"
registerTOTP: "开始设置认证应用"

View File

@@ -52,6 +52,8 @@ addToList: "加入至清單"
sendMessage: "發送訊息"
copyRSS: "複製RSS"
copyUsername: "複製使用者名稱"
copyUserId: "複製使用者ID"
copyNoteId: "複製貼文ID"
searchUser: "搜尋使用者"
reply: "回覆"
loadMore: "載入更多"
@@ -790,6 +792,7 @@ noMaintainerInformationWarning: "尚未設定管理員信息。"
noBotProtectionWarning: "尚未設定Bot防護。"
configure: "設定"
postToGallery: "發佈到相簿"
postToHashtag: "以此主題標籤發布"
gallery: "相簿"
recentPosts: "最新貼文"
popularPosts: "熱門的貼文"
@@ -823,6 +826,7 @@ translatedFrom: "從 {x} 翻譯"
accountDeletionInProgress: "正在刪除帳戶"
usernameInfo: "在伺服器上您的帳戶是唯一的識別名稱。您可以使用字母 (a ~ z, A ~ Z)、數字 (0 ~ 9) 和下底線 (_)。之後帳戶名是不能更改的。"
aiChanMode: "小藍模式"
devMode: "開發者模式"
keepCw: "保持CW"
pubSub: "Pub/Sub 帳戶"
lastCommunication: "最近的通信"
@@ -832,6 +836,8 @@ breakFollow: "解除追隨者"
breakFollowConfirm: "確定要取消被追隨嗎?"
itsOn: "已開啟"
itsOff: "已關閉"
on: "開啟"
off: "關閉"
emailRequiredForSignup: "註冊帳戶需要電子郵件地址"
unread: "未讀"
filter: "篩選"
@@ -986,10 +992,13 @@ cannotBeChangedLater: "之後不能變更。"
reactionAcceptance: "接受表情反應"
likeOnly: "僅限讚"
likeOnlyForRemote: "遠端僅限讚"
nonSensitiveOnly: "僅限非敏感"
nonSensitiveOnlyForLocalLikeOnlyForRemote: "僅限非敏感(遠端僅限按讚)"
rolesAssignedToMe: "指派給自己的角色"
resetPasswordConfirm: "重設密碼?"
sensitiveWords: "敏感詞"
sensitiveWordsDescription: "將含有設定詞彙的貼文可見性設為發送至首頁。可以用換行來進行複數的設定。"
sensitiveWordsDescription2: "用空格分隔關鍵詞構成AND格式用斜線包圍關鍵字構成正規表達式。"
notesSearchNotAvailable: "無法使用搜尋貼文功能。"
license: "授權"
unfavoriteConfirm: "要取消收錄我的最愛嗎?"
@@ -1036,7 +1045,36 @@ channelArchiveConfirmTitle: "要封存{name}嗎?"
channelArchiveConfirmDescription: "封存以後,在頻道列表與搜索結果中不會顯示,也無法發布新的貼文。"
thisChannelArchived: "這個頻道已被封存。"
displayOfNote: "顯示貼文"
youFollowing: "關注中"
initialAccountSetting: "初始設定"
youFollowing: "追隨中"
preventAiLearning: "拒絕接受生成式AI的訓練"
preventAiLearningDescription: "要求外部的文章生成式AI或圖像生成式AI不以發布的貼文和圖像等內容為學習對象。這是透過在HTML響應中包含noai旗標來實現的但不能完全防止AI的學習因為這要看該AI是否遵守這個要求。"
options: "選項"
specifyUser: "指定使用者"
failedToPreviewUrl: "無法預覽"
update: "更新"
rolesThatCanBeUsedThisEmojiAsReaction: "可以當成反應使用的角色"
rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "如果是未指定角色的情況,則任何人都可以被當成反應來使用。"
rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "角色必須是公開的角色。"
cancelReactionConfirm: "要取消做出的反應嗎?"
changeReactionConfirm: "要變更做出的反應嗎?"
later: "稍後再說"
goToMisskey: "往Misskey"
_initialAccountSetting:
accountCreated: "帳戶已建立完成!"
letsStartAccountSetup: "來進行帳戶的初始設定吧。"
letsFillYourProfile: "首先,來設定您的個人檔案吧。"
profileSetting: "個人檔案設定"
privacySetting: "隱私設定"
theseSettingsCanEditLater: "這裡的設定可以在之後變更。"
youCanEditMoreSettingsInSettingsPageLater: "除此之外,還可以在「設定」頁面進行各種設定。之後請確認看看。"
followUsers: "為了構築時間軸,試著追蹤您感興趣的使用者吧。"
pushNotificationDescription: "啟用推送通知,就可以在設備上接收{name}的通知。"
initialAccountSettingCompleted: "初始設定完成了!"
haveFun: "盡情享受{name}吧!"
ifYouNeedLearnMore: "關於如何使用{name}(Misskey)的詳細資訊,請見{link}。"
skipAreYouSure: "要略過初始設定嗎?"
laterAreYouSure: "稍後再重新進行初始設定嗎?"
_serverRules:
description: "設定伺服器的簡要規則,在新的註冊之前顯示。建議的內容是使用條款的摘要。"
_accountMigration:
@@ -1464,7 +1502,7 @@ _channel:
removeBanner: "移除橫幅圖像"
featured: "熱門貼文"
owned: "管理中"
following: "關注中"
following: "追隨中"
usersCount: "有{n}人參與"
notesCount: "有{n}個貼文"
nameAndDescription: "名稱與說明"
@@ -1586,6 +1624,16 @@ _time:
minute: "分鐘"
hour: "小時"
day: "日"
_timelineTutorial:
title: "Misskey的使用方法"
step1_1: "這個畫面是「時間軸」。發布到{name}的「貼文」按照時間順序顯示。"
step1_2: "時間軸有多種類型,例如在「首頁時間軸」中流動的是您追蹤的人的貼文;而在「本地時間軸」流動的是{name}全體的貼文。"
step2_1: "試試看,發布個貼文吧!按畫面上鉛筆圖示的按鈕開啟表格。"
step2_2: "初次貼文的內容,建議包括自我介紹以及「開始使用{name}」。"
step3_1: "貼文發出去了嗎?"
step3_2: "如果你的貼文出現在時間軸上,就代表發文成功。"
step4_1: "可以對貼文標記「反應」。"
step4_2: "點擊貼文的「+」圖示,即可選擇喜好的表情符號來標記反應。"
_2fa:
alreadyRegistered: "此設備已經被註冊過了"
registerTOTP: "開始設定驗證應用程式"

View File

@@ -1,12 +1,12 @@
{
"name": "misskey",
"version": "13.12.1",
"version": "13.13.0-beta.7",
"codename": "nasubi",
"repository": {
"type": "git",
"url": "https://github.com/misskey-dev/misskey.git"
},
"packageManager": "pnpm@8.3.1",
"packageManager": "pnpm@8.6.0",
"workspaces": [
"packages/frontend",
"packages/backend",
@@ -56,11 +56,11 @@
"devDependencies": {
"@types/gulp": "4.0.10",
"@types/gulp-rename": "2.0.1",
"@typescript-eslint/eslint-plugin": "5.59.2",
"@typescript-eslint/parser": "5.59.2",
"@typescript-eslint/eslint-plugin": "5.59.5",
"@typescript-eslint/parser": "5.59.5",
"cross-env": "7.0.3",
"cypress": "12.11.0",
"eslint": "8.39.0",
"cypress": "12.13.0",
"eslint": "8.40.0",
"start-server-and-test": "2.0.0"
},
"optionalDependencies": {

View File

@@ -0,0 +1,11 @@
export class PreventAiLarning1683682889948 {
name = 'PreventAiLarning1683682889948'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_profile" ADD "preventAiLarning" boolean NOT NULL DEFAULT true`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "preventAiLarning"`);
}
}

View File

@@ -0,0 +1,11 @@
export class PublicReactionsDefaultTrue1683683083083 {
name = 'PublicReactionsDefaultTrue1683683083083'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "publicReactions" SET DEFAULT true`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "publicReactions" SET DEFAULT false`);
}
}

View File

@@ -0,0 +1,11 @@
export class FixTypo1683789676867 {
name = 'FixTypo1683789676867'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_profile" RENAME COLUMN "preventAiLarning" TO "preventAiLearning"`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_profile" RENAME COLUMN "preventAiLearning" TO "preventAiLarning"`);
}
}

View File

@@ -0,0 +1,13 @@
export class UserList1683847157541 {
name = 'UserList1683847157541'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_list" ADD "isPublic" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`CREATE INDEX "IDX_48a00f08598662b9ca540521eb" ON "user_list" ("isPublic") `);
}
async down(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_48a00f08598662b9ca540521eb"`);
await queryRunner.query(`ALTER TABLE "user_list" DROP COLUMN "isPublic"`);
}
}

View File

@@ -0,0 +1,19 @@
export class UserListFavorites1683869758873 {
name = 'UserListFavorites1683869758873'
async up(queryRunner) {
await queryRunner.query(`CREATE TABLE "user_list_favorite" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "userListId" character varying(32) NOT NULL, CONSTRAINT "PK_c0974b21e18502a4c8178e09fe6" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE INDEX "IDX_016f613dc4feb807e03e3e7da9" ON "user_list_favorite" ("userId") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_d6765a8c2a4c17c33f9d7f948b" ON "user_list_favorite" ("userId", "userListId") `);
await queryRunner.query(`ALTER TABLE "user_list_favorite" ADD CONSTRAINT "FK_016f613dc4feb807e03e3e7da92" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "user_list_favorite" ADD CONSTRAINT "FK_4d52b20bfe32c8552e7a61e80d2" FOREIGN KEY ("userListId") REFERENCES "user_list"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_list_favorite" DROP CONSTRAINT "FK_4d52b20bfe32c8552e7a61e80d2"`);
await queryRunner.query(`ALTER TABLE "user_list_favorite" DROP CONSTRAINT "FK_016f613dc4feb807e03e3e7da92"`);
await queryRunner.query(`DROP INDEX "public"."IDX_d6765a8c2a4c17c33f9d7f948b"`);
await queryRunner.query(`DROP INDEX "public"."IDX_016f613dc4feb807e03e3e7da9"`);
await queryRunner.query(`DROP TABLE "user_list_favorite"`);
}
}

View File

@@ -0,0 +1,11 @@
export class RemoveShowTimelineReplies1684206886988 {
name = 'RemoveShowTimelineReplies1684206886988'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "showTimelineReplies"`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "user" ADD "showTimelineReplies" boolean NOT NULL DEFAULT false`);
}
}

View File

@@ -0,0 +1,15 @@
export class EmojiImprove1684386446061 {
name = 'EmojiImprove1684386446061'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "emoji" ADD "localOnly" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "emoji" ADD "isSensitive" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "emoji" ADD "roleIdsThatCanBeUsedThisEmojiAsReaction" character varying(128) array NOT NULL DEFAULT '{}'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "roleIdsThatCanBeUsedThisEmojiAsReaction"`);
await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "isSensitive"`);
await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "localOnly"`);
}
}

View File

@@ -35,47 +35,50 @@
"@swc/core-win32-x64-msvc": "1.3.56",
"@tensorflow/tfjs": "4.4.0",
"@tensorflow/tfjs-node": "4.4.0",
"slacc-android-arm-eabi": "0.0.7",
"slacc-android-arm64": "0.0.7",
"slacc-darwin-arm64": "0.0.7",
"slacc-darwin-universal": "0.0.7",
"slacc-darwin-x64": "0.0.7",
"slacc-linux-arm-gnueabihf": "0.0.7",
"slacc-linux-arm64-gnu": "0.0.7",
"slacc-linux-arm64-musl": "0.0.7",
"slacc-linux-x64-gnu": "0.0.7",
"slacc-win32-arm64-msvc": "0.0.7",
"slacc-win32-x64-msvc": "0.0.7"
"bufferutil": "^4.0.7",
"slacc-android-arm-eabi": "0.0.9",
"slacc-android-arm64": "0.0.9",
"slacc-darwin-arm64": "0.0.9",
"slacc-darwin-universal": "0.0.9",
"slacc-darwin-x64": "0.0.9",
"slacc-freebsd-x64": "0.0.9",
"slacc-linux-arm-gnueabihf": "0.0.9",
"slacc-linux-arm64-gnu": "0.0.9",
"slacc-linux-arm64-musl": "0.0.9",
"slacc-linux-x64-gnu": "0.0.9",
"slacc-win32-arm64-msvc": "0.0.9",
"slacc-win32-x64-msvc": "0.0.9",
"utf-8-validate": "^6.0.3"
},
"dependencies": {
"@aws-sdk/client-s3": "3.321.1",
"@aws-sdk/lib-storage": "3.321.1",
"@aws-sdk/node-http-handler": "3.321.1",
"@bull-board/api": "5.1.2",
"@bull-board/fastify": "5.1.2",
"@bull-board/ui": "5.1.2",
"@bull-board/api": "5.2.0",
"@bull-board/fastify": "5.2.0",
"@bull-board/ui": "5.2.0",
"@discordapp/twemoji": "14.1.2",
"@fastify/accepts": "4.1.0",
"@fastify/cookie": "8.3.0",
"@fastify/cors": "8.2.1",
"@fastify/http-proxy": "9.0.0",
"@fastify/http-proxy": "9.1.0",
"@fastify/multipart": "7.6.0",
"@fastify/static": "6.10.1",
"@fastify/view": "7.4.1",
"@nestjs/common": "9.4.0",
"@nestjs/core": "9.4.0",
"@nestjs/testing": "9.4.0",
"@nestjs/common": "9.4.2",
"@nestjs/core": "9.4.2",
"@nestjs/testing": "9.4.2",
"@peertube/http-signature": "1.7.0",
"@sinonjs/fake-timers": "10.0.2",
"@swc/cli": "0.1.62",
"@swc/core": "1.3.56",
"@swc/core": "1.3.59",
"accepts": "1.3.8",
"ajv": "8.12.0",
"archiver": "5.3.1",
"autwh": "0.1.0",
"bcryptjs": "2.4.3",
"blurhash": "2.0.5",
"bull": "4.10.4",
"bullmq": "3.14.1",
"cacheable-lookup": "6.1.0",
"cbor": "8.1.0",
"chalk": "5.2.0",
@@ -89,11 +92,11 @@
"escape-regexp": "0.0.1",
"fastify": "4.17.0",
"feed": "4.2.2",
"file-type": "18.3.0",
"file-type": "18.4.0",
"fluent-ffmpeg": "2.1.2",
"form-data": "4.0.0",
"got": "12.6.0",
"happy-dom": "9.10.2",
"happy-dom": "9.19.2",
"hpagent": "1.2.0",
"ioredis": "5.3.2",
"ip-cidr": "3.1.0",
@@ -102,21 +105,21 @@
"jsdom": "21.1.1",
"json5": "2.2.3",
"jsonld": "8.1.1",
"meilisearch": "0.32.3",
"jsrsasign": "10.8.6",
"meilisearch": "0.32.4",
"mfm-js": "0.23.3",
"mime-types": "2.1.35",
"misskey-js": "workspace:*",
"ms": "3.0.0-canary.1",
"nested-property": "4.0.0",
"node-fetch": "3.3.1",
"nodemailer": "6.9.1",
"nodemailer": "6.9.2",
"nsfwjs": "2.4.2",
"oauth": "0.10.0",
"os-utils": "0.0.14",
"otpauth": "9.1.1",
"otpauth": "9.1.2",
"parse5": "7.1.2",
"pg": "8.10.0",
"pg": "8.11.0",
"private-ip": "3.0.0",
"probe-image-size": "7.2.3",
"promise-limit": "2.7.0",
@@ -136,10 +139,10 @@
"s-age": "1.1.2",
"sanitize-html": "2.10.0",
"seedrandom": "3.0.5",
"semver": "7.5.0",
"semver": "7.5.1",
"sharp": "0.32.1",
"sharp-read-bmp": "github:misskey-dev/sharp-read-bmp",
"slacc": "0.0.7",
"slacc": "0.0.9",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
"summaly": "github:misskey-dev/summaly",
@@ -149,14 +152,13 @@
"tsc-alias": "1.8.6",
"tsconfig-paths": "4.2.0",
"twemoji-parser": "14.0.0",
"typeorm": "0.3.15",
"typeorm": "0.3.16",
"typescript": "5.0.4",
"ulid": "2.3.0",
"unzipper": "0.10.11",
"uuid": "9.0.0",
"vary": "1.1.2",
"web-push": "3.6.1",
"websocket": "1.0.34",
"ws": "8.13.0",
"xev": "3.0.2"
},
@@ -166,7 +168,6 @@
"@types/accepts": "1.3.5",
"@types/archiver": "5.3.2",
"@types/bcryptjs": "2.4.2",
"@types/bull": "4.10.0",
"@types/cbor": "6.0.0",
"@types/color-convert": "2.0.0",
"@types/content-disposition": "0.5.5",
@@ -178,11 +179,11 @@
"@types/jsonld": "1.5.8",
"@types/jsrsasign": "10.5.8",
"@types/mime-types": "2.1.1",
"@types/node": "18.16.3",
"@types/node": "20.2.3",
"@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.7",
"@types/nodemailer": "6.4.8",
"@types/oauth": "0.9.1",
"@types/pg": "8.6.6",
"@types/pg": "8.10.1",
"@types/pug": "2.0.6",
"@types/punycode": "2.1.0",
"@types/qrcode": "1.5.0",
@@ -191,22 +192,22 @@
"@types/redis": "4.0.11",
"@types/rename": "1.0.4",
"@types/sanitize-html": "2.9.0",
"@types/semver": "7.3.13",
"@types/semver": "7.5.0",
"@types/sharp": "0.32.0",
"@types/sinonjs__fake-timers": "8.1.2",
"@types/tinycolor2": "1.4.3",
"@types/tmp": "0.2.3",
"@types/unzipper": "0.10.5",
"@types/unzipper": "0.10.6",
"@types/uuid": "9.0.1",
"@types/vary": "1.1.0",
"@types/web-push": "3.3.2",
"@types/websocket": "1.0.5",
"@types/ws": "8.5.4",
"@typescript-eslint/eslint-plugin": "5.59.2",
"@typescript-eslint/parser": "5.59.2",
"aws-sdk-client-mock": "^2.1.1",
"@typescript-eslint/eslint-plugin": "5.59.5",
"@typescript-eslint/parser": "5.59.5",
"aws-sdk-client-mock": "2.1.1",
"cross-env": "7.0.3",
"eslint": "8.39.0",
"eslint": "8.40.0",
"eslint-plugin-import": "2.27.5",
"execa": "6.1.0",
"jest": "29.5.0",

View File

@@ -4,7 +4,7 @@ import * as Redis from 'ioredis';
import { DataSource } from 'typeorm';
import { MeiliSearch } from 'meilisearch';
import { DI } from './di-symbols.js';
import { loadConfig } from './config.js';
import { Config, loadConfig } from './config.js';
import { createPostgresDataSource } from './postgres.js';
import { RepositoryModule } from './models/RepositoryModule.js';
import type { Provider, OnApplicationShutdown } from '@nestjs/common';
@@ -25,7 +25,7 @@ const $db: Provider = {
const $meilisearch: Provider = {
provide: DI.meilisearch,
useFactory: (config) => {
useFactory: (config: Config) => {
if (config.meilisearch) {
return new MeiliSearch({
host: `${config.meilisearch.ssl ? 'https' : 'http' }://${config.meilisearch.host}:${config.meilisearch.port}`,
@@ -40,7 +40,7 @@ const $meilisearch: Provider = {
const $redis: Provider = {
provide: DI.redis,
useFactory: (config) => {
useFactory: (config: Config) => {
return new Redis.Redis({
port: config.redis.port,
host: config.redis.host,
@@ -55,7 +55,7 @@ const $redis: Provider = {
const $redisForPub: Provider = {
provide: DI.redisForPub,
useFactory: (config) => {
useFactory: (config: Config) => {
const redis = new Redis.Redis({
port: config.redisForPubsub.port,
host: config.redisForPubsub.host,
@@ -71,7 +71,7 @@ const $redisForPub: Provider = {
const $redisForSub: Provider = {
provide: DI.redisForSub,
useFactory: (config) => {
useFactory: (config: Config) => {
const redis = new Redis.Redis({
port: config.redisForPubsub.port,
host: config.redisForPubsub.host,
@@ -100,7 +100,7 @@ export class GlobalModule implements OnApplicationShutdown {
@Inject(DI.redisForSub) private redisForSub: Redis.Redis,
) {}
async onApplicationShutdown(signal: string): Promise<void> {
public async dispose(): Promise<void> {
if (process.env.NODE_ENV === 'test') {
// XXX:
// Shutting down the existing connections causes errors on Jest as
@@ -116,4 +116,8 @@ export class GlobalModule implements OnApplicationShutdown {
this.redisForSub.disconnect(),
]);
}
async onApplicationShutdown(signal: string): Promise<void> {
await this.dispose();
}
}

View File

@@ -18,10 +18,12 @@ export async function server() {
const serverService = app.get(ServerService);
await serverService.launch();
app.get(ChartManagementService).start();
app.get(JanitorService).start();
app.get(QueueStatsService).start();
app.get(ServerStatsService).start();
if (process.env.NODE_ENV !== 'test') {
app.get(ChartManagementService).start();
app.get(JanitorService).start();
app.get(QueueStatsService).start();
app.get(ServerStatsService).start();
}
return app;
}

View File

@@ -62,6 +62,7 @@ export type Source = {
port: string;
apiKey: string;
ssl?: boolean;
index: string;
};
proxy?: string;
@@ -143,7 +144,7 @@ export function loadConfig() {
const clientManifestExists = fs.existsSync(_dirname + '/../../../built/_vite_/manifest.json');
const clientManifest = clientManifestExists ?
JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_vite_/manifest.json`, 'utf-8'))
: { 'src/init.ts': { file: 'src/init.ts' } };
: { 'src/_boot_.ts': { file: 'src/_boot_.ts' } };
const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source;
const mixin = {} as Mixin;
@@ -164,7 +165,7 @@ export function loadConfig() {
mixin.authUrl = `${mixin.scheme}://${mixin.host}/auth`;
mixin.driveUrl = `${mixin.scheme}://${mixin.host}/files`;
mixin.userAgent = `Misskey/${meta.version} (${config.url})`;
mixin.clientEntry = clientManifest['src/init.ts'];
mixin.clientEntry = clientManifest['src/_boot_.ts'];
mixin.clientManifestExists = clientManifestExists;
const externalMediaProxy = config.mediaProxy ?
@@ -189,6 +190,6 @@ function tryCreateUrl(url: string) {
try {
return new URL(url);
} catch (e) {
throw `url="${url}" is not a valid URL.`;
throw new Error(`url="${url}" is not a valid URL.`);
}
}

View File

@@ -55,11 +55,6 @@ export class AntennaService implements OnApplicationShutdown {
this.redisForSub.on('message', this.onRedisMessage);
}
@bindThis
public onApplicationShutdown(signal?: string | undefined) {
this.redisForSub.off('message', this.onRedisMessage);
}
@bindThis
private async onRedisMessage(_: string, data: string): Promise<void> {
const obj = JSON.parse(data);
@@ -196,4 +191,14 @@ export class AntennaService implements OnApplicationShutdown {
return this.antennas;
}
@bindThis
public dispose(): void {
this.redisForSub.off('message', this.onRedisMessage);
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View File

@@ -166,7 +166,12 @@ export class CacheService implements OnApplicationShutdown {
}
@bindThis
public onApplicationShutdown(signal?: string | undefined) {
public dispose(): void {
this.redisForSub.off('message', this.onMessage);
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View File

@@ -30,7 +30,7 @@ export class CaptchaService {
}, { throwErrorWhenResponseNotOk: false });
if (!res.ok) {
throw `${res.status}`;
throw new Error(`${res.status}`);
}
return await res.json() as CaptchaResponse;
@@ -39,48 +39,48 @@ export class CaptchaService {
@bindThis
public async verifyRecaptcha(secret: string, response: string | null | undefined): Promise<void> {
if (response == null) {
throw 'recaptcha-failed: no response provided';
throw new Error('recaptcha-failed: no response provided');
}
const result = await this.getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(err => {
throw `recaptcha-request-failed: ${err}`;
throw new Error(`recaptcha-request-failed: ${err}`);
});
if (result.success !== true) {
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
throw `recaptcha-failed: ${errorCodes}`;
throw new Error(`recaptcha-failed: ${errorCodes}`);
}
}
@bindThis
public async verifyHcaptcha(secret: string, response: string | null | undefined): Promise<void> {
if (response == null) {
throw 'hcaptcha-failed: no response provided';
throw new Error('hcaptcha-failed: no response provided');
}
const result = await this.getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(err => {
throw `hcaptcha-request-failed: ${err}`;
throw new Error(`hcaptcha-request-failed: ${err}`);
});
if (result.success !== true) {
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
throw `hcaptcha-failed: ${errorCodes}`;
throw new Error(`hcaptcha-failed: ${errorCodes}`);
}
}
@bindThis
public async verifyTurnstile(secret: string, response: string | null | undefined): Promise<void> {
if (response == null) {
throw 'turnstile-failed: no response provided';
throw new Error('turnstile-failed: no response provided');
}
const result = await this.getCaptchaResponse('https://challenges.cloudflare.com/turnstile/v0/siteverify', secret, response).catch(err => {
throw `turnstile-request-failed: ${err}`;
throw new Error(`turnstile-request-failed: ${err}`);
});
if (result.success !== true) {
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
throw `turnstile-failed: ${errorCodes}`;
throw new Error(`turnstile-failed: ${errorCodes}`);
}
}
}

View File

@@ -7,7 +7,7 @@ import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import type { DriveFile } from '@/models/entities/DriveFile.js';
import type { Emoji } from '@/models/entities/Emoji.js';
import type { EmojisRepository } from '@/models/index.js';
import type { EmojisRepository, Role } from '@/models/index.js';
import { bindThis } from '@/decorators.js';
import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
import { UtilityService } from '@/core/UtilityService.js';
@@ -15,6 +15,8 @@ import type { Config } from '@/config.js';
import { query } from '@/misc/prelude/url.js';
import type { Serialized } from '@/server/api/stream/types.js';
const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/;
@Injectable()
export class CustomEmojiService {
private cache: MemoryKVCache<Emoji | null>;
@@ -63,6 +65,9 @@ export class CustomEmojiService {
aliases: string[];
host: string | null;
license: string | null;
isSensitive: boolean;
localOnly: boolean;
roleIdsThatCanBeUsedThisEmojiAsReaction: Role['id'][];
}): Promise<Emoji> {
const emoji = await this.emojisRepository.insert({
id: this.idService.genId(),
@@ -75,6 +80,9 @@ export class CustomEmojiService {
publicUrl: data.driveFile.webpublicUrl ?? data.driveFile.url,
type: data.driveFile.webpublicType ?? data.driveFile.type,
license: data.license,
isSensitive: data.isSensitive,
localOnly: data.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction,
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
if (data.host == null) {
@@ -90,10 +98,14 @@ export class CustomEmojiService {
@bindThis
public async update(id: Emoji['id'], data: {
driveFile?: DriveFile;
name?: string;
category?: string | null;
aliases?: string[];
license?: string | null;
isSensitive?: boolean;
localOnly?: boolean;
roleIdsThatCanBeUsedThisEmojiAsReaction?: Role['id'][];
}): Promise<void> {
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() });
@@ -105,6 +117,12 @@ export class CustomEmojiService {
category: data.category,
aliases: data.aliases,
license: data.license,
isSensitive: data.isSensitive,
localOnly: data.localOnly,
originalUrl: data.driveFile != null ? data.driveFile.url : undefined,
publicUrl: data.driveFile != null ? (data.driveFile.webpublicUrl ?? data.driveFile.url) : undefined,
type: data.driveFile != null ? (data.driveFile.webpublicType ?? data.driveFile.type) : undefined,
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction ?? undefined,
});
this.localEmojisCache.refresh();
@@ -259,7 +277,7 @@ export class CustomEmojiService {
@bindThis
public parseEmojiStr(emojiName: string, noteUserHost: string | null) {
const match = emojiName.match(/^(\w+)(?:@([\w.-]+))?$/);
const match = emojiName.match(parseEmojiStrRegexp);
if (!match) return { name: null, host: null };
const name = match[1];

View File

@@ -116,14 +116,14 @@ export class FetchInstanceMetadataService {
const wellknown = await this.httpRequestService.getJson('https://' + instance.host + '/.well-known/nodeinfo')
.catch(err => {
if (err.statusCode === 404) {
throw 'No nodeinfo provided';
throw new Error('No nodeinfo provided');
} else {
throw err.statusCode ?? err.message;
}
}) as Record<string, unknown>;
if (wellknown.links == null || !Array.isArray(wellknown.links)) {
throw 'No wellknown links';
throw new Error('No wellknown links');
}
const links = wellknown.links as any[];
@@ -134,7 +134,7 @@ export class FetchInstanceMetadataService {
const link = lnik2_1 ?? lnik2_0 ?? lnik1_0;
if (link == null) {
throw 'No nodeinfo link provided';
throw new Error('No nodeinfo link provided');
}
const info = await this.httpRequestService.getJson(link.href)

View File

@@ -120,8 +120,13 @@ export class MetaService implements OnApplicationShutdown {
}
@bindThis
public onApplicationShutdown(signal?: string | undefined) {
public dispose(): void {
clearInterval(this.intervalId);
this.redisForSub.off('message', this.onMessage);
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View File

@@ -83,7 +83,7 @@ export class MfmService {
if (hashtagNames && href && hashtagNames.map(x => x.toLowerCase()).includes(txt.toLowerCase())) {
text += txt;
// メンション
} else if (txt.startsWith('@') && !(rel && rel.value.match(/^me /))) {
} else if (txt.startsWith('@') && !(rel && rel.value.startsWith('me '))) {
const part = txt.split('@');
if (part.length === 2 && href) {

View File

@@ -3,6 +3,7 @@ import * as mfm from 'mfm-js';
import { In, DataSource } from 'typeorm';
import * as Redis from 'ioredis';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import RE2 from 're2';
import { extractMentions } from '@/misc/extract-mentions.js';
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
import { extractHashtags } from '@/misc/extract-hashtags.js';
@@ -238,7 +239,8 @@ export class NoteCreateService implements OnApplicationShutdown {
if (data.channel != null) data.localOnly = true;
if (data.visibility === 'public' && data.channel == null) {
if ((data.text != null) && (await this.metaService.fetch()).sensitiveWords.some(w => data.text!.includes(w))) {
const sensitiveWords = (await this.metaService.fetch()).sensitiveWords;
if (this.isSensitive(data, sensitiveWords)) {
data.visibility = 'home';
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
data.visibility = 'home';
@@ -508,7 +510,7 @@ export class NoteCreateService implements OnApplicationShutdown {
if (data.poll && data.poll.expiresAt) {
const delay = data.poll.expiresAt.getTime() - Date.now();
this.queueService.endedPollNotificationQueue.add({
this.queueService.endedPollNotificationQueue.add(note.id, {
noteId: note.id,
}, {
delay,
@@ -670,6 +672,31 @@ export class NoteCreateService implements OnApplicationShutdown {
// Register to search database
this.index(note);
}
@bindThis
private isSensitive(note: Option, sensitiveWord: string[]): boolean {
if (sensitiveWord.length > 0) {
const text = note.cw ?? note.text ?? '';
if (text === '') return false;
const matched = sensitiveWord.some(filter => {
// represents RegExp
const regexp = filter.match(/^\/(.+)\/(.*)$/);
// This should never happen due to input sanitisation.
if (!regexp) {
const words = filter.split(' ');
return words.every(keyword => text.includes(keyword));
}
try {
return new RE2(regexp[1], regexp[2]).test(text);
} catch (err) {
// This should never happen due to input sanitisation.
return false;
}
});
if (matched) return true;
}
return false;
}
@bindThis
private incRenoteCount(renote: Note) {
@@ -763,7 +790,13 @@ export class NoteCreateService implements OnApplicationShutdown {
return mentionedUsers;
}
onApplicationShutdown(signal?: string | undefined) {
@bindThis
public dispose(): void {
this.#shutdownController.abort();
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View File

@@ -122,7 +122,13 @@ export class NoteReadService implements OnApplicationShutdown {
}
}
onApplicationShutdown(signal?: string | undefined): void {
@bindThis
public dispose(): void {
this.#shutdownController.abort();
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View File

@@ -152,7 +152,13 @@ export class NotificationService implements OnApplicationShutdown {
*/
}
onApplicationShutdown(signal?: string | undefined): void {
@bindThis
public dispose(): void {
this.#shutdownController.abort();
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View File

@@ -208,7 +208,7 @@ export class QueryService {
}
@bindThis
public generateRepliesQuery(q: SelectQueryBuilder<any>, me?: Pick<User, 'id' | 'showTimelineReplies'> | null): void {
public generateRepliesQuery(q: SelectQueryBuilder<any>, withReplies: boolean, me?: Pick<User, 'id'> | null): void {
if (me == null) {
q.andWhere(new Brackets(qb => { qb
.where('note.replyId IS NULL') // 返信ではない
@@ -217,7 +217,7 @@ export class QueryService {
.andWhere('note.replyUserId = note.userId');
}));
}));
} else if (!me.showTimelineReplies) {
} else if (!withReplies) {
q.andWhere(new Brackets(qb => { qb
.where('note.replyId IS NULL') // 返信ではない
.orWhere('note.replyUserId = :meId', { meId: me.id }) // 返信だけど自分のノートへの返信

View File

@@ -1,96 +1,66 @@
import { Module } from '@nestjs/common';
import Bull from 'bull';
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 type { Provider } from '@nestjs/common';
import type { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData, EndedPollNotificationJobData, WebhookDeliverJobData, RelationshipJobData, DbJobMap } from '../queue/types.js';
function q<T>(config: Config, name: string, limitPerSec = -1) {
return new Bull<T>(name, {
redis: {
port: config.redisForJobQueue.port,
host: config.redisForJobQueue.host,
family: config.redisForJobQueue.family == null ? 0 : config.redisForJobQueue.family,
password: config.redisForJobQueue.pass,
db: config.redisForJobQueue.db ?? 0,
},
prefix: config.redisForJobQueue.prefix ? `${config.redisForJobQueue.prefix}:queue` : 'queue',
limiter: limitPerSec > 0 ? {
max: limitPerSec,
duration: 1000,
} : undefined,
settings: {
backoffStrategies: {
apBackoff,
},
},
});
}
// ref. https://github.com/misskey-dev/misskey/pull/7635#issue-971097019
function apBackoff(attemptsMade: number, err: Error) {
const baseDelay = 60 * 1000; // 1min
const maxBackoff = 8 * 60 * 60 * 1000; // 8hours
let backoff = (Math.pow(2, attemptsMade) - 1) * baseDelay;
backoff = Math.min(backoff, maxBackoff);
backoff += Math.round(backoff * Math.random() * 0.2);
return backoff;
}
import type { DeliverJobData, InboxJobData, EndedPollNotificationJobData, WebhookDeliverJobData, RelationshipJobData } from '../queue/types.js';
export type SystemQueue = Bull.Queue<Record<string, unknown>>;
export type EndedPollNotificationQueue = Bull.Queue<EndedPollNotificationJobData>;
export type DeliverQueue = Bull.Queue<DeliverJobData>;
export type InboxQueue = Bull.Queue<InboxJobData>;
export type DbQueue = Bull.Queue<DbJobData<keyof DbJobMap>>;
export type DbQueue = Bull.Queue;
export type RelationshipQueue = Bull.Queue<RelationshipJobData>;
export type ObjectStorageQueue = Bull.Queue<ObjectStorageJobData>;
export type ObjectStorageQueue = Bull.Queue;
export type WebhookDeliverQueue = Bull.Queue<WebhookDeliverJobData>;
const $system: Provider = {
provide: 'queue:system',
useFactory: (config: Config) => q(config, 'system'),
useFactory: (config: Config) => new Bull.Queue(QUEUE.SYSTEM, baseQueueOptions(config, QUEUE.SYSTEM)),
inject: [DI.config],
};
const $endedPollNotification: Provider = {
provide: 'queue:endedPollNotification',
useFactory: (config: Config) => q(config, 'endedPollNotification'),
useFactory: (config: Config) => new Bull.Queue(QUEUE.ENDED_POLL_NOTIFICATION, baseQueueOptions(config, QUEUE.ENDED_POLL_NOTIFICATION)),
inject: [DI.config],
};
const $deliver: Provider = {
provide: 'queue:deliver',
useFactory: (config: Config) => q(config, 'deliver', config.deliverJobPerSec ?? 128),
useFactory: (config: Config) => new Bull.Queue(QUEUE.DELIVER, baseQueueOptions(config, QUEUE.DELIVER)),
inject: [DI.config],
};
const $inbox: Provider = {
provide: 'queue:inbox',
useFactory: (config: Config) => q(config, 'inbox', config.inboxJobPerSec ?? 16),
useFactory: (config: Config) => new Bull.Queue(QUEUE.INBOX, baseQueueOptions(config, QUEUE.INBOX)),
inject: [DI.config],
};
const $db: Provider = {
provide: 'queue:db',
useFactory: (config: Config) => q(config, 'db'),
useFactory: (config: Config) => new Bull.Queue(QUEUE.DB, baseQueueOptions(config, QUEUE.DB)),
inject: [DI.config],
};
const $relationship: Provider = {
provide: 'queue:relationship',
useFactory: (config: Config) => q(config, 'relationship', config.relashionshipJobPerSec ?? 64),
useFactory: (config: Config) => new Bull.Queue(QUEUE.RELATIONSHIP, baseQueueOptions(config, QUEUE.RELATIONSHIP)),
inject: [DI.config],
};
const $objectStorage: Provider = {
provide: 'queue:objectStorage',
useFactory: (config: Config) => q(config, 'objectStorage'),
useFactory: (config: Config) => new Bull.Queue(QUEUE.OBJECT_STORAGE, baseQueueOptions(config, QUEUE.OBJECT_STORAGE)),
inject: [DI.config],
};
const $webhookDeliver: Provider = {
provide: 'queue:webhookDeliver',
useFactory: (config: Config) => q(config, 'webhookDeliver', 64),
useFactory: (config: Config) => new Bull.Queue(QUEUE.WEBHOOK_DELIVER, baseQueueOptions(config, QUEUE.WEBHOOK_DELIVER)),
inject: [DI.config],
};
@@ -118,4 +88,40 @@ const $webhookDeliver: Provider = {
$webhookDeliver,
],
})
export class QueueModule {}
export class QueueModule implements OnApplicationShutdown {
constructor(
@Inject('queue:system') public systemQueue: SystemQueue,
@Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue,
@Inject('queue:deliver') public deliverQueue: DeliverQueue,
@Inject('queue:inbox') public inboxQueue: InboxQueue,
@Inject('queue:db') public dbQueue: DbQueue,
@Inject('queue:relationship') public relationshipQueue: RelationshipQueue,
@Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue,
@Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue,
) {}
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);
}
await Promise.all([
this.systemQueue.close(),
this.endedPollNotificationQueue.close(),
this.deliverQueue.close(),
this.inboxQueue.close(),
this.dbQueue.close(),
this.relationshipQueue.close(),
this.objectStorageQueue.close(),
this.webhookDeliverQueue.close(),
]);
}
async onApplicationShutdown(signal: string): Promise<void> {
await this.dispose();
}
}

View File

@@ -1,6 +1,5 @@
import { Inject, Injectable } from '@nestjs/common';
import { v4 as uuid } from 'uuid';
import Bull from 'bull';
import type { IActivity } from '@/core/activitypub/type.js';
import type { DriveFile } from '@/models/entities/DriveFile.js';
import type { Webhook, webhookEventTypes } from '@/models/entities/Webhook.js';
@@ -11,6 +10,7 @@ import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js';
import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, RelationshipQueue, SystemQueue, WebhookDeliverQueue } from './QueueModule.js';
import type { DbJobData, RelationshipJobData, ThinUser } from '../queue/types.js';
import type httpSignature from '@peertube/http-signature';
import type * as Bull from 'bullmq';
@Injectable()
export class QueueService {
@@ -26,7 +26,43 @@ export class QueueService {
@Inject('queue:relationship') public relationshipQueue: RelationshipQueue,
@Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue,
@Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue,
) {}
) {
this.systemQueue.add('tickCharts', {
}, {
repeat: { pattern: '55 * * * *' },
removeOnComplete: true,
});
this.systemQueue.add('resyncCharts', {
}, {
repeat: { pattern: '0 0 * * *' },
removeOnComplete: true,
});
this.systemQueue.add('cleanCharts', {
}, {
repeat: { pattern: '0 0 * * *' },
removeOnComplete: true,
});
this.systemQueue.add('aggregateRetention', {
}, {
repeat: { pattern: '0 0 * * *' },
removeOnComplete: true,
});
this.systemQueue.add('clean', {
}, {
repeat: { pattern: '0 0 * * *' },
removeOnComplete: true,
});
this.systemQueue.add('checkExpiredMutings', {
}, {
repeat: { pattern: '*/5 * * * *' },
removeOnComplete: true,
});
}
@bindThis
public deliver(user: ThinUser, content: IActivity | null, to: string | null, isSharedInbox: boolean) {
@@ -42,11 +78,10 @@ export class QueueService {
isSharedInbox,
};
return this.deliverQueue.add(data, {
return this.deliverQueue.add(to, data, {
attempts: this.config.deliverJobMaxAttempts ?? 12,
timeout: 1 * 60 * 1000, // 1min
backoff: {
type: 'apBackoff',
type: 'custom',
},
removeOnComplete: true,
removeOnFail: true,
@@ -60,11 +95,10 @@ export class QueueService {
signature,
};
return this.inboxQueue.add(data, {
return this.inboxQueue.add('', data, {
attempts: this.config.inboxJobMaxAttempts ?? 8,
timeout: 5 * 60 * 1000, // 5min
backoff: {
type: 'apBackoff',
type: 'custom',
},
removeOnComplete: true,
removeOnFail: true,
@@ -212,7 +246,7 @@ export class QueueService {
private generateToDbJobData<T extends 'importFollowingToDb' | 'importBlockingToDb', D extends DbJobData<T>>(name: T, data: D): {
name: string,
data: D,
opts: Bull.JobOptions,
opts: Bull.JobsOptions,
} {
return {
name,
@@ -299,10 +333,10 @@ export class QueueService {
}
@bindThis
private generateRelationshipJobData(name: 'follow' | 'unfollow' | 'block' | 'unblock', data: RelationshipJobData, opts: Bull.JobOptions = {}): {
private generateRelationshipJobData(name: 'follow' | 'unfollow' | 'block' | 'unblock', data: RelationshipJobData, opts: Bull.JobsOptions = {}): {
name: string,
data: RelationshipJobData,
opts: Bull.JobOptions,
opts: Bull.JobsOptions,
} {
return {
name,
@@ -351,11 +385,10 @@ export class QueueService {
eventId: uuid(),
};
return this.webhookDeliverQueue.add(data, {
return this.webhookDeliverQueue.add(webhook.id, data, {
attempts: 4,
timeout: 1 * 60 * 1000, // 1min
backoff: {
type: 'apBackoff',
type: 'custom',
},
removeOnComplete: true,
removeOnFail: true,
@@ -367,11 +400,11 @@ export class QueueService {
this.deliverQueue.once('cleaned', (jobs, status) => {
//deliverLogger.succ(`Cleaned ${jobs.length} ${status} jobs`);
});
this.deliverQueue.clean(0, 'delayed');
this.deliverQueue.clean(0, Infinity, 'delayed');
this.inboxQueue.once('cleaned', (jobs, status) => {
//inboxLogger.succ(`Cleaned ${jobs.length} ${status} jobs`);
});
this.inboxQueue.clean(0, 'delayed');
this.inboxQueue.clean(0, Infinity, 'delayed');
}
}

View File

@@ -20,6 +20,7 @@ import { bindThis } from '@/decorators.js';
import { UtilityService } from '@/core/UtilityService.js';
import { UserBlockingService } from '@/core/UserBlockingService.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { RoleService } from '@/core/RoleService.js';
const FALLBACK = '❤';
@@ -54,6 +55,9 @@ type DecodedReaction = {
host?: string | null;
};
const isCustomEmojiRegexp = /^:([\w+-]+)(?:@\.)?:$/;
const decodeCustomEmojiRegexp = /^:([\w+-]+)(?:@([\w.-]+))?:$/;
@Injectable()
export class ReactionService {
constructor(
@@ -72,6 +76,7 @@ export class ReactionService {
private utilityService: UtilityService,
private metaService: MetaService,
private customEmojiService: CustomEmojiService,
private roleService: RoleService,
private userEntityService: UserEntityService,
private noteEntityService: NoteEntityService,
private userBlockingService: UserBlockingService,
@@ -85,7 +90,7 @@ export class ReactionService {
}
@bindThis
public async create(user: { id: User['id']; host: User['host']; isBot: User['isBot'] }, note: Note, reaction?: string | null) {
public async create(user: { id: User['id']; host: User['host']; isBot: User['isBot'] }, note: Note, _reaction?: string | null) {
// Check blocking
if (note.userId !== user.id) {
const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id);
@@ -99,10 +104,41 @@ export class ReactionService {
throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.');
}
if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote') && (user.host != null))) {
let reaction = _reaction ?? FALLBACK;
if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && (user.host != null))) {
reaction = '❤️';
} else {
reaction = await this.toDbReaction(reaction, user.host);
} else if (_reaction) {
const custom = reaction.match(isCustomEmojiRegexp);
if (custom) {
const reacterHost = this.utilityService.toPunyNullable(user.host);
const name = custom[1];
const emoji = reacterHost == null
? (await this.customEmojiService.localEmojisCache.fetch()).get(name)
: await this.emojisRepository.findOneBy({
host: reacterHost,
name,
});
if (emoji) {
if (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0 || (await this.roleService.getUserRoles(user.id)).some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.includes(r.id))) {
reaction = reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`;
// センシティブ
if ((note.reactionAcceptance === 'nonSensitiveOnly') && emoji.isSensitive) {
reaction = FALLBACK;
}
} else {
// リアクションとして使う権限がない
reaction = FALLBACK;
}
} else {
reaction = FALLBACK;
}
} else {
reaction = this.normalize(reaction ?? null);
}
}
const record: NoteReaction = {
@@ -288,11 +324,9 @@ export class ReactionService {
}
@bindThis
public async toDbReaction(reaction?: string | null, reacterHost?: string | null): Promise<string> {
public normalize(reaction: string | null): string {
if (reaction == null) return FALLBACK;
reacterHost = this.utilityService.toPunyNullable(reacterHost);
// 文字列タイプのリアクションを絵文字に変換
if (Object.keys(legacies).includes(reaction)) return legacies[reaction];
@@ -306,25 +340,12 @@ export class ReactionService {
return unicode.match('\u200d') ? unicode : unicode.replace(/\ufe0f/g, '');
}
const custom = reaction.match(/^:([\w+-]+)(?:@\.)?:$/);
if (custom) {
const name = custom[1];
const emoji = reacterHost == null
? (await this.customEmojiService.localEmojisCache.fetch()).get(name)
: await this.emojisRepository.findOneBy({
host: reacterHost,
name,
});
if (emoji) return reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`;
}
return FALLBACK;
}
@bindThis
public decodeReaction(str: string): DecodedReaction {
const custom = str.match(/^:([\w+-]+)(?:@([\w.-]+))?:$/);
const custom = str.match(decodeCustomEmojiRegexp);
if (custom) {
const name = custom[1];

View File

@@ -306,6 +306,14 @@ export class RoleService implements OnApplicationShutdown {
return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isAdministrator);
}
@bindThis
public async isExplorable(role: { id: Role['id']} | null): Promise<boolean> {
if (role == null) return false;
const check = await this.rolesRepository.findOneBy({ id: role.id });
if (check == null) return false;
return check.isExplorable;
}
@bindThis
public async getModeratorIds(includeAdmins = true): Promise<User['id'][]> {
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
@@ -425,7 +433,12 @@ export class RoleService implements OnApplicationShutdown {
}
@bindThis
public onApplicationShutdown(signal?: string | undefined) {
public dispose(): void {
this.redisForSub.off('message', this.onMessage);
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View File

@@ -68,7 +68,7 @@ export class SearchService {
private idService: IdService,
) {
if (meilisearch) {
this.meilisearchNoteIndex = meilisearch.index('notes');
this.meilisearchNoteIndex = meilisearch.index(`${config.meilisearch!.index}---notes`);
this.meilisearchNoteIndex.updateSettings({
searchableAttributes: [
'text',
@@ -82,6 +82,7 @@ export class SearchService {
'userId',
'userHost',
'channelId',
'tags',
],
typoTolerance: {
enabled: false,
@@ -107,6 +108,7 @@ export class SearchService {
channelId: note.channelId,
cw: note.cw,
text: note.text,
tags: note.tags,
}], {
primaryKey: 'id',
});

View File

@@ -16,6 +16,9 @@ type IWebFinger = {
subject: string;
};
const urlRegex = /^https?:\/\//;
const mRegex = /^([^@]+)@(.*)/;
@Injectable()
export class WebfingerService {
constructor(
@@ -35,12 +38,12 @@ export class WebfingerService {
@bindThis
private genUrl(query: string): string {
if (query.match(/^https?:\/\//)) {
if (query.match(urlRegex)) {
const u = new URL(query);
return `${u.protocol}//${u.hostname}/.well-known/webfinger?` + urlQuery({ resource: query });
}
const m = query.match(/^([^@]+)@(.*)/);
const m = query.match(mRegex);
if (m) {
const hostname = m[2];
const useHttp = process.env.MISSKEY_WEBFINGER_USE_HTTP && process.env.MISSKEY_WEBFINGER_USE_HTTP.toLowerCase() === 'true';

View File

@@ -81,7 +81,12 @@ export class WebhookService implements OnApplicationShutdown {
}
@bindThis
public onApplicationShutdown(signal?: string | undefined) {
public dispose(): void {
this.redisForSub.off('message', this.onMessage);
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View File

@@ -277,7 +277,7 @@ export class ApRendererService {
const name = reaction.replaceAll(':', '');
const emoji = (await this.customEmojiService.localEmojisCache.fetch()).get(name);
if (emoji) object.tag = [this.renderEmoji(emoji)];
if (emoji && !emoji.localOnly) object.tag = [this.renderEmoji(emoji)];
}
return object;
@@ -400,7 +400,7 @@ export class ApRendererService {
}));
const emojis = await this.getEmojis(note.emojis);
const apemojis = emojis.map(emoji => this.renderEmoji(emoji));
const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji));
const tag = [
...hashtagTags,
@@ -479,7 +479,7 @@ export class ApRendererService {
}
const emojis = await this.getEmojis(user.emojis);
const apemojis = emojis.map(emoji => this.renderEmoji(emoji));
const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji));
const hashtagTags = (user.tags ?? []).map(tag => this.renderHashtag(tag));

View File

@@ -94,7 +94,7 @@ class LdSignature {
@bindThis
private getLoader() {
return async (url: string): Promise<any> => {
if (!url.match('^https?\:\/\/')) throw `Invalid URL ${url}`;
if (!url.match('^https?\:\/\/')) throw new Error(`Invalid URL ${url}`);
if (this.preLoad) {
if (url in CONTEXTS) {
@@ -126,7 +126,7 @@ class LdSignature {
timeout: this.loderTimeout,
}, { throwErrorWhenResponseNotOk: false }).then(res => {
if (!res.ok) {
throw `${res.status} ${res.statusText}`;
throw new Error(`${res.status} ${res.statusText}`);
} else {
return res.json();
}

View File

@@ -18,6 +18,7 @@ import { PollService } from '@/core/PollService.js';
import { StatusError } from '@/misc/status-error.js';
import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
import { checkHttps } from '@/misc/check-https.js';
import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js';
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
import { ApLoggerService } from '../ApLoggerService.js';
@@ -32,7 +33,6 @@ import { ApQuestionService } from './ApQuestionService.js';
import { ApImageService } from './ApImageService.js';
import type { Resolver } from '../ApResolverService.js';
import type { IObject, IPost } from '../type.js';
import { checkHttps } from '@/misc/check-https.js';
@Injectable()
export class ApNoteService {
@@ -230,7 +230,7 @@ export class ApNoteService {
quote = results.filter((x): x is { status: 'ok', res: Note | null } => x.status === 'ok').map(x => x.res).find(x => x);
if (!quote) {
if (results.some(x => x.status === 'temperror')) {
throw 'quote resolve failed';
throw new Error('quote resolve failed');
}
}
}
@@ -311,7 +311,7 @@ export class ApNoteService {
// ブロックしてたら中断
const meta = await this.metaService.fetch();
if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) throw { statusCode: 451 };
if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) throw new StatusError('blocked host', 451);
const unlock = await this.appLockService.getApLock(uri);

View File

@@ -32,6 +32,8 @@ import type { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import type { AccountMoveService } from '@/core/AccountMoveService.js';
import { checkHttps } from '@/misc/check-https.js';
import { getApId, getApType, getOneApHrefNullable, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js';
import { extractApHashtags } from './tag.js';
import type { OnModuleInit } from '@nestjs/common';
@@ -42,8 +44,6 @@ import type { ApLoggerService } from '../ApLoggerService.js';
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
import type { ApImageService } from './ApImageService.js';
import type { IActor, IObject } from '../type.js';
import type { AccountMoveService } from '@/core/AccountMoveService.js';
import { checkHttps } from '@/misc/check-https.js';
const nameLength = 128;
const summaryLength = 2048;
@@ -306,7 +306,6 @@ export class ApPersonService implements OnModuleInit {
tags,
isBot,
isCat: (person as any).isCat === true,
showTimelineReplies: false,
})) as RemoteUser;
await transactionalEntityManager.save(new UserProfile({
@@ -696,7 +695,7 @@ export class ApPersonService implements OnModuleInit {
if (!dst.alsoKnownAs || dst.alsoKnownAs.length === 0) {
return 'skip: dst.alsoKnownAs is empty';
}
if (!dst.alsoKnownAs?.includes(src.uri)) {
if (!dst.alsoKnownAs.includes(src.uri)) {
return 'skip: alsoKnownAs does not include from.uri';
}

View File

@@ -60,7 +60,8 @@ export class ChartManagementService implements OnApplicationShutdown {
}, 1000 * 60 * 20);
}
async onApplicationShutdown(signal: string): Promise<void> {
@bindThis
public async dispose(): Promise<void> {
clearInterval(this.saveIntervalId);
if (process.env.NODE_ENV !== 'test') {
await Promise.all(
@@ -68,4 +69,9 @@ export class ChartManagementService implements OnApplicationShutdown {
);
}
}
@bindThis
async onApplicationShutdown(signal: string): Promise<void> {
await this.dispose();
}
}

View File

@@ -26,6 +26,8 @@ export class EmojiEntityService {
category: emoji.category,
// || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ)
url: emoji.publicUrl || emoji.originalUrl,
isSensitive: emoji.isSensitive ? true : undefined,
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined,
};
}
@@ -51,6 +53,9 @@ export class EmojiEntityService {
// || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ)
url: emoji.publicUrl || emoji.originalUrl,
license: emoji.license,
isSensitive: emoji.isSensitive,
localOnly: emoji.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction,
};
}

View File

@@ -445,6 +445,7 @@ export class UserEntityService implements OnModuleInit {
carefulBot: profile!.carefulBot,
autoAcceptFollowed: profile!.autoAcceptFollowed,
noCrawle: profile!.noCrawle,
preventAiLearning: profile!.preventAiLearning,
isExplorable: user.isExplorable,
isDeleted: user.isDeleted,
hideOnlineStatus: user.hideOnlineStatus,
@@ -465,7 +466,6 @@ export class UserEntityService implements OnModuleInit {
mutedInstances: profile!.mutedInstances,
mutingNotificationTypes: profile!.mutingNotificationTypes,
emailNotificationTypes: profile!.emailNotificationTypes,
showTimelineReplies: user.showTimelineReplies ?? falsy,
achievements: profile!.achievements,
loggedInDays: profile!.loggedInDates.length,
policies: this.roleService.getUserPolicies(user.id),

View File

@@ -35,6 +35,7 @@ export class UserListEntityService {
createdAt: userList.createdAt.toISOString(),
name: userList.name,
userIds: users.map(x => x.userId),
isPublic: userList.isPublic,
};
}
}

View File

@@ -34,7 +34,12 @@ export class JanitorService implements OnApplicationShutdown {
}
@bindThis
public onApplicationShutdown(signal?: string | undefined) {
public dispose(): void {
clearInterval(this.intervalId);
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View File

@@ -1,7 +1,11 @@
import { Injectable } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import Xev from 'xev';
import * as Bull from 'bullmq';
import { QueueService } from '@/core/QueueService.js';
import { bindThis } from '@/decorators.js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { QUEUE, baseQueueOptions } from '@/queue/const.js';
import type { OnApplicationShutdown } from '@nestjs/common';
const ev = new Xev();
@@ -13,6 +17,9 @@ export class QueueStatsService implements OnApplicationShutdown {
private intervalId: NodeJS.Timer;
constructor(
@Inject(DI.config)
private config: Config,
private queueService: QueueService,
) {
}
@@ -31,11 +38,14 @@ export class QueueStatsService implements OnApplicationShutdown {
let activeDeliverJobs = 0;
let activeInboxJobs = 0;
this.queueService.deliverQueue.on('global:active', () => {
const deliverQueueEvents = new Bull.QueueEvents(QUEUE.DELIVER, baseQueueOptions(this.config, QUEUE.DELIVER));
const inboxQueueEvents = new Bull.QueueEvents(QUEUE.INBOX, baseQueueOptions(this.config, QUEUE.INBOX));
deliverQueueEvents.on('active', () => {
activeDeliverJobs++;
});
this.queueService.inboxQueue.on('global:active', () => {
inboxQueueEvents.on('active', () => {
activeInboxJobs++;
});
@@ -71,9 +81,14 @@ export class QueueStatsService implements OnApplicationShutdown {
this.intervalId = setInterval(tick, interval);
}
@bindThis
public onApplicationShutdown(signal?: string | undefined) {
public dispose(): void {
clearInterval(this.intervalId);
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View File

@@ -63,9 +63,14 @@ export class ServerStatsService implements OnApplicationShutdown {
}
@bindThis
public onApplicationShutdown(signal?: string | undefined) {
public dispose(): void {
clearInterval(this.intervalId);
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}
// CPU STAT

View File

@@ -25,6 +25,7 @@ export const DI = {
userSecurityKeysRepository: Symbol('userSecurityKeysRepository'),
userPublickeysRepository: Symbol('userPublickeysRepository'),
userListsRepository: Symbol('userListsRepository'),
userListFavoritesRepository: Symbol('userListFavoritesRepository'),
userListJoiningsRepository: Symbol('userListJoiningsRepository'),
userNotePiningsRepository: Symbol('userNotePiningsRepository'),
userIpsRepository: Symbol('userIpsRepository'),

View File

@@ -21,7 +21,7 @@ function getNoise(): string {
export function genAid(date: Date): string {
const t = date.getTime();
if (isNaN(t)) throw 'Failed to create AID: Invalid Date';
if (isNaN(t)) throw new Error('Failed to create AID: Invalid Date');
counter++;
return getTime(t) + getNoise();
}

View File

@@ -5,15 +5,16 @@ const dateTimeIntervals = {
};
export function dateUTC(time: number[]): Date {
const d = time.length === 2 ? Date.UTC(time[0], time[1])
: time.length === 3 ? Date.UTC(time[0], time[1], time[2])
: time.length === 4 ? Date.UTC(time[0], time[1], time[2], time[3])
: time.length === 5 ? Date.UTC(time[0], time[1], time[2], time[3], time[4])
: time.length === 6 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5])
: time.length === 7 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5], time[6])
: null;
const d =
time.length === 2 ? Date.UTC(time[0], time[1])
: time.length === 3 ? Date.UTC(time[0], time[1], time[2])
: time.length === 4 ? Date.UTC(time[0], time[1], time[2], time[3])
: time.length === 5 ? Date.UTC(time[0], time[1], time[2], time[3], time[4])
: time.length === 6 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5])
: time.length === 7 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5], time[6])
: null;
if (!d) throw 'wrong number of arguments';
if (!d) throw new Error('wrong number of arguments');
return new Date(d);
}

View File

@@ -1,6 +1,6 @@
import { Module } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite, UserMemo } from './index.js';
import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite, UserMemo, UserListFavorite } from './index.js';
import type { DataSource } from 'typeorm';
import type { Provider } from '@nestjs/common';
@@ -112,6 +112,12 @@ const $userListsRepository: Provider = {
inject: [DI.db],
};
const $userListFavoritesRepository: Provider = {
provide: DI.userListFavoritesRepository,
useFactory: (db: DataSource) => db.getRepository(UserListFavorite),
inject: [DI.db],
};
const $userListJoiningsRepository: Provider = {
provide: DI.userListJoiningsRepository,
useFactory: (db: DataSource) => db.getRepository(UserListJoining),
@@ -416,6 +422,7 @@ const $userMemosRepository: Provider = {
$userSecurityKeysRepository,
$userPublickeysRepository,
$userListsRepository,
$userListFavoritesRepository,
$userListJoiningsRepository,
$userNotePiningsRepository,
$userIpsRepository,
@@ -483,6 +490,7 @@ const $userMemosRepository: Provider = {
$userSecurityKeysRepository,
$userPublickeysRepository,
$userListsRepository,
$userListFavoritesRepository,
$userListJoiningsRepository,
$userNotePiningsRepository,
$userIpsRepository,

View File

@@ -60,4 +60,20 @@ export class Emoji {
length: 1024, nullable: true,
})
public license: string | null;
@Column('boolean', {
default: false,
})
public localOnly: boolean;
@Column('boolean', {
default: false,
})
public isSensitive: boolean;
// TODO: 定期ジョブで存在しなくなったロールIDを除去するようにする
@Column('varchar', {
array: true, length: 128, default: '{}',
})
public roleIdsThatCanBeUsedThisEmojiAsReaction: string[];
}

View File

@@ -90,7 +90,7 @@ export class Note {
@Column('varchar', {
length: 64, nullable: true,
})
public reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | null;
public reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null;
@Column('smallint', {
default: 0,

View File

@@ -232,12 +232,6 @@ export class User {
})
public followersUri: string | null;
@Column('boolean', {
default: false,
comment: 'Whether to show users replying to other users in the timeline.',
})
public showTimelineReplies: boolean;
@Index({ unique: true })
@Column('char', {
length: 16, nullable: true, unique: true,

View File

@@ -19,6 +19,12 @@ export class UserList {
})
public userId: User['id'];
@Index()
@Column('boolean', {
default: false,
})
public isPublic: boolean;
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})

View File

@@ -0,0 +1,33 @@
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
import { id } from '../id.js';
import { User } from './User.js';
import { UserList } from './UserList.js';
@Entity()
@Index(['userId', 'userListId'], { unique: true })
export class UserListFavorite {
@PrimaryColumn(id())
public id: string;
@Column('timestamp with time zone')
public createdAt: Date;
@Index()
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user: User | null;
@Column(id())
public userListId: UserList['id'];
@ManyToOne(type => UserList, {
onDelete: 'CASCADE',
})
@JoinColumn()
public userList: UserList | null;
}

View File

@@ -76,7 +76,7 @@ export class UserProfile {
public emailNotificationTypes: string[];
@Column('boolean', {
default: false,
default: true,
})
public publicReactions: boolean;
@@ -147,6 +147,11 @@ export class UserProfile {
})
public noCrawle: boolean;
@Column('boolean', {
default: true,
})
public preventAiLearning: boolean;
@Column('boolean', {
default: false,
})

View File

@@ -49,6 +49,7 @@ import { User } from '@/models/entities/User.js';
import { UserIp } from '@/models/entities/UserIp.js';
import { UserKeypair } from '@/models/entities/UserKeypair.js';
import { UserList } from '@/models/entities/UserList.js';
import { UserListFavorite } from './entities/UserListFavorite.js';
import { UserListJoining } from '@/models/entities/UserListJoining.js';
import { UserNotePining } from '@/models/entities/UserNotePining.js';
import { UserPending } from '@/models/entities/UserPending.js';
@@ -117,6 +118,7 @@ export {
UserIp,
UserKeypair,
UserList,
UserListFavorite,
UserListJoining,
UserNotePining,
UserPending,
@@ -184,6 +186,7 @@ export type UsersRepository = Repository<User>;
export type UserIpsRepository = Repository<UserIp>;
export type UserKeypairsRepository = Repository<UserKeypair>;
export type UserListsRepository = Repository<UserList>;
export type UserListFavoritesRepository = Repository<UserListFavorite>;
export type UserListJoiningsRepository = Repository<UserListJoining>;
export type UserNotePiningsRepository = Repository<UserNotePining>;
export type UserPendingsRepository = Repository<UserPending>;

View File

@@ -22,6 +22,19 @@ export const packedEmojiSimpleSchema = {
type: 'string',
optional: false, nullable: false,
},
isSensitive: {
type: 'boolean',
optional: true, nullable: false,
},
roleIdsThatCanBeUsedThisEmojiAsReaction: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
},
} as const;
@@ -63,5 +76,22 @@ export const packedEmojiDetailedSchema = {
type: 'string',
optional: false, nullable: true,
},
isSensitive: {
type: 'boolean',
optional: false, nullable: false,
},
localOnly: {
type: 'boolean',
optional: false, nullable: false,
},
roleIdsThatCanBeUsedThisEmojiAsReaction: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
},
} as const;

View File

@@ -25,5 +25,10 @@ export const packedUserListSchema = {
format: 'id',
},
},
isPublic: {
type: 'boolean',
nullable: false,
optional: false,
},
},
} as const;

View File

@@ -302,7 +302,11 @@ export const packedMeDetailedOnlySchema = {
},
noCrawle: {
type: 'boolean',
nullable: true, optional: false,
nullable: false, optional: false,
},
preventAiLearning: {
type: 'boolean',
nullable: false, optional: false,
},
isExplorable: {
type: 'boolean',

View File

@@ -57,6 +57,7 @@ import { User } from '@/models/entities/User.js';
import { UserIp } from '@/models/entities/UserIp.js';
import { UserKeypair } from '@/models/entities/UserKeypair.js';
import { UserList } from '@/models/entities/UserList.js';
import { UserListFavorite } from '@/models/entities/UserListFavorite.js';
import { UserListJoining } from '@/models/entities/UserListJoining.js';
import { UserNotePining } from '@/models/entities/UserNotePining.js';
import { UserPending } from '@/models/entities/UserPending.js';
@@ -132,6 +133,7 @@ export const entities = [
UserKeypair,
UserPublickey,
UserList,
UserListFavorite,
UserListJoining,
UserNotePining,
UserSecurityKey,

View File

@@ -1,69 +0,0 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { bindThis } from '@/decorators.js';
import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js';
import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmojisProcessorService.js';
import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js';
import { ExportFollowingProcessorService } from './processors/ExportFollowingProcessorService.js';
import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js';
import { ExportBlockingProcessorService } from './processors/ExportBlockingProcessorService.js';
import { ExportUserListsProcessorService } from './processors/ExportUserListsProcessorService.js';
import { ExportAntennasProcessorService } from './processors/ExportAntennasProcessorService.js';
import { ImportFollowingProcessorService } from './processors/ImportFollowingProcessorService.js';
import { ImportMutingProcessorService } from './processors/ImportMutingProcessorService.js';
import { ImportBlockingProcessorService } from './processors/ImportBlockingProcessorService.js';
import { ImportUserListsProcessorService } from './processors/ImportUserListsProcessorService.js';
import { ImportCustomEmojisProcessorService } from './processors/ImportCustomEmojisProcessorService.js';
import { ImportAntennasProcessorService } from './processors/ImportAntennasProcessorService.js';
import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js';
import { ExportFavoritesProcessorService } from './processors/ExportFavoritesProcessorService.js';
import type Bull from 'bull';
@Injectable()
export class DbQueueProcessorsService {
constructor(
@Inject(DI.config)
private config: Config,
private deleteDriveFilesProcessorService: DeleteDriveFilesProcessorService,
private exportCustomEmojisProcessorService: ExportCustomEmojisProcessorService,
private exportNotesProcessorService: ExportNotesProcessorService,
private exportFavoritesProcessorService: ExportFavoritesProcessorService,
private exportFollowingProcessorService: ExportFollowingProcessorService,
private exportMutingProcessorService: ExportMutingProcessorService,
private exportBlockingProcessorService: ExportBlockingProcessorService,
private exportUserListsProcessorService: ExportUserListsProcessorService,
private exportAntennasProcessorService: ExportAntennasProcessorService,
private importFollowingProcessorService: ImportFollowingProcessorService,
private importMutingProcessorService: ImportMutingProcessorService,
private importBlockingProcessorService: ImportBlockingProcessorService,
private importUserListsProcessorService: ImportUserListsProcessorService,
private importCustomEmojisProcessorService: ImportCustomEmojisProcessorService,
private importAntennasProcessorService: ImportAntennasProcessorService,
private deleteAccountProcessorService: DeleteAccountProcessorService,
) {
}
@bindThis
public start(q: Bull.Queue): void {
q.process('deleteDriveFiles', (job, done) => this.deleteDriveFilesProcessorService.process(job, done));
q.process('exportCustomEmojis', (job, done) => this.exportCustomEmojisProcessorService.process(job, done));
q.process('exportNotes', (job, done) => this.exportNotesProcessorService.process(job, done));
q.process('exportFavorites', (job, done) => this.exportFavoritesProcessorService.process(job, done));
q.process('exportFollowing', (job, done) => this.exportFollowingProcessorService.process(job, done));
q.process('exportMuting', (job, done) => this.exportMutingProcessorService.process(job, done));
q.process('exportBlocking', (job, done) => this.exportBlockingProcessorService.process(job, done));
q.process('exportUserLists', (job, done) => this.exportUserListsProcessorService.process(job, done));
q.process('exportAntennas', (job, done) => this.exportAntennasProcessorService.process(job, done));
q.process('importFollowing', (job, done) => this.importFollowingProcessorService.process(job, done));
q.process('importFollowingToDb', (job) => this.importFollowingProcessorService.processDb(job));
q.process('importMuting', (job, done) => this.importMutingProcessorService.process(job, done));
q.process('importBlocking', (job, done) => this.importBlockingProcessorService.process(job, done));
q.process('importBlockingToDb', (job) => this.importBlockingProcessorService.processDb(job));
q.process('importUserLists', (job, done) => this.importUserListsProcessorService.process(job, done));
q.process('importCustomEmojis', (job, done) => this.importCustomEmojisProcessorService.process(job, done));
q.process('importAntennas', (job, done) => this.importAntennasProcessorService.process(job, done));
q.process('deleteAccount', (job) => this.deleteAccountProcessorService.process(job));
}
}

View File

@@ -1,25 +0,0 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js';
import { DeleteFileProcessorService } from './processors/DeleteFileProcessorService.js';
import type Bull from 'bull';
import { bindThis } from '@/decorators.js';
@Injectable()
export class ObjectStorageQueueProcessorsService {
constructor(
@Inject(DI.config)
private config: Config,
private deleteFileProcessorService: DeleteFileProcessorService,
private cleanRemoteFilesProcessorService: CleanRemoteFilesProcessorService,
) {
}
@bindThis
public start(q: Bull.Queue): void {
q.process('deleteFile', 16, (job) => this.deleteFileProcessorService.process(job));
q.process('cleanRemoteFiles', 16, (job, done) => this.cleanRemoteFilesProcessorService.process(job, done));
}
}

View File

@@ -3,14 +3,10 @@ import { CoreModule } from '@/core/CoreModule.js';
import { GlobalModule } from '@/GlobalModule.js';
import { QueueLoggerService } from './QueueLoggerService.js';
import { QueueProcessorService } from './QueueProcessorService.js';
import { DbQueueProcessorsService } from './DbQueueProcessorsService.js';
import { RelationshipQueueProcessorsService } from './RelationshipQueueProcessorsService.js';
import { ObjectStorageQueueProcessorsService } from './ObjectStorageQueueProcessorsService.js';
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
import { InboxProcessorService } from './processors/InboxProcessorService.js';
import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js';
import { SystemQueueProcessorsService } from './SystemQueueProcessorsService.js';
import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
import { CleanProcessorService } from './processors/CleanProcessorService.js';
@@ -68,10 +64,6 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
DeleteFileProcessorService,
CleanRemoteFilesProcessorService,
RelationshipProcessorService,
SystemQueueProcessorsService,
ObjectStorageQueueProcessorsService,
DbQueueProcessorsService,
RelationshipQueueProcessorsService,
WebhookDeliverProcessorService,
EndedPollNotificationProcessorService,
DeliverProcessorService,

View File

@@ -1,44 +1,116 @@
import { Inject, Injectable } from '@nestjs/common';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import * as Bull from 'bullmq';
import type { Config } from '@/config.js';
import { DI } from '@/di-symbols.js';
import type Logger from '@/logger.js';
import { QueueService } from '@/core/QueueService.js';
import { bindThis } from '@/decorators.js';
import { getJobInfo } from './get-job-info.js';
import { SystemQueueProcessorsService } from './SystemQueueProcessorsService.js';
import { ObjectStorageQueueProcessorsService } from './ObjectStorageQueueProcessorsService.js';
import { DbQueueProcessorsService } from './DbQueueProcessorsService.js';
import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js';
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
import { InboxProcessorService } from './processors/InboxProcessorService.js';
import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js';
import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmojisProcessorService.js';
import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js';
import { ExportFollowingProcessorService } from './processors/ExportFollowingProcessorService.js';
import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js';
import { ExportBlockingProcessorService } from './processors/ExportBlockingProcessorService.js';
import { ExportUserListsProcessorService } from './processors/ExportUserListsProcessorService.js';
import { ExportAntennasProcessorService } from './processors/ExportAntennasProcessorService.js';
import { ImportFollowingProcessorService } from './processors/ImportFollowingProcessorService.js';
import { ImportMutingProcessorService } from './processors/ImportMutingProcessorService.js';
import { ImportBlockingProcessorService } from './processors/ImportBlockingProcessorService.js';
import { ImportUserListsProcessorService } from './processors/ImportUserListsProcessorService.js';
import { ImportCustomEmojisProcessorService } from './processors/ImportCustomEmojisProcessorService.js';
import { ImportAntennasProcessorService } from './processors/ImportAntennasProcessorService.js';
import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js';
import { ExportFavoritesProcessorService } from './processors/ExportFavoritesProcessorService.js';
import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js';
import { DeleteFileProcessorService } from './processors/DeleteFileProcessorService.js';
import { RelationshipProcessorService } from './processors/RelationshipProcessorService.js';
import { TickChartsProcessorService } from './processors/TickChartsProcessorService.js';
import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js';
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
import { CleanProcessorService } from './processors/CleanProcessorService.js';
import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
import { QueueLoggerService } from './QueueLoggerService.js';
import { RelationshipQueueProcessorsService } from './RelationshipQueueProcessorsService.js';
import { QUEUE, baseQueueOptions } from './const.js';
// ref. https://github.com/misskey-dev/misskey/pull/7635#issue-971097019
function httpRelatedBackoff(attemptsMade: number) {
const baseDelay = 60 * 1000; // 1min
const maxBackoff = 8 * 60 * 60 * 1000; // 8hours
let backoff = (Math.pow(2, attemptsMade) - 1) * baseDelay;
backoff = Math.min(backoff, maxBackoff);
backoff += Math.round(backoff * Math.random() * 0.2);
return backoff;
}
function getJobInfo(job: Bull.Job | undefined, increment = false): string {
if (job == null) return '-';
const age = Date.now() - job.timestamp;
const formated = age > 60000 ? `${Math.floor(age / 1000 / 60)}m`
: age > 10000 ? `${Math.floor(age / 1000)}s`
: `${age}ms`;
// onActiveとかonCompletedのattemptsMadeがなぜか0始まりなのでインクリメントする
const currentAttempts = job.attemptsMade + (increment ? 1 : 0);
const maxAttempts = job.opts ? job.opts.attempts : 0;
return `id=${job.id} attempts=${currentAttempts}/${maxAttempts} age=${formated}`;
}
@Injectable()
export class QueueProcessorService {
export class QueueProcessorService implements OnApplicationShutdown {
private logger: Logger;
private systemQueueWorker: Bull.Worker;
private dbQueueWorker: Bull.Worker;
private deliverQueueWorker: Bull.Worker;
private inboxQueueWorker: Bull.Worker;
private webhookDeliverQueueWorker: Bull.Worker;
private relationshipQueueWorker: Bull.Worker;
private objectStorageQueueWorker: Bull.Worker;
private endedPollNotificationQueueWorker: Bull.Worker;
constructor(
@Inject(DI.config)
private config: Config,
private queueLoggerService: QueueLoggerService,
private queueService: QueueService,
private systemQueueProcessorsService: SystemQueueProcessorsService,
private objectStorageQueueProcessorsService: ObjectStorageQueueProcessorsService,
private dbQueueProcessorsService: DbQueueProcessorsService,
private relationshipQueueProcessorsService: RelationshipQueueProcessorsService,
private webhookDeliverProcessorService: WebhookDeliverProcessorService,
private endedPollNotificationProcessorService: EndedPollNotificationProcessorService,
private deliverProcessorService: DeliverProcessorService,
private inboxProcessorService: InboxProcessorService,
private deleteDriveFilesProcessorService: DeleteDriveFilesProcessorService,
private exportCustomEmojisProcessorService: ExportCustomEmojisProcessorService,
private exportNotesProcessorService: ExportNotesProcessorService,
private exportFavoritesProcessorService: ExportFavoritesProcessorService,
private exportFollowingProcessorService: ExportFollowingProcessorService,
private exportMutingProcessorService: ExportMutingProcessorService,
private exportBlockingProcessorService: ExportBlockingProcessorService,
private exportUserListsProcessorService: ExportUserListsProcessorService,
private exportAntennasProcessorService: ExportAntennasProcessorService,
private importFollowingProcessorService: ImportFollowingProcessorService,
private importMutingProcessorService: ImportMutingProcessorService,
private importBlockingProcessorService: ImportBlockingProcessorService,
private importUserListsProcessorService: ImportUserListsProcessorService,
private importCustomEmojisProcessorService: ImportCustomEmojisProcessorService,
private importAntennasProcessorService: ImportAntennasProcessorService,
private deleteAccountProcessorService: DeleteAccountProcessorService,
private deleteFileProcessorService: DeleteFileProcessorService,
private cleanRemoteFilesProcessorService: CleanRemoteFilesProcessorService,
private relationshipProcessorService: RelationshipProcessorService,
private tickChartsProcessorService: TickChartsProcessorService,
private resyncChartsProcessorService: ResyncChartsProcessorService,
private cleanChartsProcessorService: CleanChartsProcessorService,
private aggregateRetentionProcessorService: AggregateRetentionProcessorService,
private checkExpiredMutingsProcessorService: CheckExpiredMutingsProcessorService,
private cleanProcessorService: CleanProcessorService,
) {
this.logger = this.queueLoggerService.logger;
}
@bindThis
public start() {
function renderError(e: Error): any {
if (e) { // 何故かeがundefinedで来ることがある
return {
@@ -55,114 +127,232 @@ export class QueueProcessorService {
}
}
const systemLogger = this.logger.createSubLogger('system');
const deliverLogger = this.logger.createSubLogger('deliver');
const webhookLogger = this.logger.createSubLogger('webhook');
const inboxLogger = this.logger.createSubLogger('inbox');
const dbLogger = this.logger.createSubLogger('db');
const relationshipLogger = this.logger.createSubLogger('relationship');
const objectStorageLogger = this.logger.createSubLogger('objectStorage');
//#region system
this.systemQueueWorker = new Bull.Worker(QUEUE.SYSTEM, (job) => {
switch (job.name) {
case 'tickCharts': return this.tickChartsProcessorService.process();
case 'resyncCharts': return this.resyncChartsProcessorService.process();
case 'cleanCharts': return this.cleanChartsProcessorService.process();
case 'aggregateRetention': return this.aggregateRetentionProcessorService.process();
case 'checkExpiredMutings': return this.checkExpiredMutingsProcessorService.process();
case 'clean': return this.cleanProcessorService.process();
default: throw new Error(`unrecognized job type ${job.name} for system`);
}
}, {
...baseQueueOptions(this.config, QUEUE.SYSTEM),
autorun: false,
});
this.queueService.systemQueue
.on('waiting', (jobId) => systemLogger.debug(`waiting id=${jobId}`))
const systemLogger = this.logger.createSubLogger('system');
this.systemQueueWorker
.on('active', (job) => systemLogger.debug(`active id=${job.id}`))
.on('completed', (job, result) => systemLogger.debug(`completed(${result}) id=${job.id}`))
.on('failed', (job, err) => systemLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) }))
.on('error', (job: any, err: Error) => systemLogger.error(`error ${err}`, { job, e: renderError(err) }))
.on('stalled', (job) => systemLogger.warn(`stalled id=${job.id}`));
.on('failed', (job, err) => systemLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
.on('error', (err: Error) => systemLogger.error(`error ${err}`, { e: renderError(err) }))
.on('stalled', (jobId) => systemLogger.warn(`stalled id=${jobId}`));
//#endregion
this.queueService.deliverQueue
.on('waiting', (jobId) => deliverLogger.debug(`waiting id=${jobId}`))
.on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
.on('completed', (job, result) => deliverLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
.on('failed', (job, err) => deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`))
.on('error', (job: any, err: Error) => deliverLogger.error(`error ${err}`, { job, e: renderError(err) }))
.on('stalled', (job) => deliverLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
//#region db
this.dbQueueWorker = new Bull.Worker(QUEUE.DB, (job) => {
switch (job.name) {
case 'deleteDriveFiles': return this.deleteDriveFilesProcessorService.process(job);
case 'exportCustomEmojis': return this.exportCustomEmojisProcessorService.process(job);
case 'exportNotes': return this.exportNotesProcessorService.process(job);
case 'exportFavorites': return this.exportFavoritesProcessorService.process(job);
case 'exportFollowing': return this.exportFollowingProcessorService.process(job);
case 'exportMuting': return this.exportMutingProcessorService.process(job);
case 'exportBlocking': return this.exportBlockingProcessorService.process(job);
case 'exportUserLists': return this.exportUserListsProcessorService.process(job);
case 'exportAntennas': return this.exportAntennasProcessorService.process(job);
case 'importFollowing': return this.importFollowingProcessorService.process(job);
case 'importFollowingToDb': return this.importFollowingProcessorService.processDb(job);
case 'importMuting': return this.importMutingProcessorService.process(job);
case 'importBlocking': return this.importBlockingProcessorService.process(job);
case 'importBlockingToDb': return this.importBlockingProcessorService.processDb(job);
case 'importUserLists': return this.importUserListsProcessorService.process(job);
case 'importCustomEmojis': return this.importCustomEmojisProcessorService.process(job);
case 'importAntennas': return this.importAntennasProcessorService.process(job);
case 'deleteAccount': return this.deleteAccountProcessorService.process(job);
default: throw new Error(`unrecognized job type ${job.name} for db`);
}
}, {
...baseQueueOptions(this.config, QUEUE.DB),
autorun: false,
});
this.queueService.inboxQueue
.on('waiting', (jobId) => inboxLogger.debug(`waiting id=${jobId}`))
.on('active', (job) => inboxLogger.debug(`active ${getJobInfo(job, true)}`))
.on('completed', (job, result) => inboxLogger.debug(`completed(${result}) ${getJobInfo(job, true)}`))
.on('failed', (job, err) => inboxLogger.warn(`failed(${err}) ${getJobInfo(job)} activity=${job.data.activity ? job.data.activity.id : 'none'}`, { job, e: renderError(err) }))
.on('error', (job: any, err: Error) => inboxLogger.error(`error ${err}`, { job, e: renderError(err) }))
.on('stalled', (job) => inboxLogger.warn(`stalled ${getJobInfo(job)} activity=${job.data.activity ? job.data.activity.id : 'none'}`));
const dbLogger = this.logger.createSubLogger('db');
this.queueService.dbQueue
.on('waiting', (jobId) => dbLogger.debug(`waiting id=${jobId}`))
this.dbQueueWorker
.on('active', (job) => dbLogger.debug(`active id=${job.id}`))
.on('completed', (job, result) => dbLogger.debug(`completed(${result}) id=${job.id}`))
.on('failed', (job, err) => dbLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) }))
.on('error', (job: any, err: Error) => dbLogger.error(`error ${err}`, { job, e: renderError(err) }))
.on('stalled', (job) => dbLogger.warn(`stalled id=${job.id}`));
.on('failed', (job, err) => dbLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
.on('error', (err: Error) => dbLogger.error(`error ${err}`, { e: renderError(err) }))
.on('stalled', (jobId) => dbLogger.warn(`stalled id=${jobId}`));
//#endregion
this.queueService.relationshipQueue
.on('waiting', (jobId) => relationshipLogger.debug(`waiting id=${jobId}`))
.on('active', (job) => relationshipLogger.debug(`active id=${job.id}`))
.on('completed', (job, result) => relationshipLogger.debug(`completed(${result}) id=${job.id}`))
.on('failed', (job, err) => relationshipLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) }))
.on('error', (job: any, err: Error) => relationshipLogger.error(`error ${err}`, { job, e: renderError(err) }))
.on('stalled', (job) => relationshipLogger.warn(`stalled id=${job.id}`));
//#region deliver
this.deliverQueueWorker = new Bull.Worker(QUEUE.DELIVER, (job) => this.deliverProcessorService.process(job), {
...baseQueueOptions(this.config, QUEUE.DELIVER),
autorun: false,
concurrency: this.config.deliverJobConcurrency ?? 128,
limiter: {
max: this.config.deliverJobPerSec ?? 128,
duration: 1000,
},
settings: {
backoffStrategy: httpRelatedBackoff,
},
});
this.queueService.objectStorageQueue
.on('waiting', (jobId) => objectStorageLogger.debug(`waiting id=${jobId}`))
.on('active', (job) => objectStorageLogger.debug(`active id=${job.id}`))
.on('completed', (job, result) => objectStorageLogger.debug(`completed(${result}) id=${job.id}`))
.on('failed', (job, err) => objectStorageLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) }))
.on('error', (job: any, err: Error) => objectStorageLogger.error(`error ${err}`, { job, e: renderError(err) }))
.on('stalled', (job) => objectStorageLogger.warn(`stalled id=${job.id}`));
const deliverLogger = this.logger.createSubLogger('deliver');
this.queueService.webhookDeliverQueue
.on('waiting', (jobId) => webhookLogger.debug(`waiting id=${jobId}`))
this.deliverQueueWorker
.on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
.on('completed', (job, result) => deliverLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
.on('failed', (job, err) => deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`))
.on('error', (err: Error) => deliverLogger.error(`error ${err}`, { e: renderError(err) }))
.on('stalled', (jobId) => deliverLogger.warn(`stalled id=${jobId}`));
//#endregion
//#region inbox
this.inboxQueueWorker = new Bull.Worker(QUEUE.INBOX, (job) => this.inboxProcessorService.process(job), {
...baseQueueOptions(this.config, QUEUE.INBOX),
autorun: false,
concurrency: this.config.inboxJobConcurrency ?? 16,
limiter: {
max: this.config.inboxJobPerSec ?? 16,
duration: 1000,
},
settings: {
backoffStrategy: httpRelatedBackoff,
},
});
const inboxLogger = this.logger.createSubLogger('inbox');
this.inboxQueueWorker
.on('active', (job) => inboxLogger.debug(`active ${getJobInfo(job, true)}`))
.on('completed', (job, result) => inboxLogger.debug(`completed(${result}) ${getJobInfo(job, true)}`))
.on('failed', (job, err) => inboxLogger.warn(`failed(${err}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job, e: renderError(err) }))
.on('error', (err: Error) => inboxLogger.error(`error ${err}`, { e: renderError(err) }))
.on('stalled', (jobId) => inboxLogger.warn(`stalled id=${jobId}`));
//#endregion
//#region webhook deliver
this.webhookDeliverQueueWorker = new Bull.Worker(QUEUE.WEBHOOK_DELIVER, (job) => this.webhookDeliverProcessorService.process(job), {
...baseQueueOptions(this.config, QUEUE.WEBHOOK_DELIVER),
autorun: false,
concurrency: 64,
limiter: {
max: 64,
duration: 1000,
},
settings: {
backoffStrategy: httpRelatedBackoff,
},
});
const webhookLogger = this.logger.createSubLogger('webhook');
this.webhookDeliverQueueWorker
.on('active', (job) => webhookLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
.on('completed', (job, result) => webhookLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
.on('failed', (job, err) => webhookLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`))
.on('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`, { job, e: renderError(err) }))
.on('stalled', (job) => webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
.on('failed', (job, err) => webhookLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`))
.on('error', (err: Error) => webhookLogger.error(`error ${err}`, { e: renderError(err) }))
.on('stalled', (jobId) => webhookLogger.warn(`stalled id=${jobId}`));
//#endregion
this.queueService.deliverQueue.process(this.config.deliverJobConcurrency ?? 128, (job) => this.deliverProcessorService.process(job));
this.queueService.inboxQueue.process(this.config.inboxJobConcurrency ?? 16, (job) => this.inboxProcessorService.process(job));
this.queueService.endedPollNotificationQueue.process((job, done) => this.endedPollNotificationProcessorService.process(job, done));
this.queueService.webhookDeliverQueue.process(64, (job) => this.webhookDeliverProcessorService.process(job));
this.dbQueueProcessorsService.start(this.queueService.dbQueue);
this.relationshipQueueProcessorsService.start(this.queueService.relationshipQueue);
this.objectStorageQueueProcessorsService.start(this.queueService.objectStorageQueue);
this.queueService.systemQueue.add('tickCharts', {
//#region relationship
this.relationshipQueueWorker = new Bull.Worker(QUEUE.RELATIONSHIP, (job) => {
switch (job.name) {
case 'follow': return this.relationshipProcessorService.processFollow(job);
case 'unfollow': return this.relationshipProcessorService.processUnfollow(job);
case 'block': return this.relationshipProcessorService.processBlock(job);
case 'unblock': return this.relationshipProcessorService.processUnblock(job);
default: throw new Error(`unrecognized job type ${job.name} for relationship`);
}
}, {
repeat: { cron: '55 * * * *' },
removeOnComplete: true,
...baseQueueOptions(this.config, QUEUE.RELATIONSHIP),
autorun: false,
concurrency: this.config.relashionshipJobConcurrency ?? 16,
limiter: {
max: this.config.relashionshipJobPerSec ?? 64,
duration: 1000,
},
});
this.queueService.systemQueue.add('resyncCharts', {
const relationshipLogger = this.logger.createSubLogger('relationship');
this.relationshipQueueWorker
.on('active', (job) => relationshipLogger.debug(`active id=${job.id}`))
.on('completed', (job, result) => relationshipLogger.debug(`completed(${result}) id=${job.id}`))
.on('failed', (job, err) => relationshipLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
.on('error', (err: Error) => relationshipLogger.error(`error ${err}`, { e: renderError(err) }))
.on('stalled', (jobId) => relationshipLogger.warn(`stalled id=${jobId}`));
//#endregion
//#region object storage
this.objectStorageQueueWorker = new Bull.Worker(QUEUE.OBJECT_STORAGE, (job) => {
switch (job.name) {
case 'deleteFile': return this.deleteFileProcessorService.process(job);
case 'cleanRemoteFiles': return this.cleanRemoteFilesProcessorService.process(job);
default: throw new Error(`unrecognized job type ${job.name} for objectStorage`);
}
}, {
repeat: { cron: '0 0 * * *' },
removeOnComplete: true,
...baseQueueOptions(this.config, QUEUE.OBJECT_STORAGE),
autorun: false,
concurrency: 16,
});
this.queueService.systemQueue.add('cleanCharts', {
}, {
repeat: { cron: '0 0 * * *' },
removeOnComplete: true,
});
const objectStorageLogger = this.logger.createSubLogger('objectStorage');
this.queueService.systemQueue.add('aggregateRetention', {
}, {
repeat: { cron: '0 0 * * *' },
removeOnComplete: true,
});
this.objectStorageQueueWorker
.on('active', (job) => objectStorageLogger.debug(`active id=${job.id}`))
.on('completed', (job, result) => objectStorageLogger.debug(`completed(${result}) id=${job.id}`))
.on('failed', (job, err) => objectStorageLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
.on('error', (err: Error) => objectStorageLogger.error(`error ${err}`, { e: renderError(err) }))
.on('stalled', (jobId) => objectStorageLogger.warn(`stalled id=${jobId}`));
//#endregion
this.queueService.systemQueue.add('clean', {
}, {
repeat: { cron: '0 0 * * *' },
removeOnComplete: true,
//#region ended poll notification
this.endedPollNotificationQueueWorker = new Bull.Worker(QUEUE.ENDED_POLL_NOTIFICATION, (job) => this.endedPollNotificationProcessorService.process(job), {
...baseQueueOptions(this.config, QUEUE.ENDED_POLL_NOTIFICATION),
autorun: false,
});
//#endregion
}
this.queueService.systemQueue.add('checkExpiredMutings', {
}, {
repeat: { cron: '*/5 * * * *' },
removeOnComplete: true,
});
@bindThis
public async start(): Promise<void> {
await Promise.all([
this.systemQueueWorker.run(),
this.dbQueueWorker.run(),
this.deliverQueueWorker.run(),
this.inboxQueueWorker.run(),
this.webhookDeliverQueueWorker.run(),
this.relationshipQueueWorker.run(),
this.objectStorageQueueWorker.run(),
this.endedPollNotificationQueueWorker.run(),
]);
}
this.systemQueueProcessorsService.start(this.queueService.systemQueue);
@bindThis
public async stop(): Promise<void> {
await Promise.all([
this.systemQueueWorker.close(),
this.dbQueueWorker.close(),
this.deliverQueueWorker.close(),
this.inboxQueueWorker.close(),
this.webhookDeliverQueueWorker.close(),
this.relationshipQueueWorker.close(),
this.objectStorageQueueWorker.close(),
this.endedPollNotificationQueueWorker.close(),
]);
}
@bindThis
public async onApplicationShutdown(signal?: string | undefined): Promise<void> {
await this.stop();
}
}

View File

@@ -1,26 +0,0 @@
import { Inject, Injectable } from '@nestjs/common';
import { bindThis } from '@/decorators.js';
import { RelationshipProcessorService } from './processors/RelationshipProcessorService.js';
import type Bull from 'bull';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
@Injectable()
export class RelationshipQueueProcessorsService {
constructor(
@Inject(DI.config)
private config: Config,
private relationshipProcessorService: RelationshipProcessorService,
) {
}
@bindThis
public start(q: Bull.Queue): void {
const maxJobs = this.config.relashionshipJobConcurrency ?? 16;
q.process('follow', maxJobs, (job) => this.relationshipProcessorService.processFollow(job));
q.process('unfollow', maxJobs, (job) => this.relationshipProcessorService.processUnfollow(job));
q.process('block', maxJobs, (job) => this.relationshipProcessorService.processBlock(job));
q.process('unblock', maxJobs, (job) => this.relationshipProcessorService.processUnblock(job));
}
}

View File

@@ -1,37 +0,0 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { bindThis } from '@/decorators.js';
import { TickChartsProcessorService } from './processors/TickChartsProcessorService.js';
import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js';
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
import { CleanProcessorService } from './processors/CleanProcessorService.js';
import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
import type Bull from 'bull';
@Injectable()
export class SystemQueueProcessorsService {
constructor(
@Inject(DI.config)
private config: Config,
private tickChartsProcessorService: TickChartsProcessorService,
private resyncChartsProcessorService: ResyncChartsProcessorService,
private cleanChartsProcessorService: CleanChartsProcessorService,
private aggregateRetentionProcessorService: AggregateRetentionProcessorService,
private checkExpiredMutingsProcessorService: CheckExpiredMutingsProcessorService,
private cleanProcessorService: CleanProcessorService,
) {
}
@bindThis
public start(q: Bull.Queue): void {
q.process('tickCharts', (job, done) => this.tickChartsProcessorService.process(job, done));
q.process('resyncCharts', (job, done) => this.resyncChartsProcessorService.process(job, done));
q.process('cleanCharts', (job, done) => this.cleanChartsProcessorService.process(job, done));
q.process('aggregateRetention', (job, done) => this.aggregateRetentionProcessorService.process(job, done));
q.process('checkExpiredMutings', (job, done) => this.checkExpiredMutingsProcessorService.process(job, done));
q.process('clean', (job, done) => this.cleanProcessorService.process(job, done));
}
}

View File

@@ -0,0 +1,26 @@
import { Config } from '@/config.js';
import type * as Bull from 'bullmq';
export const QUEUE = {
DELIVER: 'deliver',
INBOX: 'inbox',
SYSTEM: 'system',
ENDED_POLL_NOTIFICATION: 'endedPollNotification',
DB: 'db',
RELATIONSHIP: 'relationship',
OBJECT_STORAGE: 'objectStorage',
WEBHOOK_DELIVER: 'webhookDeliver',
};
export function baseQueueOptions(config: Config, queueName: typeof QUEUE[keyof typeof QUEUE]): Bull.QueueOptions {
return {
connection: {
port: config.redisForJobQueue.port,
host: config.redisForJobQueue.host,
family: config.redisForJobQueue.family == null ? 0 : config.redisForJobQueue.family,
password: config.redisForJobQueue.pass,
db: config.redisForJobQueue.db ?? 0,
},
prefix: config.redisForJobQueue.prefix ? `${config.redisForJobQueue.prefix}:queue:${queueName}` : `queue:${queueName}`,
};
}

View File

@@ -1,15 +0,0 @@
import Bull from 'bull';
export function getJobInfo(job: Bull.Job, increment = false) {
const age = Date.now() - job.timestamp;
const formated = age > 60000 ? `${Math.floor(age / 1000 / 60)}m`
: age > 10000 ? `${Math.floor(age / 1000)}s`
: `${age}ms`;
// onActiveとかonCompletedのattemptsMadeがなぜか0始まりなのでインクリメントする
const currentAttempts = job.attemptsMade + (increment ? 1 : 0);
const maxAttempts = job.opts ? job.opts.attempts : 0;
return `id=${job.id} attempts=${currentAttempts}/${maxAttempts} age=${formated}`;
}

View File

@@ -9,7 +9,7 @@ import { deepClone } from '@/misc/clone.js';
import { IdService } from '@/core/IdService.js';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type Bull from 'bull';
import type * as Bull from 'bullmq';
@Injectable()
export class AggregateRetentionProcessorService {
@@ -32,7 +32,7 @@ export class AggregateRetentionProcessorService {
}
@bindThis
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
public async process(): Promise<void> {
this.logger.info('Aggregating retention...');
const now = new Date();
@@ -62,7 +62,6 @@ export class AggregateRetentionProcessorService {
} catch (err) {
if (isDuplicateKeyValueError(err)) {
this.logger.succ('Skip because it has already been processed by another worker.');
done();
return;
}
throw err;
@@ -88,6 +87,5 @@ export class AggregateRetentionProcessorService {
}
this.logger.succ('Retention aggregated.');
done();
}
}

View File

@@ -7,7 +7,7 @@ import type Logger from '@/logger.js';
import { bindThis } from '@/decorators.js';
import { UserMutingService } from '@/core/UserMutingService.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type Bull from 'bull';
import type * as Bull from 'bullmq';
@Injectable()
export class CheckExpiredMutingsProcessorService {
@@ -27,7 +27,7 @@ export class CheckExpiredMutingsProcessorService {
}
@bindThis
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
public async process(): Promise<void> {
this.logger.info('Checking expired mutings...');
const expired = await this.mutingsRepository.createQueryBuilder('muting')
@@ -41,6 +41,5 @@ export class CheckExpiredMutingsProcessorService {
}
this.logger.succ('All expired mutings checked.');
done();
}
}

View File

@@ -16,7 +16,7 @@ import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js';
import ApRequestChart from '@/core/chart/charts/ap-request.js';
import { bindThis } from '@/decorators.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type Bull from 'bull';
import type * as Bull from 'bullmq';
@Injectable()
export class CleanChartsProcessorService {
@@ -45,7 +45,7 @@ export class CleanChartsProcessorService {
}
@bindThis
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
public async process(): Promise<void> {
this.logger.info('Clean charts...');
await Promise.all([
@@ -64,6 +64,5 @@ export class CleanChartsProcessorService {
]);
this.logger.succ('All charts successfully cleaned.');
done();
}
}

View File

@@ -7,7 +7,7 @@ import type Logger from '@/logger.js';
import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type Bull from 'bull';
import type * as Bull from 'bullmq';
@Injectable()
export class CleanProcessorService {
@@ -36,7 +36,7 @@ export class CleanProcessorService {
}
@bindThis
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
public async process(): Promise<void> {
this.logger.info('Cleaning...');
this.userIpsRepository.delete({
@@ -72,6 +72,5 @@ export class CleanProcessorService {
}
this.logger.succ('Cleaned.');
done();
}
}

View File

@@ -5,9 +5,9 @@ import type { DriveFilesRepository } from '@/models/index.js';
import type { Config } from '@/config.js';
import type Logger from '@/logger.js';
import { DriveService } from '@/core/DriveService.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type Bull from 'bull';
import { bindThis } from '@/decorators.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
@Injectable()
export class CleanRemoteFilesProcessorService {
@@ -27,7 +27,7 @@ export class CleanRemoteFilesProcessorService {
}
@bindThis
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
public async process(job: Bull.Job<Record<string, unknown>>): Promise<void> {
this.logger.info('Deleting cached remote files...');
let deletedCount = 0;
@@ -47,7 +47,7 @@ export class CleanRemoteFilesProcessorService {
});
if (files.length === 0) {
job.progress(100);
job.updateProgress(100);
break;
}
@@ -62,10 +62,9 @@ export class CleanRemoteFilesProcessorService {
isLink: false,
});
job.progress(deletedCount / total);
job.updateProgress(deletedCount / total);
}
this.logger.succ('All cached remote files has been deleted.');
done();
}
}

View File

@@ -8,10 +8,10 @@ import { DriveService } from '@/core/DriveService.js';
import type { DriveFile } from '@/models/entities/DriveFile.js';
import type { Note } from '@/models/entities/Note.js';
import { EmailService } from '@/core/EmailService.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type Bull from 'bull';
import type { DbUserDeleteJobData } from '../types.js';
import { bindThis } from '@/decorators.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
import type { DbUserDeleteJobData } from '../types.js';
@Injectable()
export class DeleteAccountProcessorService {

View File

@@ -5,10 +5,10 @@ import type { UsersRepository, DriveFilesRepository } from '@/models/index.js';
import type { Config } from '@/config.js';
import type Logger from '@/logger.js';
import { DriveService } from '@/core/DriveService.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type Bull from 'bull';
import type { DbJobDataWithUser } from '../types.js';
import { bindThis } from '@/decorators.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
import type { DbJobDataWithUser } from '../types.js';
@Injectable()
export class DeleteDriveFilesProcessorService {
@@ -31,12 +31,11 @@ export class DeleteDriveFilesProcessorService {
}
@bindThis
public async process(job: Bull.Job<DbJobDataWithUser>, done: () => void): Promise<void> {
public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> {
this.logger.info(`Deleting drive files of ${job.data.user.id} ...`);
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
if (user == null) {
done();
return;
}
@@ -56,7 +55,7 @@ export class DeleteDriveFilesProcessorService {
});
if (files.length === 0) {
job.progress(100);
job.updateProgress(100);
break;
}
@@ -71,10 +70,9 @@ export class DeleteDriveFilesProcessorService {
userId: user.id,
});
job.progress(deletedCount / total);
job.updateProgress(deletedCount / total);
}
this.logger.succ(`All drive files (${deletedCount}) of ${user.id} has been deleted.`);
done();
}
}

View File

@@ -3,10 +3,10 @@ import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import type Logger from '@/logger.js';
import { DriveService } from '@/core/DriveService.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type Bull from 'bull';
import type { ObjectStorageFileJobData } from '../types.js';
import { bindThis } from '@/decorators.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
import type { ObjectStorageFileJobData } from '../types.js';
@Injectable()
export class DeleteFileProcessorService {

View File

@@ -1,4 +1,5 @@
import { Inject, Injectable } from '@nestjs/common';
import * as Bull from 'bullmq';
import { DI } from '@/di-symbols.js';
import type { DriveFilesRepository, InstancesRepository } from '@/models/index.js';
import type { Config } from '@/config.js';
@@ -16,7 +17,6 @@ import { StatusError } from '@/misc/status-error.js';
import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type Bull from 'bull';
import type { DeliverJobData } from '../types.js';
@Injectable()
@@ -121,15 +121,13 @@ export class DeliverProcessorService {
isSuspended: true,
});
});
return `${host} is gone`;
throw new Bull.UnrecoverableError(`${host} is gone`);
}
// HTTPステータスコード4xxはクライアントエラーであり、それはつまり
// 何回再送しても成功することはないということなのでエラーにはしないでおく
return `${res.statusCode} ${res.statusMessage}`;
throw new Bull.UnrecoverableError(`${res.statusCode} ${res.statusMessage}`);
}
// 5xx etc.
throw `${res.statusCode} ${res.statusMessage}`;
throw new Error(`${res.statusCode} ${res.statusMessage}`);
} else {
// DNS error, socket error, timeout ...
throw res;

View File

@@ -6,7 +6,7 @@ import type Logger from '@/logger.js';
import { NotificationService } from '@/core/NotificationService.js';
import { bindThis } from '@/decorators.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type Bull from 'bull';
import type * as Bull from 'bullmq';
import type { EndedPollNotificationJobData } from '../types.js';
@Injectable()
@@ -30,10 +30,9 @@ export class EndedPollNotificationProcessorService {
}
@bindThis
public async process(job: Bull.Job<EndedPollNotificationJobData>, done: () => void): Promise<void> {
public async process(job: Bull.Job<EndedPollNotificationJobData>): Promise<void> {
const note = await this.notesRepository.findOneBy({ id: job.data.noteId });
if (note == null || !note.hasPoll) {
done();
return;
}
@@ -51,7 +50,5 @@ export class EndedPollNotificationProcessorService {
noteId: note.id,
});
}
done();
}
}

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