Compare commits

...

190 Commits

Author SHA1 Message Date
syuilo
8d2c3bb18d 13.0.0-beta.41 2023-01-13 11:16:50 +09:00
syuilo
4e39e690b6 Revert "リモートユーザーはcanPublicNoteがfalseでもhomeにしないように"
This reverts commit b2117ba3a1.
2023-01-13 11:14:23 +09:00
syuilo
6458239a7c remoteDriveCapacityMbをロールに統合 2023-01-13 11:14:07 +09:00
syuilo
a5aaa032ca 🎨 2023-01-13 11:07:51 +09:00
syuilo
71bbef69c7 ✌️ 2023-01-13 11:04:58 +09:00
syuilo
c5c40a73b7 feat: conditional role
Resolve #9539
2023-01-13 11:03:54 +09:00
syuilo
74910f8d70 Revert "perf: build backend with swc (#9463)"
This reverts commit 4579d02296.
2023-01-13 09:32:56 +09:00
syuilo
e00003edff refactor 2023-01-13 08:56:06 +09:00
syuilo
bedb98185e 🎨 2023-01-13 08:23:52 +09:00
syuilo
da6f955d58 fix typo
Fix #9536
2023-01-13 08:22:23 +09:00
syuilo
6bdccea26b 🎨 2023-01-13 08:16:45 +09:00
tamaina
b2117ba3a1 リモートユーザーはcanPublicNoteがfalseでもhomeにしないように
https://github.com/misskey-dev/misskey/issues/9534#issuecomment-1380645073
2023-01-12 16:54:11 +00:00
tamaina
ba349fc62f Fix #9534 2023-01-12 16:33:00 +00:00
syuilo
b2c79a5f2c Update CHANGELOG.md 2023-01-12 21:41:31 +09:00
syuilo
3e415e733d Update CHANGELOG.md 2023-01-12 21:22:20 +09:00
syuilo
a5e84e5de9 Update CHANGELOG.md 2023-01-12 21:15:56 +09:00
syuilo
8673353029 13.0.0-beta.40 2023-01-12 21:07:13 +09:00
파링
4579d02296 perf: build backend with swc (#9463)
* feat: build backend with swc

* fix: swc build target

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-01-12 21:05:45 +09:00
tamaina
978a9bbb3b perf(backend): Use undici instead of node-fetch and got (#9459)
* Implement? HttpFetchService

* ✌️

* remove node-fetch

* fix

* refactor

* fix

* gateway timeout

* UndiciFetcherクラスを追加 (仮コミット, ビルドもstartもさせていない)

* fix

* add logger and fix url preview

* fix ip check

* enhance logger and error handling

* fix

* fix

* clean up

* Use custom fetcher for ApRequest / ApResolver

* bypassProxyはproxyBypassHostsに判断を委譲するように

* set maxRedirections (default 3, ApRequest/ApResolver: 0)

* fix comment

* handle error s3 upload

* add debug message

* no return await

* Revert "no return await"

This reverts commit b5b0dc58a3.

* reduce maxSockets

* apResolverのUndiciFetcherを廃止しapRequestのものを使う、 add ap logger

* Revert "apResolverのUndiciFetcherを廃止しapRequestのものを使う、 add ap logger"

This reverts commit 997243915c.

* add logger

* fix

* change logger name

* safe

* デフォルトでUser-Agentを設定
2023-01-12 21:03:02 +09:00
syuilo
2470afaa2e Role (#9437)
* wip

* Update CHANGELOG.md

* wip

* wip

* wip

* Update create.ts

* wip

* wip

* Update CHANGELOG.md

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update CHANGELOG.md

* wip

* wip

* Update delete.ts

* Update delete.ts

* wip

* wip

* wip

* Update account-info.vue

* wip

* wip

* Update settings.vue

* Update user-info.vue

* wip

* Update show-file.ts

* Update show-user.ts

* wip

* wip

* Update delete.ts

* wip

* wip

* Update overview.moderators.vue

* Create 1673500412259-Role.js

* wip

* wip

* Update roles.vue

* 色

* Update roles.vue

* integrate silence

* wip

* wip
2023-01-12 21:02:26 +09:00
syuilo
60e545b2fd Update CHANGELOG.md 2023-01-12 09:34:22 +09:00
Masaya Suzuki
6555644b88 ログイン画面のユーザー名フォームへのautocomplete設定 (#9515)
* Add username class to username form

* Fix add class process

* Set id

* Set autocomplete
2023-01-12 09:33:06 +09:00
syuilo
df56bd6d57 Update CHANGELOG.md 2023-01-11 19:01:17 +09:00
syuilo
e51432a461 remove katex 2023-01-11 08:58:13 +09:00
syuilo
90e2186872 13.0.0-beta.39 2023-01-10 20:22:22 +09:00
syuilo
3043b2f619 fix(client): 画面の幅が狭いとウィジェットドロワーを閉じる手段がなくなるのを修正
Fix #7191
2023-01-10 20:21:39 +09:00
syuilo
d2fc5a248b refactor(client): use css modules 2023-01-10 20:17:08 +09:00
syuilo
e6d666e1ee fix style 2023-01-10 20:12:38 +09:00
syuilo
c5cfbd99d0 perf(server): improve nodeinfo performance
Resolve #9505
2023-01-10 20:08:55 +09:00
syuilo
33b22a323c perf(server): improve stats api performance 2023-01-10 20:06:25 +09:00
syuilo
f032fb628a Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-01-10 19:53:53 +09:00
あずき⪥™
7761eb8897 prefer using composition (#9521) 2023-01-10 19:53:33 +09:00
syuilo
58fa8c4a01 🎨 2023-01-10 19:52:44 +09:00
syuilo
789d61d175 enhance(server): アンケート選択肢の文字数制限を緩和
Resolve #9511
2023-01-10 16:41:38 +09:00
syuilo
b52fd72727 13.0.0-beta.38 2023-01-10 16:29:22 +09:00
syuilo
d79905e141 New Crowdin updates (#9502)
* New translations ja-JP.yml (Chinese Simplified)

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

* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Greek)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Slovak)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Turkish)

* New translations ja-JP.yml (Ukrainian)

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

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

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (Bengali)

* New translations ja-JP.yml (Thai)

* New translations ja-JP.yml (Kannada)

* New translations ja-JP.yml (Kabyle)

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

* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Greek)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Slovak)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Ukrainian)

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

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

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (Bengali)

* New translations ja-JP.yml (Thai)

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

