Compare commits
242 Commits
13.0.0-bet
...
13.0.0-rc.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
af6a578fa6 | ||
![]() |
73d735a1f7 | ||
![]() |
b8b1899a9f | ||
![]() |
d52f0617a1 | ||
![]() |
c730973294 | ||
![]() |
2c2e064871 | ||
![]() |
e3c39d4b52 | ||
![]() |
5da74897ae | ||
![]() |
4b1009b34e | ||
![]() |
203a7ad073 | ||
![]() |
34a7b52105 | ||
![]() |
30fc166c08 | ||
![]() |
c84d86b368 | ||
![]() |
1e5d4db0a1 | ||
![]() |
5e02f0d325 | ||
![]() |
ce5506f331 | ||
![]() |
91105845d8 | ||
![]() |
2bedc084a3 | ||
![]() |
027ef1ea4a | ||
![]() |
668aa17eef | ||
![]() |
ebf8ef22e4 | ||
![]() |
bcb5182e86 | ||
![]() |
f45059b7b1 | ||
![]() |
d0aee58599 | ||
![]() |
68e65ed5df | ||
![]() |
367ccb9971 | ||
![]() |
4151087d3c | ||
![]() |
39c058a4bb | ||
![]() |
d1807ee5dc | ||
![]() |
e6a76b31be | ||
![]() |
98469117bf | ||
![]() |
a5becfc042 | ||
![]() |
d2204fd5c8 | ||
![]() |
519a08f8b5 | ||
![]() |
303519a1bd | ||
![]() |
161da24841 | ||
![]() |
6e40024660 | ||
![]() |
73c78d4c38 | ||
![]() |
2654936c17 | ||
![]() |
23810e3e1e | ||
![]() |
d6c89bf003 | ||
![]() |
49ab2a5f93 | ||
![]() |
bc0b8afb1f | ||
![]() |
b250456814 | ||
![]() |
0a6e237d09 | ||
![]() |
54ff4e53cb | ||
![]() |
002ccbb5f0 | ||
![]() |
7b7faf1e84 | ||
![]() |
9936088200 | ||
![]() |
990f4b52bd | ||
![]() |
4c21d83639 | ||
![]() |
d43a4a2d46 | ||
![]() |
8d2c3bb18d | ||
![]() |
4e39e690b6 | ||
![]() |
6458239a7c | ||
![]() |
a5aaa032ca | ||
![]() |
71bbef69c7 | ||
![]() |
c5c40a73b7 | ||
![]() |
74910f8d70 | ||
![]() |
e00003edff | ||
![]() |
bedb98185e | ||
![]() |
da6f955d58 | ||
![]() |
6bdccea26b | ||
![]() |
b2117ba3a1 | ||
![]() |
ba349fc62f | ||
![]() |
b2c79a5f2c | ||
![]() |
3e415e733d | ||
![]() |
a5e84e5de9 | ||
![]() |
8673353029 | ||
![]() |
4579d02296 | ||
![]() |
978a9bbb3b | ||
![]() |
2470afaa2e | ||
![]() |
60e545b2fd | ||
![]() |
6555644b88 | ||
![]() |
df56bd6d57 | ||
![]() |
e51432a461 | ||
![]() |
90e2186872 | ||
![]() |
3043b2f619 | ||
![]() |
d2fc5a248b | ||
![]() |
e6d666e1ee | ||
![]() |
c5cfbd99d0 | ||
![]() |
33b22a323c | ||
![]() |
f032fb628a | ||
![]() |
7761eb8897 | ||
![]() |
58fa8c4a01 | ||
![]() |
789d61d175 | ||
![]() |
b52fd72727 | ||
![]() |
d79905e141 | ||
![]() |
cd6b1290cb | ||
![]() |
c382497167 | ||
![]() |
a8fb578854 | ||
![]() |
ff00c90a88 | ||
![]() |
d0755b5ce8 | ||
![]() |
17fa5667b8 | ||
![]() |
01d5e385ec | ||
![]() |
af80fee899 | ||
![]() |
6b37c09274 | ||
![]() |
1453a0f5cf | ||
![]() |
1688083e9a | ||
![]() |
616594d3cd | ||
![]() |
6783178dc3 | ||
![]() |
3f033d6ab7 | ||
![]() |
d10e000883 | ||
![]() |
ce528ff22e | ||
![]() |
5e4e02235a | ||
![]() |
e4179336e4 | ||
![]() |
7823ba494f | ||
![]() |
7bdff90415 | ||
![]() |
f3c0af7e23 | ||
![]() |
72dfbfcf35 | ||
![]() |
9cbe878d0b | ||
![]() |
618405c4d3 | ||
![]() |
0b08fcac4a | ||
![]() |
eac6ebb239 | ||
![]() |
194fb14e07 | ||
![]() |
c2d05b507a | ||
![]() |
4df43a9107 | ||
![]() |
0da7fcdbed | ||
![]() |
1e50b2688a | ||
![]() |
c1cd018626 | ||
![]() |
b588e8b60b | ||
![]() |
06f55ffb37 | ||
![]() |
02df6a28cd | ||
![]() |
d64abedf9f | ||
![]() |
4d39d1caf6 | ||
![]() |
d06f61f23f | ||
![]() |
c179d6f735 | ||
![]() |
3bc0cdbfb7 | ||
![]() |
b04155e7ba | ||
![]() |
014c97fa85 | ||
![]() |
96ccf550b1 | ||
![]() |
8f28ff63f1 | ||
![]() |
b7dec6e87d | ||
![]() |
1bb2c22493 | ||
![]() |
39c3995c74 | ||
![]() |
8cc80faf20 | ||
![]() |
4d66077f85 | ||
![]() |
3ece2dc990 | ||
![]() |
6071e962f4 | ||
![]() |
ed43369797 | ||
![]() |
c65957853b | ||
![]() |
6a18360269 | ||
![]() |
c438bd2e27 | ||
![]() |
462acc9eee | ||
![]() |
e4144a17a4 | ||
![]() |
3cfd017538 | ||
![]() |
403849805a | ||
![]() |
402b234d15 | ||
![]() |
eba6b326fa | ||
![]() |
4c9b93a12f | ||
![]() |
dfee79f841 | ||
![]() |
962373cf06 | ||
![]() |
13aa4b64b4 | ||
![]() |
5ce56886a1 | ||
![]() |
2817ca03f5 | ||
![]() |
e633c3b84b | ||
![]() |
8524e9d735 | ||
![]() |
91ced90fb2 | ||
![]() |
2acb3917ba | ||
![]() |
dd78ac089c | ||
![]() |
10e526ba56 | ||
![]() |
7ed905f76b | ||
![]() |
5d13e2744f | ||
![]() |
1d7e0293a8 | ||
![]() |
8977d87021 | ||
![]() |
809400ff23 | ||
![]() |
4c8dbcc20d | ||
![]() |
416dcf884d | ||
![]() |
09d3ce444a | ||
![]() |
27c2ca5048 | ||
![]() |
fceeb1b108 | ||
![]() |
b442c38f41 | ||
![]() |
7c2d2676f7 | ||
![]() |
1f6a41cea7 | ||
![]() |
0d7ee20a77 | ||
![]() |
dcca2350dd | ||
![]() |
1cfdd4c41a | ||
![]() |
25f4ee7030 | ||
![]() |
5320f23017 | ||
![]() |
4ffbbbe6d8 | ||
![]() |
132e45dff4 | ||
![]() |
01652b72b3 | ||
![]() |
8b1fdb5a3b | ||
![]() |
192add376c | ||
![]() |
244ea9593a | ||
![]() |
f20d7cba74 | ||
![]() |
a3e282bc75 | ||
![]() |
49a95c34bf | ||
![]() |
ecbefce2aa | ||
![]() |
91356b1805 | ||
![]() |
2e2ed1385f | ||
![]() |
49f3090edd | ||
![]() |
4594fb11de | ||
![]() |
b93e56d2e5 | ||
![]() |
c550dafb81 | ||
![]() |
8709574f3d | ||
![]() |
1b7043fa79 | ||
![]() |
55ef2393fb | ||
![]() |
7769095efb | ||
![]() |
b8248bdd65 | ||
![]() |
6f4ad581dc | ||
![]() |
aec94920ab | ||
![]() |
155ca39063 | ||
![]() |
58bfb4dca4 | ||
![]() |
49a0b6c48b | ||
![]() |
799a653b44 | ||
![]() |
d09e1f4925 | ||
![]() |
cac784af8a | ||
![]() |
d7e0ddcbca | ||
![]() |
8c0811a442 | ||
![]() |
bab6f75260 | ||
![]() |
54e3fccd87 | ||
![]() |
6a992b6982 | ||
![]() |
ecd6fc1db8 | ||
![]() |
d99be6697e | ||
![]() |
d2d77b5dc1 | ||
![]() |
91503405b4 | ||
![]() |
c336201084 | ||
![]() |
0f3399753d | ||
![]() |
5ec89ea0c3 | ||
![]() |
a42b03c154 | ||
![]() |
4b181a30da | ||
![]() |
70805e00eb | ||
![]() |
3551ac328e | ||
![]() |
e36e5df635 | ||
![]() |
3e7d8b5f17 | ||
![]() |
5846198eee | ||
![]() |
c14063a921 | ||
![]() |
457670e730 | ||
![]() |
513cef50a2 | ||
![]() |
88c64ece78 | ||
![]() |
a11672d0a5 | ||
![]() |
46af9515b0 | ||
![]() |
c5cb786054 | ||
![]() |
4d2d6154a3 | ||
![]() |
495d513efd | ||
![]() |
3b617fafdd | ||
![]() |
82c4f694a0 | ||
![]() |
dc5b4a0402 | ||
![]() |
6adc0521d8 | ||
![]() |
9ac86dacbb | ||
![]() |
88f0c10d09 |
@@ -122,10 +122,12 @@ id: 'aid'
|
|||||||
# Proxy for HTTP/HTTPS
|
# Proxy for HTTP/HTTPS
|
||||||
#proxy: http://127.0.0.1:3128
|
#proxy: http://127.0.0.1:3128
|
||||||
|
|
||||||
#proxyBypassHosts: [
|
proxyBypassHosts:
|
||||||
# 'example.com',
|
- api.deepl.com
|
||||||
# '192.0.2.8'
|
- api-free.deepl.com
|
||||||
#]
|
- www.recaptcha.net
|
||||||
|
- hcaptcha.com
|
||||||
|
- challenges.cloudflare.com
|
||||||
|
|
||||||
# Proxy for SMTP/SMTPS
|
# Proxy for SMTP/SMTPS
|
||||||
#proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT
|
#proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT
|
||||||
|
74
CHANGELOG.md
@@ -11,24 +11,51 @@ You should also include the user name that made the change.
|
|||||||
|
|
||||||
## 13.0.0 (unreleased)
|
## 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
|
||||||
|
|
||||||
|
### Notable features
|
||||||
|
- ロール機能
|
||||||
|
- 従来より柔軟にユーザーの権限を管理できます。例えば、「インスタンスのパトロンはアンテナを30個まで作れる」「基本的にLTLは見れないが、許可した人だけ見れる」「招待制インスタンスだけどユーザーなら誰でも他者を招待できる」のような運用はもちろん、「ローカルユーザーかつアカウント作成から1日未満のユーザーはパブリックな投稿を行えない」のように複数条件を組み合わせて、自動でロールを付与する設定も可能です。
|
||||||
|
- Misskey Play
|
||||||
|
- 従来の動的なPagesに代わる、新しいプラットフォームです。動的なコンテンツ(アプリケーション)に特化していて、Pagesに比べてはるかに柔軟なアプリケーションを作成可能です。
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
#### For sabakans
|
#### For server admins
|
||||||
- Node.js 18.x or later is required
|
- 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のサポートが削除されました
|
||||||
- 代わりに今後任意の検索プロバイダを設定できる仕組みを構想しています。その仕組みを使えば今まで通りElasticsearchも利用できます
|
- 代わりに今後任意の検索プロバイダを設定できる仕組みを構想しています。その仕組みを使えば今まで通りElasticsearchも利用できます
|
||||||
- Migrate to Yarn Berry (v3.2.1) @ThatOneCalculator
|
- 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
|
- 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
|
#### For users
|
||||||
- ノートのウォッチ機能が削除されました
|
- ノートのウォッチ機能が削除されました
|
||||||
|
- アンケートに投票された際に通知が作成されなくなりました
|
||||||
|
- ノートの数式埋め込みが削除されました
|
||||||
- 新たに動的なPagesを作ることはできなくなりました
|
- 新たに動的なPagesを作ることはできなくなりました
|
||||||
- 代わりにAiScriptを用いてより柔軟に動的なコンテンツを作成できるMisskey Play機能が実装されています。
|
- 代わりに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.xの変更点についてはこちら https://github.com/syuilo/aiscript/blob/master/CHANGELOG.md#0120
|
||||||
- 0.12.1未満のプラグインは読み込むことはできません
|
- 0.12.x未満のプラグインは読み込むことはできません
|
||||||
- iOS15以下のデバイスはサポートされなくなりました
|
- iOS15以下のデバイスはサポートされなくなりました
|
||||||
|
- Firefox110以下はサポートされなくなりました
|
||||||
|
|
||||||
#### For developers
|
#### For app developers
|
||||||
|
- API: metaのレスポンスに`emojis`プロパティが含まれなくなりました
|
||||||
|
- カスタム絵文字一覧情報を取得するには、`emojis`エンドポイントにリクエストします
|
||||||
- API: カスタム絵文字エンティティに`url`プロパティが含まれなくなりました
|
- API: カスタム絵文字エンティティに`url`プロパティが含まれなくなりました
|
||||||
- 絵文字画像を表示するには、`<instance host>/emoji/<emoji name>.webp`にリクエストすると画像が返ります。
|
- 絵文字画像を表示するには、`<instance host>/emoji/<emoji name>.webp`にリクエストすると画像が返ります。
|
||||||
- e.g. `https://p1.a9z.dev/emoji/misskey.webp`
|
- e.g. `https://p1.a9z.dev/emoji/misskey.webp`
|
||||||
@@ -38,6 +65,7 @@ You should also include the user name that made the change.
|
|||||||
- API: `instance`エンティティに`latestStatus`、`lastCommunicatedAt`、`latestRequestSentAt`プロパティが含まれなくなりました
|
- API: `instance`エンティティに`latestStatus`、`lastCommunicatedAt`、`latestRequestSentAt`プロパティが含まれなくなりました
|
||||||
|
|
||||||
### Improvements
|
### Improvements
|
||||||
|
- Role system @syuilo
|
||||||
- Misskey Play @syuilo
|
- Misskey Play @syuilo
|
||||||
- Introduce retention-rate aggregation @syuilo
|
- Introduce retention-rate aggregation @syuilo
|
||||||
- Make possible to export favorited notes @syuilo
|
- Make possible to export favorited notes @syuilo
|
||||||
@@ -45,15 +73,23 @@ You should also include the user name that made the change.
|
|||||||
- Push notification of Antenna note @tamaina
|
- Push notification of Antenna note @tamaina
|
||||||
- AVIF support @tamaina
|
- AVIF support @tamaina
|
||||||
- Add Cloudflare Turnstile CAPTCHA support @CyberRex0
|
- Add Cloudflare Turnstile CAPTCHA support @CyberRex0
|
||||||
|
- 非モデレーターでも、権限を持つロールをアサインされたユーザーはインスタンスの招待コードを発行できるように @syuilo
|
||||||
|
- 非モデレーターでも、権限を持つロールをアサインされたユーザーはカスタム絵文字の追加、編集、削除を行えるように @syuilo
|
||||||
|
- ハードワードミュートの最大文字数を設定可能に @syuilo
|
||||||
|
- Webhookの作成可能数を設定可能に @syuilo
|
||||||
- Server: signToActivityPubGet is set to true by default @syuilo
|
- Server: signToActivityPubGet is set to true by default @syuilo
|
||||||
- Server: improve syslog performance @syuilo
|
- Server: improve syslog performance @syuilo
|
||||||
|
- Server: Use undici instead of node-fetch and got @tamaina
|
||||||
|
- Server: Judge instance block by endsWith @tamaina
|
||||||
- Server: improve note scoring for featured notes @CyberRex0
|
- 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 notifications regularly to improve db performance @syuilo
|
||||||
- Server: delete outdated hard-mutes 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: delete outdated notes of antenna regularly to improve db performance @syuilo
|
||||||
- Server: improve activitypub deliver performance @syuilo
|
- Server: improve activitypub deliver performance @syuilo
|
||||||
- Client: use tabler-icons instead of fontawesome to better design @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 new gabber kick sounds (thanks for noizenecio)
|
||||||
- Client: Add link to user RSS feed in profile menu @ssmucny
|
- Client: Add link to user RSS feed in profile menu @ssmucny
|
||||||
- Client: Compress non-animated PNG files @saschanaz
|
- Client: Compress non-animated PNG files @saschanaz
|
||||||
@@ -61,18 +97,27 @@ You should also include the user name that made the change.
|
|||||||
- Client: enhance dashboard of control panel @syuilo
|
- Client: enhance dashboard of control panel @syuilo
|
||||||
- Client: Vite is upgraded to v4 @syuilo, @tamaina
|
- Client: Vite is upgraded to v4 @syuilo, @tamaina
|
||||||
- Client: HMR is available while yarn dev @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 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: 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: show Unicode emoji tooltip with its name in MkReactionsViewer.reaction @saschanaz
|
||||||
- Client: OpenSearch support @SoniEx2 @chaoticryptidz
|
- 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 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: add heatmap of daily active users to about page @syuilo
|
||||||
- Client: introduce fluent emoji @syuilo
|
- Client: introduce fluent emoji @syuilo
|
||||||
|
- Client: add new theme @syuilo
|
||||||
|
- Client: add new mfm function (position, fg, bg) @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: show bot warning on screen when logged in as bot account @syuilo
|
||||||
- Client: improve overall performance of client @syuilo
|
- Client: improve overall performance of client @syuilo
|
||||||
- Client: ui tweaks @syuilo
|
- Client: ui tweaks @syuilo
|
||||||
|
- Client: clicker game @syuilo
|
||||||
|
|
||||||
### Bugfixes
|
### Bugfixes
|
||||||
- Server: 引用内の文章がnyaizeされてしまう問題を修正 @kabo2468
|
- Server: 引用内の文章がnyaizeされてしまう問題を修正 @kabo2468
|
||||||
@@ -83,14 +128,29 @@ You should also include the user name that made the change.
|
|||||||
- Server: アンテナの作成数上限を追加 @syuilo
|
- Server: アンテナの作成数上限を追加 @syuilo
|
||||||
- Server: pages/likeのエラーIDが重複しているのを修正 @syuilo
|
- Server: pages/likeのエラーIDが重複しているのを修正 @syuilo
|
||||||
- Server: pages/updateのパラメータによってはsummaryの値が更新されないのを修正 @syuilo
|
- Server: pages/updateのパラメータによってはsummaryの値が更新されないのを修正 @syuilo
|
||||||
|
- Server: Escape SQL LIKE @mei23
|
||||||
|
- Server: 特定のPNG画像のアップロードに失敗する問題を修正 @usbharu
|
||||||
|
- Server: 非公開のクリップのURLでOGPレンダリングされる問題を修正 @syuilo
|
||||||
|
- Server: アンテナタイムライン(ストリーミング)が、フォローしていないユーザーの鍵投稿も拾ってしまう @syuilo
|
||||||
|
- Server: follow request list api pagination @sim1222
|
||||||
|
- Server: ドライブ容量超過時のエラーが適切にレスポンスされない問題を修正 @syuilo
|
||||||
|
- Client: パスワードマネージャーなどでユーザー名がオートコンプリートされない問題を修正 @massongit
|
||||||
|
- Client: 日付形式の文字列などがカスタム絵文字として表示されるのを修正 @syuilo
|
||||||
- Client: case insensitive emoji search @saschanaz
|
- Client: case insensitive emoji search @saschanaz
|
||||||
|
- Client: 画面の幅が狭いとウィジェットドロワーを閉じる手段がなくなるのを修正 @syuilo
|
||||||
- Client: InAppウィンドウが操作できなくなることがあるのを修正 @tamaina
|
- Client: InAppウィンドウが操作できなくなることがあるのを修正 @tamaina
|
||||||
- Client: use proxied image for instance icon @syuilo
|
- Client: use proxied image for instance icon @syuilo
|
||||||
- Client: Webhookの編集画面で、内容を保存することができない問題を修正 @m-hayabusa
|
- Client: Webhookの編集画面で、内容を保存することができない問題を修正 @m-hayabusa
|
||||||
|
- Client: Page編集でブロックの移動が行えない問題を修正 @syuilo
|
||||||
- Client: update emoji picker immediately on all input @saschanaz
|
- Client: update emoji picker immediately on all input @saschanaz
|
||||||
- Client: チャートのツールチップが画面に残ることがあるのを修正 @syuilo
|
- Client: チャートのツールチップが画面に残ることがあるのを修正 @syuilo
|
||||||
- Client: fix wrong link in tutorial @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)
|
## 12.119.1 (2022/12/03)
|
||||||
### Bugfixes
|
### Bugfixes
|
||||||
- Server: Mitigate AP reference chain DoS vector @skehmatics
|
- Server: Mitigate AP reference chain DoS vector @skehmatics
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
FROM node:18.12.1-bullseye AS builder
|
FROM node:18.13.0-bullseye AS builder
|
||||||
|
|
||||||
ARG NODE_ENV=production
|
ARG NODE_ENV=production
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ COPY . ./
|
|||||||
RUN git submodule update --init
|
RUN git submodule update --init
|
||||||
RUN yarn build
|
RUN yarn build
|
||||||
|
|
||||||
FROM node:18.12.1-bullseye-slim AS runner
|
FROM node:18.13.0-bullseye-slim AS runner
|
||||||
|
|
||||||
WORKDIR /misskey
|
WORKDIR /misskey
|
||||||
|
|
||||||
|
@@ -380,6 +380,7 @@ administrator: "المدير"
|
|||||||
token: "الرمز المميز"
|
token: "الرمز المميز"
|
||||||
twoStepAuthentication: "الإستيثاق بعاملَيْن"
|
twoStepAuthentication: "الإستيثاق بعاملَيْن"
|
||||||
moderator: "مشرِف"
|
moderator: "مشرِف"
|
||||||
|
moderation: "الإشراف"
|
||||||
nUsersMentioned: "{n} مستخدمين أُشير إليهم"
|
nUsersMentioned: "{n} مستخدمين أُشير إليهم"
|
||||||
securityKey: "مفتاح الأمان"
|
securityKey: "مفتاح الأمان"
|
||||||
securityKeyName: "اسم المفتاح"
|
securityKeyName: "اسم المفتاح"
|
||||||
@@ -813,6 +814,10 @@ colored: "ملوّن"
|
|||||||
label: "التسمية"
|
label: "التسمية"
|
||||||
localOnly: "المحلي فقط"
|
localOnly: "المحلي فقط"
|
||||||
account: "الحسابات"
|
account: "الحسابات"
|
||||||
|
cannotLoad: "تعذر التحميل"
|
||||||
|
like: "أعجبني"
|
||||||
|
show: "المظهر"
|
||||||
|
color: "اللون"
|
||||||
_emailUnavailable:
|
_emailUnavailable:
|
||||||
used: "هذا البريد الإلكتروني مستخدم"
|
used: "هذا البريد الإلكتروني مستخدم"
|
||||||
format: "صيغة البريد الإلكتروني غير صالحة"
|
format: "صيغة البريد الإلكتروني غير صالحة"
|
||||||
@@ -1113,6 +1118,8 @@ _weekday:
|
|||||||
friday: "الجمعة"
|
friday: "الجمعة"
|
||||||
saturday: "السبت"
|
saturday: "السبت"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "الملف التعريفي"
|
||||||
|
instanceInfo: "معلومات مثيل الخادم"
|
||||||
memo: "ملاحظة لاصقة"
|
memo: "ملاحظة لاصقة"
|
||||||
notifications: "الإشعارات"
|
notifications: "الإشعارات"
|
||||||
timeline: "الخيط الزمني"
|
timeline: "الخيط الزمني"
|
||||||
@@ -1228,6 +1235,11 @@ _timelines:
|
|||||||
local: "المحلي"
|
local: "المحلي"
|
||||||
social: "الاجتماعي"
|
social: "الاجتماعي"
|
||||||
global: "الشامل"
|
global: "الشامل"
|
||||||
|
_play:
|
||||||
|
viewSource: "اظهر المصدر"
|
||||||
|
featured: "الأكثر شعبية"
|
||||||
|
title: "العنوان"
|
||||||
|
summary: "الوصف"
|
||||||
_pages:
|
_pages:
|
||||||
newPage: "أنشئ صفحة جديدة"
|
newPage: "أنشئ صفحة جديدة"
|
||||||
editPage: "عدّل الصفحة"
|
editPage: "عدّل الصفحة"
|
||||||
@@ -1285,7 +1297,6 @@ _notification:
|
|||||||
youGotReply: "ردّ عليك {name}"
|
youGotReply: "ردّ عليك {name}"
|
||||||
youGotQuote: "اقتبس منك {name}"
|
youGotQuote: "اقتبس منك {name}"
|
||||||
youRenoted: "إعادت نشر من {name}"
|
youRenoted: "إعادت نشر من {name}"
|
||||||
youGotPoll: "شارك {name} في استطلاع الرأي"
|
|
||||||
youGotMessagingMessageFromUser: "لقد تلقيت رسالة مِن {name}"
|
youGotMessagingMessageFromUser: "لقد تلقيت رسالة مِن {name}"
|
||||||
youGotMessagingMessageFromGroup: "لقد أرسِلَت رسالة إلى الفريق {name}"
|
youGotMessagingMessageFromGroup: "لقد أرسِلَت رسالة إلى الفريق {name}"
|
||||||
youWereFollowed: "يتابعك"
|
youWereFollowed: "يتابعك"
|
||||||
@@ -1293,6 +1304,7 @@ _notification:
|
|||||||
yourFollowRequestAccepted: "قُبل طلب المتابعة"
|
yourFollowRequestAccepted: "قُبل طلب المتابعة"
|
||||||
youWereInvitedToGroup: "دُعيت إلى فريقٍ"
|
youWereInvitedToGroup: "دُعيت إلى فريقٍ"
|
||||||
pollEnded: "ظهرت نتائج الاستطلاع"
|
pollEnded: "ظهرت نتائج الاستطلاع"
|
||||||
|
unreadAntennaNote: "هوائي {name}"
|
||||||
_types:
|
_types:
|
||||||
all: "الكل"
|
all: "الكل"
|
||||||
follow: "متابِعون جدد"
|
follow: "متابِعون جدد"
|
||||||
@@ -1301,7 +1313,6 @@ _notification:
|
|||||||
renote: "أعد النشر"
|
renote: "أعد النشر"
|
||||||
quote: "الاقتباسات"
|
quote: "الاقتباسات"
|
||||||
reaction: "التفاعلات"
|
reaction: "التفاعلات"
|
||||||
pollVote: "مصوِت شارك في الاستطلاع"
|
|
||||||
receiveFollowRequest: "طلبات المتابعة المتلقاة"
|
receiveFollowRequest: "طلبات المتابعة المتلقاة"
|
||||||
followRequestAccepted: "طلبات المتابعة المقبولة"
|
followRequestAccepted: "طلبات المتابعة المقبولة"
|
||||||
groupInvited: "دعوات الفريق"
|
groupInvited: "دعوات الفريق"
|
||||||
|
@@ -851,6 +851,9 @@ colored: "রঙ্গিন"
|
|||||||
label: "লেবেল"
|
label: "লেবেল"
|
||||||
localOnly: "শুধুমাত্র লোকাল"
|
localOnly: "শুধুমাত্র লোকাল"
|
||||||
account: "অ্যাকাউন্টগুলি"
|
account: "অ্যাকাউন্টগুলি"
|
||||||
|
like: "পছন্দ করা"
|
||||||
|
show: "প্রদর্শন"
|
||||||
|
color: "রং"
|
||||||
_emailUnavailable:
|
_emailUnavailable:
|
||||||
used: "এই ইমেইল ঠিকানাটি ইতোমধ্যে ব্যবহৃত হয়েছে"
|
used: "এই ইমেইল ঠিকানাটি ইতোমধ্যে ব্যবহৃত হয়েছে"
|
||||||
format: "এই ইমেল ঠিকানাটি সঠিকভাবে লিখা হয়নি"
|
format: "এই ইমেল ঠিকানাটি সঠিকভাবে লিখা হয়নি"
|
||||||
@@ -1198,6 +1201,8 @@ _weekday:
|
|||||||
friday: "শুক্রবার"
|
friday: "শুক্রবার"
|
||||||
saturday: "শনিবার"
|
saturday: "শনিবার"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "প্রোফাইল"
|
||||||
|
instanceInfo: "ইন্সট্যান্সের তথ্য"
|
||||||
memo: "স্টিকি নোট"
|
memo: "স্টিকি নোট"
|
||||||
notifications: "বিজ্ঞপ্তি"
|
notifications: "বিজ্ঞপ্তি"
|
||||||
timeline: "টাইমলাইন"
|
timeline: "টাইমলাইন"
|
||||||
@@ -1319,6 +1324,12 @@ _timelines:
|
|||||||
local: "স্থানীয়"
|
local: "স্থানীয়"
|
||||||
social: "সামাজিক"
|
social: "সামাজিক"
|
||||||
global: "গ্লোবাল"
|
global: "গ্লোবাল"
|
||||||
|
_play:
|
||||||
|
viewSource: "উৎস দেখুন"
|
||||||
|
featured: "জনপ্রিয়"
|
||||||
|
title: "শিরোনাম"
|
||||||
|
script: "স্ক্রিপ্ট"
|
||||||
|
summary: "বর্ণনা"
|
||||||
_pages:
|
_pages:
|
||||||
newPage: "নতুন পৃষ্ঠা বানান"
|
newPage: "নতুন পৃষ্ঠা বানান"
|
||||||
editPage: "পৃষ্ঠাটি সম্পাদনা করুন"
|
editPage: "পৃষ্ঠাটি সম্পাদনা করুন"
|
||||||
@@ -1378,7 +1389,6 @@ _notification:
|
|||||||
youGotReply: "{name} আপনাকে জবাব দিয়েছে"
|
youGotReply: "{name} আপনাকে জবাব দিয়েছে"
|
||||||
youGotQuote: "{name} আপনাকে উদ্ধৃত করেছে"
|
youGotQuote: "{name} আপনাকে উদ্ধৃত করেছে"
|
||||||
youRenoted: "{name} এর Renote"
|
youRenoted: "{name} এর Renote"
|
||||||
youGotPoll: "{name} আপনার পোলে ভোট দিয়েছে"
|
|
||||||
youGotMessagingMessageFromUser: "{name} আপনাকে মেসেজ করেছে"
|
youGotMessagingMessageFromUser: "{name} আপনাকে মেসেজ করেছে"
|
||||||
youGotMessagingMessageFromGroup: "{name} গ্রুপে একটি নতুন মেসেজ আছে"
|
youGotMessagingMessageFromGroup: "{name} গ্রুপে একটি নতুন মেসেজ আছে"
|
||||||
youWereFollowed: "আপনাকে অনুসরণ করছে"
|
youWereFollowed: "আপনাকে অনুসরণ করছে"
|
||||||
@@ -1395,7 +1405,6 @@ _notification:
|
|||||||
renote: "রিনোট"
|
renote: "রিনোট"
|
||||||
quote: "উদ্ধৃতি"
|
quote: "উদ্ধৃতি"
|
||||||
reaction: "প্রতিক্রিয়া"
|
reaction: "প্রতিক্রিয়া"
|
||||||
pollVote: "পোলে ভোট আছে"
|
|
||||||
pollEnded: "পোল শেষ"
|
pollEnded: "পোল শেষ"
|
||||||
receiveFollowRequest: "প্রাপ্ত অনুসরণের অনুরোধসমূহ"
|
receiveFollowRequest: "প্রাপ্ত অনুসরণের অনুরোধসমূহ"
|
||||||
followRequestAccepted: "গৃহীত অনুসরণের অনুরোধসমূহ"
|
followRequestAccepted: "গৃহীত অনুসরণের অনুরোধসমূহ"
|
||||||
|
@@ -399,6 +399,8 @@ _antennaSources:
|
|||||||
userList: "Publicacions d'una llista d'usuaris"
|
userList: "Publicacions d'una llista d'usuaris"
|
||||||
userGroup: "Publicacions d'usuaris d'un grup"
|
userGroup: "Publicacions d'usuaris d'un grup"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "Perfil"
|
||||||
|
instanceInfo: "Informació del fitxer d'instal·lació"
|
||||||
notifications: "Notificacions"
|
notifications: "Notificacions"
|
||||||
timeline: "Línia de temps"
|
timeline: "Línia de temps"
|
||||||
activity: "Activitat"
|
activity: "Activitat"
|
||||||
|
@@ -610,6 +610,8 @@ speed: "Rychlost"
|
|||||||
slow: "Pomalá"
|
slow: "Pomalá"
|
||||||
fast: "Rychlá"
|
fast: "Rychlá"
|
||||||
account: "Účty"
|
account: "Účty"
|
||||||
|
show: "Zobrazit"
|
||||||
|
color: "Barva"
|
||||||
_ad:
|
_ad:
|
||||||
back: "Zpět"
|
back: "Zpět"
|
||||||
_gallery:
|
_gallery:
|
||||||
@@ -693,6 +695,8 @@ _weekday:
|
|||||||
friday: "Pátek"
|
friday: "Pátek"
|
||||||
saturday: "Sobota"
|
saturday: "Sobota"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "Váš profil"
|
||||||
|
instanceInfo: "Informace o instanci"
|
||||||
notifications: "Oznámení"
|
notifications: "Oznámení"
|
||||||
timeline: "Časová osa"
|
timeline: "Časová osa"
|
||||||
calendar: "Kalendář"
|
calendar: "Kalendář"
|
||||||
@@ -748,6 +752,9 @@ _charts:
|
|||||||
_timelines:
|
_timelines:
|
||||||
home: "Domů"
|
home: "Domů"
|
||||||
global: "Globální"
|
global: "Globální"
|
||||||
|
_play:
|
||||||
|
script: "Skript"
|
||||||
|
summary: "Popis"
|
||||||
_pages:
|
_pages:
|
||||||
newPage: "Vytvořit novou stránku"
|
newPage: "Vytvořit novou stránku"
|
||||||
editPage: "Upravit stránku"
|
editPage: "Upravit stránku"
|
||||||
|
@@ -609,7 +609,7 @@ regexpErrorDescription: "Im regulären Ausdruck deiner {tab}en Wortstummschaltun
|
|||||||
instanceMute: "Instanzstummschaltungen"
|
instanceMute: "Instanzstummschaltungen"
|
||||||
userSaysSomething: "{name} hat etwas gesagt"
|
userSaysSomething: "{name} hat etwas gesagt"
|
||||||
makeActive: "Aktivieren"
|
makeActive: "Aktivieren"
|
||||||
display: "Anzeigeart"
|
display: "Anzeigen"
|
||||||
copy: "Kopieren"
|
copy: "Kopieren"
|
||||||
metrics: "Metriken"
|
metrics: "Metriken"
|
||||||
overview: "Übersicht"
|
overview: "Übersicht"
|
||||||
@@ -916,6 +916,41 @@ loggedInAsBot: "Momentan als Bot angemeldet"
|
|||||||
tools: "Werkzeuge"
|
tools: "Werkzeuge"
|
||||||
cannotLoad: "Kann nicht geladen werden"
|
cannotLoad: "Kann nicht geladen werden"
|
||||||
numberOfProfileView: "Profilaufrufe"
|
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!"
|
||||||
|
roles: "Rollen"
|
||||||
|
role: "Rolle"
|
||||||
|
normalUser: "Standardbenutzer"
|
||||||
|
undefined: "Undefiniert"
|
||||||
|
assign: "Zuweisen"
|
||||||
|
unassign: "Entfernen"
|
||||||
|
color: "Farbe"
|
||||||
|
_role:
|
||||||
|
new: "Rolle erstellen"
|
||||||
|
edit: "Rolle bearbeiten"
|
||||||
|
name: "Rollenname"
|
||||||
|
description: "Rollenbeschreibung"
|
||||||
|
permission: "Rollenberechtigungen"
|
||||||
|
isPublic: "Öffentliche Rolle"
|
||||||
|
descriptionOfIsPublic: "Ist dies aktiviert, so kann jeder die Liste der Benutzer, die dieser Rolle zugewiesen sind, einsehen. Zusätzlich wird diese Rolle im Profil zugewiesener Benutzer angezeigt."
|
||||||
|
options: "Optionen"
|
||||||
|
baseRole: "Rollenvorlage"
|
||||||
|
useBaseValue: "Wert der Rollenvorlage verwenden"
|
||||||
|
chooseRoleToAssign: "Zuzuweisende Rolle auswählen"
|
||||||
|
canEditMembersByModerator: "Moderatoren können Benutzern diese Rolle zuweisen"
|
||||||
|
descriptionOfCanEditMembersByModerator: "Wenn aktiviert, so können Moderatoren und Adminstratoren anderen Benutzern diese Rolle zuweisen bzw. diese Zuweisung aufheben. Wenn deaktiviert, so ist es nur Administratoren möglich, Zuweisungen dieser Rolle zu verwalten."
|
||||||
|
_options:
|
||||||
|
gtlAvailable: "Kann auf die globale Chronik zugreifen"
|
||||||
|
ltlAvailable: "Kann auf die lokale Chronik zugreifen"
|
||||||
|
canPublicNote: "Kann öffentliche Notizen erstellen"
|
||||||
|
driveCapacity: "Drive-Kapazität"
|
||||||
|
antennaMax: "Maximale Anzahl an Antennen"
|
||||||
_sensitiveMediaDetection:
|
_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."
|
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"
|
sensitivity: "Erkennungssensitivität"
|
||||||
@@ -1294,6 +1329,8 @@ _weekday:
|
|||||||
friday: "Freitag"
|
friday: "Freitag"
|
||||||
saturday: "Samstag"
|
saturday: "Samstag"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "Profil"
|
||||||
|
instanceInfo: "Instanzinformationen"
|
||||||
memo: "Merkzettel"
|
memo: "Merkzettel"
|
||||||
notifications: "Benachrichtigungen"
|
notifications: "Benachrichtigungen"
|
||||||
timeline: "Chronik"
|
timeline: "Chronik"
|
||||||
@@ -1315,10 +1352,12 @@ _widgets:
|
|||||||
jobQueue: "Job-Warteschlange"
|
jobQueue: "Job-Warteschlange"
|
||||||
serverMetric: "Servermetriken"
|
serverMetric: "Servermetriken"
|
||||||
aiscript: "AiScript-Konsole"
|
aiscript: "AiScript-Konsole"
|
||||||
|
aiscriptApp: "AiScript-Anwendung"
|
||||||
aichan: "Ai"
|
aichan: "Ai"
|
||||||
userList: "Benutzerliste"
|
userList: "Benutzerliste"
|
||||||
_userList:
|
_userList:
|
||||||
chooseList: "Liste auswählen"
|
chooseList: "Liste auswählen"
|
||||||
|
clicker: "Klickzähler"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "Inhalt verbergen"
|
hide: "Inhalt verbergen"
|
||||||
show: "Inhalt anzeigen"
|
show: "Inhalt anzeigen"
|
||||||
@@ -1420,6 +1459,21 @@ _timelines:
|
|||||||
local: "Lokal"
|
local: "Lokal"
|
||||||
social: "Sozial"
|
social: "Sozial"
|
||||||
global: "Global"
|
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:
|
_pages:
|
||||||
newPage: "Seite erstellen"
|
newPage: "Seite erstellen"
|
||||||
editPage: "Seite bearbeiten"
|
editPage: "Seite bearbeiten"
|
||||||
@@ -1479,7 +1533,6 @@ _notification:
|
|||||||
youGotReply: "{name} hat dir geantwortet"
|
youGotReply: "{name} hat dir geantwortet"
|
||||||
youGotQuote: "{name} hat dich zitiert"
|
youGotQuote: "{name} hat dich zitiert"
|
||||||
youRenoted: "Renote deiner Notiz von {name}"
|
youRenoted: "Renote deiner Notiz von {name}"
|
||||||
youGotPoll: "{name} hat in deiner Umfrage abgestimmt"
|
|
||||||
youGotMessagingMessageFromUser: "{name} hat dir eine Chatnachricht gesendet"
|
youGotMessagingMessageFromUser: "{name} hat dir eine Chatnachricht gesendet"
|
||||||
youGotMessagingMessageFromGroup: "In die Gruppe {name} wurde eine Chatnachricht gesendet"
|
youGotMessagingMessageFromGroup: "In die Gruppe {name} wurde eine Chatnachricht gesendet"
|
||||||
youWereFollowed: "ist dir gefolgt"
|
youWereFollowed: "ist dir gefolgt"
|
||||||
@@ -1497,7 +1550,6 @@ _notification:
|
|||||||
renote: "Renotes"
|
renote: "Renotes"
|
||||||
quote: "Zitationen"
|
quote: "Zitationen"
|
||||||
reaction: "Reaktionen"
|
reaction: "Reaktionen"
|
||||||
pollVote: "Antworten auf Umfragen"
|
|
||||||
pollEnded: "Ende von Umfragen"
|
pollEnded: "Ende von Umfragen"
|
||||||
receiveFollowRequest: "Erhaltene Follow-Anfragen"
|
receiveFollowRequest: "Erhaltene Follow-Anfragen"
|
||||||
followRequestAccepted: "Akzeptierte Follow-Anfragen"
|
followRequestAccepted: "Akzeptierte Follow-Anfragen"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
_lang_: "Ελληνικά"
|
_lang_: "Ελληνικά"
|
||||||
monthAndDay: "{μήνας}/{ημέρα}"
|
monthAndDay: "{day}/{month}"
|
||||||
search: "Αναζήτηση"
|
search: "Αναζήτηση"
|
||||||
notifications: "Ειδοποιήσεις"
|
notifications: "Ειδοποιήσεις"
|
||||||
username: "Όνομα μέλους"
|
username: "Όνομα μέλους"
|
||||||
@@ -343,6 +343,8 @@ _antennaSources:
|
|||||||
userList: "Σημειώματα από καθορισμένη λίστα μελών"
|
userList: "Σημειώματα από καθορισμένη λίστα μελών"
|
||||||
userGroup: "Σημειώματα από μέλη καθορισμένης ομάδας"
|
userGroup: "Σημειώματα από μέλη καθορισμένης ομάδας"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "Προφίλ"
|
||||||
|
instanceInfo: "Πληροφορίες του instance"
|
||||||
notifications: "Ειδοποιήσεις"
|
notifications: "Ειδοποιήσεις"
|
||||||
timeline: "Χρονολόγιο"
|
timeline: "Χρονολόγιο"
|
||||||
calendar: "Ημερολόγιο"
|
calendar: "Ημερολόγιο"
|
||||||
|
@@ -916,6 +916,41 @@ loggedInAsBot: "Currently logged in as bot"
|
|||||||
tools: "Tools"
|
tools: "Tools"
|
||||||
cannotLoad: "Unable to load"
|
cannotLoad: "Unable to load"
|
||||||
numberOfProfileView: "Profile views"
|
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!"
|
||||||
|
roles: "Roles"
|
||||||
|
role: "Role"
|
||||||
|
normalUser: "Normal user"
|
||||||
|
undefined: "Undefined"
|
||||||
|
assign: "Assign"
|
||||||
|
unassign: "Unassign"
|
||||||
|
color: "Color"
|
||||||
|
_role:
|
||||||
|
new: "New role"
|
||||||
|
edit: "Edit role"
|
||||||
|
name: "Role name"
|
||||||
|
description: "Role description"
|
||||||
|
permission: "Role permissions"
|
||||||
|
isPublic: "Public role"
|
||||||
|
descriptionOfIsPublic: "Anyone will be able to view a list of users assigned to this role. In addition, this role will be displayed in the profiles of assigned users."
|
||||||
|
options: "Role options"
|
||||||
|
baseRole: "Base role"
|
||||||
|
useBaseValue: "Use base role value"
|
||||||
|
chooseRoleToAssign: "Select the role to assign"
|
||||||
|
canEditMembersByModerator: "Allow moderators to edit the list members of this role"
|
||||||
|
descriptionOfCanEditMembersByModerator: "When turned on, moderators as well as administrators will be able to assign and unassign users to this role. When turned off, only administrators will be able to assign users."
|
||||||
|
_options:
|
||||||
|
gtlAvailable: "Viewing the global timeline"
|
||||||
|
ltlAvailable: "Viewing the local timeline"
|
||||||
|
canPublicNote: "Can send public notes"
|
||||||
|
driveCapacity: "Drive capacity"
|
||||||
|
antennaMax: "Maximum number of antennas"
|
||||||
_sensitiveMediaDetection:
|
_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."
|
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"
|
sensitivity: "Detection sensitivity"
|
||||||
@@ -1294,6 +1329,8 @@ _weekday:
|
|||||||
friday: "Friday"
|
friday: "Friday"
|
||||||
saturday: "Saturday"
|
saturday: "Saturday"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "Profile"
|
||||||
|
instanceInfo: "Instance Information"
|
||||||
memo: "Sticky notes"
|
memo: "Sticky notes"
|
||||||
notifications: "Notifications"
|
notifications: "Notifications"
|
||||||
timeline: "Timeline"
|
timeline: "Timeline"
|
||||||
@@ -1315,10 +1352,12 @@ _widgets:
|
|||||||
jobQueue: "Job Queue"
|
jobQueue: "Job Queue"
|
||||||
serverMetric: "Server metrics"
|
serverMetric: "Server metrics"
|
||||||
aiscript: "AiScript console"
|
aiscript: "AiScript console"
|
||||||
|
aiscriptApp: "AiScript App"
|
||||||
aichan: "Ai"
|
aichan: "Ai"
|
||||||
userList: "User list"
|
userList: "User list"
|
||||||
_userList:
|
_userList:
|
||||||
chooseList: "Select a list"
|
chooseList: "Select a list"
|
||||||
|
clicker: "Clicker"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "Hide"
|
hide: "Hide"
|
||||||
show: "Show content"
|
show: "Show content"
|
||||||
@@ -1420,6 +1459,21 @@ _timelines:
|
|||||||
local: "Local"
|
local: "Local"
|
||||||
social: "Social"
|
social: "Social"
|
||||||
global: "Global"
|
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:
|
_pages:
|
||||||
newPage: "Create a new Page"
|
newPage: "Create a new Page"
|
||||||
editPage: "Edit this Page"
|
editPage: "Edit this Page"
|
||||||
@@ -1479,7 +1533,6 @@ _notification:
|
|||||||
youGotReply: "{name} replied to you"
|
youGotReply: "{name} replied to you"
|
||||||
youGotQuote: "{name} quoted you"
|
youGotQuote: "{name} quoted you"
|
||||||
youRenoted: "Renote from {name}"
|
youRenoted: "Renote from {name}"
|
||||||
youGotPoll: "{name} voted on your poll"
|
|
||||||
youGotMessagingMessageFromUser: "{name} sent you a chat message"
|
youGotMessagingMessageFromUser: "{name} sent you a chat message"
|
||||||
youGotMessagingMessageFromGroup: "A chat message was sent to the {name} group"
|
youGotMessagingMessageFromGroup: "A chat message was sent to the {name} group"
|
||||||
youWereFollowed: "followed you"
|
youWereFollowed: "followed you"
|
||||||
@@ -1497,7 +1550,6 @@ _notification:
|
|||||||
renote: "Renotes"
|
renote: "Renotes"
|
||||||
quote: "Quotes"
|
quote: "Quotes"
|
||||||
reaction: "Reactions"
|
reaction: "Reactions"
|
||||||
pollVote: "Votes on polls"
|
|
||||||
pollEnded: "Polls ending"
|
pollEnded: "Polls ending"
|
||||||
receiveFollowRequest: "Received follow requests"
|
receiveFollowRequest: "Received follow requests"
|
||||||
followRequestAccepted: "Accepted follow requests"
|
followRequestAccepted: "Accepted follow requests"
|
||||||
|
@@ -916,6 +916,9 @@ loggedInAsBot: "Inicio sesión como cuenta bot."
|
|||||||
tools: "Utilidades"
|
tools: "Utilidades"
|
||||||
cannotLoad: "No se puede cargar."
|
cannotLoad: "No se puede cargar."
|
||||||
numberOfProfileView: "Número de vistas de perfil"
|
numberOfProfileView: "Número de vistas de perfil"
|
||||||
|
like: "¡Muy bien!"
|
||||||
|
show: "Apariencia"
|
||||||
|
color: "Color"
|
||||||
_sensitiveMediaDetection:
|
_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."
|
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"
|
sensitivity: "Sensibilidad de detección"
|
||||||
@@ -1294,6 +1297,8 @@ _weekday:
|
|||||||
friday: "Viernes"
|
friday: "Viernes"
|
||||||
saturday: "Sábado"
|
saturday: "Sábado"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "Perfil"
|
||||||
|
instanceInfo: "información de la instancia"
|
||||||
memo: "Nota adhesiva"
|
memo: "Nota adhesiva"
|
||||||
notifications: "Notificaciones"
|
notifications: "Notificaciones"
|
||||||
timeline: "Linea de tiempo"
|
timeline: "Linea de tiempo"
|
||||||
@@ -1420,6 +1425,12 @@ _timelines:
|
|||||||
local: "Local"
|
local: "Local"
|
||||||
social: "Social"
|
social: "Social"
|
||||||
global: "Global"
|
global: "Global"
|
||||||
|
_play:
|
||||||
|
viewSource: "Ver la fuente"
|
||||||
|
featured: "Popular"
|
||||||
|
title: "Título"
|
||||||
|
script: "Script"
|
||||||
|
summary: "Descripción"
|
||||||
_pages:
|
_pages:
|
||||||
newPage: "Crear página"
|
newPage: "Crear página"
|
||||||
editPage: "Editar página"
|
editPage: "Editar página"
|
||||||
@@ -1479,7 +1490,6 @@ _notification:
|
|||||||
youGotReply: "Respuesta de {name}"
|
youGotReply: "Respuesta de {name}"
|
||||||
youGotQuote: "Citado por {name}"
|
youGotQuote: "Citado por {name}"
|
||||||
youRenoted: "Renotado por {name}"
|
youRenoted: "Renotado por {name}"
|
||||||
youGotPoll: "Encuestado por {name}"
|
|
||||||
youGotMessagingMessageFromUser: "{name} comenzó un chat contigo"
|
youGotMessagingMessageFromUser: "{name} comenzó un chat contigo"
|
||||||
youGotMessagingMessageFromGroup: "Tienes un chat de {name}"
|
youGotMessagingMessageFromGroup: "Tienes un chat de {name}"
|
||||||
youWereFollowed: "te ha seguido"
|
youWereFollowed: "te ha seguido"
|
||||||
@@ -1497,7 +1507,6 @@ _notification:
|
|||||||
renote: "Renotar"
|
renote: "Renotar"
|
||||||
quote: "Citar"
|
quote: "Citar"
|
||||||
reaction: "Reacción"
|
reaction: "Reacción"
|
||||||
pollVote: "Votado en la encuesta"
|
|
||||||
pollEnded: "La encuesta terminó"
|
pollEnded: "La encuesta terminó"
|
||||||
receiveFollowRequest: "Recibió una solicitud de seguimiento"
|
receiveFollowRequest: "Recibió una solicitud de seguimiento"
|
||||||
followRequestAccepted: "El seguimiento fue aceptado"
|
followRequestAccepted: "El seguimiento fue aceptado"
|
||||||
|
@@ -910,6 +910,12 @@ caption: "Libellé"
|
|||||||
loggedInAsBot: "Connecté actuellement en tant que bot"
|
loggedInAsBot: "Connecté actuellement en tant que bot"
|
||||||
tools: "Outils"
|
tools: "Outils"
|
||||||
cannotLoad: "Chargement impossible"
|
cannotLoad: "Chargement impossible"
|
||||||
|
like: "J'aime"
|
||||||
|
numberOfLikes: "Favoris"
|
||||||
|
show: "Affichage"
|
||||||
|
neverShow: "Ne plus afficher"
|
||||||
|
remindMeLater: "Peut-être plus tard"
|
||||||
|
color: "Couleur"
|
||||||
_sensitiveMediaDetection:
|
_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."
|
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"
|
sensitivity: "Sensibilité de la détection"
|
||||||
@@ -1287,6 +1293,8 @@ _weekday:
|
|||||||
friday: "Vendredi"
|
friday: "Vendredi"
|
||||||
saturday: "Samedi"
|
saturday: "Samedi"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "Profil"
|
||||||
|
instanceInfo: "Informations sur l’instance"
|
||||||
memo: "Note collante"
|
memo: "Note collante"
|
||||||
notifications: "Notifications"
|
notifications: "Notifications"
|
||||||
timeline: "Fil"
|
timeline: "Fil"
|
||||||
@@ -1411,6 +1419,12 @@ _timelines:
|
|||||||
local: "Local"
|
local: "Local"
|
||||||
social: "Social"
|
social: "Social"
|
||||||
global: "Global"
|
global: "Global"
|
||||||
|
_play:
|
||||||
|
viewSource: "Afficher la source"
|
||||||
|
featured: "Populaire"
|
||||||
|
title: "Titre"
|
||||||
|
script: "Script"
|
||||||
|
summary: "Description"
|
||||||
_pages:
|
_pages:
|
||||||
newPage: "Créer une page"
|
newPage: "Créer une page"
|
||||||
editPage: "Modifier une page"
|
editPage: "Modifier une page"
|
||||||
@@ -1470,7 +1484,6 @@ _notification:
|
|||||||
youGotReply: "Réponse de {name}"
|
youGotReply: "Réponse de {name}"
|
||||||
youGotQuote: "Cité·e par {name}"
|
youGotQuote: "Cité·e par {name}"
|
||||||
youRenoted: "{name} vous a Renoté"
|
youRenoted: "{name} vous a Renoté"
|
||||||
youGotPoll: "{name} a participé à votre sondage"
|
|
||||||
youGotMessagingMessageFromUser: "{name} vous envoyé un message"
|
youGotMessagingMessageFromUser: "{name} vous envoyé un message"
|
||||||
youGotMessagingMessageFromGroup: "Un message a été envoyé au groupe {name}"
|
youGotMessagingMessageFromGroup: "Un message a été envoyé au groupe {name}"
|
||||||
youWereFollowed: "Vous suit"
|
youWereFollowed: "Vous suit"
|
||||||
@@ -1488,7 +1501,6 @@ _notification:
|
|||||||
renote: "Renotes"
|
renote: "Renotes"
|
||||||
quote: "Citations"
|
quote: "Citations"
|
||||||
reaction: "Réactions"
|
reaction: "Réactions"
|
||||||
pollVote: "Votes dans des sondages"
|
|
||||||
pollEnded: "Sondages se cloturant"
|
pollEnded: "Sondages se cloturant"
|
||||||
receiveFollowRequest: "Demande d'abonnement reçue"
|
receiveFollowRequest: "Demande d'abonnement reçue"
|
||||||
followRequestAccepted: "Demande d'abonnement acceptée"
|
followRequestAccepted: "Demande d'abonnement acceptée"
|
||||||
|
@@ -855,6 +855,11 @@ colored: "Diwarnai"
|
|||||||
label: "Label"
|
label: "Label"
|
||||||
localOnly: "Hanya lokal"
|
localOnly: "Hanya lokal"
|
||||||
account: "Akun"
|
account: "Akun"
|
||||||
|
like: "Suka"
|
||||||
|
unlike: "Tidak Suka"
|
||||||
|
numberOfLikes: "Jumlah yang disukai"
|
||||||
|
show: "Tampilkan"
|
||||||
|
color: "Warna"
|
||||||
_emailUnavailable:
|
_emailUnavailable:
|
||||||
used: "Alamat surel ini telah digunakan"
|
used: "Alamat surel ini telah digunakan"
|
||||||
format: "Format tidak valid."
|
format: "Format tidak valid."
|
||||||
@@ -1202,6 +1207,8 @@ _weekday:
|
|||||||
friday: "Jumat"
|
friday: "Jumat"
|
||||||
saturday: "Sabtu"
|
saturday: "Sabtu"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "Profil"
|
||||||
|
instanceInfo: "Informasi Instansi"
|
||||||
memo: "Catatan memo"
|
memo: "Catatan memo"
|
||||||
notifications: "Pemberitahuan"
|
notifications: "Pemberitahuan"
|
||||||
timeline: "Linimasa"
|
timeline: "Linimasa"
|
||||||
@@ -1220,6 +1227,7 @@ _widgets:
|
|||||||
jobQueue: "Antrian kerja"
|
jobQueue: "Antrian kerja"
|
||||||
serverMetric: "Statistik peladen"
|
serverMetric: "Statistik peladen"
|
||||||
aiscript: "Konsol AiScript"
|
aiscript: "Konsol AiScript"
|
||||||
|
aiscriptApp: "Aplikasi AiScript"
|
||||||
aichan: "Ai"
|
aichan: "Ai"
|
||||||
_userList:
|
_userList:
|
||||||
chooseList: "Pilih daftar"
|
chooseList: "Pilih daftar"
|
||||||
@@ -1323,6 +1331,21 @@ _timelines:
|
|||||||
local: "Lokal"
|
local: "Lokal"
|
||||||
social: "Sosial"
|
social: "Sosial"
|
||||||
global: "Global"
|
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:
|
_pages:
|
||||||
newPage: "Buat halaman baru"
|
newPage: "Buat halaman baru"
|
||||||
editPage: "Sunting halaman"
|
editPage: "Sunting halaman"
|
||||||
@@ -1382,7 +1405,6 @@ _notification:
|
|||||||
youGotReply: "{name} membalas kamu"
|
youGotReply: "{name} membalas kamu"
|
||||||
youGotQuote: "{name} mengutip kamu"
|
youGotQuote: "{name} mengutip kamu"
|
||||||
youRenoted: "{name} me-renote kamu"
|
youRenoted: "{name} me-renote kamu"
|
||||||
youGotPoll: "{name} memilih di angket kamu"
|
|
||||||
youGotMessagingMessageFromUser: "{name} mengirimi kamu pesan"
|
youGotMessagingMessageFromUser: "{name} mengirimi kamu pesan"
|
||||||
youGotMessagingMessageFromGroup: "Sebuah pesan telah dikirim ke grup {name}"
|
youGotMessagingMessageFromGroup: "Sebuah pesan telah dikirim ke grup {name}"
|
||||||
youWereFollowed: "Mengikuti kamu"
|
youWereFollowed: "Mengikuti kamu"
|
||||||
@@ -1399,7 +1421,6 @@ _notification:
|
|||||||
renote: "Renote"
|
renote: "Renote"
|
||||||
quote: "Kutip"
|
quote: "Kutip"
|
||||||
reaction: "Reaksi"
|
reaction: "Reaksi"
|
||||||
pollVote: "Memilih di angket"
|
|
||||||
pollEnded: "Jajak pendapat berakhir"
|
pollEnded: "Jajak pendapat berakhir"
|
||||||
receiveFollowRequest: "Permintaan mengikuti diterima"
|
receiveFollowRequest: "Permintaan mengikuti diterima"
|
||||||
followRequestAccepted: "Permintaan mengikuti disetujui"
|
followRequestAccepted: "Permintaan mengikuti disetujui"
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
_lang_: "Italiano"
|
_lang_: "Italiano"
|
||||||
headlineMisskey: "Rete collegata tramite note"
|
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>."
|
poweredByMisskeyDescription: "{name} è uno dei servizi (chiamati istanze) che utilizzano la piattaforma open source <b>Misskey</b>."
|
||||||
monthAndDay: "{day}/{month}"
|
monthAndDay: "{day}/{month}"
|
||||||
search: "Cerca"
|
search: "Cerca"
|
||||||
@@ -28,7 +28,7 @@ timeline: "Timeline"
|
|||||||
noAccountDescription: "L'utente non ha ancora scritto niente nella biografia di profilo."
|
noAccountDescription: "L'utente non ha ancora scritto niente nella biografia di profilo."
|
||||||
login: "Accedi"
|
login: "Accedi"
|
||||||
loggingIn: "Accesso in corso..."
|
loggingIn: "Accesso in corso..."
|
||||||
logout: "Esci"
|
logout: "Uscita"
|
||||||
signup: "Iscriviti"
|
signup: "Iscriviti"
|
||||||
uploading: "Caricamento..."
|
uploading: "Caricamento..."
|
||||||
save: "Salva"
|
save: "Salva"
|
||||||
@@ -109,7 +109,7 @@ you: "Tu"
|
|||||||
clickToShow: "Clicca per visualizzare"
|
clickToShow: "Clicca per visualizzare"
|
||||||
sensitive: "Contenuto sensibile"
|
sensitive: "Contenuto sensibile"
|
||||||
add: "Aggiungi"
|
add: "Aggiungi"
|
||||||
reaction: "Reazione"
|
reaction: "Reazioni"
|
||||||
reactionSetting: "Reazioni visualizzate sul pannello"
|
reactionSetting: "Reazioni visualizzate sul pannello"
|
||||||
reactionSettingDescription2: "Trascina per riorganizzare, clicca per cancellare, usa il pulsante \"+\" per aggiungere."
|
reactionSettingDescription2: "Trascina per riorganizzare, clicca per cancellare, usa il pulsante \"+\" per aggiungere."
|
||||||
rememberNoteVisibility: "Ricordare le impostazioni di visibilità delle note"
|
rememberNoteVisibility: "Ricordare le impostazioni di visibilità delle note"
|
||||||
@@ -226,7 +226,7 @@ currentPassword: "Password attuale"
|
|||||||
newPassword: "Nuova Password"
|
newPassword: "Nuova Password"
|
||||||
newPasswordRetype: "Conferma password"
|
newPasswordRetype: "Conferma password"
|
||||||
attachFile: "Allega file"
|
attachFile: "Allega file"
|
||||||
more: "Altri!"
|
more: "Di più!"
|
||||||
featured: "Tendenze"
|
featured: "Tendenze"
|
||||||
usernameOrUserId: "Nome utente o ID utente"
|
usernameOrUserId: "Nome utente o ID utente"
|
||||||
noSuchUser: "Nessun utente trovato"
|
noSuchUser: "Nessun utente trovato"
|
||||||
@@ -512,7 +512,7 @@ newNoteRecived: "Vedi le nuove note"
|
|||||||
sounds: "Impostazioni suoni"
|
sounds: "Impostazioni suoni"
|
||||||
sound: "Impostazioni suoni"
|
sound: "Impostazioni suoni"
|
||||||
listen: "Ascolta"
|
listen: "Ascolta"
|
||||||
none: "Niente"
|
none: "Nessuno"
|
||||||
showInPage: "Visualizza in pagina"
|
showInPage: "Visualizza in pagina"
|
||||||
popout: "Finestra pop-out"
|
popout: "Finestra pop-out"
|
||||||
volume: "Volume"
|
volume: "Volume"
|
||||||
@@ -578,7 +578,7 @@ useFullReactionPicker: "Usa la totalità del pannello di reazioni"
|
|||||||
width: "Larghezza"
|
width: "Larghezza"
|
||||||
height: "Altezza"
|
height: "Altezza"
|
||||||
large: "Grande"
|
large: "Grande"
|
||||||
medium: "Predefinito"
|
medium: "Medio"
|
||||||
small: "Piccolo"
|
small: "Piccolo"
|
||||||
generateAccessToken: "Genera token di accesso"
|
generateAccessToken: "Genera token di accesso"
|
||||||
permission: "Autorizzazioni "
|
permission: "Autorizzazioni "
|
||||||
@@ -649,7 +649,7 @@ instanceTicker: "Informazioni sull'istanza da cui vengono le note"
|
|||||||
waitingFor: "Aspettando {x}"
|
waitingFor: "Aspettando {x}"
|
||||||
random: "Casuale"
|
random: "Casuale"
|
||||||
system: "Sistema"
|
system: "Sistema"
|
||||||
switchUi: "Cambiare interfaccia utente"
|
switchUi: "Cambiare interfaccia"
|
||||||
desktop: "Desktop"
|
desktop: "Desktop"
|
||||||
clip: "Nota"
|
clip: "Nota"
|
||||||
createNew: "Crea"
|
createNew: "Crea"
|
||||||
@@ -799,7 +799,7 @@ received: "Ricevuto"
|
|||||||
searchResult: "Risultati della Ricerca"
|
searchResult: "Risultati della Ricerca"
|
||||||
hashtags: "Hashtag"
|
hashtags: "Hashtag"
|
||||||
troubleshooting: "Risoluzione problemi"
|
troubleshooting: "Risoluzione problemi"
|
||||||
useBlurEffect: "Utilizza effetto sfocatura per l'interfaccia utente"
|
useBlurEffect: "Utilizza effetto sfocatura nell'interfaccia"
|
||||||
learnMore: "Più dettagli"
|
learnMore: "Più dettagli"
|
||||||
misskeyUpdated: "Misskey è stato aggiornato!"
|
misskeyUpdated: "Misskey è stato aggiornato!"
|
||||||
whatIsNew: "Visualizza le informazioni sull'aggiornamento"
|
whatIsNew: "Visualizza le informazioni sull'aggiornamento"
|
||||||
@@ -876,7 +876,7 @@ deleteAccount: "Eliminazione profilo"
|
|||||||
document: "Documento"
|
document: "Documento"
|
||||||
numberOfPageCache: "Numero di pagine cache"
|
numberOfPageCache: "Numero di pagine cache"
|
||||||
numberOfPageCacheDescription: "Aumenta l'usabilità, ma aumenta anche il carico e l'utilizzo della memoria."
|
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"
|
lastActiveDate: "Data dell'ultimo utilizzo"
|
||||||
statusbar: "Barra di stato"
|
statusbar: "Barra di stato"
|
||||||
pleaseSelect: "Scegli un'opzione"
|
pleaseSelect: "Scegli un'opzione"
|
||||||
@@ -916,6 +916,15 @@ loggedInAsBot: "Connessione come Bot"
|
|||||||
tools: "Strumenti"
|
tools: "Strumenti"
|
||||||
cannotLoad: "Caricamento impossibile"
|
cannotLoad: "Caricamento impossibile"
|
||||||
numberOfProfileView: "Visualizzazioni profilo"
|
numberOfProfileView: "Visualizzazioni profilo"
|
||||||
|
like: "Mi piace!"
|
||||||
|
unlike: "Non mi piace"
|
||||||
|
numberOfLikes: "Numero di Like"
|
||||||
|
show: "Visualizza"
|
||||||
|
neverShow: "Non mostrare più"
|
||||||
|
remindMeLater: "Rimanda"
|
||||||
|
didYouLikeMisskey: "Ti piace Misskey?"
|
||||||
|
pleaseDonate: "Misskey è il software libero utilizzato su {host}. Offrendo una donazione è più facile continuare a svilupparlo!"
|
||||||
|
color: "Colore"
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "L'apprendimento automatico può essere utilizzato per individuare automaticamente i media sensibili da moderare. Il carico del server aumenta leggermente."
|
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"
|
sensitivity: "Sensibilità di rilevamento"
|
||||||
@@ -1067,7 +1076,7 @@ _mfm:
|
|||||||
sparkleDescription: "Aggiungere effetti particellari scintillanti."
|
sparkleDescription: "Aggiungere effetti particellari scintillanti."
|
||||||
rotate: "Ruota"
|
rotate: "Ruota"
|
||||||
rotateDescription: "Ruota con un angolo specificato."
|
rotateDescription: "Ruota con un angolo specificato."
|
||||||
plain: "aereo"
|
plain: "Testo semplice"
|
||||||
plainDescription: "Disattiva tutta la sintassi interna."
|
plainDescription: "Disattiva tutta la sintassi interna."
|
||||||
_instanceTicker:
|
_instanceTicker:
|
||||||
none: "Nascondi"
|
none: "Nascondi"
|
||||||
@@ -1088,9 +1097,9 @@ _channel:
|
|||||||
usersCount: "{n} partecipanti"
|
usersCount: "{n} partecipanti"
|
||||||
notesCount: "{n} note"
|
notesCount: "{n} note"
|
||||||
_menuDisplay:
|
_menuDisplay:
|
||||||
sideFull: "laro"
|
sideFull: "Laterale"
|
||||||
sideIcon: "Orizzontale (icona)"
|
sideIcon: "Laterale (solo icone)"
|
||||||
top: "superficie"
|
top: "In alto"
|
||||||
hide: "Nascondere"
|
hide: "Nascondere"
|
||||||
_wordMute:
|
_wordMute:
|
||||||
muteWords: "Parole da filtrare"
|
muteWords: "Parole da filtrare"
|
||||||
@@ -1192,10 +1201,10 @@ _ago:
|
|||||||
future: "Futuro"
|
future: "Futuro"
|
||||||
justNow: "Ora"
|
justNow: "Ora"
|
||||||
secondsAgo: "{n}s fa"
|
secondsAgo: "{n}s fa"
|
||||||
minutesAgo: "{n}min fa"
|
minutesAgo: "{n} min fa"
|
||||||
hoursAgo: "{n} ore fa"
|
hoursAgo: "{n} ore fa"
|
||||||
daysAgo: "{n} giorni fa"
|
daysAgo: "{n} gg fa"
|
||||||
weeksAgo: "{n} settimane fa"
|
weeksAgo: "{n} sett. fa"
|
||||||
monthsAgo: "{n} mesi fa"
|
monthsAgo: "{n} mesi fa"
|
||||||
yearsAgo: "{n} anni fa"
|
yearsAgo: "{n} anni fa"
|
||||||
_time:
|
_time:
|
||||||
@@ -1205,13 +1214,13 @@ _time:
|
|||||||
day: "giorni"
|
day: "giorni"
|
||||||
_tutorial:
|
_tutorial:
|
||||||
title: "Come usare Misskey"
|
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_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."
|
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. "
|
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_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_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\"?"
|
step3_4: "Non ti viene niente in mente? Perché non scrivi semplicemente \"Ho appena cominciato a usare Misskey\"?"
|
||||||
step4_1: "Hai pubblicato qualcosa?"
|
step4_1: "Hai pubblicato qualcosa?"
|
||||||
@@ -1223,7 +1232,7 @@ _tutorial:
|
|||||||
step6_1: "Adesso, dovresti essere in grado di vedere le note dagli altri profili sulla tua timeline."
|
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_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."
|
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_2: "Se vuoi saperne di più su Misskey, puoi dare un'occhiata alla sezione {help}."
|
||||||
step7_3: "Da ultimo, buon divertimento su Misskey! 🚀"
|
step7_3: "Da ultimo, buon divertimento su Misskey! 🚀"
|
||||||
step8_1: "Per concludere, vuoi attivare le notifiche push?"
|
step8_1: "Per concludere, vuoi attivare le notifiche push?"
|
||||||
@@ -1294,6 +1303,8 @@ _weekday:
|
|||||||
friday: "Venerdì"
|
friday: "Venerdì"
|
||||||
saturday: "Sabato"
|
saturday: "Sabato"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "Profilo"
|
||||||
|
instanceInfo: "Informazioni sull'istanza"
|
||||||
memo: "Promemoria"
|
memo: "Promemoria"
|
||||||
notifications: "Notifiche"
|
notifications: "Notifiche"
|
||||||
timeline: "Timeline"
|
timeline: "Timeline"
|
||||||
@@ -1315,10 +1326,12 @@ _widgets:
|
|||||||
jobQueue: "Coda di lavoro"
|
jobQueue: "Coda di lavoro"
|
||||||
serverMetric: "Statistiche server"
|
serverMetric: "Statistiche server"
|
||||||
aiscript: "Console AiScript"
|
aiscript: "Console AiScript"
|
||||||
aichan: "indaco (tintura)"
|
aiscriptApp: "App AiScript"
|
||||||
|
aichan: "Mascotte Ai"
|
||||||
userList: "Elenco utenti"
|
userList: "Elenco utenti"
|
||||||
_userList:
|
_userList:
|
||||||
chooseList: "Seleziona una lista"
|
chooseList: "Seleziona una lista"
|
||||||
|
clicker: "Cliccaggio"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "Nascondere"
|
hide: "Nascondere"
|
||||||
show: "Mostra di più"
|
show: "Mostra di più"
|
||||||
@@ -1420,6 +1433,21 @@ _timelines:
|
|||||||
local: "Locale"
|
local: "Locale"
|
||||||
social: "Sociale"
|
social: "Sociale"
|
||||||
global: "Federata"
|
global: "Federata"
|
||||||
|
_play:
|
||||||
|
new: "Crea un Play"
|
||||||
|
edit: "Modifica i Play"
|
||||||
|
created: "Il Play è stato creato"
|
||||||
|
updated: "Il Play è stato aggiornato"
|
||||||
|
deleted: "Il Play è stato eliminato"
|
||||||
|
pageSetting: "Impostazioni di Play"
|
||||||
|
editThisPage: "Modifica il Play"
|
||||||
|
viewSource: "Visualizza sorgente"
|
||||||
|
my: "I miei Play"
|
||||||
|
liked: "Play piaciuti"
|
||||||
|
featured: "Popolari"
|
||||||
|
title: "Titolo"
|
||||||
|
script: "Script"
|
||||||
|
summary: "Descrizione"
|
||||||
_pages:
|
_pages:
|
||||||
newPage: "Crea pagina"
|
newPage: "Crea pagina"
|
||||||
editPage: "Modifica pagina"
|
editPage: "Modifica pagina"
|
||||||
@@ -1479,7 +1507,6 @@ _notification:
|
|||||||
youGotReply: "{name} ti ha risposto"
|
youGotReply: "{name} ti ha risposto"
|
||||||
youGotQuote: "{name} ha citato il tuo Nota e ha detto"
|
youGotQuote: "{name} ha citato il tuo Nota e ha detto"
|
||||||
youRenoted: "{name} ha rinotato"
|
youRenoted: "{name} ha rinotato"
|
||||||
youGotPoll: "{name} ha votato"
|
|
||||||
youGotMessagingMessageFromUser: "{name} ti ha mandato un messaggio"
|
youGotMessagingMessageFromUser: "{name} ti ha mandato un messaggio"
|
||||||
youGotMessagingMessageFromGroup: "{name} ti ha mandato un messaggio nella chat"
|
youGotMessagingMessageFromGroup: "{name} ti ha mandato un messaggio nella chat"
|
||||||
youWereFollowed: "Ha iniziato a seguirti"
|
youWereFollowed: "Ha iniziato a seguirti"
|
||||||
@@ -1497,7 +1524,6 @@ _notification:
|
|||||||
renote: "Rinota"
|
renote: "Rinota"
|
||||||
quote: "Cita"
|
quote: "Cita"
|
||||||
reaction: "Reazioni"
|
reaction: "Reazioni"
|
||||||
pollVote: "Voti ricevuti"
|
|
||||||
pollEnded: "Sondaggio chiuso."
|
pollEnded: "Sondaggio chiuso."
|
||||||
receiveFollowRequest: "Richiesta di follow ricevuta"
|
receiveFollowRequest: "Richiesta di follow ricevuta"
|
||||||
followRequestAccepted: "Richiesta di follow accettata"
|
followRequestAccepted: "Richiesta di follow accettata"
|
||||||
|
@@ -193,7 +193,7 @@ clearQueueConfirmText: "未配達の投稿は配送されなくなります。
|
|||||||
clearCachedFiles: "キャッシュをクリア"
|
clearCachedFiles: "キャッシュをクリア"
|
||||||
clearCachedFilesConfirm: "キャッシュされたリモートファイルをすべて削除しますか?"
|
clearCachedFilesConfirm: "キャッシュされたリモートファイルをすべて削除しますか?"
|
||||||
blockedInstances: "ブロックしたインスタンス"
|
blockedInstances: "ブロックしたインスタンス"
|
||||||
blockedInstancesDescription: "ブロックしたいインスタンスのホストを改行で区切って設定します。ブロックされたインスタンスは、このインスタンスとやり取りできなくなります。"
|
blockedInstancesDescription: "ブロックしたいインスタンスのホストを改行で区切って設定します。ブロックされたインスタンスは、このインスタンスとやり取りできなくなります。サブドメインもブロックされます。"
|
||||||
muteAndBlock: "ミュートとブロック"
|
muteAndBlock: "ミュートとブロック"
|
||||||
mutedUsers: "ミュートしたユーザー"
|
mutedUsers: "ミュートしたユーザー"
|
||||||
blockedUsers: "ブロックしたユーザー"
|
blockedUsers: "ブロックしたユーザー"
|
||||||
@@ -920,6 +920,62 @@ like: "いいね!"
|
|||||||
unlike: "いいねを解除"
|
unlike: "いいねを解除"
|
||||||
numberOfLikes: "いいね数"
|
numberOfLikes: "いいね数"
|
||||||
show: "表示"
|
show: "表示"
|
||||||
|
neverShow: "今後表示しない"
|
||||||
|
remindMeLater: "また後で"
|
||||||
|
didYouLikeMisskey: "Misskeyを気に入っていただけましたか?"
|
||||||
|
pleaseDonate: "Misskeyは{host}が使用している無料のソフトウェアです。これからも開発を続けられるように、ぜひ寄付をお願いします!"
|
||||||
|
roles: "ロール"
|
||||||
|
role: "ロール"
|
||||||
|
normalUser: "一般ユーザー"
|
||||||
|
undefined: "未定義"
|
||||||
|
assign: "アサイン"
|
||||||
|
unassign: "アサインを解除"
|
||||||
|
color: "色"
|
||||||
|
manageCustomEmojis: "カスタム絵文字の管理"
|
||||||
|
|
||||||
|
_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: "パブリック投稿の許可"
|
||||||
|
canInvite: "インスタンス招待コードの発行"
|
||||||
|
canManageCustomEmojis: "カスタム絵文字の管理"
|
||||||
|
driveCapacity: "ドライブ容量"
|
||||||
|
antennaMax: "アンテナの作成可能数"
|
||||||
|
wordMuteMax: "ワードミュートの最大文字数"
|
||||||
|
webhookMax: "Webhookの作成可能数"
|
||||||
|
_condition:
|
||||||
|
isLocal: "ローカルユーザー"
|
||||||
|
isRemote: "リモートユーザー"
|
||||||
|
createdLessThan: "アカウント作成から~以内"
|
||||||
|
createdMoreThan: "アカウント作成から~経過"
|
||||||
|
followersLessThanOrEq: "フォロワー数が~以下"
|
||||||
|
followersMoreThanOrEq: "フォロワー数が~以上"
|
||||||
|
followingLessThanOrEq: "フォロー数が~以下"
|
||||||
|
followingMoreThanOrEq: "フォロー数が~以上"
|
||||||
|
and: "~かつ~"
|
||||||
|
or: "~または~"
|
||||||
|
not: "~ではない"
|
||||||
|
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。"
|
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。"
|
||||||
@@ -1331,6 +1387,8 @@ _weekday:
|
|||||||
saturday: "土曜日"
|
saturday: "土曜日"
|
||||||
|
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "プロフィール"
|
||||||
|
instanceInfo: "インスタンス情報"
|
||||||
memo: "付箋"
|
memo: "付箋"
|
||||||
notifications: "通知"
|
notifications: "通知"
|
||||||
timeline: "タイムライン"
|
timeline: "タイムライン"
|
||||||
@@ -1357,6 +1415,7 @@ _widgets:
|
|||||||
userList: "ユーザーリスト"
|
userList: "ユーザーリスト"
|
||||||
_userList:
|
_userList:
|
||||||
chooseList: "リストを選択"
|
chooseList: "リストを選択"
|
||||||
|
clicker: "クリッカー"
|
||||||
|
|
||||||
_cw:
|
_cw:
|
||||||
hide: "隠す"
|
hide: "隠す"
|
||||||
@@ -1546,7 +1605,6 @@ _notification:
|
|||||||
youGotReply: "{name}からのリプライ"
|
youGotReply: "{name}からのリプライ"
|
||||||
youGotQuote: "{name}による引用"
|
youGotQuote: "{name}による引用"
|
||||||
youRenoted: "{name}がRenoteしました"
|
youRenoted: "{name}がRenoteしました"
|
||||||
youGotPoll: "{name}が投票しました"
|
|
||||||
youGotMessagingMessageFromUser: "{name}からのチャットがあります"
|
youGotMessagingMessageFromUser: "{name}からのチャットがあります"
|
||||||
youGotMessagingMessageFromGroup: "{name}のチャットがあります"
|
youGotMessagingMessageFromGroup: "{name}のチャットがあります"
|
||||||
youWereFollowed: "フォローされました"
|
youWereFollowed: "フォローされました"
|
||||||
@@ -1565,7 +1623,6 @@ _notification:
|
|||||||
renote: "Renote"
|
renote: "Renote"
|
||||||
quote: "引用"
|
quote: "引用"
|
||||||
reaction: "リアクション"
|
reaction: "リアクション"
|
||||||
pollVote: "アンケートに投票された"
|
|
||||||
pollEnded: "アンケートが終了"
|
pollEnded: "アンケートが終了"
|
||||||
receiveFollowRequest: "フォロー申請を受け取った"
|
receiveFollowRequest: "フォロー申請を受け取った"
|
||||||
followRequestAccepted: "フォローが受理された"
|
followRequestAccepted: "フォローが受理された"
|
||||||
|
@@ -915,6 +915,40 @@ caption: "キャプション"
|
|||||||
loggedInAsBot: "Botアカウントでログイン中やで"
|
loggedInAsBot: "Botアカウントでログイン中やで"
|
||||||
tools: "ツール"
|
tools: "ツール"
|
||||||
cannotLoad: "読み込めへんで"
|
cannotLoad: "読み込めへんで"
|
||||||
|
numberOfProfileView: "プロフィール表示回数"
|
||||||
|
like: "ええやん!"
|
||||||
|
unlike: "いいねを解除"
|
||||||
|
numberOfLikes: "いいね数"
|
||||||
|
show: "表示"
|
||||||
|
neverShow: "今後表示しない"
|
||||||
|
remindMeLater: "また後で"
|
||||||
|
didYouLikeMisskey: "Misskeyを気に入っとっただけましたん?"
|
||||||
|
pleaseDonate: "Misskeyは{host}が使用している無料のソフトウェアやで。これからも開発を続けれるように、寄付したってな~。"
|
||||||
|
roles: "ロール"
|
||||||
|
role: "ロール"
|
||||||
|
undefined: "未定義"
|
||||||
|
assign: "アサイン"
|
||||||
|
unassign: "アサインを解除"
|
||||||
|
color: "色"
|
||||||
|
_role:
|
||||||
|
new: "ロールの作成"
|
||||||
|
edit: "ロールの編集"
|
||||||
|
name: "ロール名"
|
||||||
|
description: "ロールの説明"
|
||||||
|
isPublic: "ロールを公開"
|
||||||
|
descriptionOfIsPublic: "ロールにアサインされたユーザーを誰でも見ることができるで。そんで、ユーザーのプロフィールでこのロールが表示されるで。"
|
||||||
|
options: "オプション"
|
||||||
|
baseRole: "ベースロール"
|
||||||
|
useBaseValue: "ベースロールの値を使用"
|
||||||
|
chooseRoleToAssign: "アサインするロールを選択"
|
||||||
|
canEditMembersByModerator: "モデレーターのメンバー編集を許可"
|
||||||
|
descriptionOfCanEditMembersByModerator: "オンにすると、管理者に加えてモデレーターもこのロールへユーザーをアサイン/アサイン解除できるようになるで。オフにすると管理者のみが行えるで。"
|
||||||
|
_options:
|
||||||
|
gtlAvailable: "グローバルタイムラインの閲覧"
|
||||||
|
ltlAvailable: "ローカルタイムラインの閲覧"
|
||||||
|
canPublicNote: "パブリック投稿の許可"
|
||||||
|
driveCapacity: "ドライブ容量"
|
||||||
|
antennaMax: "アンテナの作成可能数"
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "機械学習を使って自動でセンシティブなメディアを検出して、モデレーションに役立てることができるで。サーバーの負荷が少し増えてまうなあ。"
|
description: "機械学習を使って自動でセンシティブなメディアを検出して、モデレーションに役立てることができるで。サーバーの負荷が少し増えてまうなあ。"
|
||||||
sensitivity: "検出感度やで"
|
sensitivity: "検出感度やで"
|
||||||
@@ -1293,6 +1327,8 @@ _weekday:
|
|||||||
friday: "金曜日"
|
friday: "金曜日"
|
||||||
saturday: "土曜日"
|
saturday: "土曜日"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "プロフィール"
|
||||||
|
instanceInfo: "インスタンス情報"
|
||||||
memo: "付箋"
|
memo: "付箋"
|
||||||
notifications: "通知"
|
notifications: "通知"
|
||||||
timeline: "タイムライン"
|
timeline: "タイムライン"
|
||||||
@@ -1314,10 +1350,12 @@ _widgets:
|
|||||||
jobQueue: "ジョブキュー"
|
jobQueue: "ジョブキュー"
|
||||||
serverMetric: "サーバーメトリクス"
|
serverMetric: "サーバーメトリクス"
|
||||||
aiscript: "AiScriptコンソール"
|
aiscript: "AiScriptコンソール"
|
||||||
|
aiscriptApp: "AiScript App"
|
||||||
aichan: "藍"
|
aichan: "藍"
|
||||||
userList: "ユーザーリスト"
|
userList: "ユーザーリスト"
|
||||||
_userList:
|
_userList:
|
||||||
chooseList: "リストを選ぶ"
|
chooseList: "リストを選ぶ"
|
||||||
|
clicker: "クリッカー"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "隠す"
|
hide: "隠す"
|
||||||
show: "続き見して!"
|
show: "続き見して!"
|
||||||
@@ -1381,6 +1419,7 @@ _profile:
|
|||||||
changeBanner: "バナー画像を変更するで"
|
changeBanner: "バナー画像を変更するで"
|
||||||
_exportOrImport:
|
_exportOrImport:
|
||||||
allNotes: "全てのノート"
|
allNotes: "全てのノート"
|
||||||
|
favoritedNotes: "お気に入りにしたノート"
|
||||||
followingList: "フォロー"
|
followingList: "フォロー"
|
||||||
muteList: "ミュート"
|
muteList: "ミュート"
|
||||||
blockingList: "ブロック"
|
blockingList: "ブロック"
|
||||||
@@ -1418,6 +1457,21 @@ _timelines:
|
|||||||
local: "ローカル"
|
local: "ローカル"
|
||||||
social: "ソーシャル"
|
social: "ソーシャル"
|
||||||
global: "グローバル"
|
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:
|
_pages:
|
||||||
newPage: "ページを作る"
|
newPage: "ページを作る"
|
||||||
editPage: "ページの編集"
|
editPage: "ページの編集"
|
||||||
@@ -1477,7 +1531,6 @@ _notification:
|
|||||||
youGotReply: "{name}からのリプライ"
|
youGotReply: "{name}からのリプライ"
|
||||||
youGotQuote: "{name}による引用"
|
youGotQuote: "{name}による引用"
|
||||||
youRenoted: "{name}がRenoteしたみたいやで"
|
youRenoted: "{name}がRenoteしたみたいやで"
|
||||||
youGotPoll: "{name}が投票したみたいやで"
|
|
||||||
youGotMessagingMessageFromUser: "{name}からのチャットがあるで"
|
youGotMessagingMessageFromUser: "{name}からのチャットがあるで"
|
||||||
youGotMessagingMessageFromGroup: "{name}のチャットがあるで"
|
youGotMessagingMessageFromGroup: "{name}のチャットがあるで"
|
||||||
youWereFollowed: "フォローされたで"
|
youWereFollowed: "フォローされたで"
|
||||||
@@ -1495,7 +1548,6 @@ _notification:
|
|||||||
renote: "Renote"
|
renote: "Renote"
|
||||||
quote: "引用"
|
quote: "引用"
|
||||||
reaction: "リアクション"
|
reaction: "リアクション"
|
||||||
pollVote: "アンケートに投票されたで"
|
|
||||||
pollEnded: "アンケートが終了したで"
|
pollEnded: "アンケートが終了したで"
|
||||||
receiveFollowRequest: "フォロー許可してほしいみたいやで"
|
receiveFollowRequest: "フォロー許可してほしいみたいやで"
|
||||||
followRequestAccepted: "フォローが受理されたで"
|
followRequestAccepted: "フォローが受理されたで"
|
||||||
|
@@ -73,6 +73,7 @@ _sfx:
|
|||||||
_permissions:
|
_permissions:
|
||||||
"write:account": "Ẓreg talɣut n umiḍan-ik·im"
|
"write:account": "Ẓreg talɣut n umiḍan-ik·im"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "Amaɣnu"
|
||||||
notifications: "Ilɣuyen"
|
notifications: "Ilɣuyen"
|
||||||
_userList:
|
_userList:
|
||||||
chooseList: "Fren tabdart"
|
chooseList: "Fren tabdart"
|
||||||
|
@@ -69,6 +69,7 @@ _mfm:
|
|||||||
_sfx:
|
_sfx:
|
||||||
notification: "ಅಧಿಸೂಚನೆಗಳು"
|
notification: "ಅಧಿಸೂಚನೆಗಳು"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "ಪ್ರೊಫೈಲು"
|
||||||
notifications: "ಅಧಿಸೂಚನೆಗಳು"
|
notifications: "ಅಧಿಸೂಚನೆಗಳು"
|
||||||
timeline: "ಸಮಯಸಾಲು"
|
timeline: "ಸಮಯಸಾಲು"
|
||||||
_cw:
|
_cw:
|
||||||
|
@@ -15,7 +15,7 @@ gotIt: "알겠어요"
|
|||||||
cancel: "취소"
|
cancel: "취소"
|
||||||
noThankYou: "나중에"
|
noThankYou: "나중에"
|
||||||
enterUsername: "유저명 입력"
|
enterUsername: "유저명 입력"
|
||||||
renotedBy: "{user}님의 리노트"
|
renotedBy: "{user}님이 리노트"
|
||||||
noNotes: "노트가 없습니다"
|
noNotes: "노트가 없습니다"
|
||||||
noNotifications: "표시할 알림이 없습니다"
|
noNotifications: "표시할 알림이 없습니다"
|
||||||
instance: "인스턴스"
|
instance: "인스턴스"
|
||||||
@@ -916,6 +916,39 @@ loggedInAsBot: "봇 계정으로 로그인중"
|
|||||||
tools: "도구"
|
tools: "도구"
|
||||||
cannotLoad: "불러오지 못했습니다"
|
cannotLoad: "불러오지 못했습니다"
|
||||||
numberOfProfileView: "프로필 뷰 수"
|
numberOfProfileView: "프로필 뷰 수"
|
||||||
|
like: "좋아요!"
|
||||||
|
unlike: "좋아요 취소"
|
||||||
|
numberOfLikes: "좋아요 수"
|
||||||
|
show: "표시"
|
||||||
|
neverShow: "다시 보지 않기"
|
||||||
|
remindMeLater: "나중에 알림"
|
||||||
|
didYouLikeMisskey: "Misskey가 마음에 드시나요?"
|
||||||
|
pleaseDonate: "{host}은(는) 무료 소프트웨어 Misskey를 사용합니다. 후원을 통해 저희의 개발이 이어질 수 있게 도와주세요!"
|
||||||
|
roles: "역할"
|
||||||
|
role: "역할"
|
||||||
|
undefined: "정의되지 않음"
|
||||||
|
assign: "할당"
|
||||||
|
unassign: "할당 취소"
|
||||||
|
color: "색"
|
||||||
|
_role:
|
||||||
|
new: "새 역할 생성"
|
||||||
|
edit: "역할 수정"
|
||||||
|
name: "역할 이름"
|
||||||
|
description: "역할 설명"
|
||||||
|
isPublic: "공개 역할"
|
||||||
|
descriptionOfIsPublic: "역할에 할당된 사용자를 누구나 볼 수 있습니다. 또한 사용자 프로필에 이 역할이 표시됩니다."
|
||||||
|
options: "옵션"
|
||||||
|
baseRole: "기본 역할"
|
||||||
|
useBaseValue: "기본값 사용"
|
||||||
|
chooseRoleToAssign: "할당할 역할 선택"
|
||||||
|
canEditMembersByModerator: "모더레이터의 역할 수정 허용"
|
||||||
|
descriptionOfCanEditMembersByModerator: "이 옵션을 켜면 모더레이터도 이 역할에 사용자를 추가하거나 삭제할 수 있습니다. 꺼져 있으면 관리자만 가능합니다."
|
||||||
|
_options:
|
||||||
|
gtlAvailable: "글로벌 타임라인 보이기"
|
||||||
|
ltlAvailable: "로컬 타임라인 보이기"
|
||||||
|
canPublicNote: "공개 노트 허용"
|
||||||
|
driveCapacity: "드라이브 용량"
|
||||||
|
antennaMax: "최대 안테나 생성 허용 수"
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "기계학습을 통해 자동으로 민감한 미디어를 탐지하여, 모더레이션에 참고할 수 있도록 합니다. 서버의 부하를 약간 증가시킵니다."
|
description: "기계학습을 통해 자동으로 민감한 미디어를 탐지하여, 모더레이션에 참고할 수 있도록 합니다. 서버의 부하를 약간 증가시킵니다."
|
||||||
sensitivity: "탐지 민감도"
|
sensitivity: "탐지 민감도"
|
||||||
@@ -1294,6 +1327,8 @@ _weekday:
|
|||||||
friday: "금요일"
|
friday: "금요일"
|
||||||
saturday: "토요일"
|
saturday: "토요일"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "프로필"
|
||||||
|
instanceInfo: "인스턴스 정보"
|
||||||
memo: "스티커 메모"
|
memo: "스티커 메모"
|
||||||
notifications: "알림"
|
notifications: "알림"
|
||||||
timeline: "타임라인"
|
timeline: "타임라인"
|
||||||
@@ -1315,10 +1350,12 @@ _widgets:
|
|||||||
jobQueue: "작업 대기열"
|
jobQueue: "작업 대기열"
|
||||||
serverMetric: "서버 통계"
|
serverMetric: "서버 통계"
|
||||||
aiscript: "AiScript 콘솔"
|
aiscript: "AiScript 콘솔"
|
||||||
|
aiscriptApp: "AiScript 앱"
|
||||||
aichan: "아이"
|
aichan: "아이"
|
||||||
userList: "사용자 목록"
|
userList: "사용자 목록"
|
||||||
_userList:
|
_userList:
|
||||||
chooseList: "리스트 선택"
|
chooseList: "리스트 선택"
|
||||||
|
clicker: "클리커"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "숨기기"
|
hide: "숨기기"
|
||||||
show: "더 보기"
|
show: "더 보기"
|
||||||
@@ -1420,6 +1457,21 @@ _timelines:
|
|||||||
local: "로컬"
|
local: "로컬"
|
||||||
social: "소셜"
|
social: "소셜"
|
||||||
global: "글로벌"
|
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:
|
_pages:
|
||||||
newPage: "페이지 만들기"
|
newPage: "페이지 만들기"
|
||||||
editPage: "페이지 수정"
|
editPage: "페이지 수정"
|
||||||
@@ -1479,7 +1531,6 @@ _notification:
|
|||||||
youGotReply: "{name}님이 답글함"
|
youGotReply: "{name}님이 답글함"
|
||||||
youGotQuote: "{name}님이 인용함"
|
youGotQuote: "{name}님이 인용함"
|
||||||
youRenoted: "{name}님이 Renote"
|
youRenoted: "{name}님이 Renote"
|
||||||
youGotPoll: "{name}님이 투표함"
|
|
||||||
youGotMessagingMessageFromUser: "{name} 님이 보낸 채팅이 있어요"
|
youGotMessagingMessageFromUser: "{name} 님이 보낸 채팅이 있어요"
|
||||||
youGotMessagingMessageFromGroup: "{name}에서 보낸 채팅이 있어요"
|
youGotMessagingMessageFromGroup: "{name}에서 보낸 채팅이 있어요"
|
||||||
youWereFollowed: "새로운 팔로워가 있습니다"
|
youWereFollowed: "새로운 팔로워가 있습니다"
|
||||||
@@ -1497,7 +1548,6 @@ _notification:
|
|||||||
renote: "리노트"
|
renote: "리노트"
|
||||||
quote: "인용"
|
quote: "인용"
|
||||||
reaction: "리액션"
|
reaction: "리액션"
|
||||||
pollVote: "투표 참여"
|
|
||||||
pollEnded: "투표가 종료됨"
|
pollEnded: "투표가 종료됨"
|
||||||
receiveFollowRequest: "팔로우 요청을 받았을 때"
|
receiveFollowRequest: "팔로우 요청을 받았을 때"
|
||||||
followRequestAccepted: "팔로우 요청이 승인되었을 때"
|
followRequestAccepted: "팔로우 요청이 승인되었을 때"
|
||||||
|
@@ -440,6 +440,8 @@ _sfx:
|
|||||||
notification: "Meldingen"
|
notification: "Meldingen"
|
||||||
chat: "Chat"
|
chat: "Chat"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "Profiel"
|
||||||
|
instanceInfo: "Serverinformatie"
|
||||||
notifications: "Meldingen"
|
notifications: "Meldingen"
|
||||||
timeline: "Tijdlijn"
|
timeline: "Tijdlijn"
|
||||||
activity: "Activiteit"
|
activity: "Activiteit"
|
||||||
|
@@ -866,6 +866,9 @@ pushNotificationNotSupported: "Przeglądarka lub instancja nie obsługuje powiad
|
|||||||
sendPushNotificationReadMessage: "Usuń powiadomienia push po przeczytaniu powiadomień i wiadomości."
|
sendPushNotificationReadMessage: "Usuń powiadomienia push po przeczytaniu powiadomień i wiadomości."
|
||||||
sendPushNotificationReadMessageCaption: "Chwilowo pojawi się powiadomienie \"{emptyPushNotificationMessage}\". Może wzrosnąć zużycie baterii urządzenia."
|
sendPushNotificationReadMessageCaption: "Chwilowo pojawi się powiadomienie \"{emptyPushNotificationMessage}\". Może wzrosnąć zużycie baterii urządzenia."
|
||||||
loggedInAsBot: "Jesteś obecnie zalogowany/a jako bot"
|
loggedInAsBot: "Jesteś obecnie zalogowany/a jako bot"
|
||||||
|
like: "Polub"
|
||||||
|
show: "Wyświetlanie"
|
||||||
|
color: "Kolor"
|
||||||
_sensitiveMediaDetection:
|
_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."
|
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"
|
setSensitiveFlagAutomatically: "Oznacz jako NSFW"
|
||||||
@@ -1211,6 +1214,8 @@ _weekday:
|
|||||||
friday: "Piątek"
|
friday: "Piątek"
|
||||||
saturday: "Sobota"
|
saturday: "Sobota"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "Profil"
|
||||||
|
instanceInfo: "Informacje o instancji"
|
||||||
memo: "Przypięte notatki"
|
memo: "Przypięte notatki"
|
||||||
notifications: "Powiadomienia"
|
notifications: "Powiadomienia"
|
||||||
timeline: "Oś czasu"
|
timeline: "Oś czasu"
|
||||||
@@ -1313,6 +1318,12 @@ _timelines:
|
|||||||
local: "Lokalne"
|
local: "Lokalne"
|
||||||
social: "Społeczność"
|
social: "Społeczność"
|
||||||
global: "Globalna"
|
global: "Globalna"
|
||||||
|
_play:
|
||||||
|
viewSource: "Zobacz źródło"
|
||||||
|
featured: "Wyróżnione"
|
||||||
|
title: "Tytuł"
|
||||||
|
script: "Skrypt"
|
||||||
|
summary: "Opis"
|
||||||
_pages:
|
_pages:
|
||||||
newPage: "Utwórz stronę"
|
newPage: "Utwórz stronę"
|
||||||
editPage: "Edytuj tę stronę"
|
editPage: "Edytuj tę stronę"
|
||||||
@@ -1372,7 +1383,6 @@ _notification:
|
|||||||
youGotReply: "{name} odpowiedział(a) Tobie"
|
youGotReply: "{name} odpowiedział(a) Tobie"
|
||||||
youGotQuote: "{name} zacytował(a) Ciebie"
|
youGotQuote: "{name} zacytował(a) Ciebie"
|
||||||
youRenoted: "{name} udostępnił(a) Twój wpis"
|
youRenoted: "{name} udostępnił(a) Twój wpis"
|
||||||
youGotPoll: "{name} zagłosował(a) w Twojej ankiecie"
|
|
||||||
youGotMessagingMessageFromUser: "{name} wysłał(a) Ci wiadomość"
|
youGotMessagingMessageFromUser: "{name} wysłał(a) Ci wiadomość"
|
||||||
youGotMessagingMessageFromGroup: "Została wysłana wiadomość do grupy {name}"
|
youGotMessagingMessageFromGroup: "Została wysłana wiadomość do grupy {name}"
|
||||||
youWereFollowed: "Zaobserwował(a) Cię"
|
youWereFollowed: "Zaobserwował(a) Cię"
|
||||||
@@ -1390,7 +1400,6 @@ _notification:
|
|||||||
renote: "Udostępnij"
|
renote: "Udostępnij"
|
||||||
quote: "Cytuj"
|
quote: "Cytuj"
|
||||||
reaction: "Reakcja"
|
reaction: "Reakcja"
|
||||||
pollVote: "Głosy w ankietach"
|
|
||||||
receiveFollowRequest: "Otrzymano prośbę o możliwość obserwacji"
|
receiveFollowRequest: "Otrzymano prośbę o możliwość obserwacji"
|
||||||
followRequestAccepted: "Przyjęto prośbę o możliwość obserwacji"
|
followRequestAccepted: "Przyjęto prośbę o możliwość obserwacji"
|
||||||
groupInvited: "Zaproszono do grup"
|
groupInvited: "Zaproszono do grup"
|
||||||
|
@@ -488,6 +488,8 @@ _sfx:
|
|||||||
notification: "Notificações"
|
notification: "Notificações"
|
||||||
chat: "Chat"
|
chat: "Chat"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "Perfil"
|
||||||
|
instanceInfo: "Informações da instância"
|
||||||
notifications: "Notificações"
|
notifications: "Notificações"
|
||||||
timeline: "Timeline"
|
timeline: "Timeline"
|
||||||
activity: "atividade"
|
activity: "atividade"
|
||||||
@@ -524,7 +526,6 @@ _notification:
|
|||||||
youGotMention: "{name} te mencionou"
|
youGotMention: "{name} te mencionou"
|
||||||
youGotReply: "{name} te respondeu"
|
youGotReply: "{name} te respondeu"
|
||||||
youGotQuote: "{name} te citou"
|
youGotQuote: "{name} te citou"
|
||||||
youGotPoll: "{name} votou em sua enquete"
|
|
||||||
youGotMessagingMessageFromUser: "{name} te mandou uma mensagem de bate-papo"
|
youGotMessagingMessageFromUser: "{name} te mandou uma mensagem de bate-papo"
|
||||||
youGotMessagingMessageFromGroup: "Uma mensagem foi mandada para o grupo {name}"
|
youGotMessagingMessageFromGroup: "Uma mensagem foi mandada para o grupo {name}"
|
||||||
youWereFollowed: "Você tem um novo seguidor"
|
youWereFollowed: "Você tem um novo seguidor"
|
||||||
@@ -541,7 +542,6 @@ _notification:
|
|||||||
renote: "Repostar"
|
renote: "Repostar"
|
||||||
quote: "Citar"
|
quote: "Citar"
|
||||||
reaction: "Reações"
|
reaction: "Reações"
|
||||||
pollVote: "Votações em enquetes"
|
|
||||||
pollEnded: "Enquetes terminando"
|
pollEnded: "Enquetes terminando"
|
||||||
receiveFollowRequest: "Recebeu pedidos de seguimento"
|
receiveFollowRequest: "Recebeu pedidos de seguimento"
|
||||||
followRequestAccepted: "Aceitou pedidos de seguimento"
|
followRequestAccepted: "Aceitou pedidos de seguimento"
|
||||||
|
@@ -647,6 +647,7 @@ middle: "Mediu"
|
|||||||
sent: "Trimite"
|
sent: "Trimite"
|
||||||
searchByGoogle: "Caută"
|
searchByGoogle: "Caută"
|
||||||
file: "Fișiere"
|
file: "Fișiere"
|
||||||
|
show: "Arată"
|
||||||
_email:
|
_email:
|
||||||
_follow:
|
_follow:
|
||||||
title: "te-a urmărit"
|
title: "te-a urmărit"
|
||||||
@@ -666,6 +667,8 @@ _sfx:
|
|||||||
notification: "Notificări"
|
notification: "Notificări"
|
||||||
chat: "Chat"
|
chat: "Chat"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "Profil"
|
||||||
|
instanceInfo: "Informații despre instanță"
|
||||||
notifications: "Notificări"
|
notifications: "Notificări"
|
||||||
timeline: "Cronologie"
|
timeline: "Cronologie"
|
||||||
activity: "Activitate"
|
activity: "Activitate"
|
||||||
@@ -690,6 +693,9 @@ _charts:
|
|||||||
federation: "Federație"
|
federation: "Federație"
|
||||||
_timelines:
|
_timelines:
|
||||||
home: "Acasă"
|
home: "Acasă"
|
||||||
|
_play:
|
||||||
|
script: "Script"
|
||||||
|
summary: "Descriere"
|
||||||
_pages:
|
_pages:
|
||||||
blocks:
|
blocks:
|
||||||
image: "Imagini"
|
image: "Imagini"
|
||||||
|
@@ -864,6 +864,9 @@ enableAutoSensitiveDescription: "Если доступно, используйт
|
|||||||
account: "Учётные записи"
|
account: "Учётные записи"
|
||||||
windowMaximize: "Развернуть"
|
windowMaximize: "Развернуть"
|
||||||
windowRestore: "Восстановить"
|
windowRestore: "Восстановить"
|
||||||
|
like: "Нравится!"
|
||||||
|
show: "Отображение"
|
||||||
|
color: "Цвет"
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "Машинное обучение может быть использовано для автоматического обнаружения чувствительных медиа для модерации. Нагрузка на сервер увеличивается незначительно."
|
description: "Машинное обучение может быть использовано для автоматического обнаружения чувствительных медиа для модерации. Нагрузка на сервер увеличивается незначительно."
|
||||||
setSensitiveFlagAutomatically: "Установить флаг NSFW"
|
setSensitiveFlagAutomatically: "Установить флаг NSFW"
|
||||||
@@ -1211,6 +1214,8 @@ _weekday:
|
|||||||
friday: "Пятница"
|
friday: "Пятница"
|
||||||
saturday: "Суббота"
|
saturday: "Суббота"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "Профиль"
|
||||||
|
instanceInfo: "Информация об инстансе"
|
||||||
memo: "Напоминания"
|
memo: "Напоминания"
|
||||||
notifications: "Уведомления"
|
notifications: "Уведомления"
|
||||||
timeline: "Лента"
|
timeline: "Лента"
|
||||||
@@ -1332,6 +1337,12 @@ _timelines:
|
|||||||
local: "Местная"
|
local: "Местная"
|
||||||
social: "Социальная"
|
social: "Социальная"
|
||||||
global: "Всеобщая"
|
global: "Всеобщая"
|
||||||
|
_play:
|
||||||
|
viewSource: "Просмотр исходника"
|
||||||
|
featured: "Популярные"
|
||||||
|
title: "Заголовок"
|
||||||
|
script: "Скрипт"
|
||||||
|
summary: "Описание"
|
||||||
_pages:
|
_pages:
|
||||||
newPage: "Создать страницу"
|
newPage: "Создать страницу"
|
||||||
editPage: "Править страницу"
|
editPage: "Править страницу"
|
||||||
@@ -1391,7 +1402,6 @@ _notification:
|
|||||||
youGotReply: "{name} отвечает вам."
|
youGotReply: "{name} отвечает вам."
|
||||||
youGotQuote: "{name} цитирует вас."
|
youGotQuote: "{name} цитирует вас."
|
||||||
youRenoted: "{name} передаёт вашу заметку."
|
youRenoted: "{name} передаёт вашу заметку."
|
||||||
youGotPoll: "{name} участвует в вашем опросе."
|
|
||||||
youGotMessagingMessageFromUser: "{name} пишет вам."
|
youGotMessagingMessageFromUser: "{name} пишет вам."
|
||||||
youGotMessagingMessageFromGroup: "Новое сообщение в группе «{name}»."
|
youGotMessagingMessageFromGroup: "Новое сообщение в группе «{name}»."
|
||||||
youWereFollowed: "У вас новый подписчик."
|
youWereFollowed: "У вас новый подписчик."
|
||||||
@@ -1406,7 +1416,6 @@ _notification:
|
|||||||
renote: "Репосты"
|
renote: "Репосты"
|
||||||
quote: "Цитаты"
|
quote: "Цитаты"
|
||||||
reaction: "Реакции"
|
reaction: "Реакции"
|
||||||
pollVote: "Голосования"
|
|
||||||
receiveFollowRequest: "Получен запрос на подписку"
|
receiveFollowRequest: "Получен запрос на подписку"
|
||||||
followRequestAccepted: "Запрос на подписку одобрен"
|
followRequestAccepted: "Запрос на подписку одобрен"
|
||||||
groupInvited: "Приглашение в группы"
|
groupInvited: "Приглашение в группы"
|
||||||
|
@@ -911,6 +911,13 @@ windowRestore: "Obnoviť"
|
|||||||
caption: "Nadpis"
|
caption: "Nadpis"
|
||||||
tools: "Nástroje"
|
tools: "Nástroje"
|
||||||
cannotLoad: "Nedá sa načítať."
|
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ť!"
|
||||||
|
color: "Farba"
|
||||||
_sensitiveMediaDetection:
|
_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."
|
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"
|
sensitivity: "Citlivosť detekcie"
|
||||||
@@ -1289,6 +1296,8 @@ _weekday:
|
|||||||
friday: "Piatok"
|
friday: "Piatok"
|
||||||
saturday: "Sobota"
|
saturday: "Sobota"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "Profil"
|
||||||
|
instanceInfo: "Informácie o serveri"
|
||||||
memo: "Prilepené poznámky"
|
memo: "Prilepené poznámky"
|
||||||
notifications: "Oznámenia"
|
notifications: "Oznámenia"
|
||||||
timeline: "Časová os"
|
timeline: "Časová os"
|
||||||
@@ -1413,6 +1422,12 @@ _timelines:
|
|||||||
local: "Lokálne"
|
local: "Lokálne"
|
||||||
social: "Sociálne"
|
social: "Sociálne"
|
||||||
global: "Globálne"
|
global: "Globálne"
|
||||||
|
_play:
|
||||||
|
viewSource: "Ukázať zdroj"
|
||||||
|
featured: "Význačné"
|
||||||
|
title: "Nadpis"
|
||||||
|
script: "Skript"
|
||||||
|
summary: "Popis"
|
||||||
_pages:
|
_pages:
|
||||||
newPage: "Vytvoriť novú stránku"
|
newPage: "Vytvoriť novú stránku"
|
||||||
editPage: "Upraviť túto stránku"
|
editPage: "Upraviť túto stránku"
|
||||||
@@ -1472,7 +1487,6 @@ _notification:
|
|||||||
youGotReply: "{name} vám odpovedal/a"
|
youGotReply: "{name} vám odpovedal/a"
|
||||||
youGotQuote: "{name} vás citoval/a"
|
youGotQuote: "{name} vás citoval/a"
|
||||||
youRenoted: "{name} preposlal/a vašu poznámku"
|
youRenoted: "{name} preposlal/a vašu poznámku"
|
||||||
youGotPoll: "{name} hlasoval/a"
|
|
||||||
youGotMessagingMessageFromUser: "{name} vám poslal/a správu"
|
youGotMessagingMessageFromUser: "{name} vám poslal/a správu"
|
||||||
youGotMessagingMessageFromGroup: "Prišla správa do skupiny {name}"
|
youGotMessagingMessageFromGroup: "Prišla správa do skupiny {name}"
|
||||||
youWereFollowed: "Máte nového sledujúceho"
|
youWereFollowed: "Máte nového sledujúceho"
|
||||||
@@ -1490,7 +1504,6 @@ _notification:
|
|||||||
renote: "Preposlať"
|
renote: "Preposlať"
|
||||||
quote: "Citovať"
|
quote: "Citovať"
|
||||||
reaction: "Reakcie"
|
reaction: "Reakcie"
|
||||||
pollVote: "Hlasy v hlasovaniach"
|
|
||||||
pollEnded: "Hlasovanie skončilo"
|
pollEnded: "Hlasovanie skončilo"
|
||||||
receiveFollowRequest: "Doručené žiadosti o sledovanie"
|
receiveFollowRequest: "Doručené žiadosti o sledovanie"
|
||||||
followRequestAccepted: "Schválené žiadosti o sledovanie"
|
followRequestAccepted: "Schválené žiadosti o sledovanie"
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
---
|
---
|
||||||
_lang_: "Svenska"
|
_lang_: "Svenska"
|
||||||
headlineMisskey: "Ett nätverk kopplat av noter"
|
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}"
|
monthAndDay: "{day}/{month}"
|
||||||
search: "Sök"
|
search: "Sök"
|
||||||
notifications: "Notifikationer"
|
notifications: "Notifikationer"
|
||||||
@@ -12,10 +13,11 @@ fetchingAsApObject: "Hämtar från Fediversum..."
|
|||||||
ok: "OK"
|
ok: "OK"
|
||||||
gotIt: "Uppfattat!"
|
gotIt: "Uppfattat!"
|
||||||
cancel: "Avbryt"
|
cancel: "Avbryt"
|
||||||
|
noThankYou: "Nej tack"
|
||||||
enterUsername: "Ange användarnamn"
|
enterUsername: "Ange användarnamn"
|
||||||
renotedBy: "Omnoterad av {user}"
|
renotedBy: "Omnoterad av {user}"
|
||||||
noNotes: "Inga noteringar"
|
noNotes: "Inga noteringar"
|
||||||
noNotifications: "Inga aviseringar"
|
noNotifications: "Inga notifikationer"
|
||||||
instance: "Instanser"
|
instance: "Instanser"
|
||||||
settings: "Inställningar"
|
settings: "Inställningar"
|
||||||
basicSettings: "Basinställningar"
|
basicSettings: "Basinställningar"
|
||||||
@@ -28,13 +30,13 @@ login: "Logga in"
|
|||||||
loggingIn: "Loggar in"
|
loggingIn: "Loggar in"
|
||||||
logout: "Logga ut"
|
logout: "Logga ut"
|
||||||
signup: "Registrera"
|
signup: "Registrera"
|
||||||
uploading: "Uppladdning sker..."
|
uploading: "Laddar upp..."
|
||||||
save: "Spara"
|
save: "Spara"
|
||||||
users: "Användare"
|
users: "Användare"
|
||||||
addUser: "Lägg till användare"
|
addUser: "Lägg till användare"
|
||||||
favorite: "Lägg till i favoriter"
|
favorite: "Lägg till i favoriter"
|
||||||
favorites: "Favoriter"
|
favorites: "Favoriter"
|
||||||
unfavorite: "Avfavorisera"
|
unfavorite: "Ta bort från favoriter"
|
||||||
favorited: "Tillagd i favoriter."
|
favorited: "Tillagd i favoriter."
|
||||||
alreadyFavorited: "Redan tillagd i favoriter."
|
alreadyFavorited: "Redan tillagd i favoriter."
|
||||||
cantFavorite: "Gick inte att lägga till 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."
|
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"
|
addToList: "Lägg till i lista"
|
||||||
sendMessage: "Skicka ett meddelande"
|
sendMessage: "Skicka ett meddelande"
|
||||||
|
copyRSS: "Kopiera RSS"
|
||||||
copyUsername: "Kopiera användarnamn"
|
copyUsername: "Kopiera användarnamn"
|
||||||
searchUser: "Sök användare"
|
searchUser: "Sök användare"
|
||||||
reply: "Svara"
|
reply: "Svara"
|
||||||
loadMore: "Ladda mer"
|
loadMore: "Ladda mer"
|
||||||
showMore: "Visa mer"
|
showMore: "Visa mer"
|
||||||
|
showLess: "Stäng"
|
||||||
youGotNewFollower: "följde dig"
|
youGotNewFollower: "följde dig"
|
||||||
receiveFollowRequest: "Följarförfrågan mottagen"
|
receiveFollowRequest: "Följarförfrågan mottagen"
|
||||||
followRequestAccepted: "Följarförfrågan accepterad"
|
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"
|
flagAsCat: "Markera konto som katt"
|
||||||
flagAsCatDescription: "Aktivera denna inställning för att markera kontot som en katt."
|
flagAsCatDescription: "Aktivera denna inställning för att markera kontot som en katt."
|
||||||
flagShowTimelineReplies: "Visa svar i tidslinje"
|
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"
|
autoAcceptFollowed: "Godkänn följarförfrågningar från användare du följer automatiskt"
|
||||||
addAccount: "Lägg till konto"
|
addAccount: "Lägg till konto"
|
||||||
loginFailed: "Inloggningen misslyckades"
|
loginFailed: "Inloggningen misslyckades"
|
||||||
@@ -238,16 +242,131 @@ saved: "Sparad"
|
|||||||
messaging: "Chatt"
|
messaging: "Chatt"
|
||||||
upload: "Ladda upp"
|
upload: "Ladda upp"
|
||||||
keepOriginalUploading: "Behåll originalbild"
|
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"
|
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"
|
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"
|
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"
|
smtpHost: "Värd"
|
||||||
smtpUser: "Användarnamn"
|
smtpUser: "Användarnamn"
|
||||||
smtpPass: "Lösenord"
|
smtpPass: "Lösenord"
|
||||||
clearCache: "Rensa cache"
|
clearCache: "Rensa cache"
|
||||||
|
enabled: "Aktiverad"
|
||||||
user: "Användare"
|
user: "Användare"
|
||||||
|
global: "Global"
|
||||||
|
squareAvatars: "Visa fyrkantiga profilbilder"
|
||||||
searchByGoogle: "Sök"
|
searchByGoogle: "Sök"
|
||||||
file: "Filer"
|
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:
|
_email:
|
||||||
_follow:
|
_follow:
|
||||||
title: "följde dig"
|
title: "följde dig"
|
||||||
@@ -256,6 +375,9 @@ _mfm:
|
|||||||
quote: "Citat"
|
quote: "Citat"
|
||||||
emoji: "Anpassa emoji"
|
emoji: "Anpassa emoji"
|
||||||
search: "Sök"
|
search: "Sök"
|
||||||
|
_channel:
|
||||||
|
setBanner: "Välj banner"
|
||||||
|
removeBanner: "Ta bort banner"
|
||||||
_theme:
|
_theme:
|
||||||
keys:
|
keys:
|
||||||
mention: "Nämn"
|
mention: "Nämn"
|
||||||
@@ -264,9 +386,19 @@ _sfx:
|
|||||||
note: "Noter"
|
note: "Noter"
|
||||||
notification: "Notifikationer"
|
notification: "Notifikationer"
|
||||||
chat: "Chatt"
|
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:
|
_widgets:
|
||||||
|
profile: "Profil"
|
||||||
|
instanceInfo: "Instansinformation"
|
||||||
notifications: "Notifikationer"
|
notifications: "Notifikationer"
|
||||||
timeline: "Tidslinje"
|
timeline: "Tidslinje"
|
||||||
|
activity: "Aktivitet"
|
||||||
federation: "Federation"
|
federation: "Federation"
|
||||||
jobQueue: "Jobbkö"
|
jobQueue: "Jobbkö"
|
||||||
_userList:
|
_userList:
|
||||||
@@ -274,18 +406,29 @@ _widgets:
|
|||||||
_cw:
|
_cw:
|
||||||
show: "Ladda mer"
|
show: "Ladda mer"
|
||||||
_visibility:
|
_visibility:
|
||||||
|
home: "Hem"
|
||||||
followers: "Följare"
|
followers: "Följare"
|
||||||
_profile:
|
_profile:
|
||||||
username: "Användarnamn"
|
username: "Användarnamn"
|
||||||
|
changeAvatar: "Ändra profilbild"
|
||||||
|
changeBanner: "Ändra banner"
|
||||||
_exportOrImport:
|
_exportOrImport:
|
||||||
|
allNotes: "Alla noter"
|
||||||
followingList: "Följer"
|
followingList: "Följer"
|
||||||
muteList: "Tysta"
|
muteList: "Tysta"
|
||||||
blockingList: "Blockera"
|
blockingList: "Blockera"
|
||||||
userLists: "Listor"
|
userLists: "Listor"
|
||||||
_charts:
|
_charts:
|
||||||
federation: "Federation"
|
federation: "Federation"
|
||||||
|
_timelines:
|
||||||
|
home: "Hem"
|
||||||
|
global: "Global"
|
||||||
|
_pages:
|
||||||
|
blocks:
|
||||||
|
image: "Bilder"
|
||||||
_notification:
|
_notification:
|
||||||
youWereFollowed: "följde dig"
|
youWereFollowed: "följde dig"
|
||||||
|
unreadAntennaNote: "Antenn {name}"
|
||||||
_types:
|
_types:
|
||||||
follow: "Följer"
|
follow: "Följer"
|
||||||
mention: "Nämn"
|
mention: "Nämn"
|
||||||
@@ -299,5 +442,6 @@ _deck:
|
|||||||
_columns:
|
_columns:
|
||||||
notifications: "Notifikationer"
|
notifications: "Notifikationer"
|
||||||
tl: "Tidslinje"
|
tl: "Tidslinje"
|
||||||
|
antenna: "Antenner"
|
||||||
list: "Listor"
|
list: "Listor"
|
||||||
mentions: "Omnämningar"
|
mentions: "Omnämningar"
|
||||||
|
@@ -8,7 +8,7 @@ search: "ค้นหา"
|
|||||||
notifications: "การเเจ้งเตือน"
|
notifications: "การเเจ้งเตือน"
|
||||||
username: "ชื่อผู้ใช้"
|
username: "ชื่อผู้ใช้"
|
||||||
password: "รหัสผ่าน"
|
password: "รหัสผ่าน"
|
||||||
forgotPassword: "ลืมรหัสผ่าน?"
|
forgotPassword: "ลืมรหัสผ่านใช่ไหม"
|
||||||
fetchingAsApObject: "กำลังดึงข้อมูล จาก เฟดิเวิร์ส..."
|
fetchingAsApObject: "กำลังดึงข้อมูล จาก เฟดิเวิร์ส..."
|
||||||
ok: "โอเค"
|
ok: "โอเค"
|
||||||
gotIt: "เข้าใจแล้ว !"
|
gotIt: "เข้าใจแล้ว !"
|
||||||
@@ -916,6 +916,39 @@ loggedInAsBot: "ล็อกอินเป็นบอตอยู่ในข
|
|||||||
tools: "เครื่องมือ"
|
tools: "เครื่องมือ"
|
||||||
cannotLoad: "ไม่สามารถโหลดได้"
|
cannotLoad: "ไม่สามารถโหลดได้"
|
||||||
numberOfProfileView: "มุมมองโปรไฟล์"
|
numberOfProfileView: "มุมมองโปรไฟล์"
|
||||||
|
like: "ชื่นชอบ"
|
||||||
|
unlike: "ไม่ชอบ"
|
||||||
|
numberOfLikes: "จำนวนไลค์"
|
||||||
|
show: "แสดงผล"
|
||||||
|
neverShow: "ไม่ต้องแสดงข้อความนี้อีก"
|
||||||
|
remindMeLater: "ไว้ครั้งหน้าแล้วกัน"
|
||||||
|
didYouLikeMisskey: "คุณเคยชอบ Misskey ไหม?"
|
||||||
|
pleaseDonate: "{host} ใช้ซอฟต์แวร์ฟรี Misskey เราขอขอบคุณการบริจาคของคุณอย่างสูงเพื่อให้การพัฒนา Misskey สามารถดำเนินต่อไปได้นะ!"
|
||||||
|
roles: "บทบาท"
|
||||||
|
role: "บทบาท"
|
||||||
|
undefined: "ไม่ได้กำหนด"
|
||||||
|
assign: "กำหนด"
|
||||||
|
unassign: "ยังไม่มอบหมาย"
|
||||||
|
color: "สี"
|
||||||
|
_role:
|
||||||
|
new: "บทบาทใหม่"
|
||||||
|
edit: "แก้ไขบทบาท"
|
||||||
|
name: "ชื่อบทบาท"
|
||||||
|
description: "คำอธิบายบทบาท"
|
||||||
|
isPublic: "บทบาทสาธารณะ"
|
||||||
|
descriptionOfIsPublic: "ทุกคนสามารถดูได้ว่าผู้ใช้งานนั้นได้รับมอบหมายบทบาทด้วยหรือไม่ \n\nบทบาทจะแสดงในโปรไฟล์ของผู้ใช้ด้วย"
|
||||||
|
options: "ตัวเลือกบทบาท"
|
||||||
|
baseRole: "บทบาทพื้นฐาน"
|
||||||
|
useBaseValue: "ใช้บทบาทพื้นฐานเริ่มต้น"
|
||||||
|
chooseRoleToAssign: "เลือกบทบาทที่ต้องการกำหนด"
|
||||||
|
canEditMembersByModerator: "อนุญาตให้ผู้ดูแลแก้ไขสมาชิก"
|
||||||
|
descriptionOfCanEditMembersByModerator: "เมื่อเปิดใช้ ผู้ดูแลนอกเหนือจากผู้ดูแลระบบแล้ว จะสามารถกำหนดและยกเลิกการมอบหมายบทบาทนี้ให้กับผู้ใช้ได้ เมื่อปิด เฉพาะผู้ดูแลระบบเท่านั้นที่จะสามารถกำหนดผู้ใช้ได้นะ"
|
||||||
|
_options:
|
||||||
|
gtlAvailable: "การดูไทม์ไลน์ทั่วโลก"
|
||||||
|
ltlAvailable: "การดูไทม์ไลน์ในท้องถิ่น"
|
||||||
|
canPublicNote: "สามารถส่งโน้ตสาธารณะ"
|
||||||
|
driveCapacity: "ความจุของไดรฟ์"
|
||||||
|
antennaMax: "จำนวนสูงสุดของเสาอากาศ"
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "ลดความพยายามในการดูแลเซิร์ฟเวอร์ผ่านการจดจำสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่อง การทำสิ่งนี้อาจจะเพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย"
|
description: "ลดความพยายามในการดูแลเซิร์ฟเวอร์ผ่านการจดจำสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่อง การทำสิ่งนี้อาจจะเพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย"
|
||||||
sensitivity: "การตรวจจับความไว"
|
sensitivity: "การตรวจจับความไว"
|
||||||
@@ -1294,6 +1327,8 @@ _weekday:
|
|||||||
friday: "วันศุกร์"
|
friday: "วันศุกร์"
|
||||||
saturday: "วันเสาร์"
|
saturday: "วันเสาร์"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "โปรไฟล์"
|
||||||
|
instanceInfo: "ข้อมูล อินสแตนซ์"
|
||||||
memo: "โน้ตแปะ"
|
memo: "โน้ตแปะ"
|
||||||
notifications: "การเเจ้งเตือน"
|
notifications: "การเเจ้งเตือน"
|
||||||
timeline: "ไทม์ไลน์"
|
timeline: "ไทม์ไลน์"
|
||||||
@@ -1315,10 +1350,12 @@ _widgets:
|
|||||||
jobQueue: "คิวงาน"
|
jobQueue: "คิวงาน"
|
||||||
serverMetric: "ตัวชี้วัดเซิร์ฟเวอร์"
|
serverMetric: "ตัวชี้วัดเซิร์ฟเวอร์"
|
||||||
aiscript: "AiScript คอนโซล"
|
aiscript: "AiScript คอนโซล"
|
||||||
|
aiscriptApp: "AiScript แอพ"
|
||||||
aichan: "เอไอ"
|
aichan: "เอไอ"
|
||||||
userList: "รายชื่อผู้ใช้"
|
userList: "รายชื่อผู้ใช้"
|
||||||
_userList:
|
_userList:
|
||||||
chooseList: "เลือกรายการ"
|
chooseList: "เลือกรายการ"
|
||||||
|
clicker: "คลิกเกอร์"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "ซ่อน"
|
hide: "ซ่อน"
|
||||||
show: "โหลดเพิ่มเติม"
|
show: "โหลดเพิ่มเติม"
|
||||||
@@ -1420,6 +1457,21 @@ _timelines:
|
|||||||
local: "ในพื้นที่"
|
local: "ในพื้นที่"
|
||||||
social: "โซเชี่ยล"
|
social: "โซเชี่ยล"
|
||||||
global: "ทั่วโลก"
|
global: "ทั่วโลก"
|
||||||
|
_play:
|
||||||
|
new: "สร้างการเล่น"
|
||||||
|
edit: "แก้ไขเล่น"
|
||||||
|
created: "สร้างการเล่นแล้ว"
|
||||||
|
updated: "แก้ไขการเล่นแล้ว"
|
||||||
|
deleted: "ลบการเล่นแล้ว"
|
||||||
|
pageSetting: "ตั้งค่าการเล่น"
|
||||||
|
editThisPage: "แก้ไข Play นี้"
|
||||||
|
viewSource: "ดูต้นฉบับ"
|
||||||
|
my: "มาย เพลย์"
|
||||||
|
liked: "ไลค์ เพลย์"
|
||||||
|
featured: "เป็นที่นิยม"
|
||||||
|
title: "หัวข้อ"
|
||||||
|
script: "สคริปต์"
|
||||||
|
summary: "รายละเอียด"
|
||||||
_pages:
|
_pages:
|
||||||
newPage: "สร้างหน้าเพจใหม่"
|
newPage: "สร้างหน้าเพจใหม่"
|
||||||
editPage: "แก้ไขหน้าเพจ"
|
editPage: "แก้ไขหน้าเพจ"
|
||||||
@@ -1479,7 +1531,6 @@ _notification:
|
|||||||
youGotReply: "{name} ตอบกลับถึงคุณ"
|
youGotReply: "{name} ตอบกลับถึงคุณ"
|
||||||
youGotQuote: "{name} อ้างถึงคุณ"
|
youGotQuote: "{name} อ้างถึงคุณ"
|
||||||
youRenoted: "รีโน้ตจาก {name}"
|
youRenoted: "รีโน้ตจาก {name}"
|
||||||
youGotPoll: "{name} โหวตบนแบบสำรวจความคิดเห็นของคุณ"
|
|
||||||
youGotMessagingMessageFromUser: "{name} ได้ส่งข้อความแชทถึงคุณ"
|
youGotMessagingMessageFromUser: "{name} ได้ส่งข้อความแชทถึงคุณ"
|
||||||
youGotMessagingMessageFromGroup: "ข้อความแชทถูกส่งไปยัง {name} กลุ่ม"
|
youGotMessagingMessageFromGroup: "ข้อความแชทถูกส่งไปยัง {name} กลุ่ม"
|
||||||
youWereFollowed: "ได้ติดตามคุณ"
|
youWereFollowed: "ได้ติดตามคุณ"
|
||||||
@@ -1497,7 +1548,6 @@ _notification:
|
|||||||
renote: "รีโน้ต"
|
renote: "รีโน้ต"
|
||||||
quote: "อ้างคำพูด"
|
quote: "อ้างคำพูด"
|
||||||
reaction: "รีแอคชั่น"
|
reaction: "รีแอคชั่น"
|
||||||
pollVote: "จำนวนโหวตที่ได้รับ"
|
|
||||||
pollEnded: "โพลนี้สิ้นสุดลงแล้ว"
|
pollEnded: "โพลนี้สิ้นสุดลงแล้ว"
|
||||||
receiveFollowRequest: "ได้รับคำขอติดตาม\n"
|
receiveFollowRequest: "ได้รับคำขอติดตาม\n"
|
||||||
followRequestAccepted: "ยอมรับคำขอติดตาม"
|
followRequestAccepted: "ยอมรับคำขอติดตาม"
|
||||||
|
@@ -53,6 +53,7 @@ _mfm:
|
|||||||
_sfx:
|
_sfx:
|
||||||
notification: "Bildirim"
|
notification: "Bildirim"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "Profil"
|
||||||
notifications: "Bildirim"
|
notifications: "Bildirim"
|
||||||
timeline: "Zaman çizelgesi"
|
timeline: "Zaman çizelgesi"
|
||||||
_profile:
|
_profile:
|
||||||
|
@@ -892,6 +892,9 @@ unsubscribePushNotification: "Вимкнути push-сповіщення"
|
|||||||
windowMaximize: "Розгорнути"
|
windowMaximize: "Розгорнути"
|
||||||
windowRestore: "Відновити"
|
windowRestore: "Відновити"
|
||||||
caption: "Підпис"
|
caption: "Підпис"
|
||||||
|
like: "Вподобати"
|
||||||
|
show: "Відображення"
|
||||||
|
color: "Колір"
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
sensitivity: "Чутливість детектування"
|
sensitivity: "Чутливість детектування"
|
||||||
setSensitiveFlagAutomatically: "Позначити як NSFW"
|
setSensitiveFlagAutomatically: "Позначити як NSFW"
|
||||||
@@ -1227,6 +1230,8 @@ _weekday:
|
|||||||
friday: "П'ятниця"
|
friday: "П'ятниця"
|
||||||
saturday: "Субота"
|
saturday: "Субота"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "Профіль"
|
||||||
|
instanceInfo: "Про цей інстанс"
|
||||||
memo: "Нагадування"
|
memo: "Нагадування"
|
||||||
notifications: "Сповіщення"
|
notifications: "Сповіщення"
|
||||||
timeline: "Стрічка"
|
timeline: "Стрічка"
|
||||||
@@ -1348,6 +1353,12 @@ _timelines:
|
|||||||
local: "Локальна"
|
local: "Локальна"
|
||||||
social: "Соціальна"
|
social: "Соціальна"
|
||||||
global: "Глобальна"
|
global: "Глобальна"
|
||||||
|
_play:
|
||||||
|
viewSource: "Переглянути вихідний код"
|
||||||
|
featured: "Популярні"
|
||||||
|
title: "Заголовок"
|
||||||
|
script: "Скрипт"
|
||||||
|
summary: "Опис"
|
||||||
_pages:
|
_pages:
|
||||||
newPage: "Створити сторінку"
|
newPage: "Створити сторінку"
|
||||||
editPage: "Редагувати сторінку"
|
editPage: "Редагувати сторінку"
|
||||||
@@ -1407,7 +1418,6 @@ _notification:
|
|||||||
youGotReply: "{name} відповідає"
|
youGotReply: "{name} відповідає"
|
||||||
youGotQuote: "{name} цитує вас"
|
youGotQuote: "{name} цитує вас"
|
||||||
youRenoted: "{name} поширює"
|
youRenoted: "{name} поширює"
|
||||||
youGotPoll: "{name} бере участь в опитуванні"
|
|
||||||
youGotMessagingMessageFromUser: "Повідомлення від {name}"
|
youGotMessagingMessageFromUser: "Повідомлення від {name}"
|
||||||
youGotMessagingMessageFromGroup: "Нове повідомлення в групі {name}"
|
youGotMessagingMessageFromGroup: "Нове повідомлення в групі {name}"
|
||||||
youWereFollowed: "Новий підписник"
|
youWereFollowed: "Новий підписник"
|
||||||
@@ -1422,7 +1432,6 @@ _notification:
|
|||||||
renote: "Поширення"
|
renote: "Поширення"
|
||||||
quote: "Цитування"
|
quote: "Цитування"
|
||||||
reaction: "Реакції"
|
reaction: "Реакції"
|
||||||
pollVote: "Опитування"
|
|
||||||
receiveFollowRequest: "Запити на підписку"
|
receiveFollowRequest: "Запити на підписку"
|
||||||
followRequestAccepted: "Прийняті підписки"
|
followRequestAccepted: "Прийняті підписки"
|
||||||
groupInvited: "Запрошення до груп"
|
groupInvited: "Запрошення до груп"
|
||||||
|
@@ -894,6 +894,9 @@ navbar: "Thanh điều hướng"
|
|||||||
shuffle: "Xáo trộn"
|
shuffle: "Xáo trộn"
|
||||||
account: "Tài khoản của bạn"
|
account: "Tài khoản của bạn"
|
||||||
move: "Di chuyển"
|
move: "Di chuyển"
|
||||||
|
like: "Thích"
|
||||||
|
show: "Hiển thị"
|
||||||
|
color: "Màu sắc"
|
||||||
_sensitiveMediaDetection:
|
_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ủ."
|
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"
|
sensitivity: "Phát hiện nhạy cảm"
|
||||||
@@ -1269,6 +1272,8 @@ _weekday:
|
|||||||
friday: "Thứ Sáu"
|
friday: "Thứ Sáu"
|
||||||
saturday: "Thứ Bảy"
|
saturday: "Thứ Bảy"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "Trang cá nhân"
|
||||||
|
instanceInfo: "Thông tin máy chủ"
|
||||||
memo: "Tút đã ghim"
|
memo: "Tút đã ghim"
|
||||||
notifications: "Thông báo"
|
notifications: "Thông báo"
|
||||||
timeline: "Bảng tin"
|
timeline: "Bảng tin"
|
||||||
@@ -1393,6 +1398,12 @@ _timelines:
|
|||||||
local: "Máy chủ này"
|
local: "Máy chủ này"
|
||||||
social: "Xã hội"
|
social: "Xã hội"
|
||||||
global: "Liên hợp"
|
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:
|
_pages:
|
||||||
newPage: "Tạo Trang mới"
|
newPage: "Tạo Trang mới"
|
||||||
editPage: "Sửa Trang này"
|
editPage: "Sửa Trang này"
|
||||||
@@ -1452,7 +1463,6 @@ _notification:
|
|||||||
youGotReply: "{name} trả lời bạn"
|
youGotReply: "{name} trả lời bạn"
|
||||||
youGotQuote: "{name} trích dẫn tút của 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"
|
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"
|
youGotMessagingMessageFromUser: "{name} nhắn tin cho bạn"
|
||||||
youGotMessagingMessageFromGroup: "Một tin nhắn trong nhóm {name}"
|
youGotMessagingMessageFromGroup: "Một tin nhắn trong nhóm {name}"
|
||||||
youWereFollowed: "đã theo dõi bạn"
|
youWereFollowed: "đã theo dõi bạn"
|
||||||
@@ -1469,7 +1479,6 @@ _notification:
|
|||||||
renote: "Đăng lại"
|
renote: "Đăng lại"
|
||||||
quote: "Trích dẫn"
|
quote: "Trích dẫn"
|
||||||
reaction: "Biểu cảm"
|
reaction: "Biểu cảm"
|
||||||
pollVote: "Lượt bình chọn"
|
|
||||||
pollEnded: "Bình chọn kết thúc"
|
pollEnded: "Bình chọn kết thúc"
|
||||||
receiveFollowRequest: "Yêu cầu theo dõi"
|
receiveFollowRequest: "Yêu cầu theo dõi"
|
||||||
followRequestAccepted: "Yêu cầu theo dõi được chấp nhận"
|
followRequestAccepted: "Yêu cầu theo dõi được chấp nhận"
|
||||||
|
@@ -13,7 +13,7 @@ fetchingAsApObject: "在联邦宇宙查询中..."
|
|||||||
ok: "OK"
|
ok: "OK"
|
||||||
gotIt: "我明白了"
|
gotIt: "我明白了"
|
||||||
cancel: "取消"
|
cancel: "取消"
|
||||||
noThankYou: "不用"
|
noThankYou: "不用,谢谢"
|
||||||
enterUsername: "输入用户名"
|
enterUsername: "输入用户名"
|
||||||
renotedBy: "由 {user} 转贴"
|
renotedBy: "由 {user} 转贴"
|
||||||
noNotes: "没有帖子"
|
noNotes: "没有帖子"
|
||||||
@@ -916,6 +916,56 @@ loggedInAsBot: "以Bot账户登录"
|
|||||||
tools: "工具"
|
tools: "工具"
|
||||||
cannotLoad: "无法加载"
|
cannotLoad: "无法加载"
|
||||||
numberOfProfileView: "个人资料展示次数"
|
numberOfProfileView: "个人资料展示次数"
|
||||||
|
like: "点赞!"
|
||||||
|
unlike: "取消赞"
|
||||||
|
numberOfLikes: "点赞数"
|
||||||
|
show: "显示"
|
||||||
|
neverShow: "不再显示"
|
||||||
|
remindMeLater: "稍后提醒我"
|
||||||
|
didYouLikeMisskey: "您喜欢Misskey吗?"
|
||||||
|
pleaseDonate: "Misskey是{host}所使用的免费软件。为了今后也能够维持Misskey的开发,请在有余力的情况下进行捐助!"
|
||||||
|
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:
|
_sensitiveMediaDetection:
|
||||||
description: "可以使用机器学习技术自动检测敏感媒体,以便进行审核。服务器负载将略微增加。"
|
description: "可以使用机器学习技术自动检测敏感媒体,以便进行审核。服务器负载将略微增加。"
|
||||||
sensitivity: "检测敏感度"
|
sensitivity: "检测敏感度"
|
||||||
@@ -1049,8 +1099,8 @@ _mfm:
|
|||||||
shakeDescription: "显示摇晃的动画效果。"
|
shakeDescription: "显示摇晃的动画效果。"
|
||||||
twitch: "动画(颤抖)"
|
twitch: "动画(颤抖)"
|
||||||
twitchDescription: "显示强烈颤抖的动画效果。"
|
twitchDescription: "显示强烈颤抖的动画效果。"
|
||||||
spin: "动画(回转)"
|
spin: "动画(旋转)"
|
||||||
spinDescription: "显示回转的动画效果。"
|
spinDescription: "显示旋转的动画效果。"
|
||||||
x2: "大"
|
x2: "大"
|
||||||
x2Description: "以大尺寸显示内容。"
|
x2Description: "以大尺寸显示内容。"
|
||||||
x3: "非常大"
|
x3: "非常大"
|
||||||
@@ -1294,6 +1344,8 @@ _weekday:
|
|||||||
friday: "星期五"
|
friday: "星期五"
|
||||||
saturday: "星期六"
|
saturday: "星期六"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "个人资料"
|
||||||
|
instanceInfo: "实例信息"
|
||||||
memo: "便签"
|
memo: "便签"
|
||||||
notifications: "通知"
|
notifications: "通知"
|
||||||
timeline: "时间线"
|
timeline: "时间线"
|
||||||
@@ -1315,10 +1367,12 @@ _widgets:
|
|||||||
jobQueue: "作业队列"
|
jobQueue: "作业队列"
|
||||||
serverMetric: "服务器指标"
|
serverMetric: "服务器指标"
|
||||||
aiscript: "AiScript控制台"
|
aiscript: "AiScript控制台"
|
||||||
|
aiscriptApp: "AiScript App"
|
||||||
aichan: "小蓝"
|
aichan: "小蓝"
|
||||||
userList: "用户列表"
|
userList: "用户列表"
|
||||||
_userList:
|
_userList:
|
||||||
chooseList: "选择列表"
|
chooseList: "选择列表"
|
||||||
|
clicker: "点击器"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "隐藏"
|
hide: "隐藏"
|
||||||
show: "查看更多"
|
show: "查看更多"
|
||||||
@@ -1420,6 +1474,21 @@ _timelines:
|
|||||||
local: "本地"
|
local: "本地"
|
||||||
social: "社交"
|
social: "社交"
|
||||||
global: "全局"
|
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:
|
_pages:
|
||||||
newPage: "创建页面"
|
newPage: "创建页面"
|
||||||
editPage: "编辑页面"
|
editPage: "编辑页面"
|
||||||
@@ -1479,7 +1548,6 @@ _notification:
|
|||||||
youGotReply: "来自{name}的回复"
|
youGotReply: "来自{name}的回复"
|
||||||
youGotQuote: "来自{name}的引用"
|
youGotQuote: "来自{name}的引用"
|
||||||
youRenoted: "来自{name}的转发"
|
youRenoted: "来自{name}的转发"
|
||||||
youGotPoll: "来自{name}的投票"
|
|
||||||
youGotMessagingMessageFromUser: "来自{name}的聊天"
|
youGotMessagingMessageFromUser: "来自{name}的聊天"
|
||||||
youGotMessagingMessageFromGroup: "来自{name}的群聊"
|
youGotMessagingMessageFromGroup: "来自{name}的群聊"
|
||||||
youWereFollowed: "关注了你。"
|
youWereFollowed: "关注了你。"
|
||||||
@@ -1497,7 +1565,6 @@ _notification:
|
|||||||
renote: "转发"
|
renote: "转发"
|
||||||
quote: "引用"
|
quote: "引用"
|
||||||
reaction: "回应"
|
reaction: "回应"
|
||||||
pollVote: "问卷调查被投票"
|
|
||||||
pollEnded: "问卷调查结束"
|
pollEnded: "问卷调查结束"
|
||||||
receiveFollowRequest: "收到关注请求"
|
receiveFollowRequest: "收到关注请求"
|
||||||
followRequestAccepted: "关注请求已通过"
|
followRequestAccepted: "关注请求已通过"
|
||||||
|
@@ -252,7 +252,7 @@ uploadFromUrlMayTakeTime: "還需要一些時間才能完成上傳。"
|
|||||||
explore: "探索"
|
explore: "探索"
|
||||||
messageRead: "已讀"
|
messageRead: "已讀"
|
||||||
noMoreHistory: "沒有更多歷史紀錄"
|
noMoreHistory: "沒有更多歷史紀錄"
|
||||||
startMessaging: "開始傳送訊息"
|
startMessaging: "開始聊天"
|
||||||
nUsersRead: "{n}人已讀"
|
nUsersRead: "{n}人已讀"
|
||||||
agreeTo: "我同意{0}"
|
agreeTo: "我同意{0}"
|
||||||
tos: "使用條款"
|
tos: "使用條款"
|
||||||
@@ -325,7 +325,7 @@ connectService: "己連結"
|
|||||||
disconnectService: "己斷開 "
|
disconnectService: "己斷開 "
|
||||||
enableLocalTimeline: "開啟本地時間軸"
|
enableLocalTimeline: "開啟本地時間軸"
|
||||||
enableGlobalTimeline: "啟用公開時間軸"
|
enableGlobalTimeline: "啟用公開時間軸"
|
||||||
disablingTimelinesInfo: "即使您關閉了時間線功能,管理員和協調人仍可以繼續使用,以方便您。"
|
disablingTimelinesInfo: "為了方便,即使您關閉了時間線功能,管理員和審核員仍可以繼續使用。"
|
||||||
registration: "註冊"
|
registration: "註冊"
|
||||||
enableRegistration: "開啟新使用者註冊"
|
enableRegistration: "開啟新使用者註冊"
|
||||||
invite: "邀請"
|
invite: "邀請"
|
||||||
@@ -388,7 +388,7 @@ aboutMisskey: "關於 Misskey"
|
|||||||
administrator: "管理員"
|
administrator: "管理員"
|
||||||
token: "權杖"
|
token: "權杖"
|
||||||
twoStepAuthentication: "兩階段驗證"
|
twoStepAuthentication: "兩階段驗證"
|
||||||
moderator: "板主"
|
moderator: "審核員"
|
||||||
moderation: "言論調節"
|
moderation: "言論調節"
|
||||||
nUsersMentioned: "提到了{n}"
|
nUsersMentioned: "提到了{n}"
|
||||||
securityKey: "安全金鑰"
|
securityKey: "安全金鑰"
|
||||||
@@ -797,7 +797,7 @@ squareAvatars: "頭像以方形顯示"
|
|||||||
sent: "發送"
|
sent: "發送"
|
||||||
received: "收取"
|
received: "收取"
|
||||||
searchResult: "搜尋結果"
|
searchResult: "搜尋結果"
|
||||||
hashtags: "#tag"
|
hashtags: "標籤"
|
||||||
troubleshooting: "故障排除"
|
troubleshooting: "故障排除"
|
||||||
useBlurEffect: "在 UI 上使用模糊效果"
|
useBlurEffect: "在 UI 上使用模糊效果"
|
||||||
learnMore: "更多資訊"
|
learnMore: "更多資訊"
|
||||||
@@ -916,6 +916,44 @@ loggedInAsBot: "以機器人帳號登入中"
|
|||||||
tools: "工具"
|
tools: "工具"
|
||||||
cannotLoad: "無法載入"
|
cannotLoad: "無法載入"
|
||||||
numberOfProfileView: "個人檔案檢視次數"
|
numberOfProfileView: "個人檔案檢視次數"
|
||||||
|
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: "指派目標"
|
||||||
|
manual: "手動"
|
||||||
|
condition: "條件"
|
||||||
|
isConditionalRole: "這是條件角色。"
|
||||||
|
isPublic: "角色為公開"
|
||||||
|
options: "選項"
|
||||||
|
baseRole: "基本角色"
|
||||||
|
useBaseValue: "使用基本角色的值"
|
||||||
|
chooseRoleToAssign: "選擇要指派的角色"
|
||||||
|
_options:
|
||||||
|
driveCapacity: "雲端硬碟容量"
|
||||||
|
_condition:
|
||||||
|
isLocal: "本地使用者"
|
||||||
|
isRemote: "遠端使用者"
|
||||||
|
createdLessThan: "自建立帳戶開始~以內"
|
||||||
|
createdMoreThan: "自建立帳戶開始~經過"
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。"
|
description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。"
|
||||||
sensitivity: "檢測敏感度"
|
sensitivity: "檢測敏感度"
|
||||||
@@ -1151,7 +1189,7 @@ _theme:
|
|||||||
navActive: "側邊欄文本 (活動)"
|
navActive: "側邊欄文本 (活動)"
|
||||||
navIndicator: "側邊欄指示符"
|
navIndicator: "側邊欄指示符"
|
||||||
link: "鏈接"
|
link: "鏈接"
|
||||||
hashtag: "#tag"
|
hashtag: "標籤"
|
||||||
mention: "提到"
|
mention: "提到"
|
||||||
mentionMe: "提到了我"
|
mentionMe: "提到了我"
|
||||||
renote: "轉發貼文"
|
renote: "轉發貼文"
|
||||||
@@ -1184,7 +1222,7 @@ _sfx:
|
|||||||
note: "貼文"
|
note: "貼文"
|
||||||
noteMy: "我的貼文"
|
noteMy: "我的貼文"
|
||||||
notification: "通知"
|
notification: "通知"
|
||||||
chat: "傳送訊息"
|
chat: "聊天"
|
||||||
chatBg: "聊天背景"
|
chatBg: "聊天背景"
|
||||||
antenna: "天線接收"
|
antenna: "天線接收"
|
||||||
channel: "頻道通知"
|
channel: "頻道通知"
|
||||||
@@ -1294,6 +1332,8 @@ _weekday:
|
|||||||
friday: "週五"
|
friday: "週五"
|
||||||
saturday: "週六"
|
saturday: "週六"
|
||||||
_widgets:
|
_widgets:
|
||||||
|
profile: "個人檔案"
|
||||||
|
instanceInfo: "實例資訊"
|
||||||
memo: "備忘錄"
|
memo: "備忘錄"
|
||||||
notifications: "通知"
|
notifications: "通知"
|
||||||
timeline: "時間軸"
|
timeline: "時間軸"
|
||||||
@@ -1315,10 +1355,12 @@ _widgets:
|
|||||||
jobQueue: "佇列"
|
jobQueue: "佇列"
|
||||||
serverMetric: "服務器指標 "
|
serverMetric: "服務器指標 "
|
||||||
aiscript: "AiScript控制台"
|
aiscript: "AiScript控制台"
|
||||||
|
aiscriptApp: "AiScript App"
|
||||||
aichan: "小藍"
|
aichan: "小藍"
|
||||||
userList: "使用者列表"
|
userList: "使用者列表"
|
||||||
_userList:
|
_userList:
|
||||||
chooseList: "選擇清單"
|
chooseList: "選擇清單"
|
||||||
|
clicker: "點擊器"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "隱藏"
|
hide: "隱藏"
|
||||||
show: "瀏覽更多"
|
show: "瀏覽更多"
|
||||||
@@ -1420,6 +1462,21 @@ _timelines:
|
|||||||
local: "本地"
|
local: "本地"
|
||||||
social: "社群"
|
social: "社群"
|
||||||
global: "公開"
|
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:
|
_pages:
|
||||||
newPage: "建立頁面"
|
newPage: "建立頁面"
|
||||||
editPage: "編輯頁面"
|
editPage: "編輯頁面"
|
||||||
@@ -1479,7 +1536,6 @@ _notification:
|
|||||||
youGotReply: "{name}回覆了您"
|
youGotReply: "{name}回覆了您"
|
||||||
youGotQuote: "{name}引用了您"
|
youGotQuote: "{name}引用了您"
|
||||||
youRenoted: "{name} 轉發了你的貼文"
|
youRenoted: "{name} 轉發了你的貼文"
|
||||||
youGotPoll: "{name}已投票"
|
|
||||||
youGotMessagingMessageFromUser: "{name}發送給您的訊息"
|
youGotMessagingMessageFromUser: "{name}發送給您的訊息"
|
||||||
youGotMessagingMessageFromGroup: "{name}發送給您的訊息"
|
youGotMessagingMessageFromGroup: "{name}發送給您的訊息"
|
||||||
youWereFollowed: "您有新的追隨者"
|
youWereFollowed: "您有新的追隨者"
|
||||||
@@ -1497,7 +1553,6 @@ _notification:
|
|||||||
renote: "轉發貼文"
|
renote: "轉發貼文"
|
||||||
quote: "引用"
|
quote: "引用"
|
||||||
reaction: "反應"
|
reaction: "反應"
|
||||||
pollVote: "統計已投票數"
|
|
||||||
pollEnded: "問卷調查結束"
|
pollEnded: "問卷調查結束"
|
||||||
receiveFollowRequest: "已收到追隨請求"
|
receiveFollowRequest: "已收到追隨請求"
|
||||||
followRequestAccepted: "追隨請求已接受"
|
followRequestAccepted: "追隨請求已接受"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "13.0.0-beta.24",
|
"version": "13.0.0-rc.2",
|
||||||
"codename": "indigo",
|
"codename": "indigo",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -53,10 +53,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/gulp": "4.0.10",
|
"@types/gulp": "4.0.10",
|
||||||
"@types/gulp-rename": "2.0.1",
|
"@types/gulp-rename": "2.0.1",
|
||||||
"@typescript-eslint/eslint-plugin": "5.47.1",
|
"@typescript-eslint/eslint-plugin": "5.48.0",
|
||||||
"@typescript-eslint/parser": "5.47.1",
|
"@typescript-eslint/parser": "5.48.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "12.2.0",
|
"cypress": "12.3.0",
|
||||||
"eslint": "^8.31.0",
|
"eslint": "^8.31.0",
|
||||||
"start-server-and-test": "1.15.2",
|
"start-server-and-test": "1.15.2",
|
||||||
"typescript": "4.9.4"
|
"typescript": "4.9.4"
|
||||||
|
BIN
packages/backend/assets/emoji-unknown.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
@@ -1,5 +0,0 @@
|
|||||||
Font Awesome Icons
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
Ⓒ Font Awesome
|
|
||||||
CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
|
|
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 577 B |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 844 B |
Before Width: | Height: | Size: 507 B |
Before Width: | Height: | Size: 689 B |
Before Width: | Height: | Size: 772 B |
Before Width: | Height: | Size: 930 B |
Before Width: | Height: | Size: 798 B |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 991 B |
24
packages/backend/assets/tabler-badges/LICENSE
Normal 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.
|
BIN
packages/backend/assets/tabler-badges/antenna.png
Normal file
After Width: | Height: | Size: 516 B |
BIN
packages/backend/assets/tabler-badges/arrow-back-up.png
Normal file
After Width: | Height: | Size: 952 B |
BIN
packages/backend/assets/tabler-badges/at.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
packages/backend/assets/tabler-badges/chart-arrows.png
Normal file
After Width: | Height: | Size: 829 B |
BIN
packages/backend/assets/tabler-badges/circle-check.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
packages/backend/assets/tabler-badges/messages.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 174 B After Width: | Height: | Size: 174 B |
BIN
packages/backend/assets/tabler-badges/plus.png
Normal file
After Width: | Height: | Size: 414 B |
BIN
packages/backend/assets/tabler-badges/quote.png
Normal file
After Width: | Height: | Size: 1011 B |
BIN
packages/backend/assets/tabler-badges/repeat.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
packages/backend/assets/tabler-badges/user-plus.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
packages/backend/assets/tabler-badges/users.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
11
packages/backend/migration/1673336077243-PollChoiceLength.js
Normal 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`);
|
||||||
|
}
|
||||||
|
}
|
37
packages/backend/migration/1673500412259-Role.js
Normal 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"`);
|
||||||
|
}
|
||||||
|
}
|
11
packages/backend/migration/1673515526953-RoleColor.js
Normal 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"`);
|
||||||
|
}
|
||||||
|
}
|
13
packages/backend/migration/1673522856499-RoleIroiro.js
Normal 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`);
|
||||||
|
}
|
||||||
|
}
|
13
packages/backend/migration/1673524604156-RoleLastUsedAt.js
Normal 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"`);
|
||||||
|
}
|
||||||
|
}
|
15
packages/backend/migration/1673570377815-RoleConditional.js
Normal 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"`);
|
||||||
|
}
|
||||||
|
}
|
11
packages/backend/migration/1673575973645-MetaClean.js
Normal 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'`);
|
||||||
|
}
|
||||||
|
}
|
@@ -21,9 +21,9 @@
|
|||||||
"@tensorflow/tfjs-node": "4.1.0"
|
"@tensorflow/tfjs-node": "4.1.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bull-board/api": "^4.10.0",
|
"@bull-board/api": "^4.10.1",
|
||||||
"@bull-board/fastify": "^4.10.0",
|
"@bull-board/fastify": "^4.10.1",
|
||||||
"@bull-board/ui": "^4.10.0",
|
"@bull-board/ui": "^4.10.1",
|
||||||
"@discordapp/twemoji": "14.0.2",
|
"@discordapp/twemoji": "14.0.2",
|
||||||
"@fastify/accepts": "4.1.0",
|
"@fastify/accepts": "4.1.0",
|
||||||
"@fastify/cookie": "^8.3.0",
|
"@fastify/cookie": "^8.3.0",
|
||||||
@@ -38,10 +38,10 @@
|
|||||||
"@peertube/http-signature": "1.7.0",
|
"@peertube/http-signature": "1.7.0",
|
||||||
"@sinonjs/fake-timers": "10.0.2",
|
"@sinonjs/fake-timers": "10.0.2",
|
||||||
"accepts": "^1.3.8",
|
"accepts": "^1.3.8",
|
||||||
"ajv": "8.11.2",
|
"ajv": "8.12.0",
|
||||||
"archiver": "5.3.1",
|
"archiver": "5.3.1",
|
||||||
"autwh": "0.1.0",
|
"autwh": "0.1.0",
|
||||||
"aws-sdk": "2.1286.0",
|
"aws-sdk": "2.1289.0",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"blurhash": "2.0.4",
|
"blurhash": "2.0.4",
|
||||||
"bull": "4.10.2",
|
"bull": "4.10.2",
|
||||||
@@ -72,12 +72,11 @@
|
|||||||
"json5-loader": "4.0.1",
|
"json5-loader": "4.0.1",
|
||||||
"jsonld": "8.1.0",
|
"jsonld": "8.1.0",
|
||||||
"jsrsasign": "10.6.1",
|
"jsrsasign": "10.6.1",
|
||||||
"mfm-js": "0.23.0",
|
"mfm-js": "0.23.3",
|
||||||
"mime-types": "2.1.35",
|
"mime-types": "2.1.35",
|
||||||
"misskey-js": "0.0.14",
|
"misskey-js": "0.0.14",
|
||||||
"ms": "3.0.0-canary.1",
|
"ms": "3.0.0-canary.1",
|
||||||
"nested-property": "4.0.0",
|
"nested-property": "4.0.0",
|
||||||
"node-fetch": "3.3.0",
|
|
||||||
"nodemailer": "6.8.0",
|
"nodemailer": "6.8.0",
|
||||||
"nsfwjs": "2.4.2",
|
"nsfwjs": "2.4.2",
|
||||||
"oauth": "^0.10.0",
|
"oauth": "^0.10.0",
|
||||||
@@ -110,14 +109,15 @@
|
|||||||
"stringz": "2.1.0",
|
"stringz": "2.1.0",
|
||||||
"summaly": "2.7.0",
|
"summaly": "2.7.0",
|
||||||
"syslog-pro": "git+https://github.com/misskey-dev/SyslogPro#0.2.9-misskey.2",
|
"syslog-pro": "git+https://github.com/misskey-dev/SyslogPro#0.2.9-misskey.2",
|
||||||
"systeminformation": "5.16.9",
|
"systeminformation": "5.17.1",
|
||||||
"tinycolor2": "1.5.1",
|
"tinycolor2": "1.5.2",
|
||||||
"tmp": "0.2.1",
|
"tmp": "0.2.1",
|
||||||
"tsc-alias": "1.8.2",
|
"tsc-alias": "1.8.2",
|
||||||
"tsconfig-paths": "4.1.2",
|
"tsconfig-paths": "4.1.2",
|
||||||
"twemoji-parser": "14.0.0",
|
"twemoji-parser": "14.0.0",
|
||||||
"typeorm": "0.3.11",
|
"typeorm": "0.3.11",
|
||||||
"ulid": "2.3.0",
|
"ulid": "2.3.0",
|
||||||
|
"undici": "^5.14.0",
|
||||||
"unzipper": "0.10.11",
|
"unzipper": "0.10.11",
|
||||||
"uuid": "9.0.0",
|
"uuid": "9.0.0",
|
||||||
"vary": "1.1.2",
|
"vary": "1.1.2",
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@redocly/openapi-core": "1.0.0-beta.117",
|
"@redocly/openapi-core": "1.0.0-beta.117",
|
||||||
"@swc/core": "1.3.24",
|
"@swc/core": "1.3.25",
|
||||||
"@swc/jest": "0.2.24",
|
"@swc/jest": "0.2.24",
|
||||||
"@types/accepts": "1.3.5",
|
"@types/accepts": "1.3.5",
|
||||||
"@types/archiver": "5.3.1",
|
"@types/archiver": "5.3.1",
|
||||||
@@ -172,14 +172,15 @@
|
|||||||
"@types/web-push": "3.3.2",
|
"@types/web-push": "3.3.2",
|
||||||
"@types/websocket": "1.0.5",
|
"@types/websocket": "1.0.5",
|
||||||
"@types/ws": "8.5.4",
|
"@types/ws": "8.5.4",
|
||||||
"@typescript-eslint/eslint-plugin": "5.47.1",
|
"@typescript-eslint/eslint-plugin": "5.48.0",
|
||||||
"@typescript-eslint/parser": "5.47.1",
|
"@typescript-eslint/parser": "5.48.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint": "8.31.0",
|
"eslint": "8.31.0",
|
||||||
"eslint-plugin-import": "2.26.0",
|
"eslint-plugin-import": "2.26.0",
|
||||||
"execa": "6.1.0",
|
"execa": "6.1.0",
|
||||||
"jest": "29.3.1",
|
"jest": "29.3.1",
|
||||||
"jest-mock": "^29.3.1",
|
"jest-mock": "^29.3.1",
|
||||||
|
"node-fetch": "3.3.0",
|
||||||
"typescript": "4.9.4"
|
"typescript": "4.9.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,8 +15,9 @@ import type { Packed } from '@/misc/schema.js';
|
|||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { MutingsRepository, BlockingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js';
|
import type { MutingsRepository, BlockingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { StreamMessages } from '@/server/api/stream/types.js';
|
||||||
|
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AntennaService implements OnApplicationShutdown {
|
export class AntennaService implements OnApplicationShutdown {
|
||||||
@@ -73,7 +74,7 @@ export class AntennaService implements OnApplicationShutdown {
|
|||||||
const obj = JSON.parse(data);
|
const obj = JSON.parse(data);
|
||||||
|
|
||||||
if (obj.channel === 'internal') {
|
if (obj.channel === 'internal') {
|
||||||
const { type, body } = obj.message;
|
const { type, body } = obj.message as StreamMessages['internal']['payload'];
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'antennaCreated':
|
case 'antennaCreated':
|
||||||
this.antennas.push(body);
|
this.antennas.push(body);
|
||||||
@@ -135,7 +136,7 @@ export class AntennaService implements OnApplicationShutdown {
|
|||||||
this.globalEventServie.publishMainStream(antenna.userId, 'unreadAntenna', antenna);
|
this.globalEventServie.publishMainStream(antenna.userId, 'unreadAntenna', antenna);
|
||||||
this.pushNotificationService.pushNotification(antenna.userId, 'unreadAntennaNote', {
|
this.pushNotificationService.pushNotification(antenna.userId, 'unreadAntennaNote', {
|
||||||
antenna: { id: antenna.id, name: antenna.name },
|
antenna: { id: antenna.id, name: antenna.name },
|
||||||
note: await this.noteEntityService.pack(note)
|
note: await this.noteEntityService.pack(note),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 2000);
|
}, 2000);
|
||||||
@@ -144,27 +145,19 @@ export class AntennaService implements OnApplicationShutdown {
|
|||||||
|
|
||||||
// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている
|
// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている
|
||||||
|
|
||||||
/**
|
|
||||||
* noteUserFollowers / antennaUserFollowing はどちらか一方が指定されていればよい
|
|
||||||
*/
|
|
||||||
@bindThis
|
@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 === '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)));
|
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 (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.withReplies && note.replyId != null) return false;
|
||||||
|
|
||||||
if (antenna.src === 'home') {
|
if (antenna.src === 'home') {
|
||||||
if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false;
|
// TODO
|
||||||
if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false;
|
|
||||||
} else if (antenna.src === 'list') {
|
} else if (antenna.src === 'list') {
|
||||||
const listUsers = (await this.userListJoiningsRepository.findBy({
|
const listUsers = (await this.userListJoiningsRepository.findBy({
|
||||||
userListId: antenna.userListId!,
|
userListId: antenna.userListId!,
|
||||||
|
@@ -1,7 +1,4 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import type { UsersRepository } from '@/models/index.js';
|
|
||||||
import type { Config } from '@/config.js';
|
|
||||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
|
||||||
@@ -13,9 +10,6 @@ type CaptchaResponse = {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class CaptchaService {
|
export class CaptchaService {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
|
||||||
private config: Config,
|
|
||||||
|
|
||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
@@ -27,16 +21,16 @@ export class CaptchaService {
|
|||||||
response,
|
response,
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await fetch(url, {
|
const res = await this.httpRequestService.fetch(
|
||||||
method: 'POST',
|
url,
|
||||||
body: params,
|
{
|
||||||
headers: {
|
method: 'POST',
|
||||||
'User-Agent': this.config.userAgent,
|
body: params,
|
||||||
},
|
},
|
||||||
// TODO
|
{
|
||||||
//timeout: 10 * 1000,
|
noOkError: true,
|
||||||
agent: (url, bypassProxy) => this.httpRequestService.getAgentByUrl(url, bypassProxy),
|
}
|
||||||
}).catch(err => {
|
).catch(err => {
|
||||||
throw `${err.message ?? err}`;
|
throw `${err.message ?? err}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -35,6 +35,7 @@ import { PushNotificationService } from './PushNotificationService.js';
|
|||||||
import { QueryService } from './QueryService.js';
|
import { QueryService } from './QueryService.js';
|
||||||
import { ReactionService } from './ReactionService.js';
|
import { ReactionService } from './ReactionService.js';
|
||||||
import { RelayService } from './RelayService.js';
|
import { RelayService } from './RelayService.js';
|
||||||
|
import { RoleService } from './RoleService.js';
|
||||||
import { S3Service } from './S3Service.js';
|
import { S3Service } from './S3Service.js';
|
||||||
import { SignupService } from './SignupService.js';
|
import { SignupService } from './SignupService.js';
|
||||||
import { TwoFactorAuthenticationService } from './TwoFactorAuthenticationService.js';
|
import { TwoFactorAuthenticationService } from './TwoFactorAuthenticationService.js';
|
||||||
@@ -97,6 +98,7 @@ import { UserGroupInvitationEntityService } from './entities/UserGroupInvitation
|
|||||||
import { UserListEntityService } from './entities/UserListEntityService.js';
|
import { UserListEntityService } from './entities/UserListEntityService.js';
|
||||||
import { FlashEntityService } from './entities/FlashEntityService.js';
|
import { FlashEntityService } from './entities/FlashEntityService.js';
|
||||||
import { FlashLikeEntityService } from './entities/FlashLikeEntityService.js';
|
import { FlashLikeEntityService } from './entities/FlashLikeEntityService.js';
|
||||||
|
import { RoleEntityService } from './entities/RoleEntityService.js';
|
||||||
import { ApAudienceService } from './activitypub/ApAudienceService.js';
|
import { ApAudienceService } from './activitypub/ApAudienceService.js';
|
||||||
import { ApDbResolverService } from './activitypub/ApDbResolverService.js';
|
import { ApDbResolverService } from './activitypub/ApDbResolverService.js';
|
||||||
import { ApDeliverManagerService } from './activitypub/ApDeliverManagerService.js';
|
import { ApDeliverManagerService } from './activitypub/ApDeliverManagerService.js';
|
||||||
@@ -158,6 +160,7 @@ const $PushNotificationService: Provider = { provide: 'PushNotificationService',
|
|||||||
const $QueryService: Provider = { provide: 'QueryService', useExisting: QueryService };
|
const $QueryService: Provider = { provide: 'QueryService', useExisting: QueryService };
|
||||||
const $ReactionService: Provider = { provide: 'ReactionService', useExisting: ReactionService };
|
const $ReactionService: Provider = { provide: 'ReactionService', useExisting: ReactionService };
|
||||||
const $RelayService: Provider = { provide: 'RelayService', useExisting: RelayService };
|
const $RelayService: Provider = { provide: 'RelayService', useExisting: RelayService };
|
||||||
|
const $RoleService: Provider = { provide: 'RoleService', useExisting: RoleService };
|
||||||
const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service };
|
const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service };
|
||||||
const $SignupService: Provider = { provide: 'SignupService', useExisting: SignupService };
|
const $SignupService: Provider = { provide: 'SignupService', useExisting: SignupService };
|
||||||
const $TwoFactorAuthenticationService: Provider = { provide: 'TwoFactorAuthenticationService', useExisting: TwoFactorAuthenticationService };
|
const $TwoFactorAuthenticationService: Provider = { provide: 'TwoFactorAuthenticationService', useExisting: TwoFactorAuthenticationService };
|
||||||
@@ -220,6 +223,7 @@ const $UserGroupInvitationEntityService: Provider = { provide: 'UserGroupInvitat
|
|||||||
const $UserListEntityService: Provider = { provide: 'UserListEntityService', useExisting: UserListEntityService };
|
const $UserListEntityService: Provider = { provide: 'UserListEntityService', useExisting: UserListEntityService };
|
||||||
const $FlashEntityService: Provider = { provide: 'FlashEntityService', useExisting: FlashEntityService };
|
const $FlashEntityService: Provider = { provide: 'FlashEntityService', useExisting: FlashEntityService };
|
||||||
const $FlashLikeEntityService: Provider = { provide: 'FlashLikeEntityService', useExisting: FlashLikeEntityService };
|
const $FlashLikeEntityService: Provider = { provide: 'FlashLikeEntityService', useExisting: FlashLikeEntityService };
|
||||||
|
const $RoleEntityService: Provider = { provide: 'RoleEntityService', useExisting: RoleEntityService };
|
||||||
|
|
||||||
const $ApAudienceService: Provider = { provide: 'ApAudienceService', useExisting: ApAudienceService };
|
const $ApAudienceService: Provider = { provide: 'ApAudienceService', useExisting: ApAudienceService };
|
||||||
const $ApDbResolverService: Provider = { provide: 'ApDbResolverService', useExisting: ApDbResolverService };
|
const $ApDbResolverService: Provider = { provide: 'ApDbResolverService', useExisting: ApDbResolverService };
|
||||||
@@ -283,6 +287,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
QueryService,
|
QueryService,
|
||||||
ReactionService,
|
ReactionService,
|
||||||
RelayService,
|
RelayService,
|
||||||
|
RoleService,
|
||||||
S3Service,
|
S3Service,
|
||||||
SignupService,
|
SignupService,
|
||||||
TwoFactorAuthenticationService,
|
TwoFactorAuthenticationService,
|
||||||
@@ -344,6 +349,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
UserListEntityService,
|
UserListEntityService,
|
||||||
FlashEntityService,
|
FlashEntityService,
|
||||||
FlashLikeEntityService,
|
FlashLikeEntityService,
|
||||||
|
RoleEntityService,
|
||||||
ApAudienceService,
|
ApAudienceService,
|
||||||
ApDbResolverService,
|
ApDbResolverService,
|
||||||
ApDeliverManagerService,
|
ApDeliverManagerService,
|
||||||
@@ -402,6 +408,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$QueryService,
|
$QueryService,
|
||||||
$ReactionService,
|
$ReactionService,
|
||||||
$RelayService,
|
$RelayService,
|
||||||
|
$RoleService,
|
||||||
$S3Service,
|
$S3Service,
|
||||||
$SignupService,
|
$SignupService,
|
||||||
$TwoFactorAuthenticationService,
|
$TwoFactorAuthenticationService,
|
||||||
@@ -463,6 +470,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$UserListEntityService,
|
$UserListEntityService,
|
||||||
$FlashEntityService,
|
$FlashEntityService,
|
||||||
$FlashLikeEntityService,
|
$FlashLikeEntityService,
|
||||||
|
$RoleEntityService,
|
||||||
$ApAudienceService,
|
$ApAudienceService,
|
||||||
$ApDbResolverService,
|
$ApDbResolverService,
|
||||||
$ApDeliverManagerService,
|
$ApDeliverManagerService,
|
||||||
@@ -522,6 +530,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
QueryService,
|
QueryService,
|
||||||
ReactionService,
|
ReactionService,
|
||||||
RelayService,
|
RelayService,
|
||||||
|
RoleService,
|
||||||
S3Service,
|
S3Service,
|
||||||
SignupService,
|
SignupService,
|
||||||
TwoFactorAuthenticationService,
|
TwoFactorAuthenticationService,
|
||||||
@@ -582,6 +591,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
UserListEntityService,
|
UserListEntityService,
|
||||||
FlashEntityService,
|
FlashEntityService,
|
||||||
FlashLikeEntityService,
|
FlashLikeEntityService,
|
||||||
|
RoleEntityService,
|
||||||
ApAudienceService,
|
ApAudienceService,
|
||||||
ApDbResolverService,
|
ApDbResolverService,
|
||||||
ApDeliverManagerService,
|
ApDeliverManagerService,
|
||||||
@@ -640,6 +650,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$QueryService,
|
$QueryService,
|
||||||
$ReactionService,
|
$ReactionService,
|
||||||
$RelayService,
|
$RelayService,
|
||||||
|
$RoleService,
|
||||||
$S3Service,
|
$S3Service,
|
||||||
$SignupService,
|
$SignupService,
|
||||||
$TwoFactorAuthenticationService,
|
$TwoFactorAuthenticationService,
|
||||||
@@ -700,6 +711,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$UserListEntityService,
|
$UserListEntityService,
|
||||||
$FlashEntityService,
|
$FlashEntityService,
|
||||||
$FlashLikeEntityService,
|
$FlashLikeEntityService,
|
||||||
|
$RoleEntityService,
|
||||||
$ApAudienceService,
|
$ApAudienceService,
|
||||||
$ApDbResolverService,
|
$ApDbResolverService,
|
||||||
$ApDeliverManagerService,
|
$ApDeliverManagerService,
|
||||||
|
@@ -53,7 +53,7 @@ export class CreateSystemUserService {
|
|||||||
usernameLower: username.toLowerCase(),
|
usernameLower: username.toLowerCase(),
|
||||||
host: null,
|
host: null,
|
||||||
token: secret,
|
token: secret,
|
||||||
isAdmin: false,
|
isRoot: false,
|
||||||
isLocked: true,
|
isLocked: true,
|
||||||
isExplorable: false,
|
isExplorable: false,
|
||||||
isBot: true,
|
isBot: true,
|
||||||
|
@@ -23,6 +23,9 @@ export class DeleteAccountService {
|
|||||||
id: string;
|
id: string;
|
||||||
host: string | null;
|
host: string | null;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
|
const _user = await this.usersRepository.findOneByOrFail({ id: user.id });
|
||||||
|
if (_user.isRoot) throw new Error('cannot delete a root account');
|
||||||
|
|
||||||
// 物理削除する前にDelete activityを送信する
|
// 物理削除する前にDelete activityを送信する
|
||||||
await this.userSuspendService.doPostSuspend(user).catch(e => {});
|
await this.userSuspendService.doPostSuspend(user).catch(e => {});
|
||||||
|
|
||||||
|
@@ -8,11 +8,12 @@ import got, * as Got from 'got';
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.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 { createTemp } from '@/misc/create-temp.js';
|
||||||
import { StatusError } from '@/misc/status-error.js';
|
import { StatusError } from '@/misc/status-error.js';
|
||||||
import { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
|
import { buildConnector } from 'undici';
|
||||||
|
|
||||||
const pipeline = util.promisify(stream.pipeline);
|
const pipeline = util.promisify(stream.pipeline);
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
@@ -20,6 +21,7 @@ import { bindThis } from '@/decorators.js';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class DownloadService {
|
export class DownloadService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
private undiciFetcher: UndiciFetcher;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
@@ -29,6 +31,24 @@ export class DownloadService {
|
|||||||
private loggerService: LoggerService,
|
private loggerService: LoggerService,
|
||||||
) {
|
) {
|
||||||
this.logger = this.loggerService.getLogger('download');
|
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
|
@bindThis
|
||||||
@@ -39,60 +59,14 @@ export class DownloadService {
|
|||||||
const operationTimeout = 60 * 1000;
|
const operationTimeout = 60 * 1000;
|
||||||
const maxSize = this.config.maxFileSize ?? 262144000;
|
const maxSize = this.config.maxFileSize ?? 262144000;
|
||||||
|
|
||||||
const req = got.stream(url, {
|
const response = await this.undiciFetcher.fetch(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 (response.body === null) {
|
||||||
if (contentLength != null) {
|
throw new StatusError('No body', 400, 'No body');
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await pipeline(stream.Readable.fromWeb(response.body), fs.createWriteStream(path));
|
||||||
|
|
||||||
this.logger.succ(`Download finished: ${chalk.cyan(url)}`);
|
this.logger.succ(`Download finished: ${chalk.cyan(url)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +98,6 @@ export class DownloadService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return PrivateIp(ip);
|
return PrivateIp(ip) ?? false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -32,11 +32,12 @@ import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.j
|
|||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { FileInfoService } from '@/core/FileInfoService.js';
|
import { FileInfoService } from '@/core/FileInfoService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import type S3 from 'aws-sdk/clients/s3.js';
|
import type S3 from 'aws-sdk/clients/s3.js';
|
||||||
|
|
||||||
type AddFileArgs = {
|
type AddFileArgs = {
|
||||||
/** User who wish to add file */
|
/** 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 */
|
/** File path */
|
||||||
path: string;
|
path: string;
|
||||||
/** Name */
|
/** Name */
|
||||||
@@ -62,7 +63,7 @@ type AddFileArgs = {
|
|||||||
|
|
||||||
type UploadFromUrlArgs = {
|
type UploadFromUrlArgs = {
|
||||||
url: string;
|
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;
|
folderId?: DriveFolder['id'] | null;
|
||||||
uri?: string | null;
|
uri?: string | null;
|
||||||
sensitive?: boolean;
|
sensitive?: boolean;
|
||||||
@@ -106,6 +107,7 @@ export class DriveService {
|
|||||||
private videoProcessingService: VideoProcessingService,
|
private videoProcessingService: VideoProcessingService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
|
private roleService: RoleService,
|
||||||
private driveChart: DriveChart,
|
private driveChart: DriveChart,
|
||||||
private perUserDriveChart: PerUserDriveChart,
|
private perUserDriveChart: PerUserDriveChart,
|
||||||
private instanceChart: InstanceChart,
|
private instanceChart: InstanceChart,
|
||||||
@@ -373,8 +375,19 @@ export class DriveService {
|
|||||||
partSize: s3.endpoint.hostname === 'storage.googleapis.com' ? 500 * 1024 * 1024 : 8 * 1024 * 1024,
|
partSize: s3.endpoint.hostname === 'storage.googleapis.com' ? 500 * 1024 * 1024 : 8 * 1024 * 1024,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await upload.promise();
|
await upload.promise()
|
||||||
if (result) this.registerLogger.debug(`Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`);
|
.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
|
@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
|
//#region Check drive usage
|
||||||
if (user && !isLink) {
|
if (user && !isLink) {
|
||||||
const usage = await this.driveFileEntityService.calcDriveUsageOf(user);
|
const usage = await this.driveFileEntityService.calcDriveUsageOf(user);
|
||||||
const u = await this.usersRepository.findOneBy({ id: user.id });
|
|
||||||
|
|
||||||
const instance = await this.metaService.fetch();
|
const role = await this.roleService.getUserRoleOptions(user.id);
|
||||||
let driveCapacity = 1024 * 1024 * (this.userEntityService.isLocalUser(user) ? instance.localDriveCapacityMb : instance.remoteDriveCapacityMb);
|
const driveCapacity = 1024 * 1024 * role.driveCapacityMb;
|
||||||
|
this.registerLogger.debug('drive capacity override applied');
|
||||||
if (this.userEntityService.isLocalUser(user) && u?.driveCapacityOverrideMb != null) {
|
this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`);
|
||||||
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`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.registerLogger.debug(`drive usage is ${usage} (max: ${driveCapacity})`);
|
this.registerLogger.debug(`drive usage is ${usage} (max: ${driveCapacity})`);
|
||||||
|
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { URL } from 'node:url';
|
import { URL } from 'node:url';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { JSDOM } from 'jsdom';
|
import { JSDOM } from 'jsdom';
|
||||||
import fetch from 'node-fetch';
|
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
import type { Instance } from '@/models/entities/Instance.js';
|
import type { Instance } from '@/models/entities/Instance.js';
|
||||||
import type { InstancesRepository } from '@/models/index.js';
|
import type { InstancesRepository } from '@/models/index.js';
|
||||||
@@ -191,11 +190,7 @@ export class FetchInstanceMetadataService {
|
|||||||
|
|
||||||
const faviconUrl = url + '/favicon.ico';
|
const faviconUrl = url + '/favicon.ico';
|
||||||
|
|
||||||
const favicon = await fetch(faviconUrl, {
|
const favicon = await this.httpRequestService.fetch(faviconUrl, {}, { noOkError: true });
|
||||||
// TODO
|
|
||||||
//timeout: 10000,
|
|
||||||
agent: url => this.httpRequestService.getAgentByUrl(url),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (favicon.ok) {
|
if (favicon.ok) {
|
||||||
return faviconUrl;
|
return faviconUrl;
|
||||||
|
@@ -398,13 +398,13 @@ export class FileInfoService {
|
|||||||
.raw()
|
.raw()
|
||||||
.ensureAlpha()
|
.ensureAlpha()
|
||||||
.resize(64, 64, { fit: 'inside' })
|
.resize(64, 64, { fit: 'inside' })
|
||||||
.toBuffer((err, buffer, { width, height }) => {
|
.toBuffer((err, buffer, info) => {
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
|
|
||||||
let hash;
|
let hash;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
hash = encode(new Uint8ClampedArray(buffer), width, height, 5, 5);
|
hash = encode(new Uint8ClampedArray(buffer), info.width, info.height, 5, 5);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return reject(e);
|
return reject(e);
|
||||||
}
|
}
|
||||||
|
@@ -1,67 +1,257 @@
|
|||||||
import * as http from 'node:http';
|
import * as http from 'node:http';
|
||||||
import * as https from 'node:https';
|
import * as https from 'node:https';
|
||||||
import CacheableLookup from 'cacheable-lookup';
|
import CacheableLookup from 'cacheable-lookup';
|
||||||
import fetch from 'node-fetch';
|
|
||||||
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
|
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { StatusError } from '@/misc/status-error.js';
|
import { StatusError } from '@/misc/status-error.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import type { Response } from 'node-fetch';
|
import * as undici from 'undici';
|
||||||
import type { URL } from 'node:url';
|
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()
|
@Injectable()
|
||||||
export class HttpRequestService {
|
export class HttpRequestService {
|
||||||
/**
|
public defaultFetcher: UndiciFetcher;
|
||||||
* Get http non-proxy agent
|
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;
|
private http: http.Agent;
|
||||||
|
|
||||||
/**
|
// https non-proxy agent
|
||||||
* Get https non-proxy agent
|
|
||||||
*/
|
|
||||||
private https: https.Agent;
|
private https: https.Agent;
|
||||||
|
|
||||||
/**
|
// http proxy or non-proxy agent
|
||||||
* Get http proxy or non-proxy agent
|
|
||||||
*/
|
|
||||||
public httpAgent: http.Agent;
|
public httpAgent: http.Agent;
|
||||||
|
|
||||||
/**
|
// https proxy or non-proxy agent
|
||||||
* Get https proxy or non-proxy agent
|
|
||||||
*/
|
|
||||||
public httpsAgent: https.Agent;
|
public httpsAgent: https.Agent;
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
public readonly dnsCache: CacheableLookup;
|
||||||
|
public readonly clientDefaults: undici.Agent.Options;
|
||||||
|
private maxSockets: number;
|
||||||
|
|
||||||
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: 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
|
maxTtl: 3600, // 1hours
|
||||||
errorTtl: 30, // 30secs
|
errorTtl: 30, // 30secs
|
||||||
lookup: false, // nativeのdns.lookupにfallbackしない
|
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({
|
this.http = new http.Agent({
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
keepAliveMsecs: 30 * 1000,
|
keepAliveMsecs: 30 * 1000,
|
||||||
lookup: cache.lookup,
|
lookup: this.dnsCache.lookup,
|
||||||
} as http.AgentOptions);
|
} as http.AgentOptions);
|
||||||
|
|
||||||
this.https = new https.Agent({
|
this.https = new https.Agent({
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
keepAliveMsecs: 30 * 1000,
|
keepAliveMsecs: 30 * 1000,
|
||||||
lookup: cache.lookup,
|
lookup: this.dnsCache.lookup,
|
||||||
} as https.AgentOptions);
|
} as https.AgentOptions);
|
||||||
|
|
||||||
const maxSockets = Math.max(256, config.deliverJobConcurrency ?? 128);
|
|
||||||
|
|
||||||
this.httpAgent = config.proxy
|
this.httpAgent = config.proxy
|
||||||
? new HttpProxyAgent({
|
? new HttpProxyAgent({
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
keepAliveMsecs: 30 * 1000,
|
keepAliveMsecs: 30 * 1000,
|
||||||
maxSockets,
|
maxSockets: this.maxSockets,
|
||||||
maxFreeSockets: 256,
|
maxFreeSockets: 256,
|
||||||
scheduling: 'lifo',
|
scheduling: 'lifo',
|
||||||
proxy: config.proxy,
|
proxy: config.proxy,
|
||||||
@@ -72,21 +262,42 @@ export class HttpRequestService {
|
|||||||
? new HttpsProxyAgent({
|
? new HttpsProxyAgent({
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
keepAliveMsecs: 30 * 1000,
|
keepAliveMsecs: 30 * 1000,
|
||||||
maxSockets,
|
maxSockets: this.maxSockets,
|
||||||
maxFreeSockets: 256,
|
maxFreeSockets: 256,
|
||||||
scheduling: 'lifo',
|
scheduling: 'lifo',
|
||||||
proxy: config.proxy,
|
proxy: config.proxy,
|
||||||
})
|
})
|
||||||
: this.https;
|
: 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 url URL
|
||||||
* @param bypassProxy Allways bypass proxy
|
* @param bypassProxy Allways bypass proxy
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@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)) {
|
if (bypassProxy || (this.config.proxyBypassHosts || []).includes(url.hostname)) {
|
||||||
return url.protocol === 'http:' ? this.http : this.https;
|
return url.protocol === 'http:' ? this.http : this.https;
|
||||||
} else {
|
} else {
|
||||||
@@ -94,67 +305,37 @@ export class HttpRequestService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check ip
|
||||||
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getJson(url: string, accept = 'application/json, */*', timeout = 10000, headers?: Record<string, string>): Promise<unknown> {
|
public getConnectorWithIpCheck(connector: undici.buildConnector.connector, checkIp: IpChecker): undici.buildConnector.connectorAsync {
|
||||||
const res = await this.getResponse({
|
return (options, cb) => {
|
||||||
url,
|
connector(options, (err, socket) => {
|
||||||
method: 'GET',
|
this.logger.debug('Socket connector (with ip checker) called', socket);
|
||||||
headers: Object.assign({
|
if (err) {
|
||||||
'User-Agent': this.config.userAgent,
|
this.logger.error(`Socket error`, err)
|
||||||
Accept: accept,
|
cb(new Error(`Error while socket connecting\n${err}`), null);
|
||||||
}, headers ?? {}),
|
return;
|
||||||
timeout,
|
}
|
||||||
size: 1024 * 256,
|
|
||||||
});
|
|
||||||
|
|
||||||
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
|
// allow
|
||||||
public async getHtml(url: string, accept = 'text/html, */*', timeout = 10000, headers?: Record<string, string>): Promise<string> {
|
if (checkIp(socket.remoteAddress)) {
|
||||||
const res = await this.getResponse({
|
this.logger.debug(`Socket connected (ip ok): ${socket.localPort} => ${socket.remoteAddress}`);
|
||||||
url,
|
cb(null, socket);
|
||||||
method: 'GET',
|
return;
|
||||||
headers: Object.assign({
|
}
|
||||||
'User-Agent': this.config.userAgent,
|
|
||||||
Accept: accept,
|
|
||||||
}, headers ?? {}),
|
|
||||||
timeout,
|
|
||||||
});
|
|
||||||
|
|
||||||
return await res.text();
|
this.logger.error('IP is not allowed', socket);
|
||||||
}
|
cb(new StatusError('IP is not allowed', 403, 'IP is not allowed'), null);
|
||||||
|
socket.destroy();
|
||||||
@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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,8 +4,9 @@ import Redis from 'ioredis';
|
|||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { Meta } from '@/models/entities/Meta.js';
|
import { Meta } from '@/models/entities/Meta.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { StreamMessages } from '@/server/api/stream/types.js';
|
||||||
|
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MetaService implements OnApplicationShutdown {
|
export class MetaService implements OnApplicationShutdown {
|
||||||
@@ -40,7 +41,7 @@ export class MetaService implements OnApplicationShutdown {
|
|||||||
const obj = JSON.parse(data);
|
const obj = JSON.parse(data);
|
||||||
|
|
||||||
if (obj.channel === 'internal') {
|
if (obj.channel === 'internal') {
|
||||||
const { type, body } = obj.message;
|
const { type, body } = obj.message as StreamMessages['internal']['payload'];
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'metaUpdated': {
|
case 'metaUpdated': {
|
||||||
this.cache = body;
|
this.cache = body;
|
||||||
|
@@ -42,6 +42,7 @@ import { NoteReadService } from '@/core/NoteReadService.js';
|
|||||||
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.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);
|
const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
|
||||||
|
|
||||||
@@ -186,6 +187,7 @@ export class NoteCreateService {
|
|||||||
private remoteUserResolveService: RemoteUserResolveService,
|
private remoteUserResolveService: RemoteUserResolveService,
|
||||||
private apDeliverManagerService: ApDeliverManagerService,
|
private apDeliverManagerService: ApDeliverManagerService,
|
||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
|
private roleService: RoleService,
|
||||||
private notesChart: NotesChart,
|
private notesChart: NotesChart,
|
||||||
private perUserNotesChart: PerUserNotesChart,
|
private perUserNotesChart: PerUserNotesChart,
|
||||||
private activeUsersChart: ActiveUsersChart,
|
private activeUsersChart: ActiveUsersChart,
|
||||||
@@ -197,7 +199,6 @@ export class NoteCreateService {
|
|||||||
id: User['id'];
|
id: User['id'];
|
||||||
username: User['username'];
|
username: User['username'];
|
||||||
host: User['host'];
|
host: User['host'];
|
||||||
isSilenced: User['isSilenced'];
|
|
||||||
createdAt: User['createdAt'];
|
createdAt: User['createdAt'];
|
||||||
isBot: User['isBot'];
|
isBot: User['isBot'];
|
||||||
}, data: Option, silent = false): Promise<Note> {
|
}, data: Option, silent = false): Promise<Note> {
|
||||||
@@ -224,9 +225,10 @@ export class NoteCreateService {
|
|||||||
if (data.channel != null) data.visibleUsers = [];
|
if (data.channel != null) data.visibleUsers = [];
|
||||||
if (data.channel != null) data.localOnly = true;
|
if (data.channel != null) data.localOnly = true;
|
||||||
|
|
||||||
// サイレンス
|
if (data.visibility === 'public' && data.channel == null) {
|
||||||
if (user.isSilenced && data.visibility === 'public' && data.channel == null) {
|
if ((await this.roleService.getUserRoleOptions(user.id)).canPublicNote === false) {
|
||||||
data.visibility = 'home';
|
data.visibility = 'home';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renote対象が「ホームまたは全体」以外の公開範囲ならreject
|
// Renote対象が「ホームまたは全体」以外の公開範囲ならreject
|
||||||
@@ -418,7 +420,6 @@ export class NoteCreateService {
|
|||||||
id: User['id'];
|
id: User['id'];
|
||||||
username: User['username'];
|
username: User['username'];
|
||||||
host: User['host'];
|
host: User['host'];
|
||||||
isSilenced: User['isSilenced'];
|
|
||||||
createdAt: User['createdAt'];
|
createdAt: User['createdAt'];
|
||||||
isBot: User['isBot'];
|
isBot: User['isBot'];
|
||||||
}, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) {
|
}, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) {
|
||||||
|
@@ -92,13 +92,6 @@ export class PollService {
|
|||||||
choice: choice,
|
choice: choice,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Notify
|
|
||||||
this.createNotificationService.createNotification(note.userId, 'pollVote', {
|
|
||||||
notifierId: user.id,
|
|
||||||
noteId: note.id,
|
|
||||||
choice: choice,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
268
packages/backend/src/core/RoleService.ts
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
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 { StreamMessages } from '@/server/api/stream/types.js';
|
||||||
|
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
|
||||||
|
export type RoleOptions = {
|
||||||
|
gtlAvailable: boolean;
|
||||||
|
ltlAvailable: boolean;
|
||||||
|
canPublicNote: boolean;
|
||||||
|
canInvite: boolean;
|
||||||
|
canManageCustomEmojis: boolean;
|
||||||
|
driveCapacityMb: number;
|
||||||
|
antennaLimit: number;
|
||||||
|
wordMuteLimit: number;
|
||||||
|
webhookLimit: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_ROLE: RoleOptions = {
|
||||||
|
gtlAvailable: true,
|
||||||
|
ltlAvailable: true,
|
||||||
|
canPublicNote: true,
|
||||||
|
canInvite: false,
|
||||||
|
canManageCustomEmojis: false,
|
||||||
|
driveCapacityMb: 100,
|
||||||
|
antennaLimit: 5,
|
||||||
|
wordMuteLimit: 200,
|
||||||
|
webhookLimit: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
@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 as StreamMessages['internal']['payload'];
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
case 'followersLessThanOrEq': {
|
||||||
|
return user.followersCount <= value.value;
|
||||||
|
}
|
||||||
|
case 'followersMoreThanOrEq': {
|
||||||
|
return user.followersCount >= value.value;
|
||||||
|
}
|
||||||
|
case 'followingLessThanOrEq': {
|
||||||
|
return user.followingCount <= value.value;
|
||||||
|
}
|
||||||
|
case 'followingMoreThanOrEq': {
|
||||||
|
return user.followingCount >= value.value;
|
||||||
|
}
|
||||||
|
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),
|
||||||
|
canInvite: getOptionValues('canInvite').some(x => x === true),
|
||||||
|
canManageCustomEmojis: getOptionValues('canManageCustomEmojis').some(x => x === true),
|
||||||
|
driveCapacityMb: Math.max(...getOptionValues('driveCapacityMb')),
|
||||||
|
antennaLimit: Math.max(...getOptionValues('antennaLimit')),
|
||||||
|
wordMuteLimit: Math.max(...getOptionValues('wordMuteLimit')),
|
||||||
|
webhookLimit: Math.max(...getOptionValues('webhookLimit')),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
@@ -33,7 +33,7 @@ export class S3Service {
|
|||||||
? false
|
? false
|
||||||
: meta.objectStorageS3ForcePathStyle,
|
: meta.objectStorageS3ForcePathStyle,
|
||||||
httpOptions: {
|
httpOptions: {
|
||||||
agent: this.httpRequestService.getAgentByUrl(new URL(u), !meta.objectStorageUseProxy),
|
agent: this.httpRequestService.getHttpAgentByUrl(new URL(u), !meta.objectStorageUseProxy),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -11,10 +11,10 @@ import { IdService } from '@/core/IdService.js';
|
|||||||
import { UserKeypair } from '@/models/entities/UserKeypair.js';
|
import { UserKeypair } from '@/models/entities/UserKeypair.js';
|
||||||
import { UsedUsername } from '@/models/entities/UsedUsername.js';
|
import { UsedUsername } from '@/models/entities/UsedUsername.js';
|
||||||
import generateUserToken from '@/misc/generate-native-user-token.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 { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { UtilityService } from './UtilityService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import UsersChart from './chart/charts/users.js';
|
||||||
|
import { UtilityService } from './UtilityService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SignupService {
|
export class SignupService {
|
||||||
@@ -112,7 +112,7 @@ export class SignupService {
|
|||||||
usernameLower: username.toLowerCase(),
|
usernameLower: username.toLowerCase(),
|
||||||
host: this.utilityService.toPunyNullable(host),
|
host: this.utilityService.toPunyNullable(host),
|
||||||
token: secret,
|
token: secret,
|
||||||
isAdmin: (await this.usersRepository.countBy({
|
isRoot: (await this.usersRepository.countBy({
|
||||||
host: IsNull(),
|
host: IsNull(),
|
||||||
})) === 0,
|
})) === 0,
|
||||||
}));
|
}));
|
||||||
|
@@ -2,11 +2,12 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||||||
import Redis from 'ioredis';
|
import Redis from 'ioredis';
|
||||||
import type { UsersRepository } from '@/models/index.js';
|
import type { UsersRepository } from '@/models/index.js';
|
||||||
import { Cache } from '@/misc/cache.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 { DI } from '@/di-symbols.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { StreamMessages } from '@/server/api/stream/types.js';
|
||||||
|
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserCacheService implements OnApplicationShutdown {
|
export class UserCacheService implements OnApplicationShutdown {
|
||||||
@@ -39,11 +40,9 @@ export class UserCacheService implements OnApplicationShutdown {
|
|||||||
const obj = JSON.parse(data);
|
const obj = JSON.parse(data);
|
||||||
|
|
||||||
if (obj.channel === 'internal') {
|
if (obj.channel === 'internal') {
|
||||||
const { type, body } = obj.message;
|
const { type, body } = obj.message as StreamMessages['internal']['payload'];
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'userChangeSuspendedState':
|
case 'userChangeSuspendedState':
|
||||||
case 'userChangeSilencedState':
|
|
||||||
case 'userChangeModeratorState':
|
|
||||||
case 'remoteUserUpdated': {
|
case 'remoteUserUpdated': {
|
||||||
const user = await this.usersRepository.findOneByOrFail({ id: body.id });
|
const user = await this.usersRepository.findOneByOrFail({ id: body.id });
|
||||||
this.userByIdCache.set(user.id, user);
|
this.userByIdCache.set(user.id, user);
|
||||||
@@ -64,12 +63,24 @@ export class UserCacheService implements OnApplicationShutdown {
|
|||||||
this.localUserByNativeTokenCache.set(body.newToken, user);
|
this.localUserByNativeTokenCache.set(body.newToken, user);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'follow': {
|
||||||
|
const follower = this.userByIdCache.get(body.followerId);
|
||||||
|
if (follower) follower.followingCount++;
|
||||||
|
const followee = this.userByIdCache.get(body.followeeId);
|
||||||
|
if (followee) followee.followersCount++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public findById(userId: User['id']) {
|
||||||
|
return this.userByIdCache.fetch(userId, () => this.usersRepository.findOneByOrFail({ id: userId }));
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public onApplicationShutdown(signal?: string | undefined) {
|
public onApplicationShutdown(signal?: string | undefined) {
|
||||||
this.redisSubscriber.off('message', this.onMessage);
|
this.redisSubscriber.off('message', this.onMessage);
|
||||||
|
@@ -62,6 +62,7 @@ export class UserFollowingService {
|
|||||||
private federatedInstanceService: FederatedInstanceService,
|
private federatedInstanceService: FederatedInstanceService,
|
||||||
private webhookService: WebhookService,
|
private webhookService: WebhookService,
|
||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
|
private globalEventService: GlobalEventService,
|
||||||
private perUserFollowingChart: PerUserFollowingChart,
|
private perUserFollowingChart: PerUserFollowingChart,
|
||||||
private instanceChart: InstanceChart,
|
private instanceChart: InstanceChart,
|
||||||
) {
|
) {
|
||||||
@@ -196,6 +197,8 @@ export class UserFollowingService {
|
|||||||
|
|
||||||
if (alreadyFollowed) return;
|
if (alreadyFollowed) return;
|
||||||
|
|
||||||
|
this.globalEventService.publishInternalEvent('follow', { followerId: follower.id, followeeId: followee.id });
|
||||||
|
|
||||||
//#region Increment counts
|
//#region Increment counts
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.usersRepository.increment({ id: follower.id }, 'followingCount', 1),
|
this.usersRepository.increment({ id: follower.id }, 'followingCount', 1),
|
||||||
@@ -314,6 +317,8 @@ export class UserFollowingService {
|
|||||||
follower: {id: User['id']; host: User['host']; },
|
follower: {id: User['id']; host: User['host']; },
|
||||||
followee: { id: User['id']; host: User['host']; },
|
followee: { id: User['id']; host: User['host']; },
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
this.globalEventService.publishInternalEvent('unfollow', { followerId: follower.id, followeeId: followee.id });
|
||||||
|
|
||||||
//#region Decrement following / followers counts
|
//#region Decrement following / followers counts
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.usersRepository.decrement({ id: follower.id }, 'followingCount', 1),
|
this.usersRepository.decrement({ id: follower.id }, 'followingCount', 1),
|
||||||
|
@@ -24,6 +24,12 @@ export class UtilityService {
|
|||||||
return this.toPuny(this.config.host) === this.toPuny(host);
|
return this.toPuny(this.config.host) === this.toPuny(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public isBlockedHost(blockedHosts: string[], host: string | null): boolean {
|
||||||
|
if (host == null) return false;
|
||||||
|
return blockedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`));
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public extractDbHost(uri: string): string {
|
public extractDbHost(uri: string): string {
|
||||||
const url = new URL(uri);
|
const url = new URL(uri);
|
||||||
|
@@ -30,7 +30,7 @@ export class WebfingerService {
|
|||||||
public async webfinger(query: string): Promise<IWebFinger> {
|
public async webfinger(query: string): Promise<IWebFinger> {
|
||||||
const url = this.genUrl(query);
|
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
|
@bindThis
|
||||||
|
@@ -3,8 +3,9 @@ import Redis from 'ioredis';
|
|||||||
import type { WebhooksRepository } from '@/models/index.js';
|
import type { WebhooksRepository } from '@/models/index.js';
|
||||||
import type { Webhook } from '@/models/entities/Webhook.js';
|
import type { Webhook } from '@/models/entities/Webhook.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { StreamMessages } from '@/server/api/stream/types.js';
|
||||||
|
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WebhookService implements OnApplicationShutdown {
|
export class WebhookService implements OnApplicationShutdown {
|
||||||
@@ -39,7 +40,7 @@ export class WebhookService implements OnApplicationShutdown {
|
|||||||
const obj = JSON.parse(data);
|
const obj = JSON.parse(data);
|
||||||
|
|
||||||
if (obj.channel === 'internal') {
|
if (obj.channel === 'internal') {
|
||||||
const { type, body } = obj.message;
|
const { type, body } = obj.message as StreamMessages['internal']['payload'];
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'webhookCreated':
|
case 'webhookCreated':
|
||||||
if (body.active) {
|
if (body.active) {
|
||||||
|
@@ -159,7 +159,7 @@ export class ApDbResolverService {
|
|||||||
if (key == null) return null;
|
if (key == null) return null;
|
||||||
|
|
||||||
return {
|
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,
|
key,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -291,7 +291,7 @@ export class ApInboxService {
|
|||||||
|
|
||||||
// アナウンス先をブロックしてたら中断
|
// アナウンス先をブロックしてたら中断
|
||||||
const meta = await this.metaService.fetch();
|
const meta = await this.metaService.fetch();
|
||||||
if (meta.blockedHosts.includes(this.utilityService.extractDbHost(uri))) return;
|
if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) return;
|
||||||
|
|
||||||
const unlock = await this.appLockService.getApLock(uri);
|
const unlock = await this.appLockService.getApLock(uri);
|
||||||
|
|
||||||
|
@@ -5,8 +5,10 @@ import { DI } from '@/di-symbols.js';
|
|||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import type { User } from '@/models/entities/User.js';
|
import type { User } from '@/models/entities/User.js';
|
||||||
import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.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 { bindThis } from '@/decorators.js';
|
||||||
|
import type Logger from '@/logger.js';
|
||||||
|
|
||||||
type Request = {
|
type Request = {
|
||||||
url: string;
|
url: string;
|
||||||
@@ -28,13 +30,21 @@ type PrivateKey = {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApRequestService {
|
export class ApRequestService {
|
||||||
|
private undiciFetcher: UndiciFetcher;
|
||||||
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
private userKeypairStoreService: UserKeypairStoreService,
|
private userKeypairStoreService: UserKeypairStoreService,
|
||||||
private httpRequestService: HttpRequestService,
|
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
|
@bindThis
|
||||||
@@ -148,16 +158,17 @@ export class ApRequestService {
|
|||||||
url,
|
url,
|
||||||
body,
|
body,
|
||||||
additionalHeaders: {
|
additionalHeaders: {
|
||||||
'User-Agent': this.config.userAgent,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.httpRequestService.getResponse({
|
await this.undiciFetcher.fetch(
|
||||||
url,
|
url,
|
||||||
method: req.request.method,
|
{
|
||||||
headers: req.request.headers,
|
method: req.request.method,
|
||||||
body,
|
headers: req.request.headers,
|
||||||
});
|
body,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -176,15 +187,16 @@ export class ApRequestService {
|
|||||||
},
|
},
|
||||||
url,
|
url,
|
||||||
additionalHeaders: {
|
additionalHeaders: {
|
||||||
'User-Agent': this.config.userAgent,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await this.httpRequestService.getResponse({
|
const res = await this.httpRequestService.fetch(
|
||||||
url,
|
url,
|
||||||
method: req.request.method,
|
{
|
||||||
headers: req.request.headers,
|
method: req.request.method,
|
||||||
});
|
headers: req.request.headers,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return await res.json();
|
return await res.json();
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,7 @@ import { InstanceActorService } from '@/core/InstanceActorService.js';
|
|||||||
import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js';
|
import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { MetaService } from '@/core/MetaService.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 { DI } from '@/di-symbols.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
@@ -12,11 +12,15 @@ import { isCollectionOrOrderedCollection } from './type.js';
|
|||||||
import { ApDbResolverService } from './ApDbResolverService.js';
|
import { ApDbResolverService } from './ApDbResolverService.js';
|
||||||
import { ApRendererService } from './ApRendererService.js';
|
import { ApRendererService } from './ApRendererService.js';
|
||||||
import { ApRequestService } from './ApRequestService.js';
|
import { ApRequestService } from './ApRequestService.js';
|
||||||
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import type { IObject, ICollection, IOrderedCollection } from './type.js';
|
import type { IObject, ICollection, IOrderedCollection } from './type.js';
|
||||||
|
import type Logger from '@/logger.js';
|
||||||
|
|
||||||
export class Resolver {
|
export class Resolver {
|
||||||
private history: Set<string>;
|
private history: Set<string>;
|
||||||
private user?: ILocalUser;
|
private user?: ILocalUser;
|
||||||
|
private undiciFetcher: UndiciFetcher;
|
||||||
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private config: Config,
|
private config: Config,
|
||||||
@@ -31,9 +35,14 @@ export class Resolver {
|
|||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
private apDbResolverService: ApDbResolverService,
|
private apDbResolverService: ApDbResolverService,
|
||||||
|
private loggerService: LoggerService,
|
||||||
private recursionLimit = 100,
|
private recursionLimit = 100,
|
||||||
) {
|
) {
|
||||||
this.history = new Set();
|
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
|
@bindThis
|
||||||
@@ -87,7 +96,7 @@ export class Resolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const meta = await this.metaService.fetch();
|
const meta = await this.metaService.fetch();
|
||||||
if (meta.blockedHosts.includes(host)) {
|
if (this.utilityService.isBlockedHost(meta.blockedHosts, host)) {
|
||||||
throw new Error('Instance is blocked');
|
throw new Error('Instance is blocked');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,8 +105,8 @@ export class Resolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const object = (this.user
|
const object = (this.user
|
||||||
? await this.apRequestService.signedGet(value, this.user)
|
? await this.apRequestService.signedGet(value, this.user) as IObject
|
||||||
: await this.httpRequestService.getJson(value, 'application/activity+json, application/ld+json')) as IObject;
|
: await this.undiciFetcher.getJson<IObject>(value, 'application/activity+json, application/ld+json'));
|
||||||
|
|
||||||
if (object == null || (
|
if (object == null || (
|
||||||
Array.isArray(object['@context']) ?
|
Array.isArray(object['@context']) ?
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import * as crypto from 'node:crypto';
|
import * as crypto from 'node:crypto';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import fetch from 'node-fetch';
|
|
||||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { CONTEXTS } from './misc/contexts.js';
|
import { CONTEXTS } from './misc/contexts.js';
|
||||||
@@ -116,14 +115,19 @@ class LdSignature {
|
|||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async fetchDocument(url: string) {
|
private async fetchDocument(url: string) {
|
||||||
const json = await fetch(url, {
|
const json = await this.httpRequestService.fetch(
|
||||||
headers: {
|
url,
|
||||||
Accept: 'application/ld+json, application/json',
|
{
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/ld+json, application/json',
|
||||||
|
},
|
||||||
|
// TODO
|
||||||
|
//timeout: this.loderTimeout,
|
||||||
},
|
},
|
||||||
// TODO
|
{
|
||||||
//timeout: this.loderTimeout,
|
noOkError: true,
|
||||||
agent: u => u.protocol === 'http:' ? this.httpRequestService.httpAgent : this.httpRequestService.httpsAgent,
|
}
|
||||||
}).then(res => {
|
).then(res => {
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw `${res.status} ${res.statusText}`;
|
throw `${res.status} ${res.statusText}`;
|
||||||
} else {
|
} else {
|
||||||
|
@@ -324,7 +324,7 @@ export class ApNoteService {
|
|||||||
|
|
||||||
// ブロックしてたら中断
|
// ブロックしてたら中断
|
||||||
const meta = await this.metaService.fetch();
|
const meta = await this.metaService.fetch();
|
||||||
if (meta.blockedHosts.includes(this.utilityService.extractDbHost(uri))) throw { statusCode: 451 };
|
if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) throw { statusCode: 451 };
|
||||||
|
|
||||||
const unlock = await this.appLockService.getApLock(uri);
|
const unlock = await this.appLockService.getApLock(uri);
|
||||||
|
|
||||||
|
@@ -61,21 +61,21 @@ export default class FederationChart extends Chart<typeof schema> {
|
|||||||
this.followingsRepository.createQueryBuilder('following')
|
this.followingsRepository.createQueryBuilder('following')
|
||||||
.select('COUNT(DISTINCT following.followeeHost)')
|
.select('COUNT(DISTINCT following.followeeHost)')
|
||||||
.where('following.followeeHost IS NOT NULL')
|
.where('following.followeeHost IS NOT NULL')
|
||||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT IN (:...blocked)', { blocked: meta.blockedHosts })
|
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||||
.andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
|
.andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
|
||||||
.getRawOne()
|
.getRawOne()
|
||||||
.then(x => parseInt(x.count, 10)),
|
.then(x => parseInt(x.count, 10)),
|
||||||
this.followingsRepository.createQueryBuilder('following')
|
this.followingsRepository.createQueryBuilder('following')
|
||||||
.select('COUNT(DISTINCT following.followerHost)')
|
.select('COUNT(DISTINCT following.followerHost)')
|
||||||
.where('following.followerHost IS NOT NULL')
|
.where('following.followerHost IS NOT NULL')
|
||||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followerHost NOT IN (:...blocked)', { blocked: meta.blockedHosts })
|
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followerHost NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||||
.andWhere(`following.followerHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
|
.andWhere(`following.followerHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
|
||||||
.getRawOne()
|
.getRawOne()
|
||||||
.then(x => parseInt(x.count, 10)),
|
.then(x => parseInt(x.count, 10)),
|
||||||
this.followingsRepository.createQueryBuilder('following')
|
this.followingsRepository.createQueryBuilder('following')
|
||||||
.select('COUNT(DISTINCT following.followeeHost)')
|
.select('COUNT(DISTINCT following.followeeHost)')
|
||||||
.where('following.followeeHost IS NOT NULL')
|
.where('following.followeeHost IS NOT NULL')
|
||||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT IN (:...blocked)', { blocked: meta.blockedHosts })
|
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||||
.andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
|
.andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
|
||||||
.andWhere(`following.followeeHost IN (${ pubsubSubQuery.getQuery() })`)
|
.andWhere(`following.followeeHost IN (${ pubsubSubQuery.getQuery() })`)
|
||||||
.setParameters(pubsubSubQuery.getParameters())
|
.setParameters(pubsubSubQuery.getParameters())
|
||||||
@@ -84,7 +84,7 @@ export default class FederationChart extends Chart<typeof schema> {
|
|||||||
this.instancesRepository.createQueryBuilder('instance')
|
this.instancesRepository.createQueryBuilder('instance')
|
||||||
.select('COUNT(instance.id)')
|
.select('COUNT(instance.id)')
|
||||||
.where(`instance.host IN (${ subInstancesQuery.getQuery() })`)
|
.where(`instance.host IN (${ subInstancesQuery.getQuery() })`)
|
||||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT IN (:...blocked)', { blocked: meta.blockedHosts })
|
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||||
.andWhere('instance.isSuspended = false')
|
.andWhere('instance.isSuspended = false')
|
||||||
.andWhere('instance.isNotResponding = false')
|
.andWhere('instance.isNotResponding = false')
|
||||||
.getRawOne()
|
.getRawOne()
|
||||||
@@ -92,7 +92,7 @@ export default class FederationChart extends Chart<typeof schema> {
|
|||||||
this.instancesRepository.createQueryBuilder('instance')
|
this.instancesRepository.createQueryBuilder('instance')
|
||||||
.select('COUNT(instance.id)')
|
.select('COUNT(instance.id)')
|
||||||
.where(`instance.host IN (${ pubInstancesQuery.getQuery() })`)
|
.where(`instance.host IN (${ pubInstancesQuery.getQuery() })`)
|
||||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT IN (:...blocked)', { blocked: meta.blockedHosts })
|
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||||
.andWhere('instance.isSuspended = false')
|
.andWhere('instance.isSuspended = false')
|
||||||
.andWhere('instance.isNotResponding = false')
|
.andWhere('instance.isNotResponding = false')
|
||||||
.getRawOne()
|
.getRawOne()
|
||||||
|
@@ -22,23 +22,25 @@ export class EmojiEntityService {
|
|||||||
@bindThis
|
@bindThis
|
||||||
public async pack(
|
public async pack(
|
||||||
src: Emoji['id'] | Emoji,
|
src: Emoji['id'] | Emoji,
|
||||||
|
opts: { omitHost?: boolean; omitId?: boolean; } = {},
|
||||||
): Promise<Packed<'Emoji'>> {
|
): Promise<Packed<'Emoji'>> {
|
||||||
const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
|
const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: emoji.id,
|
id: opts.omitId ? undefined : emoji.id,
|
||||||
aliases: emoji.aliases,
|
aliases: emoji.aliases,
|
||||||
name: emoji.name,
|
name: emoji.name,
|
||||||
category: emoji.category,
|
category: emoji.category,
|
||||||
host: emoji.host,
|
host: opts.omitHost ? undefined : emoji.host,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public packMany(
|
||||||
emojis: any[],
|
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)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,8 +7,8 @@ import type { } from '@/models/entities/Blocking.js';
|
|||||||
import type { User } from '@/models/entities/User.js';
|
import type { User } from '@/models/entities/User.js';
|
||||||
import type { Instance } from '@/models/entities/Instance.js';
|
import type { Instance } from '@/models/entities/Instance.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
import { UtilityService } from '../UtilityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { UserEntityService } from './UserEntityService.js';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InstanceEntityService {
|
export class InstanceEntityService {
|
||||||
@@ -17,6 +17,8 @@ export class InstanceEntityService {
|
|||||||
private instancesRepository: InstancesRepository,
|
private instancesRepository: InstancesRepository,
|
||||||
|
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
|
|
||||||
|
private utilityService: UtilityService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +37,7 @@ export class InstanceEntityService {
|
|||||||
followersCount: instance.followersCount,
|
followersCount: instance.followersCount,
|
||||||
isNotResponding: instance.isNotResponding,
|
isNotResponding: instance.isNotResponding,
|
||||||
isSuspended: instance.isSuspended,
|
isSuspended: instance.isSuspended,
|
||||||
isBlocked: meta.blockedHosts.includes(instance.host),
|
isBlocked: this.utilityService.isBlockedHost(meta.blockedHosts, instance.host),
|
||||||
softwareName: instance.softwareName,
|
softwareName: instance.softwareName,
|
||||||
softwareVersion: instance.softwareVersion,
|
softwareVersion: instance.softwareVersion,
|
||||||
openRegistrations: instance.openRegistrations,
|
openRegistrations: instance.openRegistrations,
|
||||||
|
@@ -98,7 +98,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||||||
}),
|
}),
|
||||||
reaction: notification.reaction,
|
reaction: notification.reaction,
|
||||||
} : {}),
|
} : {}),
|
||||||
...(notification.type === 'pollVote' ? {
|
...(notification.type === 'pollVote' ? { // TODO: そのうち消す
|
||||||
note: this.noteEntityService.pack(notification.note ?? notification.noteId!, { id: notification.notifieeId }, {
|
note: this.noteEntityService.pack(notification.note ?? notification.noteId!, { id: notification.notifieeId }, {
|
||||||
detail: true,
|
detail: true,
|
||||||
_hint_: options._hintForEachNotes_,
|
_hint_: options._hintForEachNotes_,
|
||||||
|