* New translations ja-JP.yml (Chinese Simplified)
2023-01-10 16:28:45 +09:00
syuilo
cd6b1290cb fix deck style 2023-01-10 16:27:38 +09:00
syuilo
c382497167 fix style 2023-01-10 16:26:51 +09:00
syuilo
a8fb578854 Update .node-version 2023-01-10 16:13:24 +09:00
syuilo
ff00c90a88 refactor(client): use css modules 2023-01-10 14:45:02 +09:00
syuilo
d0755b5ce8 refactor(client): use css modules 2023-01-10 14:37:32 +09:00
syuilo
17fa5667b8 refactor(client): use css modules 2023-01-10 14:20:58 +09:00
syuilo
01d5e385ec refactor(client): use css modules 2023-01-10 14:16:59 +09:00
syuilo
af80fee899 lint 2023-01-10 13:53:41 +09:00
tamaina
6b37c09274 refactor: api, apiGet関数をosから@/scripts/api.tsに分離する (#9510)
* split api?

* fix

* ✌️

* no vue split?

* Revert "no vue split?"

This reverts commit 27ccec971e.
2023-01-10 13:53:06 +09:00
あずき⪥™
1453a0f5cf 画面下部に必要なスペース関連の改善 (#9509)
* enhance: apply same safe area processing to the tab bar

* fix: remove unnecessary bottom space on messaging room

* enhance bottom spacing

* fix size of `minBottomSpacing`
2023-01-10 13:50:34 +09:00
syuilo
1688083e9a 13.0.0-beta.37 2023-01-10 11:40:31 +09:00
syuilo
616594d3cd 🎨 2023-01-10 11:34:06 +09:00
syuilo
6783178dc3 tweak css 2023-01-10 11:19:56 +09:00
syuilo
3f033d6ab7 refactor(client): use css modules 2023-01-10 11:15:29 +09:00
syuilo
d10e000883 refactor(client): use css modules 2023-01-10 10:35:02 +09:00
syuilo
ce528ff22e refactor(client): use css modules 2023-01-10 10:30:38 +09:00
syuilo
5e4e02235a 🎨 2023-01-10 09:57:41 +09:00
syuilo
e4179336e4 refactor(client): use css modules 2023-01-10 09:53:01 +09:00
syuilo
7823ba494f refactor(client): use css modules 2023-01-10 09:41:53 +09:00
syuilo
7bdff90415 update node 2023-01-10 09:32:56 +09:00
syuilo
f3c0af7e23 refactor(client): use css modules 2023-01-10 08:48:37 +09:00
syuilo
72dfbfcf35 Update vite.config.ts 2023-01-10 08:39:19 +09:00
syuilo
9cbe878d0b fix import 2023-01-10 08:28:09 +09:00
syuilo
618405c4d3 refactor(client): rename widget filename 2023-01-10 06:08:40 +09:00
syuilo
0b08fcac4a refactor(client): use css modules 2023-01-10 06:02:46 +09:00
syuilo
eac6ebb239 refactor(client): use css modules 2023-01-10 05:48:00 +09:00
syuilo
194fb14e07 refactor(client): use css modules 2023-01-10 05:45:33 +09:00
syuilo
c2d05b507a refactor(client): use css modules 2023-01-10 05:40:53 +09:00
syuilo
4df43a9107 fix style 2023-01-10 05:40:51 +09:00
syuilo
0da7fcdbed refactor 2023-01-10 05:31:11 +09:00
syuilo
1e50b2688a refactor 2023-01-10 05:31:02 +09:00
syuilo
c1cd018626 tweak style 2023-01-10 05:22:47 +09:00
syuilo
b588e8b60b Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-01-10 05:18:05 +09:00
syuilo
06f55ffb37 refactor(client): use css modules 2023-01-10 05:17:54 +09:00
tamaina
02df6a28cd fix: 続rss-ticker.vue修正 2023-01-09 15:45:22 +00:00
tamaina
d64abedf9f fix: fix rss-ticker TypeError 2023-01-09 15:41:06 +00:00
syuilo
4d39d1caf6 🎨 2023-01-09 20:41:52 +09:00
syuilo
d06f61f23f feat(client): add instance info widget 2023-01-09 20:35:36 +09:00
syuilo
c179d6f735 feat(client): add profile widget
Resolve #7722
2023-01-09 20:23:06 +09:00
syuilo
3bc0cdbfb7 typo 2023-01-09 17:22:21 +09:00
syuilo
b04155e7ba 🎨 2023-01-09 17:18:45 +09:00
syuilo
014c97fa85 13.0.0-beta.36 2023-01-09 17:13:38 +09:00
syuilo
96ccf550b1 New Crowdin updates (#9490)
* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Slovak)

* New translations ja-JP.yml (Ukrainian)

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

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

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (Bengali)

* New translations ja-JP.yml (Thai)

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

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

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

* New translations ja-JP.yml (Thai)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Thai)

* New translations ja-JP.yml (Swedish)

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

* New translations ja-JP.yml (Thai)

* New translations ja-JP.yml (Chinese Traditional)
2023-01-09 17:13:25 +09:00
syuilo
8f28ff63f1 絵文字ピッカーにフォーカスされないのを修正 2023-01-09 17:08:58 +09:00
syuilo
b7dec6e87d refactor 2023-01-09 17:04:52 +09:00
syuilo
1bb2c22493 Update vite.config.ts 2023-01-09 16:45:18 +09:00
syuilo
39c3995c74 refactor 2023-01-09 16:45:05 +09:00
syuilo
8cc80faf20 13.0.0-beta.35 2023-01-09 16:27:27 +09:00
syuilo
4d66077f85 Update MkEmojiPicker.vue 2023-01-09 16:27:09 +09:00
syuilo
3ece2dc990 13.0.0-beta.34 2023-01-09 16:16:25 +09:00
syuilo
6071e962f4 Revert "Update vite.config.ts"
This reverts commit c438bd2e27.
2023-01-09 16:16:00 +09:00
syuilo
ed43369797 Update CHANGELOG.md 2023-01-09 16:09:40 +09:00
syuilo
c65957853b 13.0.0-beta.33 2023-01-09 16:08:25 +09:00
syuilo
6a18360269 update mfm-js 2023-01-09 16:07:14 +09:00
syuilo
c438bd2e27 Update vite.config.ts 2023-01-09 15:51:36 +09:00
syuilo
462acc9eee カスタム絵文字一覧情報をmetaから分離 2023-01-09 15:50:25 +09:00
syuilo
e4144a17a4 fix(server): アンテナタイムライン(ストリーミング)が、フォローしていないユーザーの鍵投稿も拾ってしまう問題を修正
Fix #9025
2023-01-09 14:12:42 +09:00
syuilo
3cfd017538 fix(server): 特定のPNG画像のアップロードに失敗する問題を修正
Co-Authored-By: haru <64310155+usbharu@users.noreply.github.com>
2023-01-09 14:03:22 +09:00
syuilo
403849805a enhance(client): force lazy load some images 2023-01-09 14:00:04 +09:00
syuilo
402b234d15 🎨 2023-01-09 13:56:30 +09:00
syuilo
eba6b326fa 13.0.0-beta.32 2023-01-09 13:45:04 +09:00
syuilo
4c9b93a12f 🎨 2023-01-09 13:39:16 +09:00
syuilo
dfee79f841 🎨
Resolve #9498
2023-01-09 13:32:48 +09:00
syuilo
962373cf06 fix(server): 非公開のクリップのURLでOGPレンダリングされる問題を修正
Fix #9129
2023-01-09 13:26:42 +09:00
syuilo
13aa4b64b4 tweak client 2023-01-09 10:07:37 +09:00
syuilo
5ce56886a1 fix 2023-01-09 09:43:28 +09:00
syuilo
2817ca03f5 Update queue.chart.vue 2023-01-09 09:42:11 +09:00
syuilo
e633c3b84b refactor 2023-01-09 09:41:25 +09:00
syuilo
8524e9d735 tweak client 2023-01-09 09:04:35 +09:00
syuilo
91ced90fb2 fix imports 2023-01-09 08:58:16 +09:00
syuilo
2acb3917ba Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-01-09 08:46:10 +09:00
syuilo
dd78ac089c 🍪 2023-01-08 20:42:45 +09:00
MeiMei
10e526ba56 fix: Escape SQL LIKE (#9493)
* SQL LIKE escape

* CHANGELOG
2023-01-08 20:32:17 +09:00
syuilo
7ed905f76b 🍪 cps 2023-01-08 20:30:19 +09:00
syuilo
5d13e2744f 🎨 2023-01-08 20:21:32 +09:00
syuilo
1d7e0293a8 fix following chart 2023-01-08 20:02:07 +09:00
marihachi
8977d87021 fix typo (#9492) 2023-01-08 19:59:05 +09:00
syuilo
809400ff23 Update CHANGELOG.md 2023-01-08 18:08:43 +09:00
syuilo
4c8dbcc20d 13.0.0-beta.31 2023-01-08 17:44:24 +09:00
syuilo
416dcf884d 🎨 2023-01-08 17:42:51 +09:00
syuilo
09d3ce444a Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-01-08 17:41:12 +09:00
syuilo
27c2ca5048 feat(client): 🍪👈 2023-01-08 17:41:09 +09:00
syuilo
fceeb1b108 🎨 2023-01-08 17:38:33 +09:00
tamaina
b442c38f41 enhance: Push Notification badges to Tabler Icons (#9406)
* enhance: Push Notification badges to Tabler Icons

* add receiveFollowRequest icon
2023-01-08 16:47:57 +09:00
syuilo
7c2d2676f7 refactor 2023-01-08 16:17:42 +09:00
syuilo
1f6a41cea7 13.0.0-beta.30 2023-01-08 14:30:00 +09:00
syuilo
0d7ee20a77 🎨 2023-01-08 14:29:22 +09:00
syuilo
dcca2350dd 🎨 2023-01-08 14:28:14 +09:00
syuilo
1cfdd4c41a refactor 2023-01-08 14:22:04 +09:00
syuilo
25f4ee7030 Update CHANGELOG.md 2023-01-08 14:19:32 +09:00
syuilo
5320f23017 enhance(client): improve user activity page 2023-01-08 14:17:56 +09:00
syuilo
4ffbbbe6d8 🎨 2023-01-08 13:10:01 +09:00
syuilo
132e45dff4 13.0.0-beta.29 2023-01-08 11:58:00 +09:00
syuilo
01652b72b3 🎨 2023-01-08 11:57:34 +09:00
syuilo
8b1fdb5a3b enhance(client): add theme 2023-01-08 11:55:37 +09:00
syuilo
192add376c fix MkModal animation 2023-01-08 11:47:16 +09:00
syuilo
244ea9593a tweak components 2023-01-08 11:30:40 +09:00
syuilo
f20d7cba74 13.0.0-beta.28 2023-01-08 11:17:02 +09:00
syuilo
a3e282bc75 New Crowdin updates (#9478)
* New translations ja-JP.yml (Thai)

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

* New translations ja-JP.yml (Italian)

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

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Slovak)
2023-01-08 11:16:36 +09:00
syuilo
49a95c34bf Update CHANGELOG.md 2023-01-08 11:16:26 +09:00
Soni L
ecbefce2aa Support remote objects in search (#9479)
* Support remote objects in search

Closes #9428

* Use account instead of localStorage

* Use useRouter instead of mainRouter

Co-authored-by: Chaos <chaoticryptidz@owo.monster>
2023-01-08 11:15:54 +09:00
syuilo
91356b1805 tweak 2023-01-08 11:12:11 +09:00
syuilo
2e2ed1385f delete pollVote notification 2023-01-08 10:54:45 +09:00
syuilo
49f3090edd tweak note componsnt 2023-01-08 10:48:44 +09:00
syuilo
4594fb11de 🎨 2023-01-08 10:32:37 +09:00
syuilo
b93e56d2e5 🎨 2023-01-08 10:24:30 +09:00
syuilo
c550dafb81 tweak note component 2023-01-08 10:20:28 +09:00
syuilo
8709574f3d 🎨 2023-01-08 09:58:35 +09:00
syuilo
1b7043fa79 🎨 2023-01-07 19:57:48 +09:00
syuilo
55ef2393fb 13.0.0-beta.27 2023-01-07 17:44:15 +09:00
syuilo
7769095efb 🎨 2023-01-07 17:39:24 +09:00
syuilo
b8248bdd65 🎨 2023-01-07 17:37:30 +09:00
syuilo
6f4ad581dc 🎨 2023-01-07 17:27:09 +09:00
syuilo
aec94920ab 🎨 2023-01-07 17:20:50 +09:00
syuilo
155ca39063 update aiscript 2023-01-07 17:18:30 +09:00
syuilo
58bfb4dca4 refactor 2023-01-07 15:09:46 +09:00
syuilo
49a0b6c48b fix typo 2023-01-07 15:00:29 +09:00
syuilo
799a653b44 🎨 2023-01-07 15:00:00 +09:00
syuilo
d09e1f4925 refactor 2023-01-07 14:59:54 +09:00
syuilo
cac784af8a fix #9483 2023-01-07 14:44:50 +09:00
syuilo
d7e0ddcbca Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-01-07 14:33:36 +09:00
syuilo
8c0811a442 🎨 2023-01-07 14:33:33 +09:00
syuilo
bab6f75260 Update CHANGELOG.md 2023-01-07 14:24:33 +09:00
syuilo
54e3fccd87 enhance(server): refactor and tweak emoji proxy 2023-01-07 14:19:25 +09:00
syuilo
6a992b6982 fix chart rendering 2023-01-07 14:10:01 +09:00
syuilo
ecd6fc1db8 🎨 2023-01-07 11:49:04 +09:00
syuilo
d99be6697e enhance(client): donation dialog 2023-01-07 11:49:00 +09:00
syuilo
d2d77b5dc1 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-01-07 10:13:07 +09:00
syuilo
91503405b4 refactor(client): typed localStorage 2023-01-07 10:13:02 +09:00
tamaina
c336201084 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-01-06 14:01:02 +00:00
tamaina
0f3399753d chore: remove Search from the name of OpenSearch 2023-01-06 14:00:54 +00:00
tamaina
5ec89ea0c3 カスタム絵文字にプロキシを復活 (#9481)
* wip

* Revert "Update ClientServerService.ts"

This reverts commit 88c64ece78.

* Revert "disable custom emoji proxy temporary"

This reverts commit 495d513efd.

* ✌️
2023-01-06 22:34:50 +09:00
syuilo
a42b03c154 13.0.0-beta.26 2023-01-06 20:29:48 +09:00
syuilo
4b181a30da tweak confetti 2023-01-06 20:25:08 +09:00
syuilo
70805e00eb 🎨 2023-01-06 20:19:27 +09:00
syuilo
3551ac328e tweak retention heatmap 2023-01-06 20:01:32 +09:00
syuilo
e36e5df635 chore(client): tweak charts 2023-01-06 20:00:06 +09:00
syuilo
3e7d8b5f17 update deps 2023-01-06 19:59:31 +09:00
syuilo
5846198eee enhance(client): provide THIS_ID and THIS_URL vars to aiscript of flash 2023-01-06 17:19:25 +09:00
syuilo
c14063a921 🎨 2023-01-06 17:07:32 +09:00
syuilo
457670e730 Update MkTime.vue 2023-01-06 16:58:43 +09:00
syuilo
513cef50a2 tweak federation widget 2023-01-06 16:32:34 +09:00
syuilo
88c64ece78 Update ClientServerService.ts 2023-01-06 14:07:34 +09:00
syuilo
a11672d0a5 13.0.0-beta.25 2023-01-06 13:52:14 +09:00
syuilo
46af9515b0 fix style 2023-01-06 13:52:05 +09:00
syuilo
c5cb786054 🎨 2023-01-06 13:50:41 +09:00
syuilo
4d2d6154a3 New Crowdin updates (#9471)
* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Slovak)

* New translations ja-JP.yml (Ukrainian)

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

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

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (Bengali)

* New translations ja-JP.yml (Thai)

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

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

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (German)

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

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Swedish)

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

* New translations ja-JP.yml (Arabic)

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

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)
2023-01-06 13:47:34 +09:00
syuilo
495d513efd disable custom emoji proxy temporary
https://github.com/misskey-dev/misskey/pull/9431#issuecomment-1373006446
2023-01-06 13:47:18 +09:00
syuilo
3b617fafdd enhance(client): アップデート時にも花火 2023-01-06 13:43:10 +09:00
syuilo
82c4f694a0 🎨 2023-01-06 13:40:17 +09:00
syuilo
dc5b4a0402 enhance(client): show fireworks when visit user who today is birthday
Resolve #9476
2023-01-06 13:25:49 +09:00
syuilo
6adc0521d8 Update CHANGELOG.md 2023-01-06 12:26:18 +09:00
syuilo
9ac86dacbb Update CHANGELOG.md 2023-01-06 12:13:34 +09:00
syuilo
88f0c10d09 Update CHANGELOG.md 2023-01-06 11:20:09 +09:00
444 changed files with 8575 additions and 5151 deletions

View File

@@ -122,10 +122,12 @@ id: 'aid'
# Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128
#proxyBypassHosts: [
# 'example.com',
# '192.0.2.8'
#]
proxyBypassHosts:
- api.deepl.com
- api-free.deepl.com
- www.recaptcha.net
- hcaptcha.com
- challenges.cloudflare.com
# Proxy for SMTP/SMTPS
#proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT

View File

@@ -11,24 +11,44 @@ You should also include the user name that made the change.
## 13.0.0 (unreleased)
### TL;DR
- New features (Role system, Misskey Play, New widgets, New charts, 🍪👈, etc)
- Rewriten backend
- Better performance (backend and frontend)
- Various usability improvements
- Various UI tweaks
### Changes
#### For sabakans
#### For server admins
- Node.js 18.x or later is required
- PostgreSQL 15.x is required
- Misskey not using 15 specific features at 13.0.0, but may do so in the future.
- Elasticsearchのサポートが削除されました
- 代わりに今後任意の検索プロバイダを設定できる仕組みを構想しています。その仕組みを使えば今まで通りElasticsearchも利用できます
- Migrate to Yarn Berry (v3.2.1) @ThatOneCalculator
- You may have to `yarn run clean-all`, `sudo corepack enable` and `yarn set version berry` before running `yarn install` if you're still on yarn classic
- 従来のモデレーターフラグは廃止され、より高度なロール機能が導入されました
- これに伴い、アップデートを行うと全てのモデレーターフラグは失われます。そのため、予めモデレーター一覧を記録しておき、アップデート後にモデレーターロールを作りアサインし直してください。
- サイレンスはロールに統合されました。今までのユーザーは恩赦されるため、予めサイレンス一覧を記録しておくのをおすすめします。
- ユーザーごとのドライブ容量設定はロールに統合されました
- インスタンスデフォルトのドライブ容量設定はロールに統合されました。アップデート後、ベースロールのドライブ容量を編集してください。
- LTL/GTLの解放状態はロールに統合されました
#### For users
- ノートのウォッチ機能が削除されました
- アンケートに投票された際に通知が作成されなくなりました
- ノートの数式埋め込みが削除されました
- 新たに動的なPagesを作ることはできなくなりました
- 代わりにAiScriptを用いてより柔軟に動的なコンテンツを作成できるMisskey Play機能が実装されています。
- AiScriptが0.12.1にアップデートされました
- AiScriptが0.12.2にアップデートされました
- 0.12.xの変更点についてはこちら https://github.com/syuilo/aiscript/blob/master/CHANGELOG.md#0120
- 0.12.1未満のプラグインは読み込むことはできません
- 0.12.x未満のプラグインは読み込むことはできません
- iOS15以下のデバイスはサポートされなくなりました
- Firefox109以下はサポートされなくなりました
#### For developers
#### For app developers
- API: metaのレスポンスに`emojis`プロパティが含まれなくなりました
- カスタム絵文字一覧情報を取得するには、`emojis`エンドポイントにリクエストします
- API: カスタム絵文字エンティティに`url`プロパティが含まれなくなりました
- 絵文字画像を表示するには、`<instance host>/emoji/<emoji name>.webp`にリクエストすると画像が返ります。
- e.g. `https://p1.a9z.dev/emoji/misskey.webp`
@@ -38,6 +58,7 @@ You should also include the user name that made the change.
- API: `instance`エンティティに`latestStatus``lastCommunicatedAt``latestRequestSentAt`プロパティが含まれなくなりました
### Improvements
- Role system @syuilo
- Misskey Play @syuilo
- Introduce retention-rate aggregation @syuilo
- Make possible to export favorited notes @syuilo
@@ -47,13 +68,16 @@ You should also include the user name that made the change.
- Add Cloudflare Turnstile CAPTCHA support @CyberRex0
- Server: signToActivityPubGet is set to true by default @syuilo
- Server: improve syslog performance @syuilo
- Server: Use undici instead of node-fetch and got @tamaina
- Server: improve note scoring for featured notes @CyberRex0
- Server: アンケート選択肢の文字数制限を緩和 @syuilo
- Server: improve stats api performance @syuilo
- Server: improve nodeinfo performance @syuilo
- Server: delete outdated notifications regularly to improve db performance @syuilo
- Server: delete outdated hard-mutes regularly to improve db performance @syuilo
- Server: delete outdated notes of antenna regularly to improve db performance @syuilo
- Server: improve activitypub deliver performance @syuilo
- Client: use tabler-icons instead of fontawesome to better design @syuilo
- Client: Add AiScript App widget
- Client: Add new gabber kick sounds (thanks for noizenecio)
- Client: Add link to user RSS feed in profile menu @ssmucny
- Client: Compress non-animated PNG files @saschanaz
@@ -61,18 +85,26 @@ You should also include the user name that made the change.
- Client: enhance dashboard of control panel @syuilo
- Client: Vite is upgraded to v4 @syuilo, @tamaina
- Client: HMR is available while yarn dev @tamaina
- Client: Make widgets of universal/classic sync between devices @tamaina
- Client: Implement the button to subscribe push notification @tamaina
- Client: Implement the toggle to or not to close push notifications when notifications or messages are read @tamaina
- Client: Improve RSS widget @tamaina
- Client: show Unicode emoji tooltip with its name in MkReactionsViewer.reaction @saschanaz
- Client: OpenSearch support @SoniEx2 @chaoticryptidz
- Client: Support remote objects in search @SoniEx2
- Client: user activity page @syuilo
- Client: Make widgets of universal/classic sync between devices @tamaina
- Client: add user list widget @syuilo
- Client: Add AiScript App widget
- Client: add profile widget @syuilo
- Client: add instance info widget @syuilo
- Client: Improve RSS widget @tamaina
- Client: add heatmap of daily active users to about page @syuilo
- Client: introduce fluent emoji @syuilo
- Client: add new theme @syuilo
- Client: show fireworks when visit user who today is birthday @syuilo
- Client: show bot warning on screen when logged in as bot account @syuilo
- Client: improve overall performance of client @syuilo
- Client: ui tweaks @syuilo
- Client: clicker game @syuilo
### Bugfixes
- Server: 引用内の文章がnyaizeされてしまう問題を修正 @kabo2468
@@ -83,14 +115,27 @@ You should also include the user name that made the change.
- Server: アンテナの作成数上限を追加 @syuilo
- Server: pages/likeのエラーIDが重複しているのを修正 @syuilo
- Server: pages/updateのパラメータによってはsummaryの値が更新されないのを修正 @syuilo
- Server: Escape SQL LIKE @mei23
- Server: 特定のPNG画像のアップロードに失敗する問題を修正 @usbharu
- Server: 非公開のクリップのURLでOGPレンダリングされる問題を修正 @syuilo
- Server: アンテナタイムライン(ストリーミング)が、フォローしていないユーザーの鍵投稿も拾ってしまう @syuilo
- Client: パスワードマネージャーなどでユーザー名がオートコンプリートされない問題を修正 @massongit
- Client: 日付形式の文字列などがカスタム絵文字として表示されるのを修正 @syuilo
- Client: case insensitive emoji search @saschanaz
- Client: 画面の幅が狭いとウィジェットドロワーを閉じる手段がなくなるのを修正 @syuilo
- Client: InAppウィンドウが操作できなくなることがあるのを修正 @tamaina
- Client: use proxied image for instance icon @syuilo
- Client: Webhookの編集画面で、内容を保存することができない問題を修正 @m-hayabusa
- Client: Page編集でブロックの移動が行えない問題を修正 @syuilo
- Client: update emoji picker immediately on all input @saschanaz
- Client: チャートのツールチップが画面に残ることがあるのを修正 @syuilo
- Client: fix wrong link in tutorial @syuilo
### Special thanks
- All contributors
- All who have created instances for the beta test
- All who participated in the beta test
## 12.119.1 (2022/12/03)
### Bugfixes
- Server: Mitigate AP reference chain DoS vector @skehmatics

View File

@@ -1,4 +1,4 @@
FROM node:18.12.1-bullseye AS builder
FROM node:18.13.0-bullseye AS builder
ARG NODE_ENV=production
@@ -22,7 +22,7 @@ COPY . ./
RUN git submodule update --init
RUN yarn build
FROM node:18.12.1-bullseye-slim AS runner
FROM node:18.13.0-bullseye-slim AS runner
WORKDIR /misskey

View File

@@ -380,6 +380,7 @@ administrator: "المدير"
token: "الرمز المميز"
twoStepAuthentication: "الإستيثاق بعاملَيْن"
moderator: "مشرِف"
moderation: "الإشراف"
nUsersMentioned: "{n} مستخدمين أُشير إليهم"
securityKey: "مفتاح الأمان"
securityKeyName: "اسم المفتاح"
@@ -813,6 +814,9 @@ colored: "ملوّن"
label: "التسمية"
localOnly: "المحلي فقط"
account: "الحسابات"
cannotLoad: "تعذر التحميل"
like: "أعجبني"
show: "المظهر"
_emailUnavailable:
used: "هذا البريد الإلكتروني مستخدم"
format: "صيغة البريد الإلكتروني غير صالحة"
@@ -1113,6 +1117,8 @@ _weekday:
friday: "الجمعة"
saturday: "السبت"
_widgets:
profile: "الملف التعريفي"
instanceInfo: "معلومات مثيل الخادم"
memo: "ملاحظة لاصقة"
notifications: "الإشعارات"
timeline: "الخيط الزمني"
@@ -1228,6 +1234,11 @@ _timelines:
local: "المحلي"
social: "الاجتماعي"
global: "الشامل"
_play:
viewSource: "اظهر المصدر"
featured: "الأكثر شعبية"
title: "العنوان"
summary: "الوصف"
_pages:
newPage: "أنشئ صفحة جديدة"
editPage: "عدّل الصفحة"
@@ -1285,7 +1296,6 @@ _notification:
youGotReply: "ردّ عليك {name}"
youGotQuote: "اقتبس منك {name}"
youRenoted: "إعادت نشر من {name}"
youGotPoll: "شارك {name} في استطلاع الرأي"
youGotMessagingMessageFromUser: "لقد تلقيت رسالة مِن {name}"
youGotMessagingMessageFromGroup: "لقد أرسِلَت رسالة إلى الفريق {name}"
youWereFollowed: "يتابعك"
@@ -1293,6 +1303,7 @@ _notification:
yourFollowRequestAccepted: "قُبل طلب المتابعة"
youWereInvitedToGroup: "دُعيت إلى فريقٍ"
pollEnded: "ظهرت نتائج الاستطلاع"
unreadAntennaNote: "هوائي {name}"
_types:
all: "الكل"
follow: "متابِعون جدد"
@@ -1301,7 +1312,6 @@ _notification:
renote: "أعد النشر"
quote: "الاقتباسات"
reaction: "التفاعلات"
pollVote: "مصوِت شارك في الاستطلاع"
receiveFollowRequest: "طلبات المتابعة المتلقاة"
followRequestAccepted: "طلبات المتابعة المقبولة"
groupInvited: "دعوات الفريق"

View File

@@ -851,6 +851,8 @@ colored: "রঙ্গিন"
label: "লেবেল"
localOnly: "শুধুমাত্র লোকাল"
account: "অ্যাকাউন্টগুলি"
like: "পছন্দ করা"
show: "প্রদর্শন"
_emailUnavailable:
used: "এই ইমেইল ঠিকানাটি ইতোমধ্যে ব্যবহৃত হয়েছে"
format: "এই ইমেল ঠিকানাটি সঠিকভাবে লিখা হয়নি"
@@ -1198,6 +1200,8 @@ _weekday:
friday: "শুক্রবার"
saturday: "শনিবার"
_widgets:
profile: "প্রোফাইল"
instanceInfo: "ইন্সট্যান্সের তথ্য"
memo: "স্টিকি নোট"
notifications: "বিজ্ঞপ্তি"
timeline: "টাইমলাইন"
@@ -1319,6 +1323,12 @@ _timelines:
local: "স্থানীয়"
social: "সামাজিক"
global: "গ্লোবাল"
_play:
viewSource: "উৎস দেখুন"
featured: "জনপ্রিয়"
title: "শিরোনাম"
script: "স্ক্রিপ্ট"
summary: "বর্ণনা"
_pages:
newPage: "নতুন পৃষ্ঠা বানান"
editPage: "পৃষ্ঠাটি সম্পাদনা করুন"
@@ -1378,7 +1388,6 @@ _notification:
youGotReply: "{name} আপনাকে জবাব দিয়েছে"
youGotQuote: "{name} আপনাকে উদ্ধৃত করেছে"
youRenoted: "{name} এর Renote"
youGotPoll: "{name} আপনার পোলে ভোট দিয়েছে"
youGotMessagingMessageFromUser: "{name} আপনাকে মেসেজ করেছে"
youGotMessagingMessageFromGroup: "{name} গ্রুপে একটি নতুন মেসেজ আছে"
youWereFollowed: "আপনাকে অনুসরণ করছে"
@@ -1395,7 +1404,6 @@ _notification:
renote: "রিনোট"
quote: "উদ্ধৃতি"
reaction: "প্রতিক্রিয়া"
pollVote: "পোলে ভোট আছে"
pollEnded: "পোল শেষ"
receiveFollowRequest: "প্রাপ্ত অনুসরণের অনুরোধসমূহ"
followRequestAccepted: "গৃহীত অনুসরণের অনুরোধসমূহ"

View File

@@ -399,6 +399,8 @@ _antennaSources:
userList: "Publicacions d'una llista d'usuaris"
userGroup: "Publicacions d'usuaris d'un grup"
_widgets:
profile: "Perfil"
instanceInfo: "Informació del fitxer d'instal·lació"
notifications: "Notificacions"
timeline: "Línia de temps"
activity: "Activitat"

View File

@@ -610,6 +610,7 @@ speed: "Rychlost"
slow: "Pomalá"
fast: "Rychlá"
account: "Účty"
show: "Zobrazit"
_ad:
back: "Zpět"
_gallery:
@@ -693,6 +694,8 @@ _weekday:
friday: "Pátek"
saturday: "Sobota"
_widgets:
profile: "Váš profil"
instanceInfo: "Informace o instanci"
notifications: "Oznámení"
timeline: "Časová osa"
calendar: "Kalendář"
@@ -748,6 +751,9 @@ _charts:
_timelines:
home: "Domů"
global: "Globální"
_play:
script: "Skript"
summary: "Popis"
_pages:
newPage: "Vytvořit novou stránku"
editPage: "Upravit stránku"

View File

@@ -609,7 +609,7 @@ regexpErrorDescription: "Im regulären Ausdruck deiner {tab}en Wortstummschaltun
instanceMute: "Instanzstummschaltungen"
userSaysSomething: "{name} hat etwas gesagt"
makeActive: "Aktivieren"
display: "Anzeigeart"
display: "Anzeigen"
copy: "Kopieren"
metrics: "Metriken"
overview: "Übersicht"
@@ -916,6 +916,14 @@ loggedInAsBot: "Momentan als Bot angemeldet"
tools: "Werkzeuge"
cannotLoad: "Kann nicht geladen werden"
numberOfProfileView: "Profilaufrufe"
like: "Gefällt mir"
unlike: "\"Gefällt mir\" entfernen"
numberOfLikes: "\"Gefällt mir\"-Anzahl"
show: "Anzeigen"
neverShow: "Nicht wieder anzeigen"
remindMeLater: "Vielleicht später"
didYouLikeMisskey: "Gefällt dir Misskey?"
pleaseDonate: "Misskey ist die kostenlose Software, die von {host} verwendet wird. Wir würden uns über Spenden freuen, damit dessen Entwicklung weitergeführt werden kann!"
_sensitiveMediaDetection:
description: "Ermöglicht eine Erleichterung der Servermoderation durch die automatische Erkennungen von NSFW-Medien unter Verwendung von Machine Learning. Hierdurch wird die Serverlast etwas erhöht."
sensitivity: "Erkennungssensitivität"
@@ -1294,6 +1302,8 @@ _weekday:
friday: "Freitag"
saturday: "Samstag"
_widgets:
profile: "Profil"
instanceInfo: "Instanzinformationen"
memo: "Merkzettel"
notifications: "Benachrichtigungen"
timeline: "Chronik"
@@ -1315,10 +1325,12 @@ _widgets:
jobQueue: "Job-Warteschlange"
serverMetric: "Servermetriken"
aiscript: "AiScript-Konsole"
aiscriptApp: "AiScript-Anwendung"
aichan: "Ai"
userList: "Benutzerliste"
_userList:
chooseList: "Liste auswählen"
clicker: "Klickzähler"
_cw:
hide: "Inhalt verbergen"
show: "Inhalt anzeigen"
@@ -1420,6 +1432,21 @@ _timelines:
local: "Lokal"
social: "Sozial"
global: "Global"
_play:
new: "Play erstellen"
edit: "Play bearbeiten"
created: "Play erfolgreich erstellt"
updated: "Play erfolgreich aktualisiert"
deleted: "Play erfolgreich gelöscht"
pageSetting: "Play-Einstellungen"
editThisPage: "Dieses Play bearbeiten"
viewSource: "Quelltext anzeigen"
my: "Meine Plays"
liked: "Mit \"Gefällt mir\" markierte Plays"
featured: "Beliebt"
title: "Titel"
script: "Skript"
summary: "Beschreibung"
_pages:
newPage: "Seite erstellen"
editPage: "Seite bearbeiten"
@@ -1479,7 +1506,6 @@ _notification:
youGotReply: "{name} hat dir geantwortet"
youGotQuote: "{name} hat dich zitiert"
youRenoted: "Renote deiner Notiz von {name}"
youGotPoll: "{name} hat in deiner Umfrage abgestimmt"
youGotMessagingMessageFromUser: "{name} hat dir eine Chatnachricht gesendet"
youGotMessagingMessageFromGroup: "In die Gruppe {name} wurde eine Chatnachricht gesendet"
youWereFollowed: "ist dir gefolgt"
@@ -1497,7 +1523,6 @@ _notification:
renote: "Renotes"
quote: "Zitationen"
reaction: "Reaktionen"
pollVote: "Antworten auf Umfragen"
pollEnded: "Ende von Umfragen"
receiveFollowRequest: "Erhaltene Follow-Anfragen"
followRequestAccepted: "Akzeptierte Follow-Anfragen"

View File

@@ -343,6 +343,8 @@ _antennaSources:
userList: "Σημειώματα από καθορισμένη λίστα μελών"
userGroup: "Σημειώματα από μέλη καθορισμένης ομάδας"
_widgets:
profile: "Προφίλ"
instanceInfo: "Πληροφορίες του instance"
notifications: "Ειδοποιήσεις"
timeline: "Χρονολόγιο"
calendar: "Ημερολόγιο"

View File

@@ -916,6 +916,14 @@ loggedInAsBot: "Currently logged in as bot"
tools: "Tools"
cannotLoad: "Unable to load"
numberOfProfileView: "Profile views"
like: "Like"
unlike: "Unlike"
numberOfLikes: "Likes"
show: "Show"
neverShow: "Don't show again"
remindMeLater: "Maybe later"
didYouLikeMisskey: "Have you taken a liking to Misskey?"
pleaseDonate: "{host} uses the free software, Misskey. We would highly appreciate your donations so development of Misskey can continue!"
_sensitiveMediaDetection:
description: "Reduces the effort of server moderation through automatically recognizing NSFW media via Machine Learning. This will slightly increase the load on the server."
sensitivity: "Detection sensitivity"
@@ -1294,6 +1302,8 @@ _weekday:
friday: "Friday"
saturday: "Saturday"
_widgets:
profile: "Profile"
instanceInfo: "Instance Information"
memo: "Sticky notes"
notifications: "Notifications"
timeline: "Timeline"
@@ -1315,10 +1325,12 @@ _widgets:
jobQueue: "Job Queue"
serverMetric: "Server metrics"
aiscript: "AiScript console"
aiscriptApp: "AiScript App"
aichan: "Ai"
userList: "User list"
_userList:
chooseList: "Select a list"
clicker: "Clicker"
_cw:
hide: "Hide"
show: "Show content"
@@ -1420,6 +1432,21 @@ _timelines:
local: "Local"
social: "Social"
global: "Global"
_play:
new: "Create Play"
edit: "Edit Play"
created: "Play created"
updated: "Play edited"
deleted: "Play deleted"
pageSetting: "Play settings"
editThisPage: "Edit this Play"
viewSource: "View source"
my: "My Plays"
liked: "Liked Plays"
featured: "Popular"
title: "Title"
script: "Script"
summary: "Description"
_pages:
newPage: "Create a new Page"
editPage: "Edit this Page"
@@ -1479,7 +1506,6 @@ _notification:
youGotReply: "{name} replied to you"
youGotQuote: "{name} quoted you"
youRenoted: "Renote from {name}"
youGotPoll: "{name} voted on your poll"
youGotMessagingMessageFromUser: "{name} sent you a chat message"
youGotMessagingMessageFromGroup: "A chat message was sent to the {name} group"
youWereFollowed: "followed you"
@@ -1497,7 +1523,6 @@ _notification:
renote: "Renotes"
quote: "Quotes"
reaction: "Reactions"
pollVote: "Votes on polls"
pollEnded: "Polls ending"
receiveFollowRequest: "Received follow requests"
followRequestAccepted: "Accepted follow requests"

View File

@@ -916,6 +916,8 @@ loggedInAsBot: "Inicio sesión como cuenta bot."
tools: "Utilidades"
cannotLoad: "No se puede cargar."
numberOfProfileView: "Número de vistas de perfil"
like: "¡Muy bien!"
show: "Apariencia"
_sensitiveMediaDetection:
description: "Reduce el esfuerzo de la moderación el el servidor a través del reconocimiento automático de contenido NSFW usando 'Machine Learning'. Esto puede incrementar ligeramente la carga en el servidor."
sensitivity: "Sensibilidad de detección"
@@ -1294,6 +1296,8 @@ _weekday:
friday: "Viernes"
saturday: "Sábado"
_widgets:
profile: "Perfil"
instanceInfo: "información de la instancia"
memo: "Nota adhesiva"
notifications: "Notificaciones"
timeline: "Linea de tiempo"
@@ -1420,6 +1424,12 @@ _timelines:
local: "Local"
social: "Social"
global: "Global"
_play:
viewSource: "Ver la fuente"
featured: "Popular"
title: "Título"
script: "Script"
summary: "Descripción"
_pages:
newPage: "Crear página"
editPage: "Editar página"
@@ -1479,7 +1489,6 @@ _notification:
youGotReply: "Respuesta de {name}"
youGotQuote: "Citado por {name}"
youRenoted: "Renotado por {name}"
youGotPoll: "Encuestado por {name}"
youGotMessagingMessageFromUser: "{name} comenzó un chat contigo"
youGotMessagingMessageFromGroup: "Tienes un chat de {name}"
youWereFollowed: "te ha seguido"
@@ -1497,7 +1506,6 @@ _notification:
renote: "Renotar"
quote: "Citar"
reaction: "Reacción"
pollVote: "Votado en la encuesta"
pollEnded: "La encuesta terminó"
receiveFollowRequest: "Recibió una solicitud de seguimiento"
followRequestAccepted: "El seguimiento fue aceptado"

View File

@@ -910,6 +910,8 @@ caption: "Libellé"
loggedInAsBot: "Connecté actuellement en tant que bot"
tools: "Outils"
cannotLoad: "Chargement impossible"
like: "J'aime"
show: "Affichage"
_sensitiveMediaDetection:
description: "L'apprentissage automatique peut être utilisé pour détecter automatiquement les médias sensibles à modérer. La sollicitation des serveurs augmente légèrement."
sensitivity: "Sensibilité de la détection"
@@ -1287,6 +1289,8 @@ _weekday:
friday: "Vendredi"
saturday: "Samedi"
_widgets:
profile: "Profil"
instanceInfo: "Informations sur linstance"
memo: "Note collante"
notifications: "Notifications"
timeline: "Fil"
@@ -1411,6 +1415,12 @@ _timelines:
local: "Local"
social: "Social"
global: "Global"
_play:
viewSource: "Afficher la source"
featured: "Populaire"
title: "Titre"
script: "Script"
summary: "Description"
_pages:
newPage: "Créer une page"
editPage: "Modifier une page"
@@ -1470,7 +1480,6 @@ _notification:
youGotReply: "Réponse de {name}"
youGotQuote: "Cité·e par {name}"
youRenoted: "{name} vous a Renoté"
youGotPoll: "{name} a participé à votre sondage"
youGotMessagingMessageFromUser: "{name} vous envoyé un message"
youGotMessagingMessageFromGroup: "Un message a été envoyé au groupe {name}"
youWereFollowed: "Vous suit"
@@ -1488,7 +1497,6 @@ _notification:
renote: "Renotes"
quote: "Citations"
reaction: "Réactions"
pollVote: "Votes dans des sondages"
pollEnded: "Sondages se cloturant"
receiveFollowRequest: "Demande d'abonnement reçue"
followRequestAccepted: "Demande d'abonnement acceptée"

View File

@@ -855,6 +855,10 @@ colored: "Diwarnai"
label: "Label"
localOnly: "Hanya lokal"
account: "Akun"
like: "Suka"
unlike: "Tidak Suka"
numberOfLikes: "Jumlah yang disukai"
show: "Tampilkan"
_emailUnavailable:
used: "Alamat surel ini telah digunakan"
format: "Format tidak valid."
@@ -1202,6 +1206,8 @@ _weekday:
friday: "Jumat"
saturday: "Sabtu"
_widgets:
profile: "Profil"
instanceInfo: "Informasi Instansi"
memo: "Catatan memo"
notifications: "Pemberitahuan"
timeline: "Linimasa"
@@ -1220,6 +1226,7 @@ _widgets:
jobQueue: "Antrian kerja"
serverMetric: "Statistik peladen"
aiscript: "Konsol AiScript"
aiscriptApp: "Aplikasi AiScript"
aichan: "Ai"
_userList:
chooseList: "Pilih daftar"
@@ -1323,6 +1330,21 @@ _timelines:
local: "Lokal"
social: "Sosial"
global: "Global"
_play:
new: "Membuat Permainan"
edit: "Menyunting Permainan"
created: "Permainan sudah dibuat"
updated: "Permainan sudah diperbaharui"
deleted: "Hapus permainan"
pageSetting: "Pengaturan permainan"
editThisPage: "Sunting Permainan ini"
viewSource: "Lihat sumber"
my: "Permainan saya"
liked: "Permainan Disukai"
featured: "Populer"
title: "Judul"
script: "Script"
summary: "Deskripsi"
_pages:
newPage: "Buat halaman baru"
editPage: "Sunting halaman"
@@ -1382,7 +1404,6 @@ _notification:
youGotReply: "{name} membalas kamu"
youGotQuote: "{name} mengutip kamu"
youRenoted: "{name} me-renote kamu"
youGotPoll: "{name} memilih di angket kamu"
youGotMessagingMessageFromUser: "{name} mengirimi kamu pesan"
youGotMessagingMessageFromGroup: "Sebuah pesan telah dikirim ke grup {name}"
youWereFollowed: "Mengikuti kamu"
@@ -1399,7 +1420,6 @@ _notification:
renote: "Renote"
quote: "Kutip"
reaction: "Reaksi"
pollVote: "Memilih di angket"
pollEnded: "Jajak pendapat berakhir"
receiveFollowRequest: "Permintaan mengikuti diterima"
followRequestAccepted: "Permintaan mengikuti disetujui"

View File

@@ -1,7 +1,7 @@
---
_lang_: "Italiano"
headlineMisskey: "Rete collegata tramite note"
introMisskey: "Benvenut@! Misskey è un servizio di microblogging decentralizzato, libero e aperto. \nScrivi \"note\" per condividere ciò che sta succedendo adesso o per dire a tutti qualcosa di te. 📡\nGrazie alla funzione \"reazioni\" puoi anche mandare reazioni rapide alle note delle altre persone del Fediverso. 👍\nEsplora un nuovo mondo! 🚀"
introMisskey: "Eccoci! Misskey è un servizio di microblogging decentralizzato, libero e aperto. \n📡 Puoi pubblicare «Note» per condividere ciò che sta succedendo o per dire a tutti qualcosa su di te. \n👍 Puoi reagire inviando emoji rapidi alle «Note» provenienti da altri profili nel Fediverso.\n🚀 Esplora un nuovo mondo insieme a noi!"
poweredByMisskeyDescription: "{name} è uno dei servizi (chiamati istanze) che utilizzano la piattaforma open source <b>Misskey</b>."
monthAndDay: "{day}/{month}"
search: "Cerca"
@@ -28,7 +28,7 @@ timeline: "Timeline"
noAccountDescription: "L'utente non ha ancora scritto niente nella biografia di profilo."
login: "Accedi"
loggingIn: "Accesso in corso..."
logout: "Esci"
logout: "Uscita"
signup: "Iscriviti"
uploading: "Caricamento..."
save: "Salva"
@@ -876,7 +876,7 @@ deleteAccount: "Eliminazione profilo"
document: "Documento"
numberOfPageCache: "Numero di pagine cache"
numberOfPageCacheDescription: "Aumenta l'usabilità, ma aumenta anche il carico e l'utilizzo della memoria."
logoutConfirm: "Sei sicuro di voler effettuare il logout?"
logoutConfirm: "Vuoi davvero uscire da Misskey? "
lastActiveDate: "Data dell'ultimo utilizzo"
statusbar: "Barra di stato"
pleaseSelect: "Scegli un'opzione"
@@ -916,6 +916,8 @@ loggedInAsBot: "Connessione come Bot"
tools: "Strumenti"
cannotLoad: "Caricamento impossibile"
numberOfProfileView: "Visualizzazioni profilo"
like: "Mi piace!"
show: "Visualizza"
_sensitiveMediaDetection:
description: "L'apprendimento automatico può essere utilizzato per individuare automaticamente i media sensibili da moderare. Il carico del server aumenta leggermente."
sensitivity: "Sensibilità di rilevamento"
@@ -1067,7 +1069,7 @@ _mfm:
sparkleDescription: "Aggiungere effetti particellari scintillanti."
rotate: "Ruota"
rotateDescription: "Ruota con un angolo specificato."
plain: "aereo"
plain: "Testo semplice"
plainDescription: "Disattiva tutta la sintassi interna."
_instanceTicker:
none: "Nascondi"
@@ -1205,13 +1207,13 @@ _time:
day: "giorni"
_tutorial:
title: "Come usare Misskey"
step1_1: "Benvenuto/a!"
step1_1: "Eccoci!"
step1_2: "Questa pagina si chiama una \" Timeline \". Mostra in ordine cronologico le \" note \" delle persone che segui."
step1_3: "Attualmente la tua Timeline è vuota perché non segui alcun profilo e non hai pubblicato alcuna nota ancora."
step2_1: "Prima di scrivere una nota o di seguire altri profili, imposta il tuo di profilo!"
step2_1: "Prima di scrivere una «Nota» o di seguire altri profili, prepara il tuo profilo!"
step2_2: "Aggiungere qualche informazione su di te aumenterà le tue possibilità di essere seguit@ da altre persone. "
step3_1: "Hai finito di impostare il tuo profilo?"
step3_2: "Ora, puoi pubblicare una nota. Facciamo una prova! Premi il pulsante a forma di penna in cima allo schermo per aprire una finestra di dialogo. "
step3_2: "Ora puoi pubblicare una «Nota». Proviamo subito! Premi il bottone con l'icona «penna» per iniziare a scrivere in una finestra di dialogo. "
step3_3: "Scritto il testo della nota, puoi pubblicarla premendo il pulsante nella parte superiore destra della finestra di dialogo."
step3_4: "Non ti viene niente in mente? Perché non scrivi semplicemente \"Ho appena cominciato a usare Misskey\"?"
step4_1: "Hai pubblicato qualcosa?"
@@ -1223,7 +1225,7 @@ _tutorial:
step6_1: "Adesso, dovresti essere in grado di vedere le note dagli altri profili sulla tua timeline."
step6_2: "Puoi anche rispondere alle note con un click, scegliendo le reazioni immediate."
step6_3: "Per inviare una reazione, premi l'icona + della nota e scegli l'emoji che vuoi mandare."
step7_1: "Complimenti! Sei arrivat@ alla fine dell'esercitazione di base su come usare Misskey. "
step7_1: "Congratulazioni! Hai completato l'esercitazione iniziale su come usare Misskey."
step7_2: "Se vuoi saperne di più su Misskey, puoi dare un'occhiata alla sezione {help}."
step7_3: "Da ultimo, buon divertimento su Misskey! 🚀"
step8_1: "Per concludere, vuoi attivare le notifiche push?"
@@ -1294,6 +1296,8 @@ _weekday:
friday: "Venerdì"
saturday: "Sabato"
_widgets:
profile: "Profilo"
instanceInfo: "Informazioni sull'istanza"
memo: "Promemoria"
notifications: "Notifiche"
timeline: "Timeline"
@@ -1315,7 +1319,7 @@ _widgets:
jobQueue: "Coda di lavoro"
serverMetric: "Statistiche server"
aiscript: "Console AiScript"
aichan: "indaco (tintura)"
aichan: "Mascotte Ai"
userList: "Elenco utenti"
_userList:
chooseList: "Seleziona una lista"
@@ -1420,6 +1424,12 @@ _timelines:
local: "Locale"
social: "Sociale"
global: "Federata"
_play:
viewSource: "Visualizza sorgente"
featured: "Popolari"
title: "Titolo"
script: "Script"
summary: "Descrizione"
_pages:
newPage: "Crea pagina"
editPage: "Modifica pagina"
@@ -1479,7 +1489,6 @@ _notification:
youGotReply: "{name} ti ha risposto"
youGotQuote: "{name} ha citato il tuo Nota e ha detto"
youRenoted: "{name} ha rinotato"
youGotPoll: "{name} ha votato"
youGotMessagingMessageFromUser: "{name} ti ha mandato un messaggio"
youGotMessagingMessageFromGroup: "{name} ti ha mandato un messaggio nella chat"
youWereFollowed: "Ha iniziato a seguirti"
@@ -1497,7 +1506,6 @@ _notification:
renote: "Rinota"
quote: "Cita"
reaction: "Reazioni"
pollVote: "Voti ricevuti"
pollEnded: "Sondaggio chiuso."
receiveFollowRequest: "Richiesta di follow ricevuta"
followRequestAccepted: "Richiesta di follow accettata"

View File

@@ -920,6 +920,53 @@ like: "いいね!"
unlike: "いいねを解除"
numberOfLikes: "いいね数"
show: "表示"
neverShow: "今後表示しない"
remindMeLater: "また後で"
didYouLikeMisskey: "Misskeyを気に入っていただけましたか"
pleaseDonate: "Misskeyは{host}が使用している無料のソフトウェアです。これからも開発を続けられるように、ぜひ寄付をお願いします!"
roles: "ロール"
role: "ロール"
normalUser: "一般ユーザー"
undefined: "未定義"
assign: "アサイン"
unassign: "アサインを解除"
color: "色"
_role:
new: "ロールの作成"
edit: "ロールの編集"
name: "ロール名"
description: "ロールの説明"
permission: "ロールの権限"
descriptionOfPermission: "<b>モデレーター</b>は基本的なモデレーションに関する操作を行えます。\n<b>管理者</b>はインスタンスの全ての設定を変更できます。"
assignTarget: "アサインターゲット"
descriptionOfAssignTarget: "<b>マニュアル</b>は誰がこのロールに含まれるかを手動で管理します。\n<b>コンディショナル</b>は条件を設定し、それに合致するユーザーが自動で含まれるようになります。"
manual: "マニュアル"
conditional: "コンディショナル"
condition: "条件"
isConditionalRole: "これはコンディショナルロールです。"
isPublic: "ロールを公開"
descriptionOfIsPublic: "ロールにアサインされたユーザーを誰でも見ることができます。また、ユーザーのプロフィールでこのロールが表示されます。"
options: "オプション"
baseRole: "ベースロール"
useBaseValue: "ベースロールの値を使用"
chooseRoleToAssign: "アサインするロールを選択"
canEditMembersByModerator: "モデレーターのメンバー編集を許可"
descriptionOfCanEditMembersByModerator: "オンにすると、管理者に加えてモデレーターもこのロールへユーザーをアサイン/アサイン解除できるようになります。オフにすると管理者のみが行えます。"
_options:
gtlAvailable: "グローバルタイムラインの閲覧"
ltlAvailable: "ローカルタイムラインの閲覧"
canPublicNote: "パブリック投稿の許可"
driveCapacity: "ドライブ容量"
antennaMax: "アンテナの作成可能数"
_condition:
isLocal: "ローカルユーザー"
isRemote: "リモートユーザー"
createdLessThan: "アカウント作成から~以内"
createdMoreThan: "アカウント作成から~経過"
and: "~かつ~"
or: "~または~"
not: "~ではない"
_sensitiveMediaDetection:
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。"
@@ -1331,6 +1378,8 @@ _weekday:
saturday: "土曜日"
_widgets:
profile: "プロフィール"
instanceInfo: "インスタンス情報"
memo: "付箋"
notifications: "通知"
timeline: "タイムライン"
@@ -1357,6 +1406,7 @@ _widgets:
userList: "ユーザーリスト"
_userList:
chooseList: "リストを選択"
clicker: "クリッカー"
_cw:
hide: "隠す"
@@ -1546,7 +1596,6 @@ _notification:
youGotReply: "{name}からのリプライ"
youGotQuote: "{name}による引用"
youRenoted: "{name}がRenoteしました"
youGotPoll: "{name}が投票しました"
youGotMessagingMessageFromUser: "{name}からのチャットがあります"
youGotMessagingMessageFromGroup: "{name}のチャットがあります"
youWereFollowed: "フォローされました"
@@ -1565,7 +1614,6 @@ _notification:
renote: "Renote"
quote: "引用"
reaction: "リアクション"
pollVote: "アンケートに投票された"
pollEnded: "アンケートが終了"
receiveFollowRequest: "フォロー申請を受け取った"
followRequestAccepted: "フォローが受理された"

View File

@@ -915,6 +915,8 @@ caption: "キャプション"
loggedInAsBot: "Botアカウントでログイン中やで"
tools: "ツール"
cannotLoad: "読み込めへんで"
like: "ええやん!"
show: "表示"
_sensitiveMediaDetection:
description: "機械学習を使って自動でセンシティブなメディアを検出して、モデレーションに役立てることができるで。サーバーの負荷が少し増えてまうなあ。"
sensitivity: "検出感度やで"
@@ -1293,6 +1295,8 @@ _weekday:
friday: "金曜日"
saturday: "土曜日"
_widgets:
profile: "プロフィール"
instanceInfo: "インスタンス情報"
memo: "付箋"
notifications: "通知"
timeline: "タイムライン"
@@ -1418,6 +1422,12 @@ _timelines:
local: "ローカル"
social: "ソーシャル"
global: "グローバル"
_play:
viewSource: "ソースを表示"
featured: "人気"
title: "タイトル"
script: "スクリプト"
summary: "説明"
_pages:
newPage: "ページを作る"
editPage: "ページの編集"
@@ -1477,7 +1487,6 @@ _notification:
youGotReply: "{name}からのリプライ"
youGotQuote: "{name}による引用"
youRenoted: "{name}がRenoteしたみたいやで"
youGotPoll: "{name}が投票したみたいやで"
youGotMessagingMessageFromUser: "{name}からのチャットがあるで"
youGotMessagingMessageFromGroup: "{name}のチャットがあるで"
youWereFollowed: "フォローされたで"
@@ -1495,7 +1504,6 @@ _notification:
renote: "Renote"
quote: "引用"
reaction: "リアクション"
pollVote: "アンケートに投票されたで"
pollEnded: "アンケートが終了したで"
receiveFollowRequest: "フォロー許可してほしいみたいやで"
followRequestAccepted: "フォローが受理されたで"

View File

@@ -73,6 +73,7 @@ _sfx:
_permissions:
"write:account": "Ẓreg talɣut n umiḍan-ik·im"
_widgets:
profile: "Amaɣnu"
notifications: "Ilɣuyen"
_userList:
chooseList: "Fren tabdart"

View File

@@ -69,6 +69,7 @@ _mfm:
_sfx:
notification: "ಅಧಿಸೂಚನೆಗಳು"
_widgets:
profile: "ಪ್ರೊಫೈಲು"
notifications: "ಅಧಿಸೂಚನೆಗಳು"
timeline: "ಸಮಯಸಾಲು"
_cw:

View File

@@ -916,6 +916,14 @@ loggedInAsBot: "봇 계정으로 로그인중"
tools: "도구"
cannotLoad: "불러오지 못했습니다"
numberOfProfileView: "프로필 뷰 수"
like: "좋아요!"
unlike: "좋아요 취소"
numberOfLikes: "좋아요 수"
show: "표시"
neverShow: "다시 보지 않기"
remindMeLater: "나중에 알림"
didYouLikeMisskey: "Misskey가 마음에 드시나요?"
pleaseDonate: "{host}은(는) 무료 소프트웨어 Misskey를 사용합니다. 후원을 통해 저희의 개발이 이어질 수 있게 도와주세요!"
_sensitiveMediaDetection:
description: "기계학습을 통해 자동으로 민감한 미디어를 탐지하여, 모더레이션에 참고할 수 있도록 합니다. 서버의 부하를 약간 증가시킵니다."
sensitivity: "탐지 민감도"
@@ -1294,6 +1302,8 @@ _weekday:
friday: "금요일"
saturday: "토요일"
_widgets:
profile: "프로필"
instanceInfo: "인스턴스 정보"
memo: "스티커 메모"
notifications: "알림"
timeline: "타임라인"
@@ -1315,10 +1325,12 @@ _widgets:
jobQueue: "작업 대기열"
serverMetric: "서버 통계"
aiscript: "AiScript 콘솔"
aiscriptApp: "AiScript 앱"
aichan: "아이"
userList: "사용자 목록"
_userList:
chooseList: "리스트 선택"
clicker: "클리커"
_cw:
hide: "숨기기"
show: "더 보기"
@@ -1420,6 +1432,21 @@ _timelines:
local: "로컬"
social: "소셜"
global: "글로벌"
_play:
new: "Play 만들기"
edit: "Play 수정하기"
created: "Play를 생성했습니다"
updated: "Play를 갱신했습니다"
deleted: "Play를 삭제했습니다"
pageSetting: "Play 설정"
editThisPage: "이 Play를 수정"
viewSource: "소스 보기"
my: "나의 Play"
liked: "좋아요 한 Play"
featured: "인기"
title: "제목"
script: "스크립트"
summary: "설명"
_pages:
newPage: "페이지 만들기"
editPage: "페이지 수정"
@@ -1479,7 +1506,6 @@ _notification:
youGotReply: "{name}님이 답글함"
youGotQuote: "{name}님이 인용함"
youRenoted: "{name}님이 Renote"
youGotPoll: "{name}님이 투표함"
youGotMessagingMessageFromUser: "{name} 님이 보낸 채팅이 있어요"
youGotMessagingMessageFromGroup: "{name}에서 보낸 채팅이 있어요"
youWereFollowed: "새로운 팔로워가 있습니다"
@@ -1497,7 +1523,6 @@ _notification:
renote: "리노트"
quote: "인용"
reaction: "리액션"
pollVote: "투표 참여"
pollEnded: "투표가 종료됨"
receiveFollowRequest: "팔로우 요청을 받았을 때"
followRequestAccepted: "팔로우 요청이 승인되었을 때"

View File

@@ -440,6 +440,8 @@ _sfx:
notification: "Meldingen"
chat: "Chat"
_widgets:
profile: "Profiel"
instanceInfo: "Serverinformatie"
notifications: "Meldingen"
timeline: "Tijdlijn"
activity: "Activiteit"

View File

@@ -866,6 +866,8 @@ pushNotificationNotSupported: "Przeglądarka lub instancja nie obsługuje powiad
sendPushNotificationReadMessage: "Usuń powiadomienia push po przeczytaniu powiadomień i wiadomości."
sendPushNotificationReadMessageCaption: "Chwilowo pojawi się powiadomienie \"{emptyPushNotificationMessage}\". Może wzrosnąć zużycie baterii urządzenia."
loggedInAsBot: "Jesteś obecnie zalogowany/a jako bot"
like: "Polub"
show: "Wyświetlanie"
_sensitiveMediaDetection:
description: "Zmniejsza wysiłek związany z moderacją serwera dzięki automatycznemu rozpoznawaniu zawartości NSFW za pomocą uczenia maszynowego. To nieznacznie zwiększy obciążenie serwera."
setSensitiveFlagAutomatically: "Oznacz jako NSFW"
@@ -1211,6 +1213,8 @@ _weekday:
friday: "Piątek"
saturday: "Sobota"
_widgets:
profile: "Profil"
instanceInfo: "Informacje o instancji"
memo: "Przypięte notatki"
notifications: "Powiadomienia"
timeline: "Oś czasu"
@@ -1313,6 +1317,12 @@ _timelines:
local: "Lokalne"
social: "Społeczność"
global: "Globalna"
_play:
viewSource: "Zobacz źródło"
featured: "Wyróżnione"
title: "Tytuł"
script: "Skrypt"
summary: "Opis"
_pages:
newPage: "Utwórz stronę"
editPage: "Edytuj tę stronę"
@@ -1372,7 +1382,6 @@ _notification:
youGotReply: "{name} odpowiedział(a) Tobie"
youGotQuote: "{name} zacytował(a) Ciebie"
youRenoted: "{name} udostępnił(a) Twój wpis"
youGotPoll: "{name} zagłosował(a) w Twojej ankiecie"
youGotMessagingMessageFromUser: "{name} wysłał(a) Ci wiadomość"
youGotMessagingMessageFromGroup: "Została wysłana wiadomość do grupy {name}"
youWereFollowed: "Zaobserwował(a) Cię"
@@ -1390,7 +1399,6 @@ _notification:
renote: "Udostępnij"
quote: "Cytuj"
reaction: "Reakcja"
pollVote: "Głosy w ankietach"
receiveFollowRequest: "Otrzymano prośbę o możliwość obserwacji"
followRequestAccepted: "Przyjęto prośbę o możliwość obserwacji"
groupInvited: "Zaproszono do grup"

View File

@@ -488,6 +488,8 @@ _sfx:
notification: "Notificações"
chat: "Chat"
_widgets:
profile: "Perfil"
instanceInfo: "Informações da instância"
notifications: "Notificações"
timeline: "Timeline"
activity: "atividade"
@@ -524,7 +526,6 @@ _notification:
youGotMention: "{name} te mencionou"
youGotReply: "{name} te respondeu"
youGotQuote: "{name} te citou"
youGotPoll: "{name} votou em sua enquete"
youGotMessagingMessageFromUser: "{name} te mandou uma mensagem de bate-papo"
youGotMessagingMessageFromGroup: "Uma mensagem foi mandada para o grupo {name}"
youWereFollowed: "Você tem um novo seguidor"
@@ -541,7 +542,6 @@ _notification:
renote: "Repostar"
quote: "Citar"
reaction: "Reações"
pollVote: "Votações em enquetes"
pollEnded: "Enquetes terminando"
receiveFollowRequest: "Recebeu pedidos de seguimento"
followRequestAccepted: "Aceitou pedidos de seguimento"

View File

@@ -647,6 +647,7 @@ middle: "Mediu"
sent: "Trimite"
searchByGoogle: "Caută"
file: "Fișiere"
show: "Arată"
_email:
_follow:
title: "te-a urmărit"
@@ -666,6 +667,8 @@ _sfx:
notification: "Notificări"
chat: "Chat"
_widgets:
profile: "Profil"
instanceInfo: "Informații despre instanță"
notifications: "Notificări"
timeline: "Cronologie"
activity: "Activitate"
@@ -690,6 +693,9 @@ _charts:
federation: "Federație"
_timelines:
home: "Acasă"
_play:
script: "Script"
summary: "Descriere"
_pages:
blocks:
image: "Imagini"

View File

@@ -864,6 +864,8 @@ enableAutoSensitiveDescription: "Если доступно, используйт
account: "Учётные записи"
windowMaximize: "Развернуть"
windowRestore: "Восстановить"
like: "Нравится!"
show: "Отображение"
_sensitiveMediaDetection:
description: "Машинное обучение может быть использовано для автоматического обнаружения чувствительных медиа для модерации. Нагрузка на сервер увеличивается незначительно."
setSensitiveFlagAutomatically: "Установить флаг NSFW"
@@ -1211,6 +1213,8 @@ _weekday:
friday: "Пятница"
saturday: "Суббота"
_widgets:
profile: "Профиль"
instanceInfo: "Информация об инстансе"
memo: "Напоминания"
notifications: "Уведомления"
timeline: "Лента"
@@ -1332,6 +1336,12 @@ _timelines:
local: "Местная"
social: "Социальная"
global: "Всеобщая"
_play:
viewSource: "Просмотр исходника"
featured: "Популярные"
title: "Заголовок"
script: "Скрипт"
summary: "Описание"
_pages:
newPage: "Создать страницу"
editPage: "Править страницу"
@@ -1391,7 +1401,6 @@ _notification:
youGotReply: "{name} отвечает вам."
youGotQuote: "{name} цитирует вас."
youRenoted: "{name} передаёт вашу заметку."
youGotPoll: "{name} участвует в вашем опросе."
youGotMessagingMessageFromUser: "{name} пишет вам."
youGotMessagingMessageFromGroup: "Новое сообщение в группе «{name}»."
youWereFollowed: "У вас новый подписчик."
@@ -1406,7 +1415,6 @@ _notification:
renote: "Репосты"
quote: "Цитаты"
reaction: "Реакции"
pollVote: "Голосования"
receiveFollowRequest: "Получен запрос на подписку"
followRequestAccepted: "Запрос на подписку одобрен"
groupInvited: "Приглашение в группы"

View File

@@ -911,6 +911,12 @@ windowRestore: "Obnoviť"
caption: "Nadpis"
tools: "Nástroje"
cannotLoad: "Nedá sa načítať."
like: "Páči sa mi"
show: "Zobraziť"
neverShow: "Nabudúce nezobrazovať"
remindMeLater: "Pripomenúť neskôr"
didYouLikeMisskey: "Páči sa vám Misskey?"
pleaseDonate: "Misskey je bezplatný softvér, ktorý používa {host}. Prosím, prispejte, aby sme ho mohli ďalej rozvíjať!"
_sensitiveMediaDetection:
description: "Strojové učenie sa použije na automatickú detekciu citlivých médií na účely ich moderovania. Mierne sa zvýši zaťaženie servera."
sensitivity: "Citlivosť detekcie"
@@ -1289,6 +1295,8 @@ _weekday:
friday: "Piatok"
saturday: "Sobota"
_widgets:
profile: "Profil"
instanceInfo: "Informácie o serveri"
memo: "Prilepené poznámky"
notifications: "Oznámenia"
timeline: "Časová os"
@@ -1413,6 +1421,12 @@ _timelines:
local: "Lokálne"
social: "Sociálne"
global: "Globálne"
_play:
viewSource: "Ukázať zdroj"
featured: "Význačné"
title: "Nadpis"
script: "Skript"
summary: "Popis"
_pages:
newPage: "Vytvoriť novú stránku"
editPage: "Upraviť túto stránku"
@@ -1472,7 +1486,6 @@ _notification:
youGotReply: "{name} vám odpovedal/a"
youGotQuote: "{name} vás citoval/a"
youRenoted: "{name} preposlal/a vašu poznámku"
youGotPoll: "{name} hlasoval/a"
youGotMessagingMessageFromUser: "{name} vám poslal/a správu"
youGotMessagingMessageFromGroup: "Prišla správa do skupiny {name}"
youWereFollowed: "Máte nového sledujúceho"
@@ -1490,7 +1503,6 @@ _notification:
renote: "Preposlať"
quote: "Citovať"
reaction: "Reakcie"
pollVote: "Hlasy v hlasovaniach"
pollEnded: "Hlasovanie skončilo"
receiveFollowRequest: "Doručené žiadosti o sledovanie"
followRequestAccepted: "Schválené žiadosti o sledovanie"

View File

@@ -1,7 +1,8 @@
---
_lang_: "Svenska"
headlineMisskey: "Ett nätverk kopplat av noter"
introMisskey: "Välkommen! Misskey är en öppen och decentraliserad mikrobloggningstjänst.\nSkapa en \"not\" och dela dina tankar med alla runtomkring dig. 📡\nMed \"reaktioner\" kan du snabbt uttrycka dina känslor kring andras noter.👍\nLåt oss utforska en nya värld!🚀"
introMisskey: "Välkommen! Misskey är en öppen och decentraliserad mikrobloggningstjänst.\nSkapa en \"not\" och dela dina tankar med alla runtomkring dig. 📡\nMed \"reaktioner\" kan du snabbt uttrycka dina känslor kring andras noter. 👍\nLåt oss utforska en ny värld! 🚀"
poweredByMisskeyDescription: "{name} är en tjänst driven av den öppna källkodsplatformen <b>Misskey</b> (benämns \"Misskey instans\")."
monthAndDay: "{day}/{month}"
search: "Sök"
notifications: "Notifikationer"
@@ -12,10 +13,11 @@ fetchingAsApObject: "Hämtar från Fediversum..."
ok: "OK"
gotIt: "Uppfattat!"
cancel: "Avbryt"
noThankYou: "Nej tack"
enterUsername: "Ange användarnamn"
renotedBy: "Omnoterad av {user}"
noNotes: "Inga noteringar"
noNotifications: "Inga aviseringar"
noNotifications: "Inga notifikationer"
instance: "Instanser"
settings: "Inställningar"
basicSettings: "Basinställningar"
@@ -28,13 +30,13 @@ login: "Logga in"
loggingIn: "Loggar in"
logout: "Logga ut"
signup: "Registrera"
uploading: "Uppladdning sker..."
uploading: "Laddar upp..."
save: "Spara"
users: "Användare"
addUser: "Lägg till användare"
favorite: "Lägg till i favoriter"
favorites: "Favoriter"
unfavorite: "Avfavorisera"
unfavorite: "Ta bort från favoriter"
favorited: "Tillagd i favoriter."
alreadyFavorited: "Redan tillagd i favoriter."
cantFavorite: "Gick inte att lägga till i favoriter."
@@ -47,11 +49,13 @@ deleteAndEdit: "Radera och ändra"
deleteAndEditConfirm: "Är du säker att du vill radera denna not och ändra den? Du kommer förlora alla reaktioner, omnoteringar och svar till den."
addToList: "Lägg till i lista"
sendMessage: "Skicka ett meddelande"
copyRSS: "Kopiera RSS"
copyUsername: "Kopiera användarnamn"
searchUser: "Sök användare"
reply: "Svara"
loadMore: "Ladda mer"
showMore: "Visa mer"
showLess: "Stäng"
youGotNewFollower: "följde dig"
receiveFollowRequest: "Följarförfrågan mottagen"
followRequestAccepted: "Följarförfrågan accepterad"
@@ -142,7 +146,7 @@ flagAsBotDescription: "Aktivera det här alternativet om kontot är kontrollerat
flagAsCat: "Markera konto som katt"
flagAsCatDescription: "Aktivera denna inställning för att markera kontot som en katt."
flagShowTimelineReplies: "Visa svar i tidslinje"
flagShowTimelineRepliesDescription: "Visar användarsvar till andra användares noter i tidslinjen om påslagen."
flagShowTimelineRepliesDescription: "Visar användarsvar till andra användares noter i tidslinjen om aktiverad."
autoAcceptFollowed: "Godkänn följarförfrågningar från användare du följer automatiskt"
addAccount: "Lägg till konto"
loginFailed: "Inloggningen misslyckades"
@@ -238,16 +242,131 @@ saved: "Sparad"
messaging: "Chatt"
upload: "Ladda upp"
keepOriginalUploading: "Behåll originalbild"
keepOriginalUploadingDescription: "Sparar den originellt uppladdade bilden i sitt i befintliga skick. Om avstängd, kommer en webbversion bli genererad vid uppladdning."
fromDrive: "Från Drive"
fromUrl: "Från en länk"
uploadFromUrl: "Ladda upp från länk"
uploadFromUrlDescription: "Länken av filen du vill ladda upp"
uploadFromUrlRequested: "Uppladdning begärd"
uploadFromUrlMayTakeTime: "Det kan ta tid tills att uppladdningen blir klar."
explore: "Utforska"
messageRead: "Läs"
noMoreHistory: "Det finns ingen mer historik"
startMessaging: "Starta en chatt"
nUsersRead: "läst av {n}"
agreeTo: "Jag accepterar {0}"
tos: "Användarvillkor"
home: "Hem"
remoteUserCaution: "Då denna användaren kommer från en fjärrinstans, kan informationen visad vara ofullständig."
activity: "Aktivitet"
images: "Bilder"
birthday: "Födelsedag"
yearsOld: "{age} år gammal"
registeredDate: "Gick med"
location: "Plats"
theme: "Teman"
themeForLightMode: "Tema att använda i Ljust Läge"
themeForDarkMode: "Tema att använda i Mörkt Läge"
light: "Ljust"
dark: "Mörk"
lightThemes: "Ljusa teman"
darkThemes: "Mörka teman"
syncDeviceDarkMode: "Synka Mörkt Läge med din enhets inställningar"
drive: "Drive"
fileName: "Filnamn"
selectFile: "Välj en fil"
selectFiles: "Välj filer"
selectFolder: "Välj en mapp"
selectFolders: "Välj mappar"
renameFile: "Byt namn på filen"
folderName: "Mappnamn"
createFolder: "Skapa en mapp"
renameFolder: "Byt namn på mappen"
deleteFolder: "Ta bort mappen"
addFile: "Lägg till fil"
emptyDrive: "Din Drive är tom"
emptyFolder: "Denna mappen är tom"
unableToDelete: "Kunde inte ta bort"
inputNewFileName: "Ange nytt filnamn"
inputNewDescription: "Ange ny bildtext"
inputNewFolderName: "Ange nytt mappnamn"
circularReferenceFolder: "Destinationsmappen är en undermapp av mappen du vill flytta."
hasChildFilesOrFolders: "Då denna mappen inte är tom, kan den inte tas bort."
copyUrl: "Kopiera URL"
rename: "Byt namn"
avatar: "Profilbild"
banner: "Banner"
nsfw: "Känsligt innehåll"
reload: "Ladda om"
doNothing: "Ignorera"
reloadConfirm: "Vill du ladda om tidslinjen?"
accept: "Tillåt"
reject: "Neka"
normal: "Normal"
instanceName: "Instansnamn"
instanceDescription: "Instansbeskrivning"
maintainerEmail: "Administratörens epost"
tosUrl: "URL till användarvillkår"
thisYear: "Detta året"
thisMonth: "Denna månaden"
today: "Idag"
dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "Sidor"
integration: "Integrationer"
connectService: "Anslut"
disconnectService: "Koppla från"
enableLocalTimeline: "Aktivera lokal tidslinje"
enableGlobalTimeline: "Aktivera global tidslinje"
enableRegistration: "Aktivera registrering av nya användare"
inMb: "I megabyte"
iconUrl: "URL till profilbilden"
bannerUrl: "URL till banner-bilden"
pinnedNotes: "Fästad not"
enableHcaptcha: "Aktivera hCaptcha"
enableRecaptcha: "Aktivera reCAPTCHA"
enableTurnstile: "Aktivera Turnstile"
antennas: "Antenner"
manageAntennas: "Hantera Antenner"
antennaSource: "Antennkälla"
antennaKeywords: "Nyckelord att lyssna efter"
antennaExcludeKeywords: "Nyckelord att exkludera"
antennaKeywordsDescription: "Separera med mellanslag för en AND kondition, eller med nya linjer för en OR kondition"
notifyAntenna: "Notifiera om nya noter"
withFileAntenna: "Endast noter med filer"
enableServiceworker: "Aktivera pushnotiser i denna webbläsaren"
antennaUsersDescription: "Ange ett användarnamn per linje"
recentlyUpdatedUsers: "Nyligen aktiva användare"
recentlyRegisteredUsers: "Nyligen registrerade användare"
userList: "Listor"
aboutMisskey: "Om Misskey"
administrator: "Administratör"
newPasswordIs: "Det nya lösenordet är \"{password}\""
share: "Dela"
enable: "Aktivera"
serviceworkerInfo: "Måste vara aktiverad för pushnotiser."
enableInfiniteScroll: "Ladda mer automatiskt"
enablePlayer: "Öppna videospelare"
enableAll: "Aktivera alla"
enableEmail: "Aktivera epost-utskick"
smtpHost: "Värd"
smtpUser: "Användarnamn"
smtpPass: "Lösenord"
clearCache: "Rensa cache"
enabled: "Aktiverad"
user: "Användare"
global: "Global"
squareAvatars: "Visa fyrkantiga profilbilder"
searchByGoogle: "Sök"
file: "Filer"
enableAutoSensitive: "Automatisk NSFW markering"
enableAutoSensitiveDescription: "Tillåter automatiskt detektering och marketing av NSFW media genom Maskininlärning när möjligt. Även om denna inställningen är avaktiverad, kan det vara aktiverat på hela instansen."
pushNotification: "Pushnotiser"
subscribePushNotification: "Aktivera pushnotiser"
unsubscribePushNotification: "Avaktivera pushnotiser"
pushNotificationAlreadySubscribed: "Pushnotiser är redan aktiverade"
pushNotificationNotSupported: "Din webbläsare eller instans har inte stöd för pushnotiser"
_email:
_follow:
title: "följde dig"
@@ -256,6 +375,9 @@ _mfm:
quote: "Citat"
emoji: "Anpassa emoji"
search: "Sök"
_channel:
setBanner: "Välj banner"
removeBanner: "Ta bort banner"
_theme:
keys:
mention: "Nämn"
@@ -264,9 +386,19 @@ _sfx:
note: "Noter"
notification: "Notifikationer"
chat: "Chatt"
antenna: "Antenner"
_antennaSources:
all: "Alla noter"
homeTimeline: "Noter från följda användare"
users: "Noter från specifika användare"
userList: "Noter från en specificerad lista av användare"
userGroup: "Noter från användare i en specificerad grupp"
_widgets:
profile: "Profil"
instanceInfo: "Instansinformation"
notifications: "Notifikationer"
timeline: "Tidslinje"
activity: "Aktivitet"
federation: "Federation"
jobQueue: "Jobbkö"
_userList:
@@ -274,18 +406,29 @@ _widgets:
_cw:
show: "Ladda mer"
_visibility:
home: "Hem"
followers: "Följare"
_profile:
username: "Användarnamn"
changeAvatar: "Ändra profilbild"
changeBanner: "Ändra banner"
_exportOrImport:
allNotes: "Alla noter"
followingList: "Följer"
muteList: "Tysta"
blockingList: "Blockera"
userLists: "Listor"
_charts:
federation: "Federation"
_timelines:
home: "Hem"
global: "Global"
_pages:
blocks:
image: "Bilder"
_notification:
youWereFollowed: "följde dig"
unreadAntennaNote: "Antenn {name}"
_types:
follow: "Följer"
mention: "Nämn"
@@ -299,5 +442,6 @@ _deck:
_columns:
notifications: "Notifikationer"
tl: "Tidslinje"
antenna: "Antenner"
list: "Listor"
mentions: "Omnämningar"

View File

@@ -8,7 +8,7 @@ search: "ค้นหา"
notifications: "การเเจ้งเตือน"
username: "ชื่อผู้ใช้"
password: "รหัสผ่าน"
forgotPassword: "ลืมรหัสผ่าน?"
forgotPassword: "ลืมรหัสผ่านใช่ไหม"
fetchingAsApObject: "กำลังดึงข้อมูล จาก เฟดิเวิร์ส..."
ok: "โอเค"
gotIt: "เข้าใจแล้ว !"
@@ -916,6 +916,14 @@ loggedInAsBot: "ล็อกอินเป็นบอตอยู่ในข
tools: "เครื่องมือ"
cannotLoad: "ไม่สามารถโหลดได้"
numberOfProfileView: "มุมมองโปรไฟล์"
like: "ชื่นชอบ"
unlike: "ไม่ชอบ"
numberOfLikes: "จำนวนไลค์"
show: "แสดงผล"
neverShow: "ไม่ต้องแสดงข้อความนี้อีก"
remindMeLater: "ไว้ครั้งหน้าแล้วกัน"
didYouLikeMisskey: "คุณเคยชอบ Misskey ไหม?"
pleaseDonate: "{host} ใช้ซอฟต์แวร์ฟรี Misskey เราขอขอบคุณการบริจาคของคุณอย่างสูงเพื่อให้การพัฒนา Misskey สามารถดำเนินต่อไปได้นะ!"
_sensitiveMediaDetection:
description: "ลดความพยายามในการดูแลเซิร์ฟเวอร์ผ่านการจดจำสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่อง การทำสิ่งนี้อาจจะเพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย"
sensitivity: "การตรวจจับความไว"
@@ -1294,6 +1302,8 @@ _weekday:
friday: "วันศุกร์"
saturday: "วันเสาร์"
_widgets:
profile: "โปรไฟล์"
instanceInfo: "ข้อมูล อินสแตนซ์"
memo: "โน้ตแปะ"
notifications: "การเเจ้งเตือน"
timeline: "ไทม์ไลน์"
@@ -1315,10 +1325,12 @@ _widgets:
jobQueue: "คิวงาน"
serverMetric: "ตัวชี้วัดเซิร์ฟเวอร์"
aiscript: "AiScript คอนโซล"
aiscriptApp: "AiScript แอพ"
aichan: "เอไอ"
userList: "รายชื่อผู้ใช้"
_userList:
chooseList: "เลือกรายการ"
clicker: "คลิกเกอร์"
_cw:
hide: "ซ่อน"
show: "โหลดเพิ่มเติม"
@@ -1420,6 +1432,21 @@ _timelines:
local: "ในพื้นที่"
social: "โซเชี่ยล"
global: "ทั่วโลก"
_play:
new: "สร้างการเล่น"
edit: "แก้ไขเล่น"
created: "สร้างการเล่นแล้ว"
updated: "แก้ไขการเล่นแล้ว"
deleted: "ลบการเล่นแล้ว"
pageSetting: "ตั้งค่าการเล่น"
editThisPage: "แก้ไข Play นี้"
viewSource: "ดูต้นฉบับ"
my: "มาย เพลย์"
liked: "ไลค์ เพลย์"
featured: "เป็นที่นิยม"
title: "หัวข้อ"
script: "สคริปต์"
summary: "รายละเอียด"
_pages:
newPage: "สร้างหน้าเพจใหม่"
editPage: "แก้ไขหน้าเพจ"
@@ -1479,7 +1506,6 @@ _notification:
youGotReply: "{name} ตอบกลับถึงคุณ"
youGotQuote: "{name} อ้างถึงคุณ"
youRenoted: "รีโน้ตจาก {name}"
youGotPoll: "{name} โหวตบนแบบสำรวจความคิดเห็นของคุณ"
youGotMessagingMessageFromUser: "{name} ได้ส่งข้อความแชทถึงคุณ"
youGotMessagingMessageFromGroup: "ข้อความแชทถูกส่งไปยัง {name} กลุ่ม"
youWereFollowed: "ได้ติดตามคุณ"
@@ -1497,7 +1523,6 @@ _notification:
renote: "รีโน้ต"
quote: "อ้างคำพูด"
reaction: "รีแอคชั่น"
pollVote: "จำนวนโหวตที่ได้รับ"
pollEnded: "โพลนี้สิ้นสุดลงแล้ว"
receiveFollowRequest: "ได้รับคำขอติดตาม\n"
followRequestAccepted: "ยอมรับคำขอติดตาม"

View File

@@ -53,6 +53,7 @@ _mfm:
_sfx:
notification: "Bildirim"
_widgets:
profile: "Profil"
notifications: "Bildirim"
timeline: "Zaman çizelgesi"
_profile:

View File

@@ -892,6 +892,8 @@ unsubscribePushNotification: "Вимкнути push-сповіщення"
windowMaximize: "Розгорнути"
windowRestore: "Відновити"
caption: "Підпис"
like: "Вподобати"
show: "Відображення"
_sensitiveMediaDetection:
sensitivity: "Чутливість детектування"
setSensitiveFlagAutomatically: "Позначити як NSFW"
@@ -1227,6 +1229,8 @@ _weekday:
friday: "П'ятниця"
saturday: "Субота"
_widgets:
profile: "Профіль"
instanceInfo: "Про цей інстанс"
memo: "Нагадування"
notifications: "Сповіщення"
timeline: "Стрічка"
@@ -1348,6 +1352,12 @@ _timelines:
local: "Локальна"
social: "Соціальна"
global: "Глобальна"
_play:
viewSource: "Переглянути вихідний код"
featured: "Популярні"
title: "Заголовок"
script: "Скрипт"
summary: "Опис"
_pages:
newPage: "Створити сторінку"
editPage: "Редагувати сторінку"
@@ -1407,7 +1417,6 @@ _notification:
youGotReply: "{name} відповідає"
youGotQuote: "{name} цитує вас"
youRenoted: "{name} поширює"
youGotPoll: "{name} бере участь в опитуванні"
youGotMessagingMessageFromUser: "Повідомлення від {name}"
youGotMessagingMessageFromGroup: "Нове повідомлення в групі {name}"
youWereFollowed: "Новий підписник"
@@ -1422,7 +1431,6 @@ _notification:
renote: "Поширення"
quote: "Цитування"
reaction: "Реакції"
pollVote: "Опитування"
receiveFollowRequest: "Запити на підписку"
followRequestAccepted: "Прийняті підписки"
groupInvited: "Запрошення до груп"

View File

@@ -894,6 +894,8 @@ navbar: "Thanh điều hướng"
shuffle: "Xáo trộn"
account: "Tài khoản của bạn"
move: "Di chuyển"
like: "Thích"
show: "Hiển thị"
_sensitiveMediaDetection:
description: "Giảm nỗ lực kiểm duyệt máy chủ thông qua việc tự động nhận dạng media NSFW thông qua học máy. Điều này sẽ làm tăng một chút áp lực trên máy chủ."
sensitivity: "Phát hiện nhạy cảm"
@@ -1269,6 +1271,8 @@ _weekday:
friday: "Thứ Sáu"
saturday: "Thứ Bảy"
_widgets:
profile: "Trang cá nhân"
instanceInfo: "Thông tin máy chủ"
memo: "Tút đã ghim"
notifications: "Thông báo"
timeline: "Bảng tin"
@@ -1393,6 +1397,12 @@ _timelines:
local: "Máy chủ này"
social: "Xã hội"
global: "Liên hợp"
_play:
viewSource: "Xem mã nguồn"
featured: "Nổi tiếng"
title: "Tựa đề"
script: "Kịch bản"
summary: "Mô tả"
_pages:
newPage: "Tạo Trang mới"
editPage: "Sửa Trang này"
@@ -1452,7 +1462,6 @@ _notification:
youGotReply: "{name} trả lời bạn"
youGotQuote: "{name} trích dẫn tút của bạn"
youRenoted: "{name} đăng lại tút của bạn"
youGotPoll: "{name} bình chọn tút của bạn"
youGotMessagingMessageFromUser: "{name} nhắn tin cho bạn"
youGotMessagingMessageFromGroup: "Một tin nhắn trong nhóm {name}"
youWereFollowed: "đã theo dõi bạn"
@@ -1469,7 +1478,6 @@ _notification:
renote: "Đăng lại"
quote: "Trích dẫn"
reaction: "Biểu cảm"
pollVote: "Lượt bình chọn"
pollEnded: "Bình chọn kết thúc"
receiveFollowRequest: "Yêu cầu theo dõi"
followRequestAccepted: "Yêu cầu theo dõi được chấp nhận"

View File

@@ -916,6 +916,14 @@ loggedInAsBot: "以Bot账户登录"
tools: "工具"
cannotLoad: "无法加载"
numberOfProfileView: "个人资料展示次数"
like: "点赞!"
unlike: "取消赞"
numberOfLikes: "点赞数"
show: "显示"
neverShow: "不再显示"
remindMeLater: "稍后提醒我"
didYouLikeMisskey: "您喜欢Misskey吗"
pleaseDonate: "Misskey是{host}所使用的免费软件。为了今后也能够维持Misskey的开发请在有余力的情况下进行捐助"
_sensitiveMediaDetection:
description: "可以使用机器学习技术自动检测敏感媒体,以便进行审核。服务器负载将略微增加。"
sensitivity: "检测敏感度"
@@ -1049,8 +1057,8 @@ _mfm:
shakeDescription: "显示摇晃的动画效果。"
twitch: "动画(颤抖)"
twitchDescription: "显示强烈颤抖的动画效果。"
spin: "动画(转)"
spinDescription: "显示转的动画效果。"
spin: "动画(转)"
spinDescription: "显示转的动画效果。"
x2: "大"
x2Description: "以大尺寸显示内容。"
x3: "非常大"
@@ -1294,6 +1302,8 @@ _weekday:
friday: "星期五"
saturday: "星期六"
_widgets:
profile: "个人资料"
instanceInfo: "实例信息"
memo: "便签"
notifications: "通知"
timeline: "时间线"
@@ -1315,10 +1325,12 @@ _widgets:
jobQueue: "作业队列"
serverMetric: "服务器指标"
aiscript: "AiScript控制台"
aiscriptApp: "AiScript App"
aichan: "小蓝"
userList: "用户列表"
_userList:
chooseList: "选择列表"
clicker: "点击器"
_cw:
hide: "隐藏"
show: "查看更多"
@@ -1420,6 +1432,21 @@ _timelines:
local: "本地"
social: "社交"
global: "全局"
_play:
new: "创建Play"
edit: "编辑Play"
created: "创建了一个Play"
updated: "更新了Play"
deleted: "删除了Play"
pageSetting: "Play设置"
editThisPage: "编辑此Play"
viewSource: "查看源代码"
my: "我的Play"
liked: "点赞的Play"
featured: "热门"
title: "标题"
script: "脚本"
summary: "描述"
_pages:
newPage: "创建页面"
editPage: "编辑页面"
@@ -1479,7 +1506,6 @@ _notification:
youGotReply: "来自{name}的回复"
youGotQuote: "来自{name}的引用"
youRenoted: "来自{name}的转发"
youGotPoll: "来自{name}的投票"
youGotMessagingMessageFromUser: "来自{name}的聊天"
youGotMessagingMessageFromGroup: "来自{name}的群聊"
youWereFollowed: "关注了你。"
@@ -1497,7 +1523,6 @@ _notification:
renote: "转发"
quote: "引用"
reaction: "回应"
pollVote: "问卷调查被投票"
pollEnded: "问卷调查结束"
receiveFollowRequest: "收到关注请求"
followRequestAccepted: "关注请求已通过"

View File

@@ -252,7 +252,7 @@ uploadFromUrlMayTakeTime: "還需要一些時間才能完成上傳。"
explore: "探索"
messageRead: "已讀"
noMoreHistory: "沒有更多歷史紀錄"
startMessaging: "開始傳送訊息"
startMessaging: "開始聊天"
nUsersRead: "{n}人已讀"
agreeTo: "我同意{0}"
tos: "使用條款"
@@ -797,7 +797,7 @@ squareAvatars: "頭像以方形顯示"
sent: "發送"
received: "收取"
searchResult: "搜尋結果"
hashtags: "#tag"
hashtags: "標籤"
troubleshooting: "故障排除"
useBlurEffect: "在 UI 上使用模糊效果"
learnMore: "更多資訊"
@@ -916,6 +916,14 @@ loggedInAsBot: "以機器人帳號登入中"
tools: "工具"
cannotLoad: "無法載入"
numberOfProfileView: "個人檔案檢視次數"
like: "讚"
unlike: "收回讚"
numberOfLikes: "讚數"
show: "檢視"
neverShow: "不再顯示"
remindMeLater: "以後再說"
didYouLikeMisskey: "您是否喜愛Misskey呢"
pleaseDonate: "Misskey是由{host}使用的免費軟體。請贊助我們,讓開發能夠持續!"
_sensitiveMediaDetection:
description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。"
sensitivity: "檢測敏感度"
@@ -1151,7 +1159,7 @@ _theme:
navActive: "側邊欄文本 (活動)"
navIndicator: "側邊欄指示符"
link: "鏈接"
hashtag: "#tag"
hashtag: "標籤"
mention: "提到"
mentionMe: "提到了我"
renote: "轉發貼文"
@@ -1184,7 +1192,7 @@ _sfx:
note: "貼文"
noteMy: "我的貼文"
notification: "通知"
chat: "傳送訊息"
chat: "聊天"
chatBg: "聊天背景"
antenna: "天線接收"
channel: "頻道通知"
@@ -1294,6 +1302,8 @@ _weekday:
friday: "週五"
saturday: "週六"
_widgets:
profile: "個人檔案"
instanceInfo: "實例資訊"
memo: "備忘錄"
notifications: "通知"
timeline: "時間軸"
@@ -1315,10 +1325,12 @@ _widgets:
jobQueue: "佇列"
serverMetric: "服務器指標 "
aiscript: "AiScript控制台"
aiscriptApp: "AiScript App"
aichan: "小藍"
userList: "使用者列表"
_userList:
chooseList: "選擇清單"
clicker: "點擊器"
_cw:
hide: "隱藏"
show: "瀏覽更多"
@@ -1420,6 +1432,21 @@ _timelines:
local: "本地"
social: "社群"
global: "公開"
_play:
new: "新增Play"
edit: "編輯Play"
created: "已新增Play"
updated: "已更新Play"
deleted: "已刪除Play"
pageSetting: "Play設定"
editThisPage: "編輯這個Play"
viewSource: "檢視原始碼"
my: "自己的Play"
liked: "按了讚的Play"
featured: "人氣"
title: "標題"
script: "腳本"
summary: "描述"
_pages:
newPage: "建立頁面"
editPage: "編輯頁面"
@@ -1479,7 +1506,6 @@ _notification:
youGotReply: "{name}回覆了您"
youGotQuote: "{name}引用了您"
youRenoted: "{name} 轉發了你的貼文"
youGotPoll: "{name}已投票"
youGotMessagingMessageFromUser: "{name}發送給您的訊息"
youGotMessagingMessageFromGroup: "{name}發送給您的訊息"
youWereFollowed: "您有新的追隨者"
@@ -1497,7 +1523,6 @@ _notification:
renote: "轉發貼文"
quote: "引用"
reaction: "反應"
pollVote: "統計已投票數"
pollEnded: "問卷調查結束"
receiveFollowRequest: "已收到追隨請求"
followRequestAccepted: "追隨請求已接受"

View File

@@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "13.0.0-beta.24",
"version": "13.0.0-beta.41",
"codename": "indigo",
"repository": {
"type": "git",
@@ -53,10 +53,10 @@
"devDependencies": {
"@types/gulp": "4.0.10",
"@types/gulp-rename": "2.0.1",
"@typescript-eslint/eslint-plugin": "5.47.1",
"@typescript-eslint/parser": "5.47.1",
"@typescript-eslint/eslint-plugin": "5.48.0",
"@typescript-eslint/parser": "5.48.0",
"cross-env": "7.0.3",
"cypress": "12.2.0",
"cypress": "12.3.0",
"eslint": "^8.31.0",
"start-server-and-test": "1.15.2",
"typescript": "4.9.4"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -1,5 +0,0 @@
Font Awesome Icons
-------------------------
Ⓒ Font Awesome
CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 844 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 507 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 991 B

View File

@@ -0,0 +1,24 @@
Tabler Icons
https://github.com/tabler/tabler-icons/blob/master/LICENSE
====
MIT License
Copyright (c) 2020-2022 Paweł Kuna
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 174 B

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1011 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,11 @@
export class PollChoiceLength1673336077243 {
name = 'PollChoiceLength1673336077243'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "poll" ALTER COLUMN "choices" TYPE character varying(256) array`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "poll" ALTER COLUMN "choices" TYPE character varying(128) array`);
}
}

View File

@@ -0,0 +1,37 @@
export class Role1673500412259 {
name = 'Role1673500412259'
async up(queryRunner) {
await queryRunner.query(`CREATE TABLE "role" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "name" character varying(256) NOT NULL, "description" character varying(1024) NOT NULL, "isPublic" boolean NOT NULL DEFAULT false, "isModerator" boolean NOT NULL DEFAULT false, "isAdministrator" boolean NOT NULL DEFAULT false, "options" jsonb NOT NULL DEFAULT '{}', CONSTRAINT "PK_b36bcfe02fc8de3c57a8b2391c2" PRIMARY KEY ("id")); COMMENT ON COLUMN "role"."createdAt" IS 'The created date of the Role.'; COMMENT ON COLUMN "role"."updatedAt" IS 'The updated date of the Role.'`);
await queryRunner.query(`CREATE TABLE "role_assignment" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "roleId" character varying(32) NOT NULL, CONSTRAINT "PK_7e79671a8a5db18936173148cb4" PRIMARY KEY ("id")); COMMENT ON COLUMN "role_assignment"."createdAt" IS 'The created date of the RoleAssignment.'; COMMENT ON COLUMN "role_assignment"."userId" IS 'The user ID.'; COMMENT ON COLUMN "role_assignment"."roleId" IS 'The role ID.'`);
await queryRunner.query(`CREATE INDEX "IDX_db5b72c16227c97ca88734d5c2" ON "role_assignment" ("userId") `);
await queryRunner.query(`CREATE INDEX "IDX_f0de67fd09cd3cd0aabca79994" ON "role_assignment" ("roleId") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0953deda7ce6e1448e935859e5" ON "role_assignment" ("userId", "roleId") `);
await queryRunner.query(`ALTER TABLE "user" RENAME COLUMN "isAdmin" TO "isRoot"`);
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isModerator"`);
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "driveCapacityOverrideMb"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "disableLocalTimeline"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "disableGlobalTimeline"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "localDriveCapacityMb"`);
await queryRunner.query(`ALTER TABLE "meta" ADD "defaultRoleOverride" jsonb NOT NULL DEFAULT '{}'`);
await queryRunner.query(`ALTER TABLE "role_assignment" ADD CONSTRAINT "FK_db5b72c16227c97ca88734d5c2b" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "role_assignment" ADD CONSTRAINT "FK_f0de67fd09cd3cd0aabca79994d" FOREIGN KEY ("roleId") REFERENCES "role"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "role_assignment" DROP CONSTRAINT "FK_f0de67fd09cd3cd0aabca79994d"`);
await queryRunner.query(`ALTER TABLE "role_assignment" DROP CONSTRAINT "FK_db5b72c16227c97ca88734d5c2b"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "defaultRoleOverride"`);
await queryRunner.query(`ALTER TABLE "meta" ADD "localDriveCapacityMb" integer NOT NULL DEFAULT '1024'`);
await queryRunner.query(`ALTER TABLE "meta" ADD "disableGlobalTimeline" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "meta" ADD "disableLocalTimeline" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "user" ADD "driveCapacityOverrideMb" integer`);
await queryRunner.query(`ALTER TABLE "user" ADD "isModerator" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "user" RENAME COLUMN "isRoot" TO "isAdmin"`);
await queryRunner.query(`DROP INDEX "public"."IDX_0953deda7ce6e1448e935859e5"`);
await queryRunner.query(`DROP INDEX "public"."IDX_f0de67fd09cd3cd0aabca79994"`);
await queryRunner.query(`DROP INDEX "public"."IDX_db5b72c16227c97ca88734d5c2"`);
await queryRunner.query(`DROP TABLE "role_assignment"`);
await queryRunner.query(`DROP TABLE "role"`);
}
}

View File

@@ -0,0 +1,11 @@
export class RoleColor1673515526953 {
name = 'RoleColor1673515526953'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "role" ADD "color" character varying(256)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "color"`);
}
}

View File

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

View File

@@ -0,0 +1,13 @@
export class RoleLastUsedAt1673524604156 {
name = 'RoleLastUsedAt1673524604156'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "role" ADD "lastUsedAt" TIMESTAMP WITH TIME ZONE NOT NULL`);
await queryRunner.query(`COMMENT ON COLUMN "role"."lastUsedAt" IS 'The last used date of the Role.'`);
}
async down(queryRunner) {
await queryRunner.query(`COMMENT ON COLUMN "role"."lastUsedAt" IS 'The last used date of the Role.'`);
await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "lastUsedAt"`);
}
}

View File

@@ -0,0 +1,15 @@
export class RoleConditional1673570377815 {
name = 'RoleConditional1673570377815'
async up(queryRunner) {
await queryRunner.query(`CREATE TYPE "public"."role_target_enum" AS ENUM('manual', 'conditional')`);
await queryRunner.query(`ALTER TABLE "role" ADD "target" "public"."role_target_enum" NOT NULL DEFAULT 'manual'`);
await queryRunner.query(`ALTER TABLE "role" ADD "condFormula" jsonb NOT NULL DEFAULT '{}'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "condFormula"`);
await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "target"`);
await queryRunner.query(`DROP TYPE "public"."role_target_enum"`);
}
}

View File

@@ -0,0 +1,11 @@
export class MetaClean1673575973645 {
name = 'MetaClean1673575973645'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteDriveCapacityMb"`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteDriveCapacityMb" integer NOT NULL DEFAULT '32'`);
}
}

View File

@@ -21,9 +21,9 @@
"@tensorflow/tfjs-node": "4.1.0"
},
"dependencies": {
"@bull-board/api": "^4.10.0",
"@bull-board/fastify": "^4.10.0",
"@bull-board/ui": "^4.10.0",
"@bull-board/api": "^4.10.1",
"@bull-board/fastify": "^4.10.1",
"@bull-board/ui": "^4.10.1",
"@discordapp/twemoji": "14.0.2",
"@fastify/accepts": "4.1.0",
"@fastify/cookie": "^8.3.0",
@@ -38,10 +38,10 @@
"@peertube/http-signature": "1.7.0",
"@sinonjs/fake-timers": "10.0.2",
"accepts": "^1.3.8",
"ajv": "8.11.2",
"ajv": "8.12.0",
"archiver": "5.3.1",
"autwh": "0.1.0",
"aws-sdk": "2.1286.0",
"aws-sdk": "2.1289.0",
"bcryptjs": "2.4.3",
"blurhash": "2.0.4",
"bull": "4.10.2",
@@ -72,12 +72,11 @@
"json5-loader": "4.0.1",
"jsonld": "8.1.0",
"jsrsasign": "10.6.1",
"mfm-js": "0.23.0",
"mfm-js": "0.23.1",
"mime-types": "2.1.35",
"misskey-js": "0.0.14",
"ms": "3.0.0-canary.1",
"nested-property": "4.0.0",
"node-fetch": "3.3.0",
"nodemailer": "6.8.0",
"nsfwjs": "2.4.2",
"oauth": "^0.10.0",
@@ -110,14 +109,15 @@
"stringz": "2.1.0",
"summaly": "2.7.0",
"syslog-pro": "git+https://github.com/misskey-dev/SyslogPro#0.2.9-misskey.2",
"systeminformation": "5.16.9",
"tinycolor2": "1.5.1",
"systeminformation": "5.17.1",
"tinycolor2": "1.5.2",
"tmp": "0.2.1",
"tsc-alias": "1.8.2",
"tsconfig-paths": "4.1.2",
"twemoji-parser": "14.0.0",
"typeorm": "0.3.11",
"ulid": "2.3.0",
"undici": "^5.14.0",
"unzipper": "0.10.11",
"uuid": "9.0.0",
"vary": "1.1.2",
@@ -128,7 +128,7 @@
},
"devDependencies": {
"@redocly/openapi-core": "1.0.0-beta.117",
"@swc/core": "1.3.24",
"@swc/core": "1.3.25",
"@swc/jest": "0.2.24",
"@types/accepts": "1.3.5",
"@types/archiver": "5.3.1",
@@ -172,14 +172,15 @@
"@types/web-push": "3.3.2",
"@types/websocket": "1.0.5",
"@types/ws": "8.5.4",
"@typescript-eslint/eslint-plugin": "5.47.1",
"@typescript-eslint/parser": "5.47.1",
"@typescript-eslint/eslint-plugin": "5.48.0",
"@typescript-eslint/parser": "5.48.0",
"cross-env": "7.0.3",
"eslint": "8.31.0",
"eslint-plugin-import": "2.26.0",
"execa": "6.1.0",
"jest": "29.3.1",
"jest-mock": "^29.3.1",
"node-fetch": "3.3.0",
"typescript": "4.9.4"
}
}

View File

@@ -15,8 +15,8 @@ import type { Packed } from '@/misc/schema.js';
import { DI } from '@/di-symbols.js';
import type { MutingsRepository, BlockingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js';
import { UtilityService } from '@/core/UtilityService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
import { bindThis } from '@/decorators.js';
import type { OnApplicationShutdown } from '@nestjs/common';
@Injectable()
export class AntennaService implements OnApplicationShutdown {
@@ -135,7 +135,7 @@ export class AntennaService implements OnApplicationShutdown {
this.globalEventServie.publishMainStream(antenna.userId, 'unreadAntenna', antenna);
this.pushNotificationService.pushNotification(antenna.userId, 'unreadAntennaNote', {
antenna: { id: antenna.id, name: antenna.name },
note: await this.noteEntityService.pack(note)
note: await this.noteEntityService.pack(note),
});
}
}, 2000);
@@ -144,27 +144,19 @@ export class AntennaService implements OnApplicationShutdown {
// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている
/**
* noteUserFollowers / antennaUserFollowing はどちらか一方が指定されていればよい
*/
@bindThis
public async checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { id: User['id']; username: string; host: string | null; }, noteUserFollowers?: User['id'][], antennaUserFollowing?: User['id'][]): Promise<boolean> {
public async checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { id: User['id']; username: string; host: string | null; }): Promise<boolean> {
if (note.visibility === 'specified') return false;
if (note.visibility === 'followers') return false;
// アンテナ作成者がノート作成者にブロックされていたらスキップ
const blockings = await this.blockingCache.fetch(noteUser.id, () => this.blockingsRepository.findBy({ blockerId: noteUser.id }).then(res => res.map(x => x.blockeeId)));
if (blockings.some(blocking => blocking === antenna.userId)) return false;
if (note.visibility === 'followers') {
if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false;
if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false;
}
if (!antenna.withReplies && note.replyId != null) return false;
if (antenna.src === 'home') {
if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false;
if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false;
// TODO
} else if (antenna.src === 'list') {
const listUsers = (await this.userListJoiningsRepository.findBy({
userListId: antenna.userListId!,

View File

@@ -1,7 +1,4 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js';
import { Injectable } from '@nestjs/common';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { bindThis } from '@/decorators.js';
@@ -13,9 +10,6 @@ type CaptchaResponse = {
@Injectable()
export class CaptchaService {
constructor(
@Inject(DI.config)
private config: Config,
private httpRequestService: HttpRequestService,
) {
}
@@ -27,16 +21,16 @@ export class CaptchaService {
response,
});
const res = await fetch(url, {
method: 'POST',
body: params,
headers: {
'User-Agent': this.config.userAgent,
const res = await this.httpRequestService.fetch(
url,
{
method: 'POST',
body: params,
},
// TODO
//timeout: 10 * 1000,
agent: (url, bypassProxy) => this.httpRequestService.getAgentByUrl(url, bypassProxy),
}).catch(err => {
{
noOkError: true,
}
).catch(err => {
throw `${err.message ?? err}`;
});

View File

@@ -35,6 +35,7 @@ import { PushNotificationService } from './PushNotificationService.js';
import { QueryService } from './QueryService.js';
import { ReactionService } from './ReactionService.js';
import { RelayService } from './RelayService.js';
import { RoleService } from './RoleService.js';
import { S3Service } from './S3Service.js';
import { SignupService } from './SignupService.js';
import { TwoFactorAuthenticationService } from './TwoFactorAuthenticationService.js';
@@ -97,6 +98,7 @@ import { UserGroupInvitationEntityService } from './entities/UserGroupInvitation
import { UserListEntityService } from './entities/UserListEntityService.js';
import { FlashEntityService } from './entities/FlashEntityService.js';
import { FlashLikeEntityService } from './entities/FlashLikeEntityService.js';
import { RoleEntityService } from './entities/RoleEntityService.js';
import { ApAudienceService } from './activitypub/ApAudienceService.js';
import { ApDbResolverService } from './activitypub/ApDbResolverService.js';
import { ApDeliverManagerService } from './activitypub/ApDeliverManagerService.js';
@@ -158,6 +160,7 @@ const $PushNotificationService: Provider = { provide: 'PushNotificationService',
const $QueryService: Provider = { provide: 'QueryService', useExisting: QueryService };
const $ReactionService: Provider = { provide: 'ReactionService', useExisting: ReactionService };
const $RelayService: Provider = { provide: 'RelayService', useExisting: RelayService };
const $RoleService: Provider = { provide: 'RoleService', useExisting: RoleService };
const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service };
const $SignupService: Provider = { provide: 'SignupService', useExisting: SignupService };
const $TwoFactorAuthenticationService: Provider = { provide: 'TwoFactorAuthenticationService', useExisting: TwoFactorAuthenticationService };
@@ -220,6 +223,7 @@ const $UserGroupInvitationEntityService: Provider = { provide: 'UserGroupInvitat
const $UserListEntityService: Provider = { provide: 'UserListEntityService', useExisting: UserListEntityService };
const $FlashEntityService: Provider = { provide: 'FlashEntityService', useExisting: FlashEntityService };
const $FlashLikeEntityService: Provider = { provide: 'FlashLikeEntityService', useExisting: FlashLikeEntityService };
const $RoleEntityService: Provider = { provide: 'RoleEntityService', useExisting: RoleEntityService };
const $ApAudienceService: Provider = { provide: 'ApAudienceService', useExisting: ApAudienceService };
const $ApDbResolverService: Provider = { provide: 'ApDbResolverService', useExisting: ApDbResolverService };
@@ -283,6 +287,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
QueryService,
ReactionService,
RelayService,
RoleService,
S3Service,
SignupService,
TwoFactorAuthenticationService,
@@ -344,6 +349,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
UserListEntityService,
FlashEntityService,
FlashLikeEntityService,
RoleEntityService,
ApAudienceService,
ApDbResolverService,
ApDeliverManagerService,
@@ -402,6 +408,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$QueryService,
$ReactionService,
$RelayService,
$RoleService,
$S3Service,
$SignupService,
$TwoFactorAuthenticationService,
@@ -463,6 +470,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$UserListEntityService,
$FlashEntityService,
$FlashLikeEntityService,
$RoleEntityService,
$ApAudienceService,
$ApDbResolverService,
$ApDeliverManagerService,
@@ -522,6 +530,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
QueryService,
ReactionService,
RelayService,
RoleService,
S3Service,
SignupService,
TwoFactorAuthenticationService,
@@ -582,6 +591,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
UserListEntityService,
FlashEntityService,
FlashLikeEntityService,
RoleEntityService,
ApAudienceService,
ApDbResolverService,
ApDeliverManagerService,
@@ -640,6 +650,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$QueryService,
$ReactionService,
$RelayService,
$RoleService,
$S3Service,
$SignupService,
$TwoFactorAuthenticationService,
@@ -700,6 +711,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$UserListEntityService,
$FlashEntityService,
$FlashLikeEntityService,
$RoleEntityService,
$ApAudienceService,
$ApDbResolverService,
$ApDeliverManagerService,

View File

@@ -53,7 +53,7 @@ export class CreateSystemUserService {
usernameLower: username.toLowerCase(),
host: null,
token: secret,
isAdmin: false,
isRoot: false,
isLocked: true,
isExplorable: false,
isBot: true,

View File

@@ -23,6 +23,9 @@ export class DeleteAccountService {
id: string;
host: string | null;
}): Promise<void> {
const _user = await this.usersRepository.findOneByOrFail({ id: user.id });
if (_user.isRoot) throw new Error('cannot delete a root account');
// 物理削除する前にDelete activityを送信する
await this.userSuspendService.doPostSuspend(user).catch(e => {});

View File

@@ -8,11 +8,12 @@ import got, * as Got from 'got';
import chalk from 'chalk';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { HttpRequestService, UndiciFetcher } from '@/core/HttpRequestService.js';
import { createTemp } from '@/misc/create-temp.js';
import { StatusError } from '@/misc/status-error.js';
import { LoggerService } from '@/core/LoggerService.js';
import type Logger from '@/logger.js';
import { buildConnector } from 'undici';
const pipeline = util.promisify(stream.pipeline);
import { bindThis } from '@/decorators.js';
@@ -20,6 +21,7 @@ import { bindThis } from '@/decorators.js';
@Injectable()
export class DownloadService {
private logger: Logger;
private undiciFetcher: UndiciFetcher;
constructor(
@Inject(DI.config)
@@ -29,70 +31,42 @@ export class DownloadService {
private loggerService: LoggerService,
) {
this.logger = this.loggerService.getLogger('download');
this.undiciFetcher = new UndiciFetcher(this.httpRequestService.getStandardUndiciFetcherOption(
{
connect: process.env.NODE_ENV === 'development' ?
this.httpRequestService.clientDefaults.connect
:
this.httpRequestService.getConnectorWithIpCheck(
buildConnector({
...this.httpRequestService.clientDefaults.connect,
}),
(ip) => !this.isPrivateIp(ip)
),
bodyTimeout: 30 * 1000,
},
{
connect: this.httpRequestService.clientDefaults.connect,
}
), this.logger);
}
@bindThis
public async downloadUrl(url: string, path: string): Promise<void> {
this.logger.info(`Downloading ${chalk.cyan(url)} to ${chalk.cyanBright(path)} ...`);
const timeout = 30 * 1000;
const operationTimeout = 60 * 1000;
const maxSize = this.config.maxFileSize ?? 262144000;
const req = got.stream(url, {
headers: {
'User-Agent': this.config.userAgent,
},
timeout: {
lookup: timeout,
connect: timeout,
secureConnect: timeout,
socket: timeout, // read timeout
response: timeout,
send: timeout,
request: operationTimeout, // whole operation timeout
},
agent: {
http: this.httpRequestService.httpAgent,
https: this.httpRequestService.httpsAgent,
},
http2: false, // default
retry: {
limit: 0,
},
}).on('response', (res: Got.Response) => {
if ((process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') && !this.config.proxy && res.ip) {
if (this.isPrivateIp(res.ip)) {
this.logger.warn(`Blocked address: ${res.ip}`);
req.destroy();
}
}
const contentLength = res.headers['content-length'];
if (contentLength != null) {
const size = Number(contentLength);
if (size > maxSize) {
this.logger.warn(`maxSize exceeded (${size} > ${maxSize}) on response`);
req.destroy();
}
}
}).on('downloadProgress', (progress: Got.Progress) => {
if (progress.transferred > maxSize) {
this.logger.warn(`maxSize exceeded (${progress.transferred} > ${maxSize}) on downloadProgress`);
req.destroy();
}
});
try {
await pipeline(req, fs.createWriteStream(path));
} catch (e) {
if (e instanceof Got.HTTPError) {
throw new StatusError(`${e.response.statusCode} ${e.response.statusMessage}`, e.response.statusCode, e.response.statusMessage);
} else {
throw e;
}
const response = await this.undiciFetcher.fetch(url);
if (response.body === null) {
throw new StatusError('No body', 400, 'No body');
}
await pipeline(stream.Readable.fromWeb(response.body), fs.createWriteStream(path));
this.logger.succ(`Download finished: ${chalk.cyan(url)}`);
}
@@ -114,7 +88,7 @@ export class DownloadService {
cleanup();
}
}
@bindThis
private isPrivateIp(ip: string): boolean {
for (const net of this.config.allowedPrivateNetworks ?? []) {
@@ -124,6 +98,6 @@ export class DownloadService {
}
}
return PrivateIp(ip);
return PrivateIp(ip) ?? false;
}
}

View File

@@ -32,11 +32,12 @@ import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.j
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { FileInfoService } from '@/core/FileInfoService.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import type S3 from 'aws-sdk/clients/s3.js';
type AddFileArgs = {
/** User who wish to add file */
user: { id: User['id']; host: User['host']; driveCapacityOverrideMb: User['driveCapacityOverrideMb'] } | null;
user: { id: User['id']; host: User['host'] } | null;
/** File path */
path: string;
/** Name */
@@ -62,7 +63,7 @@ type AddFileArgs = {
type UploadFromUrlArgs = {
url: string;
user: { id: User['id']; host: User['host']; driveCapacityOverrideMb: User['driveCapacityOverrideMb'] } | null;
user: { id: User['id']; host: User['host'] } | null;
folderId?: DriveFolder['id'] | null;
uri?: string | null;
sensitive?: boolean;
@@ -106,6 +107,7 @@ export class DriveService {
private videoProcessingService: VideoProcessingService,
private globalEventService: GlobalEventService,
private queueService: QueueService,
private roleService: RoleService,
private driveChart: DriveChart,
private perUserDriveChart: PerUserDriveChart,
private instanceChart: InstanceChart,
@@ -373,8 +375,19 @@ export class DriveService {
partSize: s3.endpoint.hostname === 'storage.googleapis.com' ? 500 * 1024 * 1024 : 8 * 1024 * 1024,
});
const result = await upload.promise();
if (result) this.registerLogger.debug(`Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`);
await upload.promise()
.then(
result => {
if (result) {
this.registerLogger.debug(`Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`);
} else {
this.registerLogger.error(`Upload Result Empty: key = ${key}, filename = ${filename}`);
}
},
err => {
this.registerLogger.error(`Upload Failed: key = ${key}, filename = ${filename}`, err);
},
);
}
@bindThis
@@ -460,19 +473,16 @@ export class DriveService {
}
}
this.registerLogger.debug(`ADD DRIVE FILE: user ${user?.id ?? 'not set'}, name ${detectedName}, tmp ${path}`);
//#region Check drive usage
if (user && !isLink) {
const usage = await this.driveFileEntityService.calcDriveUsageOf(user);
const u = await this.usersRepository.findOneBy({ id: user.id });
const instance = await this.metaService.fetch();
let driveCapacity = 1024 * 1024 * (this.userEntityService.isLocalUser(user) ? instance.localDriveCapacityMb : instance.remoteDriveCapacityMb);
if (this.userEntityService.isLocalUser(user) && u?.driveCapacityOverrideMb != null) {
driveCapacity = 1024 * 1024 * u.driveCapacityOverrideMb;
this.registerLogger.debug('drive capacity override applied');
this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`);
}
const role = await this.roleService.getUserRoleOptions(user.id);
const driveCapacity = 1024 * 1024 * role.driveCapacityMb;
this.registerLogger.debug('drive capacity override applied');
this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`);
this.registerLogger.debug(`drive usage is ${usage} (max: ${driveCapacity})`);

View File

@@ -1,7 +1,6 @@
import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
import { JSDOM } from 'jsdom';
import fetch from 'node-fetch';
import tinycolor from 'tinycolor2';
import type { Instance } from '@/models/entities/Instance.js';
import type { InstancesRepository } from '@/models/index.js';
@@ -191,11 +190,7 @@ export class FetchInstanceMetadataService {
const faviconUrl = url + '/favicon.ico';
const favicon = await fetch(faviconUrl, {
// TODO
//timeout: 10000,
agent: url => this.httpRequestService.getAgentByUrl(url),
});
const favicon = await this.httpRequestService.fetch(faviconUrl, {}, { noOkError: true });
if (favicon.ok) {
return faviconUrl;

View File

@@ -398,13 +398,13 @@ export class FileInfoService {
.raw()
.ensureAlpha()
.resize(64, 64, { fit: 'inside' })
.toBuffer((err, buffer, { width, height }) => {
.toBuffer((err, buffer, info) => {
if (err) return reject(err);
let hash;
try {
hash = encode(new Uint8ClampedArray(buffer), width, height, 5, 5);
hash = encode(new Uint8ClampedArray(buffer), info.width, info.height, 5, 5);
} catch (e) {
return reject(e);
}

View File

@@ -1,67 +1,257 @@
import * as http from 'node:http';
import * as https from 'node:https';
import CacheableLookup from 'cacheable-lookup';
import fetch from 'node-fetch';
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { StatusError } from '@/misc/status-error.js';
import { bindThis } from '@/decorators.js';
import type { Response } from 'node-fetch';
import type { URL } from 'node:url';
import * as undici from 'undici';
import { LookupFunction } from 'node:net';
import { LoggerService } from '@/core/LoggerService.js';
import type Logger from '@/logger.js';
// true to allow, false to deny
export type IpChecker = (ip: string) => boolean;
/*
* Child class to create and save Agent for fetch.
* You should construct this when you want
* to change timeout, size limit, socket connect function, etc.
*/
export class UndiciFetcher {
/**
* Get http non-proxy agent (undici)
*/
public nonProxiedAgent: undici.Agent;
/**
* Get http proxy or non-proxy agent (undici)
*/
public agent: undici.ProxyAgent | undici.Agent;
private proxyBypassHosts: string[];
private userAgent: string | undefined;
private logger: Logger | undefined;
constructor(
args: {
agentOptions: undici.Agent.Options;
proxy?: {
uri: string;
options?: undici.Agent.Options; // Override of agentOptions
},
proxyBypassHosts?: string[];
userAgent?: string;
},
logger?: Logger,
) {
this.logger = logger;
this.logger?.debug('UndiciFetcher constructor', args);
this.proxyBypassHosts = args.proxyBypassHosts ?? [];
this.userAgent = args.userAgent;
this.nonProxiedAgent = new undici.Agent({
...args.agentOptions,
connect: (process.env.NODE_ENV !== 'production' && typeof args.agentOptions.connect !== 'function')
? (options, cb) => {
// Custom connector for debug
undici.buildConnector(args.agentOptions.connect as undici.buildConnector.BuildOptions)(options, (err, socket) => {
this.logger?.debug('Socket connector called', socket);
if (err) {
this.logger?.debug(`Socket error`, err);
cb(new Error(`Error while socket connecting\n${err}`), null);
return;
}
this.logger?.debug(`Socket connected: port ${socket.localPort} => remote ${socket.remoteAddress}`);
cb(null, socket);
});
} : args.agentOptions.connect,
});
this.agent = args.proxy
? new undici.ProxyAgent({
...args.agentOptions,
...args.proxy.options,
uri: args.proxy.uri,
connect: (process.env.NODE_ENV !== 'production' && typeof (args.proxy?.options?.connect ?? args.agentOptions.connect) !== 'function')
? (options, cb) => {
// Custom connector for debug
undici.buildConnector((args.proxy?.options?.connect ?? args.agentOptions.connect) as undici.buildConnector.BuildOptions)(options, (err, socket) => {
this.logger?.debug('Socket connector called (secure)', socket);
if (err) {
this.logger?.debug(`Socket error`, err);
cb(new Error(`Error while socket connecting\n${err}`), null);
return;
}
this.logger?.debug(`Socket connected (secure): port ${socket.localPort} => remote ${socket.remoteAddress}`);
cb(null, socket);
});
} : (args.proxy?.options?.connect ?? args.agentOptions.connect),
})
: this.nonProxiedAgent;
}
/**
* Get agent by URL
* @param url URL
* @param bypassProxy Allways bypass proxy
*/
@bindThis
public getAgentByUrl(url: URL, bypassProxy = false): undici.Agent | undici.ProxyAgent {
if (bypassProxy || this.proxyBypassHosts.includes(url.hostname)) {
return this.nonProxiedAgent;
} else {
return this.agent;
}
}
@bindThis
public async fetch(
url: string | URL,
options: undici.RequestInit = {},
privateOptions: { noOkError?: boolean; bypassProxy?: boolean; } = { noOkError: false, bypassProxy: false }
): Promise<undici.Response> {
const res = await undici.fetch(url, {
dispatcher: this.getAgentByUrl(new URL(url), privateOptions.bypassProxy),
...options,
headers: {
'User-Agent': this.userAgent ?? '',
...(options.headers ?? {}),
},
}).catch((err) => {
this.logger?.error('fetch error', err);
throw new StatusError('Resource Unreachable', 500, 'Resource Unreachable');
});
if (!res.ok && !privateOptions.noOkError) {
throw new StatusError(`${res.status} ${res.statusText}`, res.status, res.statusText);
}
return res;
}
@bindThis
public async getJson<T extends unknown>(url: string, accept = 'application/json, */*', headers?: Record<string, string>): Promise<T> {
const res = await this.fetch(
url,
{
headers: Object.assign({
Accept: accept,
}, headers ?? {}),
}
);
return await res.json() as T;
}
@bindThis
public async getHtml(url: string, accept = 'text/html, */*', headers?: Record<string, string>): Promise<string> {
const res = await this.fetch(
url,
{
headers: Object.assign({
Accept: accept,
}, headers ?? {}),
}
);
return await res.text();
}
}
@Injectable()
export class HttpRequestService {
/**
* Get http non-proxy agent
*/
public defaultFetcher: UndiciFetcher;
public fetch: UndiciFetcher['fetch'];
public getHtml: UndiciFetcher['getHtml'];
public defaultJsonFetcher: UndiciFetcher;
public getJson: UndiciFetcher['getJson'];
//#region for old http/https, only used in S3Service
// http non-proxy agent
private http: http.Agent;
/**
* Get https non-proxy agent
*/
// https non-proxy agent
private https: https.Agent;
/**
* Get http proxy or non-proxy agent
*/
// http proxy or non-proxy agent
public httpAgent: http.Agent;
/**
* Get https proxy or non-proxy agent
*/
// https proxy or non-proxy agent
public httpsAgent: https.Agent;
//#endregion
public readonly dnsCache: CacheableLookup;
public readonly clientDefaults: undici.Agent.Options;
private maxSockets: number;
private logger: Logger;
constructor(
@Inject(DI.config)
private config: Config,
private loggerService: LoggerService,
) {
const cache = new CacheableLookup({
this.logger = this.loggerService.getLogger('http-request');
this.dnsCache = new CacheableLookup({
maxTtl: 3600, // 1hours
errorTtl: 30, // 30secs
lookup: false, // nativeのdns.lookupにfallbackしない
});
this.clientDefaults = {
keepAliveTimeout: 30 * 1000,
keepAliveMaxTimeout: 10 * 60 * 1000,
keepAliveTimeoutThreshold: 1 * 1000,
strictContentLength: true,
headersTimeout: 10 * 1000,
bodyTimeout: 10 * 1000,
maxHeaderSize: 16364, // default
maxResponseSize: 10 * 1024 * 1024,
maxRedirections: 3,
connect: {
timeout: 10 * 1000, // コネクションが確立するまでのタイムアウト
maxCachedSessions: 300, // TLSセッションのキャッシュ数 https://github.com/nodejs/undici/blob/v5.14.0/lib/core/connect.js#L80
lookup: this.dnsCache.lookup as LookupFunction, // https://github.com/nodejs/undici/blob/v5.14.0/lib/core/connect.js#L98
},
}
this.maxSockets = Math.max(64, this.config.deliverJobConcurrency ?? 128);
this.defaultFetcher = new UndiciFetcher(this.getStandardUndiciFetcherOption(), this.logger);
this.fetch = this.defaultFetcher.fetch;
this.getHtml = this.defaultFetcher.getHtml;
this.defaultJsonFetcher = new UndiciFetcher(this.getStandardUndiciFetcherOption({
maxResponseSize: 1024 * 256,
}), this.logger);
this.getJson = this.defaultJsonFetcher.getJson;
//#region for old http/https, only used in S3Service
this.http = new http.Agent({
keepAlive: true,
keepAliveMsecs: 30 * 1000,
lookup: cache.lookup,
lookup: this.dnsCache.lookup,
} as http.AgentOptions);
this.https = new https.Agent({
keepAlive: true,
keepAliveMsecs: 30 * 1000,
lookup: cache.lookup,
lookup: this.dnsCache.lookup,
} as https.AgentOptions);
const maxSockets = Math.max(256, config.deliverJobConcurrency ?? 128);
this.httpAgent = config.proxy
? new HttpProxyAgent({
keepAlive: true,
keepAliveMsecs: 30 * 1000,
maxSockets,
maxSockets: this.maxSockets,
maxFreeSockets: 256,
scheduling: 'lifo',
proxy: config.proxy,
@@ -72,21 +262,42 @@ export class HttpRequestService {
? new HttpsProxyAgent({
keepAlive: true,
keepAliveMsecs: 30 * 1000,
maxSockets,
maxSockets: this.maxSockets,
maxFreeSockets: 256,
scheduling: 'lifo',
proxy: config.proxy,
})
: this.https;
//#endregion
}
@bindThis
public getStandardUndiciFetcherOption(opts: undici.Agent.Options = {}, proxyOpts: undici.Agent.Options = {}) {
return {
agentOptions: {
...this.clientDefaults,
...opts,
},
...(this.config.proxy ? {
proxy: {
uri: this.config.proxy,
options: {
connections: this.maxSockets,
...proxyOpts,
}
}
} : {}),
userAgent: this.config.userAgent,
}
}
/**
* Get agent by URL
* Get http agent by URL
* @param url URL
* @param bypassProxy Allways bypass proxy
*/
@bindThis
public getAgentByUrl(url: URL, bypassProxy = false): http.Agent | https.Agent {
public getHttpAgentByUrl(url: URL, bypassProxy = false): http.Agent | https.Agent {
if (bypassProxy || (this.config.proxyBypassHosts || []).includes(url.hostname)) {
return url.protocol === 'http:' ? this.http : this.https;
} else {
@@ -94,67 +305,37 @@ export class HttpRequestService {
}
}
/**
* check ip
*/
@bindThis
public async getJson(url: string, accept = 'application/json, */*', timeout = 10000, headers?: Record<string, string>): Promise<unknown> {
const res = await this.getResponse({
url,
method: 'GET',
headers: Object.assign({
'User-Agent': this.config.userAgent,
Accept: accept,
}, headers ?? {}),
timeout,
size: 1024 * 256,
});
public getConnectorWithIpCheck(connector: undici.buildConnector.connector, checkIp: IpChecker): undici.buildConnector.connectorAsync {
return (options, cb) => {
connector(options, (err, socket) => {
this.logger.debug('Socket connector (with ip checker) called', socket);
if (err) {
this.logger.error(`Socket error`, err)
cb(new Error(`Error while socket connecting\n${err}`), null);
return;
}
return await res.json();
}
if (socket.remoteAddress == undefined) {
this.logger.error(`Socket error: remoteAddress is undefined`);
cb(new Error('remoteAddress is undefined (maybe socket destroyed)'), null);
return;
}
@bindThis
public async getHtml(url: string, accept = 'text/html, */*', timeout = 10000, headers?: Record<string, string>): Promise<string> {
const res = await this.getResponse({
url,
method: 'GET',
headers: Object.assign({
'User-Agent': this.config.userAgent,
Accept: accept,
}, headers ?? {}),
timeout,
});
// allow
if (checkIp(socket.remoteAddress)) {
this.logger.debug(`Socket connected (ip ok): ${socket.localPort} => ${socket.remoteAddress}`);
cb(null, socket);
return;
}
return await res.text();
}
@bindThis
public async getResponse(args: {
url: string,
method: string,
body?: string,
headers: Record<string, string>,
timeout?: number,
size?: number,
}): Promise<Response> {
const timeout = args.timeout ?? 10 * 1000;
const controller = new AbortController();
setTimeout(() => {
controller.abort();
}, timeout * 6);
const res = await fetch(args.url, {
method: args.method,
headers: args.headers,
body: args.body,
timeout,
size: args.size ?? 10 * 1024 * 1024,
agent: (url) => this.getAgentByUrl(url),
signal: controller.signal,
});
if (!res.ok) {
throw new StatusError(`${res.status} ${res.statusText}`, res.status, res.statusText);
}
return res;
this.logger.error('IP is not allowed', socket);
cb(new StatusError('IP is not allowed', 403, 'IP is not allowed'), null);
socket.destroy();
});
};
}
}

View File

@@ -42,6 +42,7 @@ import { NoteReadService } from '@/core/NoteReadService.js';
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
import { bindThis } from '@/decorators.js';
import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import { RoleService } from '@/core/RoleService.js';
const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
@@ -186,6 +187,7 @@ export class NoteCreateService {
private remoteUserResolveService: RemoteUserResolveService,
private apDeliverManagerService: ApDeliverManagerService,
private apRendererService: ApRendererService,
private roleService: RoleService,
private notesChart: NotesChart,
private perUserNotesChart: PerUserNotesChart,
private activeUsersChart: ActiveUsersChart,
@@ -197,7 +199,6 @@ export class NoteCreateService {
id: User['id'];
username: User['username'];
host: User['host'];
isSilenced: User['isSilenced'];
createdAt: User['createdAt'];
isBot: User['isBot'];
}, data: Option, silent = false): Promise<Note> {
@@ -224,9 +225,10 @@ export class NoteCreateService {
if (data.channel != null) data.visibleUsers = [];
if (data.channel != null) data.localOnly = true;
// サイレンス
if (user.isSilenced && data.visibility === 'public' && data.channel == null) {
data.visibility = 'home';
if (data.visibility === 'public' && data.channel == null) {
if ((await this.roleService.getUserRoleOptions(user.id)).canPublicNote === false) {
data.visibility = 'home';
}
}
// Renote対象が「ホームまたは全体」以外の公開範囲ならreject
@@ -418,7 +420,6 @@ export class NoteCreateService {
id: User['id'];
username: User['username'];
host: User['host'];
isSilenced: User['isSilenced'];
createdAt: User['createdAt'];
isBot: User['isBot'];
}, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) {

View File

@@ -92,13 +92,6 @@ export class PollService {
choice: choice,
userId: user.id,
});
// Notify
this.createNotificationService.createNotification(note.userId, 'pollVote', {
notifierId: user.id,
noteId: note.id,
choice: choice,
});
}
@bindThis

View File

@@ -0,0 +1,243 @@
import { Inject, Injectable } from '@nestjs/common';
import Redis from 'ioredis';
import { In } from 'typeorm';
import type { Role, RoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/index.js';
import { Cache } from '@/misc/cache.js';
import type { CacheableLocalUser, CacheableUser, ILocalUser, User } from '@/models/entities/User.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js';
import { UserCacheService } from '@/core/UserCacheService.js';
import { RoleCondFormulaValue } from '@/models/entities/Role.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
export type RoleOptions = {
gtlAvailable: boolean;
ltlAvailable: boolean;
canPublicNote: boolean;
driveCapacityMb: number;
antennaLimit: number;
};
export const DEFAULT_ROLE: RoleOptions = {
gtlAvailable: true,
ltlAvailable: true,
canPublicNote: true,
driveCapacityMb: 100,
antennaLimit: 5,
};
@Injectable()
export class RoleService implements OnApplicationShutdown {
private rolesCache: Cache<Role[]>;
private roleAssignmentByUserIdCache: Cache<RoleAssignment[]>;
constructor(
@Inject(DI.redisSubscriber)
private redisSubscriber: Redis.Redis,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.rolesRepository)
private rolesRepository: RolesRepository,
@Inject(DI.roleAssignmentsRepository)
private roleAssignmentsRepository: RoleAssignmentsRepository,
private metaService: MetaService,
private userCacheService: UserCacheService,
private userEntityService: UserEntityService,
) {
//this.onMessage = this.onMessage.bind(this);
this.rolesCache = new Cache<Role[]>(Infinity);
this.roleAssignmentByUserIdCache = new Cache<RoleAssignment[]>(Infinity);
this.redisSubscriber.on('message', this.onMessage);
}
@bindThis
private async onMessage(_: string, data: string): Promise<void> {
const obj = JSON.parse(data);
if (obj.channel === 'internal') {
const { type, body } = obj.message;
switch (type) {
case 'roleCreated': {
const cached = this.rolesCache.get(null);
if (cached) {
body.createdAt = new Date(body.createdAt);
body.updatedAt = new Date(body.updatedAt);
body.lastUsedAt = new Date(body.lastUsedAt);
cached.push(body);
}
break;
}
case 'roleUpdated': {
const cached = this.rolesCache.get(null);
if (cached) {
const i = cached.findIndex(x => x.id === body.id);
if (i > -1) {
body.createdAt = new Date(body.createdAt);
body.updatedAt = new Date(body.updatedAt);
body.lastUsedAt = new Date(body.lastUsedAt);
cached[i] = body;
}
}
break;
}
case 'roleDeleted': {
const cached = this.rolesCache.get(null);
if (cached) {
this.rolesCache.set(null, cached.filter(x => x.id !== body.id));
}
break;
}
case 'userRoleAssigned': {
const cached = this.roleAssignmentByUserIdCache.get(body.userId);
if (cached) {
body.createdAt = new Date(body.createdAt);
cached.push(body);
}
break;
}
case 'userRoleUnassigned': {
const cached = this.roleAssignmentByUserIdCache.get(body.userId);
if (cached) {
this.roleAssignmentByUserIdCache.set(body.userId, cached.filter(x => x.id !== body.id));
}
break;
}
default:
break;
}
}
}
@bindThis
private evalCond(user: User, value: RoleCondFormulaValue): boolean {
try {
switch (value.type) {
case 'and': {
return value.values.every(v => this.evalCond(user, v));
}
case 'or': {
return value.values.some(v => this.evalCond(user, v));
}
case 'not': {
return !this.evalCond(user, value.value);
}
case 'isLocal': {
return this.userEntityService.isLocalUser(user);
}
case 'isRemote': {
return this.userEntityService.isRemoteUser(user);
}
case 'createdLessThan': {
return user.createdAt.getTime() > (Date.now() - (value.sec * 1000));
}
case 'createdMoreThan': {
return user.createdAt.getTime() < (Date.now() - (value.sec * 1000));
}
default:
return false;
}
} catch (err) {
// TODO: log error
return false;
}
}
@bindThis
public async getUserRoles(userId: User['id']) {
const assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId }));
const assignedRoleIds = assigns.map(x => x.roleId);
const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({}));
const assignedRoles = roles.filter(r => assignedRoleIds.includes(r.id));
const user = roles.some(r => r.target === 'conditional') ? await this.userCacheService.findById(userId) : null;
const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, r.condFormula));
return [...assignedRoles, ...matchedCondRoles];
}
@bindThis
public async getUserRoleOptions(userId: User['id'] | null): Promise<RoleOptions> {
const meta = await this.metaService.fetch();
const baseRoleOptions = { ...DEFAULT_ROLE, ...meta.defaultRoleOverride };
if (userId == null) return baseRoleOptions;
const roles = await this.getUserRoles(userId);
function getOptionValues(option: keyof RoleOptions) {
if (roles.length === 0) return [baseRoleOptions[option]];
return roles.map(role => (role.options[option] && (role.options[option].useDefault !== true)) ? role.options[option].value : baseRoleOptions[option]);
}
return {
gtlAvailable: getOptionValues('gtlAvailable').some(x => x === true),
ltlAvailable: getOptionValues('ltlAvailable').some(x => x === true),
canPublicNote: getOptionValues('canPublicNote').some(x => x === true),
driveCapacityMb: Math.max(...getOptionValues('driveCapacityMb')),
antennaLimit: Math.max(...getOptionValues('antennaLimit')),
};
}
@bindThis
public async isModerator(user: { id: User['id']; isRoot: User['isRoot'] } | null): Promise<boolean> {
if (user == null) return false;
return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isModerator || r.isAdministrator);
}
@bindThis
public async isAdministrator(user: { id: User['id']; isRoot: User['isRoot'] } | null): Promise<boolean> {
if (user == null) return false;
return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isAdministrator);
}
@bindThis
public async getModeratorIds(includeAdmins = true): Promise<User['id'][]> {
const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({}));
const moderatorRoles = includeAdmins ? roles.filter(r => r.isModerator || r.isAdministrator) : roles.filter(r => r.isModerator);
const assigns = moderatorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({
roleId: In(moderatorRoles.map(r => r.id)),
}) : [];
// TODO: isRootなアカウントも含める
return assigns.map(a => a.userId);
}
@bindThis
public async getModerators(includeAdmins = true): Promise<User[]> {
const ids = await this.getModeratorIds(includeAdmins);
const users = ids.length > 0 ? await this.usersRepository.findBy({
id: In(ids),
}) : [];
return users;
}
@bindThis
public async getAdministratorIds(): Promise<User['id'][]> {
const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({}));
const administratorRoles = roles.filter(r => r.isAdministrator);
const assigns = administratorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({
roleId: In(administratorRoles.map(r => r.id)),
}) : [];
// TODO: isRootなアカウントも含める
return assigns.map(a => a.userId);
}
@bindThis
public async getAdministrators(): Promise<User[]> {
const ids = await this.getAdministratorIds();
const users = ids.length > 0 ? await this.usersRepository.findBy({
id: In(ids),
}) : [];
return users;
}
@bindThis
public onApplicationShutdown(signal?: string | undefined) {
this.redisSubscriber.off('message', this.onMessage);
}
}

View File

@@ -33,7 +33,7 @@ export class S3Service {
? false
: meta.objectStorageS3ForcePathStyle,
httpOptions: {
agent: this.httpRequestService.getAgentByUrl(new URL(u), !meta.objectStorageUseProxy),
agent: this.httpRequestService.getHttpAgentByUrl(new URL(u), !meta.objectStorageUseProxy),
},
});
}

View File

@@ -11,10 +11,10 @@ import { IdService } from '@/core/IdService.js';
import { UserKeypair } from '@/models/entities/UserKeypair.js';
import { UsedUsername } from '@/models/entities/UsedUsername.js';
import generateUserToken from '@/misc/generate-native-user-token.js';
import UsersChart from './chart/charts/users.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { UtilityService } from './UtilityService.js';
import { bindThis } from '@/decorators.js';
import UsersChart from './chart/charts/users.js';
import { UtilityService } from './UtilityService.js';
@Injectable()
export class SignupService {
@@ -112,7 +112,7 @@ export class SignupService {
usernameLower: username.toLowerCase(),
host: this.utilityService.toPunyNullable(host),
token: secret,
isAdmin: (await this.usersRepository.countBy({
isRoot: (await this.usersRepository.countBy({
host: IsNull(),
})) === 0,
}));

View File

@@ -2,11 +2,11 @@ import { Inject, Injectable } from '@nestjs/common';
import Redis from 'ioredis';
import type { UsersRepository } from '@/models/index.js';
import { Cache } from '@/misc/cache.js';
import type { CacheableLocalUser, CacheableUser, ILocalUser } from '@/models/entities/User.js';
import type { CacheableLocalUser, CacheableUser, ILocalUser, User } from '@/models/entities/User.js';
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
import { bindThis } from '@/decorators.js';
import type { OnApplicationShutdown } from '@nestjs/common';
@Injectable()
export class UserCacheService implements OnApplicationShutdown {
@@ -42,8 +42,6 @@ export class UserCacheService implements OnApplicationShutdown {
const { type, body } = obj.message;
switch (type) {
case 'userChangeSuspendedState':
case 'userChangeSilencedState':
case 'userChangeModeratorState':
case 'remoteUserUpdated': {
const user = await this.usersRepository.findOneByOrFail({ id: body.id });
this.userByIdCache.set(user.id, user);
@@ -70,6 +68,11 @@ export class UserCacheService implements OnApplicationShutdown {
}
}
@bindThis
public findById(userId: User['id']) {
return this.userByIdCache.fetch(userId, () => this.usersRepository.findOneByOrFail({ id: userId }));
}
@bindThis
public onApplicationShutdown(signal?: string | undefined) {
this.redisSubscriber.off('message', this.onMessage);

View File

@@ -30,7 +30,7 @@ export class WebfingerService {
public async webfinger(query: string): Promise<IWebFinger> {
const url = this.genUrl(query);
return await this.httpRequestService.getJson(url, 'application/jrd+json, application/json') as IWebFinger;
return await this.httpRequestService.getJson<IWebFinger>(url, 'application/jrd+json, application/json');
}
@bindThis

View File

@@ -159,7 +159,7 @@ export class ApDbResolverService {
if (key == null) return null;
return {
user: await this.userCacheService.userByIdCache.fetch(key.userId, () => this.usersRepository.findOneByOrFail({ id: key.userId })) as CacheableRemoteUser,
user: await this.userCacheService.findById(key.userId) as CacheableRemoteUser,
key,
};
}

View File

@@ -5,8 +5,10 @@ import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import type { User } from '@/models/entities/User.js';
import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { HttpRequestService, UndiciFetcher } from '@/core/HttpRequestService.js';
import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js';
import type Logger from '@/logger.js';
type Request = {
url: string;
@@ -28,13 +30,21 @@ type PrivateKey = {
@Injectable()
export class ApRequestService {
private undiciFetcher: UndiciFetcher;
private logger: Logger;
constructor(
@Inject(DI.config)
private config: Config,
private userKeypairStoreService: UserKeypairStoreService,
private httpRequestService: HttpRequestService,
private loggerService: LoggerService,
) {
this.logger = this.loggerService?.getLogger('ap-request'); // なぜか TypeError: Cannot read properties of undefined (reading 'getLogger') と言われる
this.undiciFetcher = new UndiciFetcher(this.httpRequestService.getStandardUndiciFetcherOption({
maxRedirections: 0,
}), this.logger );
}
@bindThis
@@ -148,16 +158,17 @@ export class ApRequestService {
url,
body,
additionalHeaders: {
'User-Agent': this.config.userAgent,
},
});
await this.httpRequestService.getResponse({
await this.undiciFetcher.fetch(
url,
method: req.request.method,
headers: req.request.headers,
body,
});
{
method: req.request.method,
headers: req.request.headers,
body,
}
);
}
/**
@@ -176,15 +187,16 @@ export class ApRequestService {
},
url,
additionalHeaders: {
'User-Agent': this.config.userAgent,
},
});
const res = await this.httpRequestService.getResponse({
const res = await this.httpRequestService.fetch(
url,
method: req.request.method,
headers: req.request.headers,
});
{
method: req.request.method,
headers: req.request.headers,
}
);
return await res.json();
}

View File

@@ -4,7 +4,7 @@ import { InstanceActorService } from '@/core/InstanceActorService.js';
import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js';
import { MetaService } from '@/core/MetaService.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { HttpRequestService, UndiciFetcher } from '@/core/HttpRequestService.js';
import { DI } from '@/di-symbols.js';
import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
@@ -12,11 +12,15 @@ import { isCollectionOrOrderedCollection } from './type.js';
import { ApDbResolverService } from './ApDbResolverService.js';
import { ApRendererService } from './ApRendererService.js';
import { ApRequestService } from './ApRequestService.js';
import { LoggerService } from '@/core/LoggerService.js';
import type { IObject, ICollection, IOrderedCollection } from './type.js';
import type Logger from '@/logger.js';
export class Resolver {
private history: Set<string>;
private user?: ILocalUser;
private undiciFetcher: UndiciFetcher;
private logger: Logger;
constructor(
private config: Config,
@@ -31,9 +35,14 @@ export class Resolver {
private httpRequestService: HttpRequestService,
private apRendererService: ApRendererService,
private apDbResolverService: ApDbResolverService,
private loggerService: LoggerService,
private recursionLimit = 100,
) {
this.history = new Set();
this.logger = this.loggerService?.getLogger('ap-resolve'); // なぜか TypeError: Cannot read properties of undefined (reading 'getLogger') と言われる
this.undiciFetcher = new UndiciFetcher(this.httpRequestService.getStandardUndiciFetcherOption({
maxRedirections: 0,
}), this.logger);
}
@bindThis
@@ -96,8 +105,8 @@ export class Resolver {
}
const object = (this.user
? await this.apRequestService.signedGet(value, this.user)
: await this.httpRequestService.getJson(value, 'application/activity+json, application/ld+json')) as IObject;
? await this.apRequestService.signedGet(value, this.user) as IObject
: await this.undiciFetcher.getJson<IObject>(value, 'application/activity+json, application/ld+json'));
if (object == null || (
Array.isArray(object['@context']) ?

View File

@@ -1,6 +1,5 @@
import * as crypto from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common';
import fetch from 'node-fetch';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { bindThis } from '@/decorators.js';
import { CONTEXTS } from './misc/contexts.js';
@@ -116,14 +115,19 @@ class LdSignature {
@bindThis
private async fetchDocument(url: string) {
const json = await fetch(url, {
headers: {
Accept: 'application/ld+json, application/json',
const json = await this.httpRequestService.fetch(
url,
{
headers: {
Accept: 'application/ld+json, application/json',
},
// TODO
//timeout: this.loderTimeout,
},
// TODO
//timeout: this.loderTimeout,
agent: u => u.protocol === 'http:' ? this.httpRequestService.httpAgent : this.httpRequestService.httpsAgent,
}).then(res => {
{
noOkError: true,
}
).then(res => {
if (!res.ok) {
throw `${res.status} ${res.statusText}`;
} else {

View File

@@ -22,23 +22,25 @@ export class EmojiEntityService {
@bindThis
public async pack(
src: Emoji['id'] | Emoji,
opts: { omitHost?: boolean; omitId?: boolean; } = {},
): Promise<Packed<'Emoji'>> {
const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
return {
id: emoji.id,
id: opts.omitId ? undefined : emoji.id,
aliases: emoji.aliases,
name: emoji.name,
category: emoji.category,
host: emoji.host,
host: opts.omitHost ? undefined : emoji.host,
};
}
@bindThis
public packMany(
emojis: any[],
opts: { omitHost?: boolean; omitId?: boolean; } = {},
) {
return Promise.all(emojis.map(x => this.pack(x)));
return Promise.all(emojis.map(x => this.pack(x, opts)));
}
}

View File

@@ -98,7 +98,7 @@ export class NotificationEntityService implements OnModuleInit {
}),
reaction: notification.reaction,
} : {}),
...(notification.type === 'pollVote' ? {
...(notification.type === 'pollVote' ? { // TODO: そのうち消す
note: this.noteEntityService.pack(notification.note ?? notification.noteId!, { id: notification.notifieeId }, {
detail: true,
_hint_: options._hintForEachNotes_,

View File

@@ -0,0 +1,82 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { RoleAssignmentsRepository, RolesRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js';
import type { User } from '@/models/entities/User.js';
import type { Role } from '@/models/entities/Role.js';
import { bindThis } from '@/decorators.js';
import { DEFAULT_ROLE } from '@/core/RoleService.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
export class RoleEntityService {
constructor(
@Inject(DI.rolesRepository)
private rolesRepository: RolesRepository,
@Inject(DI.roleAssignmentsRepository)
private roleAssignmentsRepository: RoleAssignmentsRepository,
private userEntityService: UserEntityService,
) {
}
@bindThis
public async pack(
src: Role['id'] | Role,
me?: { id: User['id'] } | null | undefined,
options?: {
detail?: boolean;
},
) {
const opts = Object.assign({
detail: true,
}, options);
const role = typeof src === 'object' ? src : await this.rolesRepository.findOneByOrFail({ id: src });
const assigns = await this.roleAssignmentsRepository.findBy({
roleId: role.id,
});
const roleOptions = { ...role.options };
for (const [k, v] of Object.entries(DEFAULT_ROLE)) {
if (roleOptions[k] == null) roleOptions[k] = {
useDefault: true,
value: v,
};
}
return await awaitAll({
id: role.id,
createdAt: role.createdAt.toISOString(),
updatedAt: role.updatedAt.toISOString(),
name: role.name,
description: role.description,
color: role.color,
target: role.target,
condFormula: role.condFormula,
isPublic: role.isPublic,
isAdministrator: role.isAdministrator,
isModerator: role.isModerator,
canEditMembersByModerator: role.canEditMembersByModerator,
options: roleOptions,
...(opts.detail ? {
users: this.userEntityService.packMany(assigns.map(x => x.userId), me),
} : {}),
});
}
@bindThis
public packMany(
roles: any[],
me: { id: User['id'] },
options?: {
detail?: boolean;
},
) {
return Promise.all(roles.map(x => this.pack(x, me, options)));
}
}

View File

@@ -13,6 +13,8 @@ import type { Instance } from '@/models/entities/Instance.js';
import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js';
import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/entities/User.js';
import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, NotificationsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository, AnnouncementsRepository, AntennaNotesRepository, PagesRepository } from '@/models/index.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import type { OnModuleInit } from '@nestjs/common';
import type { AntennaService } from '../AntennaService.js';
import type { CustomEmojiService } from '../CustomEmojiService.js';
@@ -41,7 +43,6 @@ function isRemoteUser<T extends { host: User['host'] }>(user: T): user is T & {
function isRemoteUser(user: User | { host: User['host'] }): boolean {
return !isLocalUser(user);
}
import { bindThis } from '@/decorators.js';
@Injectable()
export class UserEntityService implements OnModuleInit {
@@ -50,6 +51,7 @@ export class UserEntityService implements OnModuleInit {
private pageEntityService: PageEntityService;
private customEmojiService: CustomEmojiService;
private antennaService: AntennaService;
private roleService: RoleService;
private userInstanceCache: Cache<Instance | null>;
constructor(
@@ -120,6 +122,7 @@ export class UserEntityService implements OnModuleInit {
//private pageEntityService: PageEntityService,
//private customEmojiService: CustomEmojiService,
//private antennaService: AntennaService,
//private roleService: RoleService,
) {
this.userInstanceCache = new Cache<Instance | null>(1000 * 60 * 60 * 3);
}
@@ -130,6 +133,7 @@ export class UserEntityService implements OnModuleInit {
this.pageEntityService = this.moduleRef.get('PageEntityService');
this.customEmojiService = this.moduleRef.get('CustomEmojiService');
this.antennaService = this.moduleRef.get('AntennaService');
this.roleService = this.moduleRef.get('RoleService');
}
//#region Validators
@@ -383,6 +387,9 @@ export class UserEntityService implements OnModuleInit {
(profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount :
null;
const isModerator = isMe && opts.detail ? this.roleService.isModerator(user) : null;
const isAdmin = isMe && opts.detail ? this.roleService.isAdministrator(user) : null;
const falsy = opts.detail ? false : undefined;
const packed = {
@@ -392,8 +399,6 @@ export class UserEntityService implements OnModuleInit {
host: user.host,
avatarUrl: this.getAvatarUrlSync(user),
avatarBlurhash: user.avatar?.blurhash ?? null,
isAdmin: user.isAdmin ?? falsy,
isModerator: user.isModerator ?? falsy,
isBot: user.isBot ?? falsy,
isCat: user.isCat ?? falsy,
instance: user.host ? this.userInstanceCache.fetch(user.host,
@@ -418,7 +423,7 @@ export class UserEntityService implements OnModuleInit {
bannerUrl: user.banner ? this.driveFileEntityService.getPublicUrl(user.banner, false) : null,
bannerBlurhash: user.banner?.blurhash ?? null,
isLocked: user.isLocked,
isSilenced: user.isSilenced ?? falsy,
isSilenced: this.roleService.getUserRoleOptions(user.id).then(r => !r.canPublicNote),
isSuspended: user.isSuspended ?? falsy,
description: profile!.description,
location: profile!.location,
@@ -443,14 +448,13 @@ export class UserEntityService implements OnModuleInit {
userId: user.id,
}).then(result => result >= 1)
: false,
...(isMe || opts.includeSecrets ? {
driveCapacityOverrideMb: user.driveCapacityOverrideMb,
} : {}),
} : {}),
...(opts.detail && isMe ? {
avatarId: user.avatarId,
bannerId: user.bannerId,
isModerator: isModerator,
isAdmin: isAdmin,
injectFeaturedNote: profile!.injectFeaturedNote,
receiveAnnouncementEmail: profile!.receiveAnnouncementEmail,
alwaysMarkNsfw: profile!.alwaysMarkNsfw,
@@ -484,6 +488,7 @@ export class UserEntityService implements OnModuleInit {
} : {}),
...(opts.includeSecrets ? {
role: this.roleService.getUserRoleOptions(user.id),
email: profile!.email,
emailVerified: profile!.emailVerified,
securityKeysList: profile!.twoFactorEnabled

View File

@@ -69,6 +69,8 @@ export const DI = {
adsRepository: Symbol('adsRepository'),
passwordResetRequestsRepository: Symbol('passwordResetRequestsRepository'),
retentionAggregationsRepository: Symbol('retentionAggregationsRepository'),
rolesRepository: Symbol('rolesRepository'),
roleAssignmentsRepository: Symbol('roleAssignmentsRepository'),
flashsRepository: Symbol('flashsRepository'),
flashLikesRepository: Symbol('flashLikesRepository'),
//#endregion

View File

@@ -3,6 +3,7 @@ import { FILE_TYPE_BROWSERSAFE } from '@/const.js';
const dictionary = {
'safe-file': FILE_TYPE_BROWSERSAFE,
'sharp-convertible-image': ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp', 'image/avif', 'image/svg+xml'],
'sharp-animation-convertible-image': ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif', 'image/svg+xml'],
};
export const isMimeImage = (mime: string, type: keyof typeof dictionary): boolean => dictionary[type].includes(mime);

View File

@@ -0,0 +1,3 @@
export function sqlLikeEscape(s: string) {
return s.replace(/([%_])/g, '\\$1');
}

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, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserGroup, UserGroupJoining, UserGroupInvitation, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, MessagingMessage, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, AntennaNote, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelNotePining, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash } from './index.js';
import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserGroup, UserGroupJoining, UserGroupInvitation, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, MessagingMessage, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, AntennaNote, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelNotePining, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment } from './index.js';
import type { DataSource } from 'typeorm';
import type { Provider } from '@nestjs/common';
@@ -400,6 +400,18 @@ const $flashLikesRepository: Provider = {
inject: [DI.db],
};
const $rolesRepository: Provider = {
provide: DI.rolesRepository,
useFactory: (db: DataSource) => db.getRepository(Role),
inject: [DI.db],
};
const $roleAssignmentsRepository: Provider = {
provide: DI.roleAssignmentsRepository,
useFactory: (db: DataSource) => db.getRepository(RoleAssignment),
inject: [DI.db],
};
@Module({
imports: [
],
@@ -468,6 +480,8 @@ const $flashLikesRepository: Provider = {
$adsRepository,
$passwordResetRequestsRepository,
$retentionAggregationsRepository,
$rolesRepository,
$roleAssignmentsRepository,
$flashsRepository,
$flashLikesRepository,
],
@@ -536,6 +550,8 @@ const $flashLikesRepository: Provider = {
$adsRepository,
$passwordResetRequestsRepository,
$retentionAggregationsRepository,
$rolesRepository,
$roleAssignmentsRepository,
$flashsRepository,
$flashLikesRepository,
],

View File

@@ -42,16 +42,6 @@ export class Meta {
})
public disableRegistration: boolean;
@Column('boolean', {
default: false,
})
public disableLocalTimeline: boolean;
@Column('boolean', {
default: false,
})
public disableGlobalTimeline: boolean;
@Column('boolean', {
default: false,
})
@@ -227,18 +217,6 @@ export class Meta {
})
public enableSensitiveMediaDetectionForVideos: boolean;
@Column('integer', {
default: 1024,
comment: 'Drive capacity of a local user (MB)',
})
public localDriveCapacityMb: number;
@Column('integer', {
default: 32,
comment: 'Drive capacity of a remote user (MB)',
})
public remoteDriveCapacityMb: number;
@Column('varchar', {
length: 128,
nullable: true,
@@ -476,4 +454,9 @@ export class Meta {
default: true,
})
public enableActiveEmailValidation: boolean;
@Column('jsonb', {
default: { },
})
public defaultRoleOverride: Record<string, any>;
}

View File

@@ -55,11 +55,11 @@ export class Notification {
* 通知の種類。
* follow - フォローされた
* mention - 投稿で自分が言及された
* reply - (自分または自分がWatchしている)投稿返信された
* renote - (自分または自分がWatchしている)投稿がRenoteされた
* quote - (自分または自分がWatchしている)投稿が引用Renoteされた
* reaction - (自分または自分がWatchしている)投稿にリアクションされた
* pollVote - (自分または自分がWatchしている)投稿のアンケートに投票された
* reply - 投稿返信された
* renote - 投稿がRenoteされた
* quote - 投稿が引用Renoteされた
* reaction - 投稿にリアクションされた
* pollVote - 投稿のアンケートに投票された (廃止)
* pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した
* receiveFollowRequest - フォローリクエストされた
* followRequestAccepted - 自分の送ったフォローリクエストが承認された

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