1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-05-12 04:15:31 +02:00

Compare commits

..

108 Commits

Author SHA1 Message Date
syuilo
7f9180e045 Merge branch 'develop' into lowpowermode 2025-08-01 15:20:01 +09:00
かっこかり
62f68de800 fix(frontend); Playのボタンがはみ出している問題を修正 (#16303) 2025-08-01 14:31:49 +09:00
syuilo
5bf13c4cc2 Update CHANGELOG.md 2025-08-01 13:44:06 +09:00
syuilo
16f47adcc6 Update CHANGELOG.md 2025-08-01 13:43:09 +09:00
github-actions[bot]
8eba8c7218 Bump version to 2025.8.0-alpha.1 2025-08-01 04:06:20 +00:00
syuilo
b214a19d5f New Crowdin updates (#16300)
* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Russian)

* New translations ja-jp.yml (Russian)

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

* New translations ja-jp.yml (Russian)

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

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

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

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

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Russian)

* New translations ja-jp.yml (English)

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

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

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (Arabic)

* New translations ja-jp.yml (Czech)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Norwegian)

* New translations ja-jp.yml (Polish)

* New translations ja-jp.yml (Portuguese)

* New translations ja-jp.yml (Slovak)

* New translations ja-jp.yml (Ukrainian)

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

* New translations ja-jp.yml (Vietnamese)

* New translations ja-jp.yml (Indonesian)

* New translations ja-jp.yml (Bengali)

* New translations ja-jp.yml (Thai)
2025-08-01 13:04:32 +09:00
syuilo
1082145c74 enhance: ジョブのログを表示できるように 2025-08-01 12:54:33 +09:00
syuilo
2a836047e3 Update CHANGELOG.md 2025-08-01 12:38:50 +09:00
syuilo
b2b07e5f21 enhance(backend): 連合関係のサーバー設定のデフォルト値をウィザード側に移動
- サーバー初期設定ウィザードでデフォルト値を設定できるため、データベース上のデフォルト値でオンにしておく必要がない
- 連合は初期設定が終わるまで閉じられている方が安全
2025-08-01 12:36:25 +09:00
github-actions[bot]
da06f75455 Bump version to 2025.8.0-alpha.0 2025-08-01 02:50:01 +00:00
syuilo
d624da9c1a feat: remote notes cleaning (#16292)
* Create CleanRemoteNotesProcessorService.ts

* Update CleanRemoteNotesProcessorService.ts

* Update CleanRemoteNotesProcessorService.ts

* wip

* Update CleanRemoteNotesProcessorService.ts

* Update CleanRemoteNotesProcessorService.ts

* Update CleanRemoteNotesProcessorService.ts

* Update CleanRemoteNotesProcessorService.ts

* Update CleanRemoteNotesProcessorService.ts

* Update CleanRemoteNotesProcessorService.ts

* Update CleanRemoteNotesProcessorService.ts

* Update CleanRemoteNotesProcessorService.ts

* Update job-queue.job.vue

* wip

* Update CleanRemoteNotesProcessorService.ts

* wip

* wip

* wip

* Update CleanRemoteNotesProcessorService.ts

* wip

* Update CHANGELOG.md

* Revert "wip"

This reverts commit 89d455d302.

* wip

* woip

* Update QueueService.ts

* Update QueueService.ts

* ピン留め考慮

* Update CleanRemoteNotesProcessorService.ts

* Update QueueService.ts

* Update CleanRemoteNotesProcessorService.ts

* add log

* Update CHANGELOG.md

* wip

* Update MkServerSetupWizard.vue
2025-08-01 11:49:12 +09:00
syuilo
4c520fa693 enhance(frontend): サーバーの初期設定ウィザードをやり直せるように 2025-08-01 11:07:09 +09:00
syuilo
a7d1c94f48 enhance(backend): tweak system job log 2025-08-01 09:51:43 +09:00
かっこかり
4f5d3f6f7d fix(frontend): MkNotesTimelineの日付dividerのスタイル修正 (#16306) 2025-07-31 21:45:34 +09:00
syuilo
4be0045826 update minimum nodejs version 2025-07-31 21:21:44 +09:00
syuilo
18daf43f70 clean up
ワイルドカードセレクタはexpensive
2025-07-31 21:12:07 +09:00
syuilo
862a6fae79 enhance(backend): 古いバージョンで作成され現在使われなくなったrepeatableジョブをクリーンアップするように 2025-07-31 20:57:36 +09:00
かっこかり
a45e89c300 fix(frontend): 適用中のテーマを保持する際にリアクティビティも保持される問題を修正 (#16304)
* fix(frontend): 現在のテーマを保持する際にリアクティビティが保持される問題を修正

* Update Changelog

* Update theme.ts
2025-07-31 18:47:22 +09:00
syuilo
35888eb8f4 enhance(backend): BullMQの廃止されたRepeatableからJob Schedulersに移行 2025-07-31 18:16:21 +09:00
syuilo
f2a23fb55e ノートの脱CASCADE削除 (#16332)
* wip

* Update CHANGELOG.md

* Update QueryService.ts

* Update QueryService.ts

* wip

* Update MkNoteDetailed.vue

* Update NoteEntityService.ts

* wip

* Update antennas.ts

* Update create.ts

* Update NoteEntityService.ts

* wip

* Update CHANGELOG.md

* Update NoteEntityService.ts

* Update NoteCreateService.ts

* Update note.test.ts

* Update note.test.ts

* Update ClientServerService.ts

* Update ClientServerService.ts

* add error handling

* Update NoteDeleteService.ts

* Update CHANGELOG.md

* Update entities.ts

* Update entities.ts

* Update misskey-js.api.md
2025-07-31 14:40:51 +09:00
tamaina
414d5958c1 fix(test): Fix name of a test in e2e/timelines.ts (#16334) 2025-07-31 14:22:32 +09:00
tamaina
8c65d8d020 test(backend): e2e/timelines.ts: 非FTT時のテストを追加, 凍結のテストを追加, これにかかる幾つかのバグ修正 (#16284)
* test(backend): 非FTT時のテストを追加

* clean up

* skip test about reply

* Fix #16289

* clean up

* cherry pick

* add renote test

* Fix https://github.com/misskey-dev/misskey/issues/16293

* remove debug log
2025-07-30 21:41:46 +09:00
かっこかり
927aa9dc3d fix(frontend): inline な SearchMarker のパスが正しくない問題を修正 (#16301)
* replace URL path for inlined SearchMarkers

The search index looks like:

```ts
[
 {
   id: 'foo', label: 'security',
   path: '/settings/security', inlining: ['2fa'],
 },
 {
   id: '2fa',
   label: 'two-factor auth',
   path: '/settings/2fa', // guessed wrong by the index generation
 },
 {
   id: 'aaaa',
   parentId: '2fa',
   label: 'totp',
 },
 …
]
```

This file post-processes that index and re-parents the inlined
sections. Problem was, it left the (wrong) `path` untouched.

Replacing the `path` makes the search work fine.

* Update Changelog

---------

Co-authored-by: dakkar <dakkar@thenautilus.net>
2025-07-30 14:39:55 +09:00
かっこかり
1dec8b2329 fix(frontend/test): Cypressが失敗する問題を修正 (#16307)
* attempt to fix test

* fix(frontend/test): Cypressが失敗する問題を修正
2025-07-30 14:12:59 +09:00
syuilo
e8b5aa5c2f wip 2025-07-30 13:52:52 +09:00
zyoshoka
b0493abe93 chore: continue backend E2E test even if fail with minimum Node.js version (#16324)
* chore: continue backend E2E test even if fail with minimum Node.js version

* chore: disable `fail-fast`
2025-07-30 12:32:24 +09:00
かっこかり
4f653f2fbc enhance(frontend): typed nirax (#16309)
* enhance(frontend): typed nirax

* migrate router.replace

* fix
2025-07-30 12:30:35 +09:00
tamaina
b660769288 perf(frontend): draw-blurhash workerの結果をpostMessageする際にImageBitmapを移譲する (#16330) 2025-07-30 09:30:07 +09:00
かっこかり
48246bd166 fix(deps): regenerate lockfile (#16302) 2025-07-19 14:00:19 +09:00
github-actions[bot]
73419e8a61 [skip ci] Update CHANGELOG.md (prepend template) 2025-07-18 00:28:02 +00:00
github-actions[bot]
9852196ddc Release: 2025.7.0 2025-07-18 00:27:57 +00:00
github-actions[bot]
598641de48 Bump version to 2025.7.0-rc.1 2025-07-17 11:07:00 +00:00
syuilo
4d643c77c5 New Crowdin updates (#16274)
* New translations ja-jp.yml (Chinese Simplified)

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

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Portuguese)

* New translations ja-jp.yml (English)
2025-07-17 20:03:13 +09:00
syuilo
a686592734 enhance(frontend): disable InfiniteScroll to improve stability
#16229
2025-07-17 20:02:55 +09:00
syuilo
0619dba04d remove unused code 2025-07-17 20:00:05 +09:00
github-actions[bot]
fbd6b67f1f Bump version to 2025.7.0-rc.0 2025-07-17 08:33:52 +00:00
かっこかり
e5c2be15f7 fix(deps): Node.jsの最小バージョンを引き上げ (#16296)
* Update package.json

* Update min.node-version

* Update CHANGELOG.md
2025-07-17 13:20:43 +09:00
renovate[bot]
1b791258ce fix(deps): update [frontend] update dependencies (#16202)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-17 12:14:19 +09:00
renovate[bot]
49cac2f72b fix(deps): update [root] update dependencies (#16200)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-17 11:56:55 +09:00
renovate[bot]
5d5aa09459 chore(deps): update [misskey-js] update dependencies (#16199)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-17 11:42:34 +09:00
github-actions[bot]
e1834beae8 Bump version to 2025.7.0-beta.2 2025-07-16 06:52:02 +00:00
anatawa12
6f6fdfe28e Migration cleanup (#16288)
* chore: apply several @Index and @ManyToOne to match actual migration code

* chore: several decorator updates with typeorm bug workaround with patches

* feat: add final cleanup migration

* dev: add .editorconfig settings for generated migrations

* chore: update dockerfile to build package with patches

* chore: update federation test compose to include patches

* chore: revert few dependency update

* chore: don't check disableRegistration on test env

* test: add test for checking migration script

* chore: set proxyRemoteFiles true in test config

* chore: enter invitation code in signup test

* fix: register send button is not disabled when invitationCode is not input
2025-07-16 15:49:05 +09:00
tamaina
ad7bf096e1 enhance(backend): usernameに対してもprohibitedWordsForNameOfUserを適用 (#16282)
* enhance(backend): usernameに対してもprohibitedWordsForNameOfUserを適用
Resolve #16281

* fix locales/index/d.ts
2025-07-15 09:32:46 +09:00
anatawa12
08cc5a99bb Don't remove notes when reply / renote is removed (#16287)
* chore: make NO ACTION on channel/reply/renote removal

* chore(docs): add description to show a possibility of reply null with replyId non-null

* fix: packing NoteDraft fails when reply / renote is removed

* feat: show drafts targeting removed renote / reply as "削除された投稿への投稿"
2025-07-15 09:20:48 +09:00
github-actions[bot]
f954b1e276 Bump version to 2025.7.0-beta.1 2025-07-12 06:17:58 +00:00
syuilo
5ecaf5095e enhance: ウォーターマーク機能をロールで制御可能に 2025-07-12 15:13:35 +09:00
syuilo
d2c4f79886 New Crowdin updates (#16258)
* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Korean)

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

* New translations ja-jp.yml (Spanish)

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

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Ukrainian)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)
2025-07-12 15:00:30 +09:00
anatawa12
e26369ed48 fix: unable to horizontally scroll when pull to refresh is enabled (#16273) 2025-07-12 15:00:06 +09:00
syuilo
c165749a29 chore(frontend): fix type errors 2025-07-06 20:54:02 +09:00
syuilo
c4fdf5a47c chore(frontend): fix type errors 2025-07-06 20:47:31 +09:00
syuilo
288f0abeac chore(frontend): fix type errors 2025-07-06 20:37:09 +09:00
かっこかり
89ed8be8ff fix(frontend): MkRange/MkSelectでdisabledが効かなくなっている問題を修正 (#16263)
* fix(frontend): MkRange/MkSelectでdisabledが効かなくなっている問題を修正

* Update Changelog

* 誤字
2025-07-06 19:38:09 +09:00
かっこかり
a8abb03d17 refactor(frontend): Formまわりの型強化 (#16260)
* refactor(frontend): Formまわりの型強化

* fix

* avoid non-null assertion and add null check for safety

* refactor

* avoid non-null assertion and add null check for safety

* Update clip.vue

---------

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2025-07-06 19:36:11 +09:00
syuilo
c2a01551a7 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2025-07-06 19:32:33 +09:00
syuilo
553ccff77c chore(frontend): tweak selector to improve rendering performance 2025-07-06 19:32:31 +09:00
かっこかり
9dddc84750 refactor(frontend): menuの型定義の可読性向上 (#16261) 2025-07-06 17:24:34 +09:00
syuilo
004cfd5e4b clean up 2025-07-06 15:57:21 +09:00
syuilo
40a35968f0 clean up 2025-07-06 15:54:33 +09:00
syuilo
e6ec15e397 feat: 特定のドライブファイルを添付しているチャットメッセージを一覧できるように 2025-07-06 09:54:49 +09:00
syuilo
8430256f22 clean up 2025-07-05 19:29:18 +09:00
Souma
abde15979b enhance(backend): Add display name to email (#16256)
* feat(backend): Add display name to email

Make it clear who sent emails.

* docs(changelog): Add a description about this change

Users can notice what's changed by this PR.
2025-07-05 18:22:08 +09:00
syuilo
f128682200 fix type errors 2025-07-05 17:13:29 +09:00
syuilo
cc4cdd1ec0 clean up 2025-07-05 12:13:08 +09:00
syuilo
075df75afa Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2025-07-05 11:56:22 +09:00
syuilo
64eb338d65 🎨 2025-07-05 11:56:20 +09:00
tamaina
d986da745b ノートのサーバー情報(InstanceTicker)のデザイン/パフォーマンス改善(-webkit-text-stroke ver) (#16225)
* Revert "enhance(frontend): Instance Tickerのデザイン改善 (#15946)"

This reverts commit 04928ba7d1.

* enhance(frontend): Instance Tickerのデザイン改善(-webkit-text-stroke)

* 🎨

* use theme fg/bg

* use panel
2025-07-05 09:59:48 +09:00
syuilo
50f5b29290 New Crowdin updates (#16237)
* New translations ja-jp.yml (Indonesian)

* New translations ja-jp.yml (Indonesian)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Portuguese)

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

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

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Catalan)

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

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

* New translations ja-jp.yml (Indonesian)
2025-07-05 09:07:25 +09:00
syuilo
a460bb7913 perf(frontend): improve rendering performance 2025-07-05 09:05:47 +09:00
syuilo
7cf1eccd04 clean up 2025-07-05 08:31:20 +09:00
syuilo
73397e1b7e Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2025-07-05 08:18:56 +09:00
tamaina
bf17092b41 test: VS Code上で複数のjestテストを表示できるように (#16251) 2025-07-05 08:18:15 +09:00
github-actions[bot]
6d1018f42b Bump version to 2025.7.0-beta.0 2025-07-04 09:52:01 +00:00
かっこかり
7667011266 fix(frontend): ウェルカムタイムラインのメディア表示がCWを考慮していない問題を修正 (#16247)
* fix(frontend): ウェルカムタイムラインのメディア表示がCWを考慮していない問題を修正

* Update Changelog
2025-07-04 18:49:21 +09:00
syuilo
a45ccc18b4 refactor 2025-07-04 18:33:41 +09:00
syuilo
c29a4d9503 fix(test): Play検索機能でBackend Unit Testが壊れている 2025-07-04 18:31:34 +09:00
syuilo
5caf2b27cf fix(test): Play検索機能でBackend Unit Testが壊れている
Fix #16248
2025-07-04 16:32:56 +09:00
syuilo
dd87d26bdc feat: Playを検索できるように
#13115
2025-07-04 10:20:00 +09:00
かっこかり
b7a6301c2e fix(frontend): プラグインのアンインストール時にローカルのセーブデータを削除するように (#16246)
* fix(frontend): プラグインのアンインストール時にローカルのセーブデータを削除するように

* Update Changelog

* remove unused import

---------

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2025-07-04 07:40:00 +09:00
syuilo
73e8d950df enhance(frontend): 投稿フォームにファイルをペースト/ドロップした際のUXを改善
Resolve #16205
2025-07-03 19:11:46 +09:00
syuilo
45033974f7 Update CHANGELOG.md 2025-07-03 19:03:34 +09:00
github-actions[bot]
7acfbc23d6 Bump version to 2025.7.0-alpha.0 2025-07-03 09:57:13 +00:00
github-actions[bot]
a9a746edce Bump version to 2025.6.4-alpha.4 2025-07-03 09:55:33 +00:00
かっこかり
179d990c39 fix(frontend): タブが不可視なあいだのpaginationのアップデートを停止するように (#16243)
* fix(frontend): タブが不可視なあいだのpaginationのアップデートを停止するように

* fix lint

* 待たない
2025-07-03 18:52:16 +09:00
4ster1sk
7c44881ca8 enhance(backend): avatarUrlの上限文字数の引き上げ (#16235) 2025-07-03 18:03:02 +09:00
tamaina
ccbc4cffaa enhance(frontend): 共有ページで、titleとtextに同じ内容が入っていた際の削除ロジックを強化 (#16226)
* enhance(frontend): 共有ページで、titleとtextに同じ内容が入っていた際の削除ロジックを強化
Fix #16224

* fix

* +→*

* fix

* use RegExp.test

* Update packages/frontend/src/pages/share.vue

Co-authored-by: zyoshoka <107108195+zyoshoka@users.noreply.github.com>

---------

Co-authored-by: zyoshoka <107108195+zyoshoka@users.noreply.github.com>
2025-07-03 18:00:43 +09:00
tamaina
706244925d fix(frontend): 条件により保存できない場合のメッセージを汎用的なものへ (#16238)
Fix #16228
2025-07-03 17:59:55 +09:00
かっこかり
09a5e4b10a fix(frontend): Paginatorの型エラー解消 (#16230)
* fix(frontend): fix paginator type error

* fix

* refactor

* fix

* fix

* fix(paginator): remove readonly type

* fix

* typo

* fix: R -> E

* remove any

---------

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2025-07-03 11:20:26 +09:00
syuilo
c48acad04b 🎨 2025-06-29 17:21:43 +09:00
github-actions[bot]
5d3bb02f4b Bump version to 2025.6.4-alpha.3 2025-06-29 06:47:43 +00:00
syuilo
933e252687 fix of f1deb89e34 2025-06-29 15:36:39 +09:00
syuilo
f1deb89e34 refactor(frontend): improve pagination implementation 2025-06-29 15:11:25 +09:00
syuilo
8bc822d829 feat(backend): クリップ内でノートを検索できるように 2025-06-29 15:10:51 +09:00
syuilo
c215cccf1d enhance(frontend): ファイルアップロード時にセンシティブ設定されているか表示するように 2025-06-29 08:50:55 +09:00
github-actions[bot]
0685bdf05c Bump version to 2025.6.4-alpha.2 2025-06-28 12:52:32 +00:00
syuilo
3394ed2122 New Crowdin updates (#16207)
* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Thai)

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

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Portuguese)

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

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

* New translations ja-jp.yml (English)

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

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

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

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (English)

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

* New translations ja-jp.yml (Portuguese)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Portuguese)

* New translations ja-jp.yml (English)
2025-06-28 21:43:22 +09:00
syuilo
c5a440cf22 Update types.ts 2025-06-28 21:43:14 +09:00
syuilo
3c6f07fc8c feat: モデログを検索できるように 2025-06-28 21:38:54 +09:00
syuilo
3c5ed0ffbb enhance(frontend): improve modlog pagination 2025-06-28 21:18:36 +09:00
syuilo
b8e8f3ad25 enhance: ページネーション(一覧表示)の基準日時を指定できるように sinceId/untilIdが指定可能なエンドポイントにおいて、sinceDate/untilDateも指定可能に 2025-06-28 20:21:21 +09:00
syuilo
012b2a9764 enhance(frontend): improve MkTl rendering 2025-06-28 19:24:55 +09:00
syuilo
dfbc40f868 lint 2025-06-28 19:20:02 +09:00
syuilo
32ddaa0cf8 Update about-misskey.vue 2025-06-28 12:02:16 +09:00
syuilo
bf6e218355 refactor 2025-06-28 12:00:15 +09:00
syuilo
19ef6c0b14 Update about-misskey.vue 2025-06-27 20:10:17 +09:00
syuilo
535b86f05e lint 2025-06-27 10:02:49 +09:00
taichan
01a94eaecb chore(CI): cache ffmpeg (#16223)
* ci: use daily cache for ffmpeg

* fix(ci): input type

* Fix current date

* Just use Daily cache

* fix condition
2025-06-26 19:08:47 +09:00
syuilo
9a28fa0534 refactor(frontend/pref): refactor preferences manager
Refactored preferences manager to decouple account context and storage provider, improving normalization and loading of profiles. Replaced static profile creation/normalization with instance-based logic, and updated usage in preferences.ts to pass account context explicitly. This enhances maintainability and prepares for better guest account handling.
2025-06-26 16:25:43 +09:00
github-actions[bot]
899273554a Bump version to 2025.6.4-alpha.1 2025-06-26 04:38:12 +00:00
360 changed files with 10519 additions and 6798 deletions

View File

@@ -13,3 +13,7 @@ trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_style = space
[packages/backend/migration/*.js]
indent_style = space
indent_size = 4

View File

@@ -1 +1 @@
20.10.0
22.15.0

View File

@@ -15,3 +15,5 @@ redis:
host: 127.0.0.1
port: 56312
id: aidx
proxyRemoteFiles: true

View File

@@ -18,6 +18,14 @@ on:
- packages/misskey-js/**
- .github/workflows/test-backend.yml
- .github/misskey/test.yml
workflow_dispatch:
inputs:
force_ffmpeg_cache_update:
description: 'Force update ffmpeg cache'
required: false
default: false
type: boolean
jobs:
unit:
name: Unit tests (backend)
@@ -47,7 +55,22 @@ jobs:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Get current date
id: current-date
run: echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Setup and Restore ffmpeg/ffprobe Cache
id: cache-ffmpeg
uses: actions/cache@v4
with:
path: |
/usr/local/bin/ffmpeg
/usr/local/bin/ffprobe
# daily cache
key: ${{ runner.os }}-ffmpeg-${{ steps.current-date.outputs.today }}
restore-keys: |
${{ runner.os }}-ffmpeg-${{ steps.current-date.outputs.today }}
- name: Install FFmpeg
if: steps.cache-ffmpeg.outputs.cache-hit != 'true' || github.event.inputs.force_ffmpeg_cache_update == true
run: |
for i in {1..3}; do
echo "Attempt $i: Installing FFmpeg..."
@@ -86,6 +109,7 @@ jobs:
name: E2E tests (backend)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version-file:
- .node-version
@@ -129,3 +153,47 @@ jobs:
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./packages/backend/coverage/coverage-final.json
migration:
name: Migration tests (backend)
runs-on: ubuntu-latest
strategy:
matrix:
node-version-file:
- .node-version
#- .github/min.node-version
services:
postgres:
image: postgres:15
ports:
- 54312:5432
env:
POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust
steps:
- uses: actions/checkout@v4.2.2
with:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Get current date
id: current-date
run: echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Use Node.js
uses: actions/setup-node@v4.4.0
with:
node-version-file: ${{ matrix.node-version-file }}
cache: 'pnpm'
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .github/misskey/test.yml .config
- name: Build
run: pnpm build
- name: Run migrations
run: MISSKEY_CONFIG_YML=test.yml pnpm --filter backend migrate
- name: Check no migrations are remaining
run: MISSKEY_CONFIG_YML=test.yml pnpm --filter backend check-migrations

View File

@@ -14,6 +14,13 @@ on:
- packages/backend/**
- packages/misskey-js/**
- .github/workflows/test-federation.yml
workflow_dispatch:
inputs:
force_ffmpeg_cache_update:
description: 'Force update ffmpeg cache'
required: false
default: false
type: boolean
jobs:
test:
@@ -30,7 +37,22 @@ jobs:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Get current date
id: current-date
run: echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Setup and Restore ffmpeg/ffprobe Cache
id: cache-ffmpeg
uses: actions/cache@v4
with:
path: |
/usr/local/bin/ffmpeg
/usr/local/bin/ffprobe
# daily cache
key: ${{ runner.os }}-ffmpeg-${{ steps.current-date.outputs.today }}
restore-keys: |
${{ runner.os }}-ffmpeg-${{ steps.current-date.outputs.today }}
- name: Install FFmpeg
if: steps.cache-ffmpeg.outputs.cache-hit != 'true' || github.event.inputs.force_ffmpeg_cache_update == true
run: |
for i in {1..3}; do
echo "Attempt $i: Installing FFmpeg..."

View File

@@ -6,8 +6,12 @@
"files.associations": {
"*.test.ts": "typescript"
},
"jest.jestCommandLine": "pnpm run jest",
"jest.runMode": "on-demand",
"jest.virtualFolders": [
{ "name": "backend unit", "jestCommandLine": "pnpm -F backend run test" },
{ "name": "backend e2e", "jestCommandLine": "pnpm -F backend run test:e2e"},
{ "name": "misskey-js", "jestCommandLine": "pnpm -F misskey-js run jest" }
],
"editor.codeActionsOnSave": {
"source.fixAll": "explicit"
},

View File

@@ -1,15 +1,61 @@
## 2025.6.4
## 2025.8.0
### Note
- サポートされるNode.jsの最小バージョンが**22.15.0**になりました
### General
- ノートを削除した際、関連するノートが同時に削除されないようになりました
- APIで、「replyIdが存在しているのにreplyがnull」や「renoteIdが存在しているのにrenoteがnull」であるという、今までにはなかったパターンが表れることになります
- 定期的に参照されていない古いリモートの投稿を削除する機能が実装されました(コントロールパネル→パフォーマンス→Remote Notes Cleaning)
- 既存のサーバーでは**デフォルトでオフ**、新規サーバーでは**デフォルトでオン**になります
- データベースの肥大化を防止することが可能です
- 既存のサーバーで当機能を有効化した場合は、処理量が多くなるため、一時的にストレージ使用量が増加する可能性があります。
- 増加量を抑えるには、最大処理継続時間をデフォルトより短くしてください。
- サーバーの初期設定が完了するまでは連合がオンにならないようになりました
### Client
- Fix: 一部の設定検索結果が存在しないパスになる問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1171)
- Fix: テーマエディタが動作しない問題を修正
### Server
- Enhance: ノートの削除処理の効率化
- Enhance: 全体的なパフォーマンスの向上
## 2025.7.0
### Note
- Node.jsの最小バージョンを20.10.0から20.18.1に引き上げました
- なお、特に必要がない限りNode.jsは推奨バージョンであるv22を使用するようにしてください
### General
- Feat: ノートの下書き機能
- Feat: クリップ内でノートを検索できるように
- Feat: Playを検索できるように
- Feat: モデレーションにおいて、特定のドライブファイルを添付しているチャットメッセージを一覧できるように
- Enhance: ウォーターマーク機能をロールで制御可能に
### Client
- Note: 「自動でもっと見る」オプションは無効になっています
- Feat: モデログを検索できるように
- Enhance: 設定の自動バックアップをオンにした直後に自動バックアップするように
- Enhance: ファイルアップロード前にキャプション設定を行えるように
- Enhance: ページネーションの並び順を逆にできるように
- Enhance: ファイルアップロード時にセンシティブ設定されているか表示するように
- Enhance: 投稿フォームにファイルをペースト/ドロップした際のUXを改善
- Enhance: ページネーション(一覧表示)の並び順を逆にできるように
- Enhance: ページネーション(一覧表示)の基準日時を指定できるように
- Enhance: レンダリングパフォーマンスの向上
- Fix: ファイルがドライブの既定アップロード先に指定したフォルダにアップロードされない問題を修正
- Fix: プラグインをアンインストールしてもセーブデータが残る問題を修正
- Fix: 数時間後Misskeyのタブに戻った際に、タブがスロットリングされている間の更新アニメーションを延々見せ続けられる問題を修正
- Fix: 非ログイン時のハイライトートの画像がCWの有無を考慮せず表示される問題を修正
- Fix: レンジ選択・ドロップダウンにて、操作を無効にすべきところで無効にならない問題を修正
- Fix: Pull to refreshが有効なときに横スクロールができない問題を修正
### Server
- Enhance: sinceId/untilIdが指定可能なエンドポイントにおいて、sinceDate/untilDateも指定可能に
- Enhance: メールの送信者としてサーバー名を表示するように (サーバー名が設定されている場合)
- Fix: ジョブキューのProgressの値を正しく計算する

View File

@@ -18,6 +18,7 @@ WORKDIR /misskey
COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"]
COPY --link ["scripts", "./scripts"]
COPY --link ["patches", "./patches"]
COPY --link ["packages/backend/package.json", "./packages/backend/"]
COPY --link ["packages/frontend-shared/package.json", "./packages/frontend-shared/"]
COPY --link ["packages/frontend/package.json", "./packages/frontend/"]
@@ -53,6 +54,7 @@ WORKDIR /misskey
COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"]
COPY --link ["scripts", "./scripts"]
COPY --link ["patches", "./patches"]
COPY --link ["packages/backend/package.json", "./packages/backend/"]
COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"]
COPY --link ["packages/misskey-reversi/package.json", "./packages/misskey-reversi/"]

View File

@@ -78,6 +78,8 @@ describe('After setup instance', () => {
cy.get('[data-cy-signup-password] input').type('alice1234');
cy.get('[data-cy-signup-submit]').should('be.disabled');
cy.get('[data-cy-signup-password-retype] input').type('alice1234');
cy.get('[data-cy-signup-submit]').should('be.disabled');
cy.get('[data-cy-signup-invitation-code] input').type('test-invitation-code');
cy.get('[data-cy-signup-submit]').should('not.be.disabled');
cy.get('[data-cy-signup-submit]').click();

View File

@@ -1008,6 +1008,8 @@ lastNDays: "آخر {n} أيام"
surrender: "ألغِ"
postForm: "أنشئ ملاحظة"
information: "عن"
inMinutes: "د"
inDays: "ي"
_chat:
invitations: "دعوة"
noHistory: "السجل فارغ"

View File

@@ -848,6 +848,8 @@ sourceCode: "সোর্স কোড"
flip: "উল্টান"
postForm: "নোট লিখুন"
information: "আপনার সম্পর্কে"
inMinutes: "মিনিট"
inDays: "দিন"
_chat:
invitations: "আমন্ত্রণ"
noHistory: "কোনো ইতিহাস নেই"

View File

@@ -896,7 +896,7 @@ searchResult: "Resultats de la cerca"
hashtags: "Etiquetes"
troubleshooting: "Solucionar problemes"
useBlurEffect: "Fes servir efectes de desenfocament a la interfície"
learnMore: "Saber més "
learnMore: "Saber-ne més "
misskeyUpdated: "Misskey s'ha actualitzat "
whatIsNew: "Mostra canvis"
translate: "Traduir "
@@ -1313,6 +1313,7 @@ availableRoles: "Roles disponibles "
acknowledgeNotesAndEnable: "Activa'l després de comprendre els possibles perills."
federationSpecified: "Aquest servidor treballa amb una federació de llistes blanques. No pot interactuar amb altres servidors que no siguin els especificats per l'administrador."
federationDisabled: "La unió es troba deshabilitada en aquest servidor. No es pot interactuar amb usuaris d'altres servidors."
draft: "Esborrany "
confirmOnReact: "Confirmar en reaccionar"
reactAreYouSure: "Vols reaccionar amb \"{emoji}\"?"
markAsSensitiveConfirm: "Vols marcar aquest contingut com a sensible?"
@@ -1350,7 +1351,7 @@ embed: "Incrustar"
settingsMigrating: "Estem migrant la teva configuració. Si us plau espera un moment... (També pots fer la migració més tard, manualment, anant a Preferències → Altres → Migrar configuració antiga)"
readonly: "Només lectura"
goToDeck: "Tornar al tauler"
federationJobs: "Treballs sindicats "
federationJobs: "Treballs de federació"
driveAboutTip: "Al Disc veure's una llista de tots els arxius que has anat pujant.<br>\nPots tornar-los a fer servir adjuntant-los a notes noves o pots adelantar-te i pujar arxius per publicar-los més tard!<br>\n<b>Tingués en compte que si esborres un arxiu també desapareixerà de tots els llocs on l'has fet servir (notes, pàgines, avatars, imatges de capçalera, etc.)</b><br>\nTambé pots crear carpetes per organitzar les."
scrollToClose: "Desplaçar per tancar"
advice: "Consell"
@@ -1367,6 +1368,11 @@ redisplayAllTips: "Torna ha mostrat tots els trucs i consells"
hideAllTips: "Amagar tots els trucs i consells"
defaultImageCompressionLevel: "Nivell de comprensió de la imatge per defecte"
defaultImageCompressionLevel_description: "Baixa, conserva la qualitat de la imatge però la mida de l'arxiu és més gran. <br>Alta, redueix la mida de l'arxiu però també la qualitat de la imatge."
inMinutes: "Minut(s)"
inDays: "Di(a)(es)"
_order:
newest: "Més recent"
oldest: "Cronològic"
_chat:
noMessagesYet: "Encara no tens missatges "
newMessage: "Missatge nou"
@@ -1993,6 +1999,8 @@ _role:
uploadableFileTypes: "Tipus de fitxers que en podeu pujar"
uploadableFileTypes_caption: "Especifica el tipus MIME. Es poden especificar diferents tipus MIME separats amb una nova línia, i es poden especificar comodins amb asteriscs (*). (Per exemple: image/*)"
uploadableFileTypes_caption2: "Pot que no sigui possible determinar el tipus MIME d'alguns arxius. Per permetre aquests tipus d'arxius afegeix {x} a les especificacions."
noteDraftLimit: "Nombre possible d'esborranys de notes al servidor"
watermarkAvailable: "Pots fer servir la marca d'aigua"
_condition:
roleAssignedTo: "Assignat a rols manuals"
isLocal: "Usuari local"
@@ -2152,6 +2160,7 @@ _theme:
install: "Instal·lar un tema"
manage: "Gestionar els temes "
code: "Codi del tema"
copyThemeCode: "Copiar el codi del tema"
description: "Descripció"
installed: "{name} Instal·lat "
installedThemes: "Temes instal·lats "
@@ -2800,6 +2809,7 @@ _fileViewer:
url: "URL"
uploadedAt: "Pujat el"
attachedNotes: "Notes amb aquest fitxer"
usage: "Ús "
thisPageCanBeSeenFromTheAuthor: "Aquesta pàgina només la pot veure l'usuari que ha pujat aquest fitxer."
_externalResourceInstaller:
title: "Instal·lar des d'un lloc extern"
@@ -3103,6 +3113,7 @@ _serverSetupWizard:
text2: "Si ho desitges, agrairíem molt la teva donació per poder seguir desenvolupant el projecte."
text3: "També hi ha privilegis especials per als donants!"
_uploader:
editImage: "Edició d'imatges"
compressedToX: "Comprimit a {x}"
savedXPercent: "{x}% d'estalvi "
abortConfirm: "Hi ha un arxiu que no s'ha pujat, vols cancel·lar?"
@@ -3171,3 +3182,18 @@ _imageEffector:
checker: "Escacs"
blockNoise: "Bloqueig de soroll"
tearing: "Trencament d'imatge "
drafts: "Esborrany "
_drafts:
select: "Seleccionar esborrany"
cannotCreateDraftAnymore: "S'ha sobrepassat el nombre màxim d'esborranys que es poden crear."
cannotCreateDraft: "Amb aquest contingut no es poden crear esborranys."
delete: "Esborrar esborranys"
deleteAreYouSure: "Vols esborrar els esborranys?"
noDrafts: "No hi ha esborranys"
replyTo: "Respondre a {user}"
quoteOf: "Citar les notes de {user}"
postTo: "Destinat a {channel}"
saveToDraft: "Desar com a esborrany"
restoreFromDraft: "Restaurar des dels esborranys"
restore: "Restaurar esborrany"
listDrafts: "Llistat d'esborranys"

View File

@@ -1107,6 +1107,8 @@ lastNDays: "Posledních {n} dnů"
surrender: "Zrušit"
postForm: "Formulář pro odeslání"
information: "Informace"
inMinutes: "Minut"
inDays: "Dnů"
_chat:
invitations: "Pozvat"
noHistory: "Žádná historie"

View File

@@ -1313,6 +1313,7 @@ availableRoles: "Verfügbare Rollen"
acknowledgeNotesAndEnable: "Schalten Sie dies erst ein, wenn Sie die Vorsichtsmaßnahmen verstanden haben."
federationSpecified: "Dieser Server arbeitet mit Whitelist-Föderation. Er kann nicht mit anderen als den vom Administrator angegebenen Servern interagieren."
federationDisabled: "Föderation ist auf diesem Server deaktiviert. Es ist nicht möglich, mit Benutzern auf anderen Servern zu interagieren."
draft: "Entwurf"
confirmOnReact: "Reagieren bestätigen"
reactAreYouSure: "Willst du eine \"{emoji}\"-Reaktion hinzufügen?"
markAsSensitiveConfirm: "Möchtest du dieses Medium als sensibel kennzeichnen?"
@@ -1347,7 +1348,7 @@ right: "Rechts"
bottom: "Unten"
top: "Oben"
embed: "Einbetten"
settingsMigrating: "Ihre Einstellungen werden gerade migriert, Bitte warten Sie einen Moment... (Sie können die Einstellungen später auch manuell migrieren, indem Sie zu Einstellungen → Sonstiges → Alte Einstellungen migrieren gehen)"
settingsMigrating: "Deine Einstellungen werden gerade migriert. Bitte warte einen Moment... (Du kannst die Einstellungen später auch manuell migrieren, indem du zu Einstellungen → Anderes → Alte Einstellungen migrieren gehst)"
readonly: "Nur Lesezugriff"
goToDeck: "Zurück zum Deck"
federationJobs: "Föderation Jobs"
@@ -1367,6 +1368,11 @@ redisplayAllTips: "Alle „Tipps und Tricks“ wieder anzeigen"
hideAllTips: "Alle „Tipps und Tricks“ ausblenden"
defaultImageCompressionLevel: "Standard-Bildkomprimierungsstufe"
defaultImageCompressionLevel_description: "Ein niedrigerer Wert erhält die Bildqualität, erhöht aber die Dateigröße. <br>Höhere Werte reduzieren die Dateigröße, verringern aber die Bildqualität."
inMinutes: "Minute(n)"
inDays: "Tag(en)"
_order:
newest: "Neueste zuerst"
oldest: "Älteste zuerst"
_chat:
noMessagesYet: "Noch keine Nachrichten"
newMessage: "Neue Nachricht"
@@ -1645,6 +1651,7 @@ _serverSettings:
allowExternalApRedirect_description: "Wenn diese Option aktiviert ist, können andere Server Inhalte von Drittanbietern über diesen Server abfragen, was jedoch zu Content-Spoofing führen kann."
userGeneratedContentsVisibilityForVisitor: "Sichtbarkeit von nutzergenerierten Inhalten für Gäste"
userGeneratedContentsVisibilityForVisitor_description: "Dies ist nützlich, um zu verhindern, dass unangemessene Inhalte, die nicht gut moderiert sind, ungewollt über deinen eigenen Server im Internet veröffentlicht werden."
userGeneratedContentsVisibilityForVisitor_description2: "Die uneingeschränkte Veröffentlichung aller Inhalte des Servers im Internet, einschließlich der vom Server empfangenen Fremdinhalte, birgt Risiken. Dies ist besonders wichtig für Betrachter, die sich des dezentralen Charakters der Inhalte nicht bewusst sind, da sie selbst fremde Inhalte fälschlicherweise als auf dem Server erstellte Inhalte wahrnehmen könnten."
_userGeneratedContentsVisibilityForVisitor:
all: "Alles ist öffentlich"
localOnly: "Nur lokale Inhalte werden veröffentlicht, fremde Inhalte bleiben privat"
@@ -1992,6 +1999,8 @@ _role:
uploadableFileTypes: "Hochladbare Dateitypen"
uploadableFileTypes_caption: "Gibt die zulässigen MIME-/Dateitypen an. Mehrere MIME-Typen können durch einen Zeilenumbruch getrennt angegeben werden, und Platzhalter können mit einem Sternchen (*) angegeben werden. (z. B. image/*)"
uploadableFileTypes_caption2: "Bei manchen Dateien ist es nicht möglich, den Typ zu bestimmen. Um solche Dateien zuzulassen, füge {x} der Spezifikation hinzu."
noteDraftLimit: "Anzahl der möglichen Entwürfe für serverseitige Notizen"
watermarkAvailable: "Kann die Wasserzeichenfunktion verwenden"
_condition:
roleAssignedTo: "Manuellen Rollen zugewiesen"
isLocal: "Lokaler Benutzer"
@@ -2151,6 +2160,7 @@ _theme:
install: "Farbschemata installieren"
manage: "Farbschemaverwaltung"
code: "Farbschemencode"
copyThemeCode: "Farbschemencode kopieren"
description: "Beschreibung"
installed: "{name} wurde installiert"
installedThemes: "Installierte Farbschemata"
@@ -2464,6 +2474,8 @@ _visibility:
disableFederation: "Deföderieren"
disableFederationDescription: "Nicht an andere Instanzen übertragen"
_postForm:
quitInspiteOfThereAreUnuploadedFilesConfirm: "Es gibt Dateien, die nicht hochgeladen wurden. Möchtest du diese verwerfen und das Formular schließen?"
uploaderTip: "Die Datei wurde noch nicht hochgeladen. Über das Dateimenü kannst du sie umbenennen, das Bild zuschneiden, ein Wasserzeichen hinzufügen, komprimieren usw. Die Datei wird automatisch hochgeladen, wenn du eine Notiz veröffentlichst."
replyPlaceholder: "Dieser Notiz antworten …"
quotePlaceholder: "Diese Notiz zitieren …"
channelPlaceholder: "In einen Kanal senden"
@@ -2844,8 +2856,12 @@ _dataSaver:
_avatar:
title: "Animierte Profilbilder deaktivieren"
description: "Die Animation von Profilbildern wird angehalten. Da animierte Bilder eine größere Dateigröße haben können als normale Bilder, kann dies den Datenverkehr weiter reduzieren."
_urlPreviewThumbnail:
title: "URL-Vorschaubilder ausblenden"
description: "URL-Vorschaubilder werden nicht mehr geladen."
_disableUrlPreview:
title: "URL-Vorschau deaktivieren"
description: "Deaktiviert die URL-Vorschaufunktion. Anders als bei reinen Vorschaubildern wird dadurch das Laden der verlinkten Informationen selbst reduziert."
_code:
title: "Code-Hervorhebungen ausblenden"
description: "Wenn Code-Hervorhebungen in MFM usw. verwendet werden, werden sie erst geladen, wenn sie angetippt werden. Die Syntaxhervorhebung erfordert das Herunterladen der Definitionsdateien für jede Programmiersprache. Es ist daher zu erwarten, dass die Deaktivierung des automatischen Ladens dieser Dateien die Menge des Datenverkehrs reduziert."
@@ -2903,6 +2919,8 @@ _offlineScreen:
_urlPreviewSetting:
title: "Einstellungen der URL-Vorschau"
enable: "URL-Vorschau aktivieren"
allowRedirect: "Umleitung von URL-Vorschauen erlauben"
allowRedirectDescription: "Wenn für eine URL eine Umleitung festgelegt ist, kann diese Funktion aktiviert werden, um der Umleitung zu folgen und eine Vorschau des umgeleiteten Inhalts anzuzeigen. Die Deaktivierung spart Serverressourcen, aber der Inhalt des Weiterleitungsziels wird nicht angezeigt."
timeout: "Zeitüberschreitung beim Abrufen der Vorschau (ms)"
timeoutDescription: "Übersteigt die für die Vorschau benötigte Zeit diesen Wert, wird keine Vorschau generiert."
maximumContentLength: "Maximale Content-Length (Bytes)"
@@ -3064,8 +3082,11 @@ _serverSetupWizard:
single_description: "Verwende den Server alleine als deinen eigenen."
single_youCanCreateMultipleAccounts: "Bei Bedarf können mehrere Konten eingerichtet werden, auch wenn es sich um einen Ein-Personen-Server handelt."
group: "Gruppenserver"
group_description: "Lade andere vertrauenswürdige Benutzer ein und verwende es mit mehreren Personen."
open: "Offener Server"
open_description: "Registrierung für alle öffnen."
openServerAdvice: "Die Aufnahme einer unbestimmten Anzahl von Nutzern birgt Risiken. Es wird empfohlen, mit einem zuverlässigen Moderationssystem zu arbeiten, um eventuell auftretende Probleme behandeln zu können."
openServerAntiSpamAdvice: "Große Sorgfalt muss auch auf die Sicherheit gelegt werden, z. B. durch die Aktivierung von Anti-Bot-Funktionen wie reCAPTCHA, um sicherzustellen, dass der Server nicht zum Verbreiten von Spam genutzt wird."
howManyUsersDoYouExpect: "Mit wie vielen Benutzern rechnest du?"
_scale:
small: "Weniger als 100 (kleiner Maßstab)"
@@ -3076,6 +3097,8 @@ _serverSetupWizard:
doYouConnectToFediverse_description1: "Bei Anschluss an ein Netz von verteilten Servern (Fediverse) können Inhalte mit anderen Servern ausgetauscht werden."
doYouConnectToFediverse_description2: "Die Verbindung mit dem Fediverse wird auch als „Föderation“ bezeichnet."
youCanConfigureMoreFederationSettingsLater: "Erweiterte Einstellungen, wie z. B. die Angabe von föderierbaren Servern, können später vorgenommen werden."
adminInfo: "Administrator-Informationen"
adminInfo_description: "Legt die Administrator-Informationen fest, die für den Empfang von Anfragen verwendet werden."
adminInfo_mustBeFilled: "Dies ist auf einem offenen Server oder bei aktivierter Föderation erforderlich."
followingSettingsAreRecommended: "Die folgenden Einstellungen werden empfohlen"
applyTheseSettings: "Diese Einstellungen anwenden"
@@ -3089,6 +3112,7 @@ _serverSetupWizard:
text2: "Wir würden uns über deine Unterstützung freuen, damit wir dieses Projekt auch in Zukunft weiterentwickeln können."
text3: "Für Unterstützer gibt es auch besondere Vorteile!"
_uploader:
editImage: "Bild bearbeiten"
compressedToX: "Komprimiert zu {x}"
savedXPercent: "{x}% gespart"
abortConfirm: "Einige Dateien wurden nicht hochgeladen. Möchtest du den Vorgang abbrechen?"
@@ -3110,10 +3134,12 @@ _userLists:
watermark: "Wasserzeichen"
defaultPreset: "Standard-Voreinstellungen"
_watermarkEditor:
tip: "Dem Bild kann ein Wasserzeichen, z. B. eine Quellenangabe, hinzugefügt werden."
quitWithoutSaveConfirm: "Nicht gespeicherte Änderungen verwerfen?"
driveFileTypeWarn: "Diese Datei wird nicht unterstützt"
driveFileTypeWarnDescription: "Bilddatei auswählen"
title: "Wasserzeichen bearbeiten"
cover: "Alles bedecken"
opacity: "Transparenz"
scale: "Größe"
text: "Text"
@@ -3121,7 +3147,16 @@ _watermarkEditor:
type: "Art"
image: "Bilder"
advanced: "Fortgeschritten"
stripe: "Streifen"
stripeWidth: "Linienbreite"
stripeFrequency: "Linienanzahl"
angle: "Winkel"
polkadot: "Punktmuster"
polkadotMainDotOpacity: "Deckkraft des Hauptpunktes"
polkadotMainDotRadius: "Größe des Hauptpunktes"
polkadotSubDotOpacity: "Deckkraft des Unterpunktes"
polkadotSubDotRadius: "Größe des Unterpunktes"
polkadotSubDotDivisions: "Anzahl der Unterpunkte"
_imageEffector:
title: "Effekte"
addEffect: "Effekte hinzufügen"
@@ -3133,3 +3168,22 @@ _imageEffector:
invert: "Farben umkehren"
grayscale: "Schwarzweiß"
colorAdjust: "Farbkorrektur"
colorClamp: "Farbkomprimierung"
colorClampAdvanced: "Farbkomprimierung (erweitert)"
distort: "Verzerrung"
stripe: "Streifen"
polkadot: "Punktmuster"
drafts: "Entwurf"
_drafts:
select: "Entwurf auswählen"
cannotCreateDraftAnymore: "Die Anzahl der Entwürfe, die erstellt werden können, wurde überschritten."
cannotCreateDraft: "Mit diesem Inhalt kann kein Entwurf erstellt werden."
delete: "Entwurf löschen"
deleteAreYouSure: "Entwurf löschen?"
noDrafts: "Keine Entwürfe"
replyTo: "Antwort an {user}"
quoteOf: "Zitat von {user}s Notiz"
saveToDraft: "Als Entwurf speichern"
restoreFromDraft: "Aus Entwurf wiederherstellen"
restore: "Wiederherstellen"
listDrafts: "Liste der Entwürfe"

View File

@@ -327,7 +327,7 @@ dark: "Dark"
lightThemes: "Light themes"
darkThemes: "Dark themes"
syncDeviceDarkMode: "Sync Dark Mode with your device settings"
switchDarkModeManuallyWhenSyncEnabledConfirm: "\"{x}\" is turned on, Would you like to turn off synchronization and switch modes manually?"
switchDarkModeManuallyWhenSyncEnabledConfirm: "\"{x}\" is turned on. Would you like to turn off synchronization and switch modes manually?"
drive: "Drive"
fileName: "Filename"
selectFile: "Select a file"
@@ -1302,7 +1302,7 @@ passkeyVerificationSucceededButPasswordlessLoginDisabled: "Passkey verification
messageToFollower: "Message to followers"
target: "Target"
testCaptchaWarning: "This function is intended for CAPTCHA testing purposes.\n<strong>Do not use in a production environment.</strong>"
prohibitedWordsForNameOfUser: "Prohibited words for user names"
prohibitedWordsForNameOfUser: "Prohibited words for usernames"
prohibitedWordsForNameOfUserDescription: "If any of the strings in this list are included in the user's name, the name will be denied. Users with moderator privileges are not affected by this restriction."
yourNameContainsProhibitedWords: "Your name contains prohibited words"
yourNameContainsProhibitedWordsDescription: "If you wish to use this name, please contact your server administrator."
@@ -1313,6 +1313,7 @@ availableRoles: "Available roles"
acknowledgeNotesAndEnable: "Turn on after understanding the precautions."
federationSpecified: "This server is operated in a whitelist federation. Interacting with servers other than those designated by the administrator is not allowed."
federationDisabled: "Federation is disabled on this server. You cannot interact with users on other servers."
draft: "Drafts"
confirmOnReact: "Confirm when reacting"
reactAreYouSure: "Would you like to add a \"{emoji}\" reaction?"
markAsSensitiveConfirm: "Do you want to set this media as sensitive?"
@@ -1366,7 +1367,12 @@ tip: "Tips & Tricks"
redisplayAllTips: "Show all “Tips & Tricks” again"
hideAllTips: "Hide all \"Tips & Tricks\""
defaultImageCompressionLevel: "Default image compression level"
defaultImageCompressionLevel_description: "High, reduces the file size but also the image quality. <br>High, reduces the file size but also the image quality."
defaultImageCompressionLevel_description: "Lower level preserves image quality but increases file size.<br>Higher level reduce file size, but reduce image quality."
inMinutes: "Minute(s)"
inDays: "Day(s)"
_order:
newest: "Newest First"
oldest: "Oldest First"
_chat:
noMessagesYet: "No messages yet"
newMessage: "New message"
@@ -1993,6 +1999,8 @@ _role:
uploadableFileTypes: "Uploadable file types"
uploadableFileTypes_caption: "Specifies the allowed MIME/file types. Multiple MIME types can be specified by separating them with a new line, and wildcards can be specified with an asterisk (*). (e.g., image/*)"
uploadableFileTypes_caption2: "Some files types might fail to be detected. To allow such files, add {x} to the specification."
noteDraftLimit: "Number of possible drafts of server notes"
watermarkAvailable: "Availability of watermark function"
_condition:
roleAssignedTo: "Assigned to manual roles"
isLocal: "Local user"
@@ -2152,6 +2160,7 @@ _theme:
install: "Install a theme"
manage: "Manage themes"
code: "Theme code"
copyThemeCode: "Copy theme code"
description: "Description"
installed: "{name} has been installed"
installedThemes: "Installed themes"
@@ -2800,6 +2809,7 @@ _fileViewer:
url: "URL"
uploadedAt: "Uploaded at"
attachedNotes: "Attached notes"
usage: "Used"
thisPageCanBeSeenFromTheAuthor: "This page can only be seen by the user who uploaded this file."
_externalResourceInstaller:
title: "Install from external site"
@@ -3103,6 +3113,7 @@ _serverSetupWizard:
text2: "We would appreciate your support so that we can continue to develop this software further into the future."
text3: "There are also special benefits for supporters!"
_uploader:
editImage: "Edit Image"
compressedToX: "Compressed to {x}"
savedXPercent: "Saving {x}%"
abortConfirm: "Some files have not been uploaded, do you want to abort?"
@@ -3159,8 +3170,8 @@ _imageEffector:
glitch: "Glitch"
mirror: "Mirror"
invert: "Invert Colors"
grayscale: "white-black"
colorAdjust: "Colour Correction"
grayscale: "Grayscale"
colorAdjust: "Color Correction"
colorClamp: "Color Compression"
colorClampAdvanced: "Color Compression (Advanced)"
distort: "Distortion"
@@ -3171,3 +3182,18 @@ _imageEffector:
checker: "Checker"
blockNoise: "Block Noise"
tearing: "Tearing"
drafts: "Drafts"
_drafts:
select: "Select Draft"
cannotCreateDraftAnymore: "The number of drafts that can be created has been exceeded."
cannotCreateDraft: "You cannot create a draft with this content."
delete: "Delete Draft"
deleteAreYouSure: "Delete draft?"
noDrafts: "No drafts"
replyTo: "Reply to {user}"
quoteOf: "Citation to {user}'s note"
postTo: "Posting to {channel}"
saveToDraft: "Save to Draft"
restoreFromDraft: "Restore from Draft"
restore: "Restore"
listDrafts: "List of Drafts"

View File

@@ -61,11 +61,11 @@ copyRSS: "Copiar RSS"
copyUsername: "Copiar nombre de usuario"
copyUserId: "Copiar ID del usuario"
copyNoteId: "Copiar ID de la nota"
copyFileId: "Copiar ID del archivo"
copyFileId: "Copiar ID de archivo"
copyFolderId: "Copiar ID de carpeta"
copyProfileUrl: "Copiar la URL del perfil"
searchUser: "Buscar un usuario"
searchThisUsersNotes: ""
searchThisUsersNotes: "Buscar en las notas de este usuario"
reply: "Responder"
loadMore: "Ver más"
showMore: "Ver más"
@@ -83,10 +83,10 @@ files: "Archivos"
download: "Descargar"
driveFileDeleteConfirm: "¿Desea borrar el archivo \"{name}\"? Las notas que tengan este archivo como adjunto serán eliminadas"
unfollowConfirm: "¿Desea dejar de seguir a {name}?"
exportRequested: "Se ha solicitado la exportación. Puede tomar un tiempo. Cuando termine la exportación, se añadirá en el drive"
importRequested: "Se ha solicitado la importación. Puede tomar un tiempo."
exportRequested: "Has solicitado la exportación. Puede llevar un tiempo. Cuando termine la exportación, se añadirá al drive"
importRequested: "Has solicitado la importación. Puede llevar un tiempo."
lists: "Listas"
noLists: "No tiene listas"
noLists: "No tienes ninguna lista"
note: "Notas"
notes: "Notas"
following: "Siguiendo"
@@ -99,9 +99,9 @@ somethingHappened: "Ocurrió un error"
retry: "Reintentar"
pageLoadError: "Error al leer la página"
pageLoadErrorDescription: "Normalmente es debido a la red o al caché del navegador. Por favor limpie el caché o intente más tarde."
serverIsDead: "No hay respuesta del servidor. Espere un momento y vuelva a intentarlo."
youShouldUpgradeClient: "Para ver esta página, por favor refrezca el navegador y utiliza una versión más reciente del cliente."
enterListName: "Ingrese nombre de lista"
serverIsDead: "No hay respuesta del servidor. Espera un momento y vuelve a intentarlo."
youShouldUpgradeClient: "Para ver esta página, recarga el navegador para actualizar el cliente."
enterListName: "Introduce un nombre para la lista"
privacy: "Privacidad"
makeFollowManuallyApprove: "Aprobar manualmente las solicitudes de seguimiento"
defaultNoteVisibility: "Visibilidad por defecto"
@@ -125,7 +125,7 @@ renoteToOtherChannel: "Renotar a otro canal"
pinnedNote: "Nota fijada"
pinned: "Fijar al perfil"
you: "Tú"
clickToShow: "Click para ver"
clickToShow: "Haz clic para verlo"
sensitive: "Marcado como sensible"
add: "Agregar"
reaction: "Reacción"
@@ -134,28 +134,28 @@ emojiPicker: "Selector de emojis"
pinnedEmojisForReactionSettingDescription: "Puedes seleccionar reacciones para fijarlos en el selector"
pinnedEmojisSettingDescription: "Puedes seleccionar emojis para fijarlos en el selector"
emojiPickerDisplay: "Mostrar el selector de emojis"
overwriteFromPinnedEmojisForReaction: "Sobreescribir las reacciones fijadas"
overwriteFromPinnedEmojis: "Sobreescribir los emojis fijados"
reactionSettingDescription2: "Arrastre para reordenar, click para borrar, apriete la tecla + para añadir."
overwriteFromPinnedEmojisForReaction: "Sobreescribir los ajustes de reacciones"
overwriteFromPinnedEmojis: "Sobreescribir los ajustes generales"
reactionSettingDescription2: "Arrastra para reordenar, click para borrar, pulsa \"+\" para añadir."
rememberNoteVisibility: "Recordar visibilidad"
attachCancel: "Quitar adjunto"
deleteFile: "Archivo eliminado"
deleteFile: "Eliminar archivo"
markAsSensitive: "Marcar como sensible"
unmarkAsSensitive: "Desmarcar como sensible"
enterFileName: "Ingrese el nombre del archivo"
enterFileName: "Introduce el nombre del archivo"
mute: "Silenciar"
unmute: "Dejar de silenciar"
renoteMute: "Silenciar renota"
renoteUnmute: "Desilenciar renota"
block: "Bloquear"
unblock: "Dejar de bloquear"
unblock: "Desbloquear"
suspend: "Suspender"
unsuspend: "Dejar de suspender"
blockConfirm: "¿Quiere bloquear esta cuenta?"
unblockConfirm: "¿Quiere dejar de bloquear esta cuenta?"
suspendConfirm: "¿Quiere suspender esta cuenta?"
unsuspendConfirm: "¿Quiere dejar de suspender esta cuenta?"
selectList: "Seleccione una lista"
blockConfirm: "¿Quieres bloquear esta cuenta?"
unblockConfirm: "¿Quieres desbloquear esta cuenta?"
suspendConfirm: "¿Quieres suspender esta cuenta?"
unsuspendConfirm: "¿Quieres dejar de suspender esta cuenta?"
selectList: "Selecciona una lista"
editList: "Editar lista"
selectChannel: "Seleccionar canal"
selectAntenna: "Seleccionar antena"
@@ -163,55 +163,55 @@ editAntenna: "Editar antena"
createAntenna: "Crear una antena"
selectWidget: "Seleccionar widget"
editWidgets: "Editar widgets"
editWidgetsExit: "Terminar edición"
editWidgetsExit: "Hecho"
customEmojis: "Emojis personalizados"
emoji: "Emoji"
emojis: "Emoji"
emojis: "Emojis"
emojiName: "Nombre del emoji"
emojiUrl: "URL de la imágen del emoji"
addEmoji: "Agregar emoji"
settingGuide: "Configuración sugerida"
cacheRemoteFiles: "Mantener en cache los archivos remotos"
cacheRemoteFilesDescription: "Si desactiva esta configuración, Los archivos remotos se cargarán desde el link directo sin usar la caché. Con eso se puede ahorrar almacenamiento del servidor, pero eso aumentará el tráfico al no crear miniaturas."
emojiUrl: "URL del emoji"
addEmoji: "Añadir emoji"
settingGuide: "Configuración recomendada"
cacheRemoteFiles: "Mantener los archivos remotos en caché"
cacheRemoteFilesDescription: "Si desactivas esta configuración, los archivos remotos se cargarán directamente de los servidores remotos. Desactivar esto reducirá el uso de almacenamiento, pero incrementará el uso de tráfico, ya que no se generarán miniaturas."
youCanCleanRemoteFilesCache: "Puedes vaciar la caché pulsando en el botón 🗑️ en el administrador de archivos."
cacheRemoteSensitiveFiles: "Cachear archivos remotos sensibles"
cacheRemoteSensitiveFilesDescription: "Cuando esta opción está desactivada, los archivos remotos sensibles son cargador directamente de la instancia origen sin ser cacheados."
cacheRemoteSensitiveFiles: "Mantener los archivos remotos sensibles en caché"
cacheRemoteSensitiveFilesDescription: "Cuando esta opción está desactivada, los archivos remotos sensibles se cargarán directamente desde los servidores remotos."
flagAsBot: "Esta cuenta es un bot"
flagAsBotDescription: "En caso de que esta cuenta fuera usada por un programa, active esta opción. Al hacerlo, esta opción servirá para otros desarrolladores para evitar cadenas infinitas de reacciones, y ajustará los sistemas internos de Misskey para que trate a esta cuenta como un bot."
flagAsCat: "Esta cuenta es un gato"
flagAsCatDescription: "En caso de que declare que esta cuenta es de un gato, active esta opción."
flagShowTimelineReplies: "Mostrar respuestas a las notas en la biografía"
flagShowTimelineRepliesDescription: "Cuando se marca, la línea de tiempo muestra respuestas a otras notas además de las notas del usuario"
flagAsBotDescription: "Activa esta opción si la cuenta es utilizada por un programa. Si se activa, actuará como una etiqueta para otros desarrolladores para prevenir cadenas eternas de interacción con otros bots, y ajustará los sistemas internos de Misskey para tratar esta cuenta de manera acorde."
flagAsCat: "Marcar esta cuenta como gato"
flagAsCatDescription: "Activa esta opción para marcar esta cuenta como un gato."
flagShowTimelineReplies: "Mostrar respuestas en la línea de tiempo"
flagShowTimelineRepliesDescription: "Muestra respuestas de los usuarios a las notas de otros usuarios en la línea de tiempo al activar esta opción."
autoAcceptFollowed: "Aceptar automáticamente las solicitudes de seguimiento de los usuarios que sigues"
addAccount: "Agregar Cuenta"
addAccount: "Agregar cuenta"
reloadAccountsList: "Recargar lista de cuentas"
loginFailed: "Error al iniciar sesión."
showOnRemote: "Ver en una instancia remota"
continueOnRemote: "Ver en una instancia remota"
showOnRemote: "Ver en instancia remota"
continueOnRemote: "Continuar en una instancia remota"
chooseServerOnMisskeyHub: "Elegir un servidor en Misskey Hub"
specifyServerHost: "Especifica una instancia directamente"
inputHostName: "Introduzca el dominio"
inputHostName: "Introduce el dominio"
general: "General"
wallpaper: "Fondo de pantalla"
setWallpaper: "Establecer fondo de pantalla"
removeWallpaper: "Quitar fondo de pantalla"
searchWith: "Buscar: {q}"
youHaveNoLists: "No tienes listas"
followConfirm: "¿Desea seguir a {name}?"
youHaveNoLists: "No tienes ninguna lista"
followConfirm: "¿Quieres seguir a {name}?"
proxyAccount: "Cuenta proxy"
proxyAccountDescription: "Una cuenta proxy es una cuenta que actúa como un seguidor remoto de un usuario bajo ciertas condiciones. Por ejemplo, cuando un usuario añade un usuario remoto a una lista, si ningún usuario local sigue al usuario agregado a la lista, la instancia no puede obtener su actividad. Así que la cuenta proxy sigue al usuario añadido a la lista"
host: "Host"
proxyAccountDescription: "Una cuenta proxy es una cuenta que actúa como un seguidor remoto de un usuario bajo ciertas condiciones. Por ejemplo, cuando un usuario añade un usuario remoto a una lista, si ningún usuario local sigue al usuario agregado a la lista, la instancia no puede obtener su actividad, así que la cuenta proxy sigue al usuario añadido a la lista"
host: "Instancia"
selectSelf: "Elígete a ti mismo"
selectUser: "Elegir usuario"
recipient: "Recipiente"
recipient: "Receptor"
annotation: "Anotación"
federation: "Federación"
instances: "Instancia"
instances: "Instancias"
registeredAt: "Registrado en"
latestRequestReceivedAt: "Ultimo pedido recibido"
latestStatus: "Último status"
latestRequestReceivedAt: "Última petición recibida"
latestStatus: "Último estado"
storageUsage: "Almacenamiento usado"
charts: "Chat"
charts: "Métricas"
perHour: "por hora"
perDay: "por día"
stopActivityDelivery: "Dejar de enviar actividades"
@@ -226,40 +226,40 @@ metadata: "Metadatos"
withNFiles: "{n} archivos"
monitor: "Monitor"
jobQueue: "Cola de trabajos"
cpuAndMemory: "CPU y Memoria"
cpuAndMemory: "CPU y memoria"
network: "Red"
disk: "Disco"
instanceInfo: "información de la instancia"
instanceInfo: "Información de la instancia"
statistics: "Estadísticas"
clearQueue: "Limpiar cola"
clearQueueConfirmTitle: "¿Desea limpiar la cola?"
clearQueueConfirmTitle: "¿Quieres limpiar la cola?"
clearQueueConfirmText: "Las notas aún no entregadas no se federarán. Normalmente no se necesita ejecutar esta operación"
clearCachedFiles: "Limpiar caché"
clearCachedFilesConfirm: "¿Desea borrar todos los archivos remotos cacheados?"
clearCachedFilesConfirm: "¿Quieres borrar todos los archivos remotos en caché?"
blockedInstances: "Instancias bloqueadas"
blockedInstancesDescription: "Seleccione los hosts de las instancias que desea bloquear, separadas por una linea nueva. Las instancias bloqueadas no podrán comunicarse con esta instancia."
blockedInstancesDescription: "La lista de los dominios de las instancias que quieres bloquear, separadas por una linea nueva. Las instancias bloqueadas no podrán comunicarse con esta instancia."
silencedInstances: "Instancias silenciadas"
silencedInstancesDescription: "Listar los hostname de las instancias que quieres silenciar. Todas las cuentas de las instancias listadas serán tratadas como silenciadas, solo podrán hacer peticiones de seguimiento, y no podrán mencionar cuentas locales si no las siguen. Esto no afecta a las instancias bloqueadas."
mediaSilencedInstances: "Servidores silenciados (Multimedia)"
mediaSilencedInstancesDescription: "Listar las instancias que quieres silenciar. Todas las cuentas de las instancias listadas serán tratadas como silenciadas, solo podrán hacer peticiones de seguimiento, y no podrán mencionar cuentas locales si no las siguen. Esto no afecta a las instancias bloqueadas."
silencedInstancesDescription: "La lista de los dominios de las instancias que quieres silenciar. Todas las cuentas de las instancias listadas serán tratadas como silenciadas, solo podrán hacer peticiones de seguimiento, y no podrán mencionar cuentas locales si no las siguen. Esto no afecta a las instancias bloqueadas."
mediaSilencedInstances: "Servidores con multimedia silenciada"
mediaSilencedInstancesDescription: "La lista de los dominios de las instancias cuya multimedia quieres silenciar. Todas las cuentas que pertenezcan a estas instancias serán marcadas como sensibles, y no podrán usar sus emojis personalizados. Esto no afecta a las instancias bloqueadas"
federationAllowedHosts: "Servidores federados"
federationAllowedHostsDescription: "Establezca los nombres de los servidores que pueden federarse, separados por una nueva línea."
federationAllowedHostsDescription: "La lista de los dominios de las instancias cuya federación está permitida, separadas por saltos de línea."
muteAndBlock: "Silenciar y bloquear"
mutedUsers: "Usuarios silenciados"
blockedUsers: "Usuarios bloqueados"
noUsers: "No hay usuarios"
editProfile: "Editar perfil"
noteDeleteConfirm: "¿Desea borrar esta nota?"
pinLimitExceeded: "Ya no se pueden fijar más posts"
noteDeleteConfirm: "¿Quieres borrar esta nota?"
pinLimitExceeded: "Ya no se pueden fijar más notas"
done: "Terminado"
processing: "Procesando"
processing: "Procesando..."
preview: "Vista previa"
default: "Predeterminado"
defaultValueIs: "Por defecto: {value}"
noCustomEmojis: "No hay emojis personalizados"
noJobs: "No hay trabajos"
federating: "Federando"
blocked: "Bloqueando"
blocked: "Bloqueado"
suspended: "Suspendido"
all: "Todo"
subscribing: "Suscribiendo"
@@ -280,8 +280,8 @@ featured: "Destacados"
usernameOrUserId: "Nombre o ID del usuario"
noSuchUser: "No se encuentra el usuario"
lookup: "Búsqueda"
announcements: "Anuncios"
imageUrl: "URL de la imágen"
announcements: "Avisos"
imageUrl: "URL de la imagen."
remove: "Borrar"
removed: "Borrado"
removeAreYouSure: "¿Desea borrar \"{x}\"?"
@@ -842,13 +842,13 @@ unlikeConfirm: "¿Quitar como favorito?"
fullView: "Vista completa"
quitFullView: "quitar vista completa"
addDescription: "Agregar descripción"
userPagePinTip: "Puede mantener sus notas visibles aquí seleccionando Pin en el menú de notas individuales"
userPagePinTip: "Puede mantener sus notas visibles aquí seleccionando 'Fijar al perfil' en el menú de notas individuales"
notSpecifiedMentionWarning: "Algunas menciones no están incluidas en el destino"
info: "Información"
userInfo: "Información del usuario"
unknown: "Desconocido"
onlineStatus: "En línea"
hideOnlineStatus: "mostrarse como desconectado"
hideOnlineStatus: "Mostrarse como desconectado"
hideOnlineStatusDescription: "Ocultar su estado en línea puede reducir la eficacia de algunas funciones, como la búsqueda"
online: "En línea"
active: "Activo"
@@ -877,7 +877,7 @@ popularPosts: "Más vistos"
shareWithNote: "Compartir con una nota"
ads: "Anuncios"
expiration: "Termina el"
startingperiod: "periodo de inicio"
startingperiod: "Comienzo"
memo: "Notas"
priority: "Prioridad"
high: "Alta"
@@ -1143,7 +1143,7 @@ channelArchiveConfirmTitle: "¿Seguro de archivar {name}?"
channelArchiveConfirmDescription: "Un canal archivado no aparecerá en la lista de canales ni en los resultados. Las nuevas publicaciones tampoco serán añadidas."
thisChannelArchived: "El canal ha sido archivado."
displayOfNote: "Mostrar notas"
initialAccountSetting: "Configración inicial de su cuenta\nか\nConfigración de inicio"
initialAccountSetting: "Configración inicial de su cuenta"
youFollowing: "Siguiendo"
preventAiLearning: "Rechazar el uso en el Aprendizaje de Máquinas. (IA Generativa)"
preventAiLearningDescription: "Pedirle a las arañas (crawlers) no usar los textos publicados o imágenes en el aprendizaje automático (IA Predictiva / Generativa). Ésto se logra añadiendo una marca respuesta HTML con la cadena \"noai\" al cantenido. Una prevención total no podría lograrse sólo usando ésta marca, ya que puede ser simplemente ignorada."
@@ -1313,6 +1313,7 @@ availableRoles: "Roles disponibles "
acknowledgeNotesAndEnable: "Activar después de comprender las precauciones"
federationSpecified: "Este servidor opera en una federación de listas blancas. No puede interactuar con otros servidores que no sean los especificados por el administrador."
federationDisabled: "La federación está desactivada en este servidor. No puede interactuar con usuarios de otros servidores"
draft: "Borrador"
confirmOnReact: "Confirmar la reacción"
reactAreYouSure: "¿Quieres añadir una reacción «{emoji}»?"
markAsSensitiveConfirm: "¿Desea establecer este medio multimedia(Imagen,vídeo...) como sensible?"
@@ -1357,8 +1358,8 @@ advice: "Consejos"
realtimeMode: "Modo en tiempo real"
turnItOn: "Activar"
turnItOff: "Desactivar"
emojiMute: "Silenciar emojis"
emojiUnmute: "No Silenciar emojis"
emojiMute: "Silenciar emoji"
emojiUnmute: "No silenciar emoji"
muteX: "Silenciar {x}"
unmuteX: "Dejar de silenciar {x}"
abort: "Abortar"
@@ -1367,6 +1368,11 @@ redisplayAllTips: "Volver a mostrar todos \"Trucos y consejos\""
hideAllTips: "Ocultar todos los \"Trucos y consejos\""
defaultImageCompressionLevel: "Nivel de compresión de la imagen por defecto"
defaultImageCompressionLevel_description: "Baja, conserva la calidad de la imagen pero la medida del archivo es más grande. <br>Alta, reduce la medida del archivo pero también la calidad de la imagen."
inMinutes: "Minutos"
inDays: "Días"
_order:
newest: "Los más recientes primero"
oldest: "Los más antiguos primero"
_chat:
noMessagesYet: "Aún no hay mensajes"
newMessage: "Mensajes nuevos"
@@ -1382,7 +1388,7 @@ _chat:
noInvitations: "No hay invitación."
history: "Historial"
noHistory: "No hay datos en el historial"
noRooms: "Sala no encontrada"
noRooms: "No te has unido a ninguna sala "
inviteUser: "Invitar usuarios"
sentInvitations: "Invitaciones enviadas"
join: "Unirse"
@@ -1483,7 +1489,7 @@ _accountSettings:
makeNotesHiddenBeforeDescription: "Mientras esta función esté activada, las notas que hayan pasado la fecha y hora fijadas o hayan transcurrido el tiempo establecido sólo serán visibles para ti (se harán privadas). Si la desactivas, también se restablecerá el estado público de las notas."
mayNotEffectForFederatedNotes: "Notas federadas por un servidor remoto pueden no verse afectadas."
mayNotEffectSomeSituations: "Estas restricciones son simplificadas. Pueden no aplicarse en algunas situaciones, como cuando se visualiza en un servidor remoto o durante la moderación."
notesHavePassedSpecifiedPeriod: "Ten en cuenta que el tiempo especificado ha pasado"
notesHavePassedSpecifiedPeriod: "Notas publicadas durante el siguiente tiempo específico"
notesOlderThanSpecifiedDateAndTime: "Notas antes de la fecha y hora especificadas"
_abuseUserReport:
forward: "Reenviar"
@@ -1526,7 +1532,7 @@ _announcement:
tooManyActiveAnnouncementDescription: "Tener demasiados anuncios activos empeora la experiencia de usuario. Por favor, considera archivar aquellos anuncios que hayan quedado obsoletos."
readConfirmTitle: "¿Marcar como leído?"
readConfirmText: "Esto marcará el contenido de \"{title}\" como leído."
shouldNotBeUsedToPresentPermanentInfo: "Dado que puede impactar en la experiencia de usuario de forma significativa, es recomendable usar notificaciones en el flujo de información en vez de información persistente."
shouldNotBeUsedToPresentPermanentInfo: "Se recomienda utilizar los avisos para publicar información que requiera inmediatez, en lugar de hacerlo constantemente, ya que esto perjudica especialmente la UX de los nuevos usuarios."
dialogAnnouncementUxWarn: "Mostrar dos o más notificaciones en formato diálogo a la vez puede impactar en la experiencia de usuario de forma significativa, úsalos con cuidado."
silence: "Silenciar notificaciones"
silenceDescription: "Si lo activas, no enviarás notificación sobre este anuncio y el usuario no tendrá que leerlo."
@@ -1993,6 +1999,8 @@ _role:
uploadableFileTypes: "Tipos de archivos que se pueden cargar."
uploadableFileTypes_caption: "Especifica los tipos MIME/archivos permitidos. Se pueden especificar varios tipos MIME separándolos con una nueva línea, y se pueden especificar comodines con un asterisco (*). (por ejemplo, image/*)"
uploadableFileTypes_caption2: "Es posible que no se detecten algunos tipos de archivos. Para permitir estos archivos, añade {x} a la especificación."
noteDraftLimit: "Número de posibles borradores de notas del servidor"
watermarkAvailable: "Disponibilidad de la función de marca de agua"
_condition:
roleAssignedTo: "Asignado a roles manuales"
isLocal: "Usuario local"
@@ -2152,6 +2160,7 @@ _theme:
install: "Instalar tema"
manage: "Gestor de temas"
code: "Código del tema"
copyThemeCode: "Copiar el código del tema"
description: "Descripción"
installed: "{name} ha sido instalado"
installedThemes: "Temas instalados"
@@ -2262,7 +2271,7 @@ _2fa:
setupCompleted: "Configuración completada"
step4: "Ahora cuando inicie sesión, ingrese el mismo token"
securityKeyNotSupported: "Tu navegador no soporta claves de autenticación."
registerTOTPBeforeKey: "Please set up an authenticator app to register a security or pass key.\npor favor. configura una aplicación de autenticación para registrar una llave de seguridad."
registerTOTPBeforeKey: "Por favor. configura una aplicación de autenticación para registrar una llave de seguridad."
securityKeyInfo: "Se puede configurar el inicio de sesión usando una clave de seguridad de hardware que soporte FIDO2 o con un certificado de huella digital o con un PIN"
registerSecurityKey: "Registrar una llave de seguridad"
securityKeyName: "Ingresa un nombre para la clave"
@@ -2800,6 +2809,7 @@ _fileViewer:
url: "URL"
uploadedAt: "Subido el"
attachedNotes: "Notas adjuntas"
usage: "Utilizado"
thisPageCanBeSeenFromTheAuthor: "Esta página solo puede ser vista por el autor."
_externalResourceInstaller:
title: "Instalar desde sitio externo"
@@ -2902,7 +2912,7 @@ _reversi:
opponentHasSettingsChanged: "El oponente ha cambiado su configuración"
allowIrregularRules: "Reglas irregulares (completamente libre)"
disallowIrregularRules: "Sin reglas irregulares "
showBoardLabels: "Mostrar el número de línea y de columna en el tablero de juego."
showBoardLabels: "Mostrar el número de línea y la letra de columna en el tablero de juego."
useAvatarAsStone: "Usar los avatares de los usuarios como fichas\n"
_offlineScreen:
title: "Fuera de línea. No se puede conectar con el servidor"
@@ -3103,6 +3113,7 @@ _serverSetupWizard:
text2: "Agradeceríamos su apoyo para que podamos seguir desarrollando este software en el futuro."
text3: "También hay beneficios especiales para los donantes"
_uploader:
editImage: "Editar la imagen"
compressedToX: "Comprimir a {x}"
savedXPercent: "Guardando {x}%"
abortConfirm: "Algunos archivos no se han cargado, ¿deseas cancelar?"
@@ -3112,7 +3123,7 @@ _uploader:
tip: "El archivo aún no se ha cargado, por lo que este cuadro de diálogo te permite confirmar, renombrar, comprimir y recortar el archivo antes de cargarlo. Cuando esté listo, puedes iniciar la carga pulsando el botón \"Cargar\"."
_clientPerformanceIssueTip:
title: "Si crees que el consumo de batería es demasiado alto"
makeSureDisabledAdBlocker: "Por favor, desactive el bloqueador de publicidad."
makeSureDisabledAdBlocker: "Por favor, desactiva el bloqueador de publicidad."
makeSureDisabledAdBlocker_description: "Los bloqueadores de anuncios pueden afectar al rendimiento. Asegúrate de que no están activados en tu sistema o en las funciones/extensiones de tu navegador."
makeSureDisabledCustomCss: "Desactiva el CSS personalizado"
makeSureDisabledCustomCss_description: "Anular estilos puede afectar al rendimiento. Asegúrate de que el CSS personalizado o las extensiones que sobrescriben estilos no están activados."
@@ -3171,3 +3182,18 @@ _imageEffector:
checker: "Corrector"
blockNoise: "Bloquear Ruido"
tearing: "Rasgado de Imagen (Tearing)"
drafts: "Borrador"
_drafts:
select: "Seleccionar borradores"
cannotCreateDraftAnymore: "Se ha superado el número de borradores que se pueden crear."
cannotCreateDraft: "No se pueden crear borradores con este contenido."
delete: "Eliminar borrador"
deleteAreYouSure: "¿Quieres borrar el borrador?"
noDrafts: "No hay borradores disponibles."
replyTo: "Responder a {user}"
quoteOf: "Citar las notas de {user}"
postTo: "Destino a {channel}"
saveToDraft: "Guardar como borrador"
restoreFromDraft: "Restaurar desde los borradores"
restore: "Restaurar"
listDrafts: "Listar los borradores"

View File

@@ -1272,6 +1272,8 @@ pleaseSelectAccount: "Sélectionner un compte"
availableRoles: "Rôles disponibles"
postForm: "Formulaire de publication"
information: "Informations"
inMinutes: "min"
inDays: "j"
_chat:
invitations: "Inviter"
noHistory: "Pas d'historique"

View File

@@ -5,9 +5,13 @@ introMisskey: "Selamat datang! Misskey adalah perangkat mikroblog tercatu bersif
poweredByMisskeyDescription: "{name} adalah sebuah layanan (instance) yang menggunakan platform sumber terbuka <b>Misskey</b>."
monthAndDay: "{day} {month}"
search: "Penelusuran"
reset: "Reset"
notifications: "Notifikasi"
username: "Nama Pengguna"
password: "Kata sandi"
initialPasswordForSetup: "Kata sandi untuk memulai konfigurasi awal"
initialPasswordIsIncorrect: "Kata sandi untuk memulai konfigurasi awal salah."
initialPasswordForSetupDescription: "Jika Anda menginstal Misskey sendiri, gunakan kata sandi yang Anda masukkan di berkas konfigurasi.\nJika Anda menggunakan layanan hosting Misskey, gunakan kata sandi yang diberikan.\nJika Anda belum mengatur kata sandi, biarkan kosong dan lanjutkan."
forgotPassword: "Lupa Kata Sandi"
fetchingAsApObject: "Mengambil data dari Fediverse..."
ok: "OK"
@@ -45,6 +49,7 @@ pin: "Sematkan ke profil"
unpin: "Lepas sematan dari profil"
copyContent: "Salin konten"
copyLink: "Salin tautan"
copyRemoteLink: "Salin tautan jarak jauh"
copyLinkRenote: "Salin tautan renote"
delete: "Hapus"
deleteAndEdit: "Hapus dan sunting"
@@ -212,8 +217,10 @@ perDay: "per Hari"
stopActivityDelivery: "Berhenti mengirim aktivitas"
blockThisInstance: "Blokir instansi ini"
silenceThisInstance: "Senyapkan instansi ini"
mediaSilenceThisInstance: "Server media senyap"
operations: "Tindakan"
software: "Perangkat lunak"
softwareName: "Nama Perangkat Lunak"
version: "Versi"
metadata: "Metadata"
withNFiles: "{n} berkas"
@@ -1040,7 +1047,7 @@ disableFederationConfirmWarn: "Mematikan federasi tidak membuat kiriman menjadi
disableFederationOk: "Matikan federasi"
invitationRequiredToRegister: "Instansi ini dalam mode undangan-saja. Kamu harus memasukkan kode undangan yang valid untuk mendaftar."
emailNotSupported: "Instansi ini tidak mendukung mengirim surel"
postToTheChannel: "Catat ke kanal"
postToTheChannel: "Buat Catatan ke Kanal"
cannotBeChangedLater: "Hal ini nantinya tidak dapat diubah lagi."
reactionAcceptance: "Penerimaan reaksi"
likeOnly: "Hanya suka"
@@ -1256,6 +1263,8 @@ thereAreNChanges: "Ada {n} perubahan"
prohibitedWordsForNameOfUser: "Kata yang dilarang untuk nama pengguna"
postForm: "Buat catatan"
information: "Informasi"
inMinutes: "menit"
inDays: "hari"
_chat:
invitations: "Undang"
noHistory: "Tidak ada riwayat"
@@ -2400,7 +2409,7 @@ _deck:
main: "Utama"
widgets: "Widget"
notifications: "Notifikasi"
tl: "Lini masa"
tl: "Beranda"
antenna: "Antena"
list: "Daftar"
channel: "Kanal"

66
locales/index.d.ts vendored
View File

@@ -2567,11 +2567,11 @@ export interface Locale extends ILocale {
*/
"serviceworkerInfo": string;
/**
* 削除された投稿
* 削除されたノート
*/
"deletedNote": string;
/**
* 非公開の投稿
* 非公開のノート
*/
"invisibleNote": string;
/**
@@ -5231,7 +5231,7 @@ export interface Locale extends ILocale {
*/
"prohibitedWordsForNameOfUser": string;
/**
* このリストに含まれる文字列がユーザーの名前に含まれる場合、ユーザーの名前の変更を拒否します。モデレーター権限を持つユーザーはこの制限の影響を受けません。
* このリストに含まれる文字列がユーザーの名前に含まれる場合、ユーザーの名前の変更を拒否します。モデレーター権限を持つユーザーはこの制限の影響を受けません。ユーザー名(username)に対しても全て小文字に置き換えて検査します。
*/
"prohibitedWordsForNameOfUserDescription": string;
/**
@@ -5493,6 +5493,18 @@ export interface Locale extends ILocale {
* 低くすると画質を保てますが、ファイルサイズは増加します。<br>高くするとファイルサイズを減らせますが、画質は低下します。
*/
"defaultImageCompressionLevel_description": string;
/**
* 低電力モード
*/
"lowPowerMode": string;
/**
* 分
*/
"inMinutes": string;
/**
* 日
*/
"inDays": string;
"_order": {
/**
* 新しい順
@@ -5799,6 +5811,10 @@ export interface Locale extends ILocale {
* UIのアニメーション
*/
"uiAnimations": string;
/**
* アニメーション画像を再生
*/
"playAnimatedImages": string;
/**
* ナビゲーションバーに副ボタンを表示
*/
@@ -6486,6 +6502,22 @@ export interface Locale extends ILocale {
* 有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。
*/
"reactionsBufferingDescription": string;
/**
* リモート投稿の自動クリーニング
*/
"remoteNotesCleaning": string;
/**
* 有効にすると、参照されていない古いリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。
*/
"remoteNotesCleaning_description": string;
/**
* 最大クリーニング処理継続時間
*/
"remoteNotesCleaningMaxProcessingDuration": string;
/**
* 最低ノート保持日数
*/
"remoteNotesCleaningExpiryDaysForEachNotes": string;
/**
* 問い合わせ先URL
*/
@@ -6558,6 +6590,14 @@ export interface Locale extends ILocale {
* サーバーで受信したリモートのコンテンツを含め、サーバー内の全てのコンテンツを無条件でインターネットに公開することはリスクが伴います。特に、分散型の特性を知らない閲覧者にとっては、リモートのコンテンツであってもサーバー内で作成されたコンテンツであると誤って認識してしまう可能性があるため、注意が必要です。
*/
"userGeneratedContentsVisibilityForVisitor_description2": string;
/**
* サーバーの初期設定ウィザードをやり直しますか?
*/
"restartServerSetupWizardConfirm_title": string;
/**
* 現在の一部の設定はリセットされます。
*/
"restartServerSetupWizardConfirm_text": string;
"_userGeneratedContentsVisibilityForVisitor": {
/**
* 全て公開
@@ -7795,6 +7835,10 @@ export interface Locale extends ILocale {
* サーバーサイドのノートの下書きの作成可能数
*/
"noteDraftLimit": string;
/**
* ウォーターマーク機能の使用可否
*/
"watermarkAvailable": string;
};
"_condition": {
/**
@@ -10890,6 +10934,10 @@ export interface Locale extends ILocale {
* 添付されているノート
*/
"attachedNotes": string;
/**
* 利用
*/
"usage": string;
/**
* このページは、このファイルをアップロードしたユーザーしか閲覧できません。
*/
@@ -11935,6 +11983,14 @@ export interface Locale extends ILocale {
* 連合可能なサーバーの指定など、高度な設定も後ほど可能です。
*/
"youCanConfigureMoreFederationSettingsLater": string;
/**
* 受信コンテンツの自動クリーニング
*/
"remoteContentsCleaning": string;
/**
* 連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、参照されていない古くなったコンテンツを自動でサーバーから削除し、ストレージを節約できます。
*/
"remoteContentsCleaning_description": string;
/**
* 管理者情報
*/
@@ -12270,9 +12326,9 @@ export interface Locale extends ILocale {
*/
"cannotCreateDraftAnymore": string;
/**
* リノートの下書き作成できません。
* この内容では下書き作成できません。
*/
"cannotCreateDraftOfRenote": string;
"cannotCreateDraft": string;
/**
* 下書きを削除
*/

View File

@@ -1313,6 +1313,7 @@ availableRoles: "Ruoli disponibili"
acknowledgeNotesAndEnable: "Attivare dopo averne compreso il comportamento."
federationSpecified: "Questo server è federato solo con istanze specifiche del Fediverso. Puoi interagire solo con quelle scelte dall'amministrazione."
federationDisabled: "Questo server ha la federazione disabilitata. Non puoi interagire con profili provenienti da altri server."
draft: "Bozza"
confirmOnReact: "Confermare le reazioni"
reactAreYouSure: "Vuoi davvero reagire con {emoji} ?"
markAsSensitiveConfirm: "Vuoi davvero indicare questo contenuto multimediale come esplicito?"
@@ -1367,6 +1368,11 @@ redisplayAllTips: "Mostra tutti i suggerimenti"
hideAllTips: "Nascondi tutti i suggerimenti"
defaultImageCompressionLevel: "Livello predefinito di compressione immagini"
defaultImageCompressionLevel_description: "La compressione diminuisce la qualità dell'immagine, poca compressione mantiene alta qualità delle immagini. Aumentandola, si riducono le dimensioni del file, a discapito della qualità dell'immagine."
inMinutes: "min"
inDays: "giorni"
_order:
newest: "Prima i più recenti"
oldest: "Meno recenti prima"
_chat:
noMessagesYet: "Ancora nessun messaggio"
newMessage: "Nuovo messaggio"
@@ -1993,6 +1999,8 @@ _role:
uploadableFileTypes: "Tipi di file caricabili"
uploadableFileTypes_caption: "Specifica il tipo MIME. Puoi specificare più valori separandoli andando a capo, oppure indicare caratteri jolly con un asterisco (*). Ad esempio: image/*"
uploadableFileTypes_caption2: "A seconda del file, il tipo potrebbe non essere determinato. Se si desidera consentire tali file, aggiungere {x} alla specifica."
noteDraftLimit: "Numero massimo di Note in bozza, lato server"
watermarkAvailable: "Disponibilità della funzione filigrana"
_condition:
roleAssignedTo: "Assegnato a ruoli manualmente"
isLocal: "Profilo locale"
@@ -2152,6 +2160,7 @@ _theme:
install: "Installa un tema"
manage: "Gestione dei temi"
code: "Codice tema"
copyThemeCode: "Copia il codice del Tema"
description: "Descrizione"
installed: "{name} è installato"
installedThemes: "Temi installati"
@@ -2800,6 +2809,7 @@ _fileViewer:
url: "URL"
uploadedAt: "Caricato il"
attachedNotes: "Note a cui è allegato"
usage: "In uso"
thisPageCanBeSeenFromTheAuthor: "Questa pagina può essere vista solo da chi ha caricato il file."
_externalResourceInstaller:
title: "Installa da sito esterno"
@@ -3103,6 +3113,7 @@ _serverSetupWizard:
text2: "Se puoi, ti preghiamo di prendere in considerazione l'idea di fare una donazione, così potremo continuare a sviluppare."
text3: "Sono previsti anche dei vantaggi speciali per i sostenitori!"
_uploader:
editImage: "Modifica immagine"
compressedToX: "Compresso in {x}"
savedXPercent: "{x}% risparmiati"
abortConfirm: "Alcuni file non sono stati caricati. Vuoi annullare l'operazione?"
@@ -3169,3 +3180,20 @@ _imageEffector:
stripe: "Strisce"
polkadot: "A pallini"
checker: "revisore"
blockNoise: "Attenua rumore"
tearing: "Strappa immagine"
drafts: "Bozza"
_drafts:
select: "Selezionare bozza"
cannotCreateDraftAnymore: "Hai superato il numero massimo di bozze ammissibili."
cannotCreateDraft: "Impossibile creare una bozza di questo contenuto."
delete: "Elimina bozza"
deleteAreYouSure: "Vuoi davvero eliminare la bozza?"
noDrafts: "Non c'è nessuna bozza."
replyTo: "Rispondere a {user}"
quoteOf: "Citare la nota di {user}"
postTo: "Inserire in {channel}"
saveToDraft: "Salva come bozza"
restoreFromDraft: "Recuperare dalle bozze"
restore: "Ripristina"
listDrafts: "Elenco bozze"

View File

@@ -637,8 +637,8 @@ addRelay: "リレーの追加"
inboxUrl: "inboxのURL"
addedRelays: "追加済みのリレー"
serviceworkerInfo: "プッシュ通知を行うには有効にする必要があります。"
deletedNote: "削除された投稿"
invisibleNote: "非公開の投稿"
deletedNote: "削除されたノート"
invisibleNote: "非公開のノート"
enableInfiniteScroll: "自動でもっと見る"
visibility: "公開範囲"
poll: "アンケート"
@@ -1303,7 +1303,7 @@ messageToFollower: "フォロワーへのメッセージ"
target: "対象"
testCaptchaWarning: "CAPTCHAのテストを目的とした機能です。<strong>本番環境で使用しないでください。</strong>"
prohibitedWordsForNameOfUser: "禁止ワード(ユーザーの名前)"
prohibitedWordsForNameOfUserDescription: "このリストに含まれる文字列がユーザーの名前に含まれる場合、ユーザーの名前の変更を拒否します。モデレーター権限を持つユーザーはこの制限の影響を受けません。"
prohibitedWordsForNameOfUserDescription: "このリストに含まれる文字列がユーザーの名前に含まれる場合、ユーザーの名前の変更を拒否します。モデレーター権限を持つユーザーはこの制限の影響を受けません。ユーザー名(username)に対しても全て小文字に置き換えて検査します。"
yourNameContainsProhibitedWords: "変更しようとした名前に禁止された文字列が含まれています"
yourNameContainsProhibitedWordsDescription: "名前に禁止されている文字列が含まれています。この名前を使用したい場合は、サーバー管理者にお問い合わせください。"
thisContentsAreMarkedAsSigninRequiredByAuthor: "投稿者により、表示にはログインが必要と設定されています"
@@ -1368,6 +1368,9 @@ redisplayAllTips: "全ての「ヒントとコツ」を再表示"
hideAllTips: "全ての「ヒントとコツ」を非表示"
defaultImageCompressionLevel: "デフォルトの画像圧縮度"
defaultImageCompressionLevel_description: "低くすると画質を保てますが、ファイルサイズは増加します。<br>高くするとファイルサイズを減らせますが、画質は低下します。"
lowPowerMode: "低電力モード"
inMinutes: "分"
inDays: "日"
_order:
newest: "新しい順"
@@ -1451,6 +1454,7 @@ _settings:
useStickyIcons: "アイコンをスクロールに追従させる"
enableHighQualityImagePlaceholders: "高品質な画像のプレースホルダを表示"
uiAnimations: "UIのアニメーション"
playAnimatedImages: "アニメーション画像を再生"
showNavbarSubButtons: "ナビゲーションバーに副ボタンを表示"
ifOn: "オンのとき"
ifOff: "オフのとき"
@@ -1649,6 +1653,10 @@ _serverSettings:
fanoutTimelineDbFallback: "データベースへのフォールバック"
fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。"
reactionsBufferingDescription: "有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。"
remoteNotesCleaning: "リモート投稿の自動クリーニング"
remoteNotesCleaning_description: "有効にすると、参照されていない古いリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。"
remoteNotesCleaningMaxProcessingDuration: "最大クリーニング処理継続時間"
remoteNotesCleaningExpiryDaysForEachNotes: "最低ノート保持日数"
inquiryUrl: "問い合わせ先URL"
inquiryUrlDescription: "サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定します。"
openRegistration: "アカウントの作成をオープンにする"
@@ -1667,6 +1675,8 @@ _serverSettings:
userGeneratedContentsVisibilityForVisitor: "非利用者に対するユーザー作成コンテンツの公開範囲"
userGeneratedContentsVisibilityForVisitor_description: "モデレーションが行き届きにくい不適切なリモートコンテンツなどが、自サーバー経由で図らずもインターネットに公開されてしまうことによるトラブル防止などに役立ちます。"
userGeneratedContentsVisibilityForVisitor_description2: "サーバーで受信したリモートのコンテンツを含め、サーバー内の全てのコンテンツを無条件でインターネットに公開することはリスクが伴います。特に、分散型の特性を知らない閲覧者にとっては、リモートのコンテンツであってもサーバー内で作成されたコンテンツであると誤って認識してしまう可能性があるため、注意が必要です。"
restartServerSetupWizardConfirm_title: "サーバーの初期設定ウィザードをやり直しますか?"
restartServerSetupWizardConfirm_text: "現在の一部の設定はリセットされます。"
_userGeneratedContentsVisibilityForVisitor:
all: "全て公開"
@@ -2019,6 +2029,7 @@ _role:
uploadableFileTypes_caption: "MIMEタイプを指定します。改行で区切って複数指定できるほか、アスタリスク(*)でワイルドカード指定できます。(例: image/*)"
uploadableFileTypes_caption2: "ファイルによっては種別を判定できないことがあります。そのようなファイルを許可する場合は {x} を指定に追加してください。"
noteDraftLimit: "サーバーサイドのノートの下書きの作成可能数"
watermarkAvailable: "ウォーターマーク機能の使用可否"
_condition:
roleAssignedTo: "マニュアルロールにアサイン済み"
isLocal: "ローカルユーザー"
@@ -2885,6 +2896,7 @@ _fileViewer:
url: "URL"
uploadedAt: "追加日"
attachedNotes: "添付されているノート"
usage: "利用"
thisPageCanBeSeenFromTheAuthor: "このページは、このファイルをアップロードしたユーザーしか閲覧できません。"
_externalResourceInstaller:
@@ -3192,6 +3204,8 @@ _serverSetupWizard:
doYouConnectToFediverse_description1: "分散型サーバーで構成されるネットワーク(Fediverse)に接続すると、他のサーバーと相互にコンテンツのやり取りが可能です。"
doYouConnectToFediverse_description2: "Fediverseと接続することは「連合」とも呼ばれます。"
youCanConfigureMoreFederationSettingsLater: "連合可能なサーバーの指定など、高度な設定も後ほど可能です。"
remoteContentsCleaning: "受信コンテンツの自動クリーニング"
remoteContentsCleaning_description: "連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、参照されていない古くなったコンテンツを自動でサーバーから削除し、ストレージを節約できます。"
adminInfo: "管理者情報"
adminInfo_description: "問い合わせを受け付けるために使用される管理者情報を設定します。"
adminInfo_mustBeFilled: "オープンサーバー、または連合がオンの場合は必ず入力が必要です。"
@@ -3288,7 +3302,7 @@ drafts: "下書き"
_drafts:
select: "下書きを選択"
cannotCreateDraftAnymore: "下書きの作成可能数を超えています。"
cannotCreateDraftOfRenote: "リノートの下書き作成できません。"
cannotCreateDraft: "この内容では下書き作成できません。"
delete: "下書きを削除"
deleteAreYouSure: "下書きを削除しますか?"
noDrafts: "下書きはありません"

View File

@@ -300,6 +300,7 @@ uploadFromUrlMayTakeTime: "アップロード終わるんにちょい時間か
explore: "みつける"
messageRead: "もう読んだ"
noMoreHistory: "これより昔のんはあらへんで"
startChat: "チャットを始めよか"
nUsersRead: "{n}人が読んでもうた"
agreeTo: "{0}に同意したで"
agree: "せやな"
@@ -324,6 +325,7 @@ dark: "ダーク"
lightThemes: "デイゲーム"
darkThemes: "ナイトゲーム"
syncDeviceDarkMode: "デバイスのダークモードと一緒にする"
switchDarkModeManuallyWhenSyncEnabledConfirm: "「{x}」がオンになってるで。同期をオフにして手動でモードを切り替えることにします?"
drive: "ドライブ"
fileName: "ファイル名"
selectFile: "ファイル選んでや"
@@ -422,6 +424,7 @@ antennaExcludeBots: "Botアカウントを除外"
antennaKeywordsDescription: "スペースで区切ったるとAND指定で、改行で区切ったるとOR指定や"
notifyAntenna: "新しいノートを通知すんで"
withFileAntenna: "なんか添付されたノートだけ"
excludeNotesInSensitiveChannel: "センシティブなチャンネルのノートは入れんとくわ"
enableServiceworker: "ブラウザにプッシュ通知が行くようにする"
antennaUsersDescription: "ユーザー名を改行で区切ったってな"
caseSensitive: "大文字と小文字は別もんや"
@@ -693,6 +696,7 @@ userSaysSomethingAbout: "{name}が「{word}」についてなんか言うてた
makeActive: "使うで"
display: "表示"
copy: "コピー"
copiedToClipboard: "クリップボードにコピーされたで"
metrics: "メトリクス"
overview: "概要"
logs: "ログ"
@@ -787,6 +791,7 @@ wide: "広い"
narrow: "狭い"
reloadToApplySetting: "設定はページリロード後に反映されるで。今リロードしとくか?"
needReloadToApply: "反映には再起動せなあかんで"
needToRestartServerToApply: "反映にはサーバーを再起動せなあかんのよ。"
showTitlebar: "タイトルバーを見せる"
clearCache: "キャッシュをほかす"
onlineUsersCount: "{n}人が起きとるで"
@@ -974,6 +979,7 @@ document: "ドキュメント"
numberOfPageCache: "ページ、どんだけキャッシュすんの?"
numberOfPageCacheDescription: "増やすと使いやすくなるけど、負荷とメモリ使用量が増えてくで。一長一短やな。"
logoutConfirm: "ログアウトしまっか?"
logoutWillClearClientData: "ログアウトするとクライアントの設定情報がブラウザから消されてまうで。再ログイン時に設定情報を復元できるようにするためには、設定の自動バックアップを有効にするとええで。"
lastActiveDate: "最後に使った日時"
statusbar: "ステータスバー"
pleaseSelect: "選んだってやー"
@@ -992,6 +998,7 @@ failedToUpload: "アップロードに失敗してもうたわ…"
cannotUploadBecauseInappropriate: "きわどい内容を含むかもしれへんって言われたからアップロードできへんわ。"
cannotUploadBecauseNoFreeSpace: "ドライブがもうパンパンやからアップロードできへんわ。"
cannotUploadBecauseExceedsFileSizeLimit: "ファイルが思うたよりも大きいさかいアップロードできへんでこれ。"
cannotUploadBecauseUnallowedFileType: "許可されてへんファイル種別やからアップロードできへんっぽい。"
beta: "ベータ"
enableAutoSensitive: "自動できわどいか判断する"
enableAutoSensitiveDescription: "使える時は、機械学習を使って自動でメディアにNSFWフラグを設定するで。この機能をオフにしても、サーバーによっては自動で設定されることがあるで。"
@@ -1304,11 +1311,37 @@ federationSpecified: "このサーバーはホワイトリスト連合で運用
federationDisabled: "このサーバーは連合が無効化されてるで。他のサーバーのユーザーとやり取りすることはできひんで。"
confirmOnReact: "ツッコむときに確認とる"
reactAreYouSure: "\" {emoji} \" でツッコむ?"
markAsSensitiveConfirm: "このメディアをきわどい扱いしときますか?"
unmarkAsSensitiveConfirm: "このメディアはやっぱきわどくなかったってことでええんか?"
noName: "名前はあらへんで"
preferenceSyncConflictTitle: "サーバーに設定値があるみたいやわ"
preferenceSyncConflictText: "同期が有効にされた設定項目は設定値をサーバーに保存するねんけど、この設定項目はサーバーに保存されたやつがあるみたいやわ。どないするん?"
preferenceSyncConflictChoiceMerge: "ガッチャンコしよか"
preferenceSyncConflictChoiceCancel: "同期の有効化はやめとくわ"
postForm: "投稿フォーム"
information: "情報"
migrateOldSettings: "旧設定情報をお引っ越し"
migrateOldSettings_description: "通常これは自動で行われるはずなんやけど、なんかの理由で上手く移行できへんかったときは手動で移行処理をポチっとできるで。今の設定情報は上書きされるで。"
settingsMigrating: "設定を移行しとるで。ちょっと待っとってな... (後で、設定→その他→旧設定情報を移行 で手動で移行することもできるで)"
driveAboutTip: "ドライブでは、今までアップロードしたファイルがずらーっと表示されるで。<br>\nートにファイルをもっかいのっけたり、あとで投稿するファイルをその辺に置いとくこともできるねん。<br>\n<b>ファイルをほかすと、前にそのファイルをのっけた全部の場所(ノート、ページ、アバター、バナー等)からも見えんくなるから気いつけてな。</b><br>\nフォルダを作って整理することもできるで。"
turnItOn: "オンにしとこ"
turnItOff: "オフでええわ"
emojiUnmute: "絵文字ミュートやめたる"
unmuteX: "{x}のミュートやめたる"
redisplayAllTips: "全部の「ヒントとコツ」をもっかい見して"
hideAllTips: "「ヒントとコツ」は全部表示せんでええ"
defaultImageCompressionLevel_description: "低くすると画質は保てるんやけど、ファイルサイズが増えるで。<br>高くするとファイルサイズは減らせるんやけど、画質が落ちるで。"
inMinutes: "分"
inDays: "日"
_chat:
noMessagesYet: "まだメッセージはあらへんで"
individualChat_description: "特定のユーザーと一対一でチャットができるで。"
roomChat_description: "複数人でチャットできるで。\nあと、個人チャットを許可してへんユーザーとでも、相手がええって言うならチャットできるで。"
inviteUserToChat: "ユーザーを招待してチャットを始めてみ"
invitations: "来てや"
noInvitations: "招待はあらへんで"
noHistory: "履歴はないわ。"
noRooms: "ルームはあらへんで"
members: "メンバーはん"
home: "ホーム"
send: "送信"
@@ -2617,7 +2650,7 @@ _externalResourceInstaller:
_errors:
_invalidParams:
title: ""
description: ""
description: "外部サイトからデータを持ってくるのに欲しい情報が足らへんみたいやわ。URLは合っとる"
_resourceTypeNotSupported:
title: ""
description: ""
@@ -2648,7 +2681,7 @@ _dataSaver:
title: "アイコンの絵"
description: "アイコン画像のアニメが止まるで。普通の画像よりもデータ量がでかいから、もっと通信量を節約できるねん。"
_code:
title: "コードハイライト"
title: "コードハイライトは表示せんでええ"
description: "MFMとかでコードハイライト記法が使われてるとき、タップするまで読み込まれへんくなるで。コードハイライトではハイライトする言語ごとにその決めてるファイルを読む必要はあんねんな。けどな、それは自動で読み込まれなくなるから、通信量を少なくできることができるねん。"
_hemisphere:
N: "北半球"
@@ -2858,3 +2891,8 @@ _watermarkEditor:
image: "画像"
advanced: "高度"
angle: "角度"
_imageEffector:
discardChangesConfirm: "変更をせんで終わるか?"
_drafts:
deleteAreYouSure: "下書きをほかしてもええか?"
noDrafts: "下書きはあらへん"

View File

@@ -1313,6 +1313,7 @@ availableRoles: "사용 가능한 역할"
acknowledgeNotesAndEnable: "활성화 하기 전에 주의 사항을 확인했습니다."
federationSpecified: "이 서버는 화이트 리스트 제도로 운영 중 입니다. 정해진 리모트 서버가 아닌 경우 연합되지 않습니다."
federationDisabled: "이 서버는 연합을 하지 않고 있습니다. 리모트 서버 유저와 통신을 할 수 없습니다."
draft: "초안"
confirmOnReact: "리액션할 때 확인"
reactAreYouSure: "\" {emoji} \"로 리액션하시겠습니까?"
markAsSensitiveConfirm: "이 미디어를 민감한 미디어로 설정하시겠습니까?"
@@ -1367,6 +1368,11 @@ redisplayAllTips: "모든 '팁과 유용한 정보'를 재표시"
hideAllTips: "모든 '팁과 유용한 정보'를 비표시"
defaultImageCompressionLevel: "기본 이미지 압축 정도"
defaultImageCompressionLevel_description: "낮추면 화질을 유지합니다만 파일 크기는 증가합니다. <br>높이면 파일 크기를 줄일 수 있습니다만 화질은 저하됩니다."
inMinutes: "분"
inDays: "일"
_order:
newest: "최신 순"
oldest: "오래된 순"
_chat:
noMessagesYet: "아직 메시지가 없습니다"
newMessage: "새로운 메시지"
@@ -1993,6 +1999,8 @@ _role:
uploadableFileTypes: "업로드 가능한 파일 유형"
uploadableFileTypes_caption: "MIME 유형을 "
uploadableFileTypes_caption2: "파일에 따라서는 유형을 검사하지 못하는 경우가 있습니다. 그러한 파일을 허가하는 경우에는 {x}를 지정으로 추가해주십시오."
noteDraftLimit: "서버측 노트 초안 작성 가능 수"
watermarkAvailable: "워터마크 기능의 사용 여부"
_condition:
roleAssignedTo: "수동 역할에 이미 할당됨"
isLocal: "로컬 유저"
@@ -2152,6 +2160,7 @@ _theme:
install: "테마 설치"
manage: "테마 관리"
code: "테마 코드"
copyThemeCode: "테마 코드 복사"
description: "설명"
installed: "{name} 테마가 설치되었습니다"
installedThemes: "설치된 테마"
@@ -2800,6 +2809,7 @@ _fileViewer:
url: "URL"
uploadedAt: "업로드 날짜"
attachedNotes: "첨부된 노트"
usage: "이용"
thisPageCanBeSeenFromTheAuthor: "이 페이지는 파일 소유자만 열람할 수 있습니다"
_externalResourceInstaller:
title: "외부 사이트로부터 설치"
@@ -3103,6 +3113,7 @@ _serverSetupWizard:
text2: "앞으로도 계속해서 개발을 할 수 있도록 괜찮으시다면 부디 기부를 부탁드립니다."
text3: "지원자 대상 특전도 있습니다!"
_uploader:
editImage: "이미지 편집"
compressedToX: "{x}로 압축"
savedXPercent: "{x}% 절약"
abortConfirm: "업로드되지 않은 파일이 있습니다만, 그만 두시겠습니까?"
@@ -3171,3 +3182,18 @@ _imageEffector:
checker: "체크 무늬"
blockNoise: "노이즈 방지"
tearing: "티어링"
drafts: "초안"
_drafts:
select: "초안 선택"
cannotCreateDraftAnymore: "초안 작성 가능 수를 초과했습니다."
cannotCreateDraft: "이 내용으로는 초안을 작성할 수 없습니다. "
delete: "초안 삭제\n"
deleteAreYouSure: "초안을 삭제하시겠습니까?"
noDrafts: "초안 없음\n"
replyTo: "{user}에 회신"
quoteOf: "{user} 노트에 인용"
postTo: "{channel}에 게시"
saveToDraft: "초안에 저장"
restoreFromDraft: "초안에서 복원\n"
restore: "복원"
listDrafts: "초안 목록"

View File

@@ -461,6 +461,8 @@ replies: "Svar"
renotes: "Renote"
surrender: "Avbryt"
information: "Informasjon"
inMinutes: "Minutter"
inDays: "Dager"
_chat:
invitations: "Inviter"
members: "Medlemmer"

View File

@@ -1040,6 +1040,8 @@ surrender: "Odrzuć"
gameRetry: "Spróbuj ponownie"
postForm: "Formularz tworzenia wpisu"
information: "Informacje"
inMinutes: "minuta"
inDays: "dzień"
_chat:
invitations: "Zaproś"
noHistory: "Brak historii"

View File

@@ -298,6 +298,7 @@ uploadFromUrl: "Enviar por URL"
uploadFromUrlDescription: "URL do arquivo que você deseja enviar"
uploadFromUrlRequested: "Upload solicitado"
uploadFromUrlMayTakeTime: "Pode levar algum tempo para que o upload seja concluído."
uploadNFiles: "Enviar {n} arquivos"
explore: "Explorar"
messageRead: "Lida"
noMoreHistory: "Não existe histórico anterior"
@@ -326,6 +327,7 @@ dark: "Escuro"
lightThemes: "Tema claro"
darkThemes: "Tema escuro"
syncDeviceDarkMode: "Sincronize com o modo escuro do dispositivo"
switchDarkModeManuallyWhenSyncEnabledConfirm: "\"{x}\" está ativado. Você gostaria de desligar a sincronização e alterar manualmente?"
drive: "Drive"
fileName: "Nome do Ficheiro"
selectFile: "Selecione os arquivos"
@@ -444,7 +446,7 @@ exploreUsersCount: "Há um utilizador de {count}"
exploreFediverse: "Explorar Fediverse"
popularTags: "Tags populares"
userList: "Listas"
about: "Informações"
about: "Sobre"
aboutMisskey: "Sobre Misskey"
administrator: "Administrador"
token: "Símbolo"
@@ -578,6 +580,7 @@ newNoteRecived: "Nova nota recebida"
newNote: "Nova Nota"
sounds: "Sons"
sound: "Sons"
notificationSoundSettings: "Configurações de som de notificações"
listen: "Ouvir"
none: "Nenhum"
showInPage: "Ver na página"
@@ -999,6 +1002,7 @@ failedToUpload: "Falha ao enviar"
cannotUploadBecauseInappropriate: "Esse arquivo não pôde ser enviado porque partes dele foram detectadas como potencialmente inapropriadas."
cannotUploadBecauseNoFreeSpace: "Envio falhou devido à falta de capacidade no Drive."
cannotUploadBecauseExceedsFileSizeLimit: "Não é possível realizar o upload deste arquivo porque ele excede o tamanho máximo permitido."
cannotUploadBecauseUnallowedFileType: "Não foi possível fazer o envio, pois o formato do arquivo não foi autorizado."
beta: "Beta"
enableAutoSensitive: "Marcar automaticamente como conteúdo sensível"
enableAutoSensitiveDescription: "Quando disponível, a marcação de mídia sensível será automaticamente atribuído ao conteúdo de mídia usando aprendizado de máquina. Mesmo que você desative essa função, em alguns servidores, isso pode ser configurado automaticamente."
@@ -1309,6 +1313,7 @@ availableRoles: "Cargos disponíveis"
acknowledgeNotesAndEnable: "Ative após compreender as precauções."
federationSpecified: "Esse servidor opera com uma lista branca de federação. Interagir com servidores diferentes daqueles designados pela administração não é permitido."
federationDisabled: "Federação está desabilitada nesse servidor. Você não pode interagir com usuários de outros servidores."
draft: "Rascunhos"
confirmOnReact: "Confirmar ao reagir"
reactAreYouSure: "Você deseja adicionar uma reação \"{emoji}\"?"
markAsSensitiveConfirm: "Você deseja definir essa mídia como sensível?"
@@ -1326,6 +1331,7 @@ restore: "Redefinir"
syncBetweenDevices: "Sincronizar entre dispositivos"
preferenceSyncConflictTitle: "O valor configurado já existe no servidor."
preferenceSyncConflictText: "As preferências com a sincronização ativada irão salvar os seus valores no servidor. Porém, já existem valores no servidor. Qual conjunto de valores você deseja sobrescrever?"
preferenceSyncConflictChoiceMerge: "Combinar"
preferenceSyncConflictChoiceServer: "Valor configurado no servidor"
preferenceSyncConflictChoiceDevice: "Valor configurado no dispositivo"
preferenceSyncConflictChoiceCancel: "Cancelar a habilitação de sincronização"
@@ -1333,7 +1339,7 @@ paste: "Colar"
emojiPalette: "Paleta de emojis"
postForm: "Campo de postagem"
textCount: "Contagem de caracteres"
information: "Informações"
information: "Sobre"
chat: "Conversas"
migrateOldSettings: "Migrar configurações antigas de cliente"
migrateOldSettings_description: "Isso deve ser feito automaticamente. Caso o processo de migração tenha falhado, você pode acioná-lo manualmente. As informações atuais de migração serão substituídas."
@@ -1356,6 +1362,17 @@ emojiMute: "Silenciar emoji"
emojiUnmute: "Reativar emoji"
muteX: "Silenciar {x}"
unmuteX: "Reativar {x}"
abort: "Abortar"
tip: "Dicas e Truques"
redisplayAllTips: "Mostrar todas as \"Dicas e Truques\" novamente"
hideAllTips: "Ocultas todas as \"Dicas e Truques\""
defaultImageCompressionLevel: "Nível de compressão de imagem padrão"
defaultImageCompressionLevel_description: "Alto, reduz o tamanho do arquivo mas, também, a qualidade da imagem.<br>Alto, reduz o tamanho do arquivo mas, também, a qualidade da imagem."
inMinutes: "Minuto(s)"
inDays: "Dia(s)"
_order:
newest: "Priorizar Mais Novos"
oldest: "Priorizar Mais Antigos"
_chat:
noMessagesYet: "Ainda não há mensagens"
newMessage: "Nova mensagem"
@@ -1442,6 +1459,8 @@ _settings:
contentsUpdateFrequency: "Frequência da obtenção de conteúdo"
contentsUpdateFrequency_description: "Quanto maior o valor, mais o conteúdo atualiza. Porém, há uma diminuição do desempenho e aumento do tráfego e consumo de memória."
contentsUpdateFrequency_description2: "Quando o modo tempo-real está ativado, o conteúdo é atualizado em tempo real, ignorando essa opção."
showUrlPreview: "Exibir prévia de URL"
showAvailableReactionsFirstInNote: "Exibir reações disponíveis no topo."
_chat:
showSenderName: "Exibir nome de usuário do remetente"
sendOnEnter: "Pressionar Enter para enviar"
@@ -1977,6 +1996,11 @@ _role:
canImportMuting: "Permitir importação de silenciamentos"
canImportUserLists: "Permitir importação de listas"
chatAvailability: "Permitir Conversas"
uploadableFileTypes: "Tipos de arquivo enviáveis"
uploadableFileTypes_caption: "Especifica tipos MIME permitidos. Múltiplos tipos MIME podem ser especificados separando-os por linha. Curingas podem ser especificados com um asterisco (*). (exemplo, image/*)"
uploadableFileTypes_caption2: "Alguns tipos de arquivos podem não ser detectados. Para permiti-los, adicione {x} à especificação."
noteDraftLimit: "Limite de rascunhos possíveis"
watermarkAvailable: "Disponibilidade da função de marca d'água"
_condition:
roleAssignedTo: "Atribuído a cargos manuais"
isLocal: "Usuário local"
@@ -2136,6 +2160,7 @@ _theme:
install: "Instalar um tema"
manage: "Gerenciar temas"
code: "Código do tema"
copyThemeCode: "Copiar código do tema"
description: "Descrição"
installed: "{name} foi instalado"
installedThemes: "Temas instalados"
@@ -2449,6 +2474,8 @@ _visibility:
disableFederation: "Defederar"
disableFederationDescription: "Não transmitir às outras instâncias"
_postForm:
quitInspiteOfThereAreUnuploadedFilesConfirm: "Há arquivos que não foram enviados, gostaria de descartá-los e fechar o editor?"
uploaderTip: "O arquivo ainda não foi enviado. No menu do arquivo, você pode renomear, cortar, adicionar uma marca d'água, comprimir ou descomprimir um arquivo. Arquivos serão enviados automaticamente ao publicar a nota."
replyPlaceholder: "Responder a essa nota..."
quotePlaceholder: "Citar essa nota..."
channelPlaceholder: "Postar em canal..."
@@ -2782,6 +2809,7 @@ _fileViewer:
url: "URL"
uploadedAt: "Adicionado em"
attachedNotes: "Notas anexadas"
usage: "Usado"
thisPageCanBeSeenFromTheAuthor: "Essa página só pode ser vista pelo usuário que enviou esse arquivo."
_externalResourceInstaller:
title: "Instalar de site externo"
@@ -2829,6 +2857,12 @@ _dataSaver:
_avatar:
title: "Imagem do avatar"
description: "Parar animação de avatares. Imagens animadas podem ter um arquivo mais pesado do que imagens normais, potencialmente levando a reduções no tráfego de dados."
_urlPreviewThumbnail:
title: "Esconder miniaturas em prévias de URL"
description: "Miniaturas em prévias de URL não serão carregadas."
_disableUrlPreview:
title: "Desabilitar prévias de URL"
description: "Desabilita a função de prévias de URL. Diferente das miniaturas, essa função impede o carregamento de toda informação do link."
_code:
title: "Destaque de código"
description: "Se as notações de formatação de código forem utilizadas em MFM, elas não irão carregar até serem selecionadas. Destaque de código exige baixar arquivos de alta definição para cada linguagem de programação. Logo, desabilitar o carregamento automático desses arquivos diminui a quantidade de informação comunicada."
@@ -2886,6 +2920,8 @@ _offlineScreen:
_urlPreviewSetting:
title: "Configurações da prévia de URL"
enable: "Habilitar prévia de URL"
allowRedirect: "Permitir redirecionamentos de URL em prévias."
allowRedirectDescription: "Se um URL tem um redirecionamento, você pode habilitar essa função para segui-lo e exibir a prévia do conteúdo redirecionado. Desabilitar isso irá economizar recursos, mas o conteúdo não será exibido."
timeout: "Tempo máximo para obter a prévia (ms)"
timeoutDescription: "Se demorar mais que esse valor para obter uma prévia, ela não será gerada."
maximumContentLength: "Content-Length máximo (em bytes)"
@@ -3076,6 +3112,15 @@ _serverSetupWizard:
text1: "Misskey é software aberto desenvolvido por voluntários."
text2: "Nós apreciaríamos o seu apoio para podermos continuar o desenvolvimento desse software no futuro."
text3: "Também há benefícios especiais para apoiadores!"
_uploader:
editImage: "Editar Imagem"
compressedToX: "Comprimido para {x}"
savedXPercent: "Salvando {x}%"
abortConfirm: "Alguns arquivos não foram enviados, deseja abortar?"
doneConfirm: "Alguns arquivos não foram enviados, deseja continuar mesmo assim?"
maxFileSizeIsX: "O tamanho máximo de arquivos enviados é {x}"
allowedTypes: "Tipos de arquivo enviáveis"
tip: "O arquivo não foi enviado. Então, esse diálogo permite que você confirme, renomeie, comprima e recorte o arquivo antes de enviar. Quando estiver pronto, você pode enviar apertando o botão \"Enviar\"."
_clientPerformanceIssueTip:
title: "Dicas de desempenho"
makeSureDisabledAdBlocker: "Desative o seu bloqueador de anúncios"
@@ -3084,8 +3129,20 @@ _clientPerformanceIssueTip:
makeSureDisabledCustomCss_description: "Substituir o estilo da página pode afetar o desempenho. Certifique-se que o CSS personalizado ou extensões que modifiquem o estilo da página estejam desabilitados."
makeSureDisabledAddons: "Desabilite extensões"
makeSureDisabledAddons_description: "Algumas extensões podem afetar comportamentos do cliente e afetar o desempenho. Por favor, desative as extensões do seu navegador e veja se isso melhora a situação."
_clip:
tip: "Clip é uma função que permite organização das suas notas."
_userLists:
tip: "Listas podem conter qualquer usuário que você especificar em sua criação. A lista criada aparece como uma linha do tempo exibindo usuários selecionados."
watermark: "Marca d'água"
defaultPreset: "Predefinição Padrão"
_watermarkEditor:
tip: "Uma marca d'água, como informação de autoria, pode ser adicionada à imagem."
quitWithoutSaveConfirm: "Descartar mudanças?"
driveFileTypeWarn: "Esse arquivo não é compatível"
driveFileTypeWarnDescription: "Escolha um arquivo de imagem"
title: "Editar marca d'água"
cover: "Cobrir tudo"
repeat: "Espalhar pelo conteúdo"
opacity: "Opacidade"
scale: "Tamanho"
text: "Texto"
@@ -3093,4 +3150,50 @@ _watermarkEditor:
type: "Tipo"
image: "imagem"
advanced: "Avançado"
stripe: "Listras"
stripeWidth: "Largura da linha"
stripeFrequency: "Número de linhas"
angle: "Ângulo"
polkadot: "Bolinhas"
checker: "Xadrez"
polkadotMainDotOpacity: "Opacidade da bolinha principal"
polkadotMainDotRadius: "Raio da bolinha principal"
polkadotSubDotOpacity: "Opacidade da bolinha secundária"
polkadotSubDotRadius: "Raio das bolinhas adicionais"
polkadotSubDotDivisions: "Número de bolinhas adicionais"
_imageEffector:
title: "Efeitos"
addEffect: "Adicionar efeitos"
discardChangesConfirm: "Tem certeza que deseja sair? Há mudanças não salvas."
_fxs:
chromaticAberration: "Aberração cromática"
glitch: "Glitch"
mirror: "Espelho"
invert: "Inverter Cores"
grayscale: "Tons de Cinza"
colorAdjust: "Correção de Cores"
colorClamp: "Compressão de Cores"
colorClampAdvanced: "Compressão Avançada de Cores"
distort: "Distorção"
threshold: "Limiarização Binária"
zoomLines: "Linhas de Ação"
stripe: "Listras"
polkadot: "Bolinhas"
checker: "Xadrez"
blockNoise: "Bloquear Ruído"
tearing: "Descontinuidade"
drafts: "Rascunhos"
_drafts:
select: "Selecionar Rascunho"
cannotCreateDraftAnymore: "O número máximo de rascunhos foi excedido."
cannotCreateDraft: "Você não pode criar um rascunho com esse conteúdo."
delete: "Excluir Rascunho"
deleteAreYouSure: "Excluir rascunho?"
noDrafts: "Sem rascunhos"
replyTo: "Resposta a {user}"
quoteOf: "Citação à nota de {user}"
postTo: "Publicando em {channel}"
saveToDraft: "Salvar como Rascunho"
restoreFromDraft: "Restaurar de Rascunho"
restore: "Redefinir"
listDrafts: "Lista de Rascunhos"

View File

@@ -2,7 +2,7 @@
_lang_: "Русский"
headlineMisskey: "Сеть, сплетённая из заметок"
introMisskey: "Добро пожаловать! Misskey — это децентрализованный сервис микроблогов с открытым исходным кодом.\nПишите «заметки» — делитесь со всеми происходящим вокруг или рассказывайте о себе 📡\nСтавьте «реакции» — выражайте свои чувства и эмоции от заметок других 👍\nОткройте для себя новый мир 🚀"
poweredByMisskeyDescription: "{name} сервис на платформе с открытым исходным кодом <b>Misskey</b>, называемый экземпляром Misskey."
poweredByMisskeyDescription: "{name} один из инстансов (также называемый экземпляром Misskey), использующий платформу с открытым исходным кодом <b>Misskey</b>."
monthAndDay: "{day}.{month}"
search: "Поиск"
reset: "Сброс"
@@ -82,7 +82,7 @@ export: "Экспорт"
files: "Файлы"
download: "Скачать"
driveFileDeleteConfirm: "Удалить файл «{name}»? Заметки с ним также будут удалены."
unfollowConfirm: "Удалить из подписок пользователя {name}?"
unfollowConfirm: "Отписаться от {name} ?"
exportRequested: "Вы запросили экспорт. Это может занять некоторое время. Результат будет добавлен на «Диск»."
importRequested: "Вы запросили импорт. Это может занять некоторое время."
lists: "Списки"
@@ -298,6 +298,7 @@ uploadFromUrl: "Загрузить по ссылке"
uploadFromUrlDescription: "Ссылка на файл, который хотите загрузить"
uploadFromUrlRequested: "Загрузка выбранного"
uploadFromUrlMayTakeTime: "Загрузка может занять некоторое время."
uploadNFiles: "Загрузить {n} файл"
explore: "Обзор"
messageRead: "Прочитали"
noMoreHistory: "История закончилась"
@@ -575,8 +576,10 @@ showFixedPostForm: "Показывать поле для ввода новой
showFixedPostFormInChannel: "Показывать поле для ввода новой заметки наверху ленты (каналы)"
withRepliesByDefaultForNewlyFollowed: "По умолчанию включайте ответы новых пользователей, на которых вы подписались, во временную шкалу"
newNoteRecived: "Появилась новая заметка"
newNote: "Новая заметка"
sounds: "Звуки"
sound: "Звуки"
notificationSoundSettings: "Настройки звука уведомлений"
listen: "Слушать"
none: "Ничего"
showInPage: "Показать страницу"
@@ -791,6 +794,7 @@ wide: "Толстый"
narrow: "Тонкий"
reloadToApplySetting: "Это настройка вступает в силу при загрузке страницы. Перезагрузить сейчас?"
needReloadToApply: "Изменения вступят в силу после перезагрузки страницы."
needToRestartServerToApply: "Для вступления изменений в силу необходимо перезапустить сервер."
showTitlebar: "Показать заголовок"
clearCache: "Очистить кэш"
onlineUsersCount: "Пользователей сейчас в сети: {n}"
@@ -1176,13 +1180,25 @@ unused: "Неиспользованное"
used: "Использован"
expired: "Срок действия приглашения истёк"
doYouAgree: "Согласны?"
beSureToReadThisAsItIsImportant: "Это важно, поэтому, пожалуйста, прочтите это."
iHaveReadXCarefullyAndAgree: "Я прочитал(а) и согласен(сна) с условиями \"{x}"
dialog: "Диалог"
icon: "Аватар"
currentAnnouncements: "Текущие новости"
pastAnnouncements: "Предыдущие новости"
youHaveUnreadAnnouncements: "У вас есть непрочитанные уведомления"
replies: "Ответы"
renotes: "Репост"
loadReplies: "Показать ответы"
loadConversation: "Загрузить беседу"
pinnedList: "Закреплённый список"
keepScreenOn: "Держать экран включённым"
unnotifyNotes: "Отписаться от сообщений"
authentication: "Аутентификация"
authenticationRequiredToContinue: "Пожалуйста, пройдите аутентификацию, чтобы продолжить"
dateAndTime: "Дата и время"
showRenotes: "Показывать репосты"
edited: "Изменено"
mutualFollow: "Взаимные подписки"
followingOrFollower: "Подписки или подписчики"
fileAttachedOnly: "Только заметки с файлами"
@@ -1193,30 +1209,71 @@ sourceCode: "Исходный код"
sourceCodeIsNotYetProvided: "Исходный код пока не доступен. Свяжитесь с администратором, чтобы исправить эту проблему."
repositoryUrl: "Ссылка на репозиторий"
repositoryUrlDescription: "Если вы используете Misskey как есть (без изменений в исходном коде), введите https://github.com/misskey-dev/misskey"
feedback: "Обратная связь"
privacyPolicy: "Политика Конфиденциальности"
privacyPolicyUrl: "Ссылка на Политику Конфиденциальности"
tosAndPrivacyPolicy: "Условия использования и политика конфиденциальности"
avatarDecorations: "Украшения для аватара"
attach: "Прикрепить"
angle: "Угол"
flip: "Переворот"
showAvatarDecorations: "Показать украшения для аватара"
pullDownToRefresh: "Опустите что бы обновить"
useGroupedNotifications: "Отображать уведомления сгруппировано"
signupPendingError: "Возникла проблема с подтверждением вашего адреса электронной почты. Возможно, срок действия ссылки истёк."
cwNotationRequired: "Если включена опция «Скрыть содержимое», необходимо написать аннотацию."
doReaction: "Добавить реакцию"
code: "Код"
reloadRequiredToApplySettings: "Для применения настроек необходима обновить страницу."
remainingN: "Остаётся: {n}"
overwriteContentConfirm: "Текущее содержимое будет перезаписано. Вы уверены?"
seasonalScreenEffect: "Эффект времени года на экране"
decorate: "Украсить"
addMfmFunction: "Добавить MFM"
bubbleGame: "BubbleGame"
sfx: "Звуковые эффекты"
soundWillBePlayed: "Будет воспроизведен звук"
showReplay: "Показать повтор"
endReplay: "Конец повтора"
lastNDays: "Последние {n} сут"
hemisphere: "Место проживания"
userSaysSomethingSensitive: "Сообщение, содержит конфиденциальные файлы от {name}"
enableHorizontalSwipe: "Смахните в сторону, чтобы сменить вкладки"
surrender: "Этот пост не может быть отменен."
gameRetry: "Повторить попытку"
notUsePleaseLeaveBlank: "Если не используется, оставьте пустым"
useNativeUIForVideoAudioPlayer: "Использовать интерфейс браузера при проигрывании видео и звука"
keepOriginalFilename: "Сохранять исходное имя файла"
keepOriginalFilenameDescription: "Если вы выключите данную настройку, имена файлов будут автоматически заменены случайной строкой при загрузке."
alwaysConfirmFollow: "Всегда подтверждать подписку"
inquiry: "Связаться"
fromX: "Из {x}"
genEmbedCode: "Сгенерировать код для "
noteOfThisUser: "Список заметок этого пользователя"
clipNoteLimitExceeded: "К этому клипу больше нельзя добавить заметки"
performance: "Производительность"
modified: "Изменено"
signinWithPasskey: "Войдите в систему, используя свой пароль"
unknownWebAuthnKey: "Не известный ключ "
passkeyVerificationFailed: "Ошибка проверка ключа доступа "
messageToFollower: "Сообщение подписчикам"
testCaptchaWarning: "Эта функция предназначена для тестирования CAPTCHA. <strong>Не использовать это в рабочей среде</strong>"
prohibitedWordsForNameOfUser: "Запрещенные слова (имя пользователя)"
prohibitedWordsForNameOfUserDescription: "Если имя пользователя содержит строку из этого списка, изменение имени пользователя будет запрещено. На пользователей с правами модератора это ограничение не распространяется. Имена пользователей также проверяются путём замены всех букв в нижнем регистре"
yourNameContainsProhibitedWords: "Имя, которое вы пытаетесь изменить, содержит запрещенную строку символов"
yourNameContainsProhibitedWordsDescription: "Имя содержит запрещённую строку символов. Если вы хотите использовать это имя, обратитесь к администратору сервера"
thisContentsAreMarkedAsSigninRequiredByAuthor: "Автор сообщения установил требование в виде авторизации для просмотра"
lockdown: "Доступ ограничен"
pleaseSelectAccount: "Выберите свой аккаунт"
availableRoles: "Доступные роли"
federationDisabled: "Федерация отключена для этого сервера. Вы не можете взаимодействовать с пользователями на других серверах."
draft: "Черновик"
markAsSensitiveConfirm: "Отметить контент как чувствительный?"
resetToDefaultValue: "Сбросить настройки до стандартных"
postForm: "Форма отправки"
information: "Описание"
inMinutes: "мин"
inDays: "сут"
_chat:
invitations: "Пригласить"
noHistory: "История пока пуста"
@@ -2200,3 +2257,4 @@ _watermarkEditor:
image: "Изображения"
advanced: "Для продвинутых"
angle: "Угол"
drafts: "Черновик"

View File

@@ -913,6 +913,8 @@ flip: "Preklopiť"
lastNDays: "Posledných {n} dní"
postForm: "Napísať poznámku"
information: "Informácie"
inMinutes: "min"
inDays: "dní"
_chat:
invitations: "Pozvať"
noHistory: "Žiadna história"

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,9 @@ search: "Пошук"
notifications: "Сповіщення"
username: "Ім'я користувача"
password: "Пароль"
initialPasswordForSetup: "Початковий пароль для налаштування"
initialPasswordIsIncorrect: "Початковий пароль для налаштування неправильний"
initialPasswordForSetupDescription: "Використайте пароль, вказаний у конфігураційному файлі, якщо ви встановлювали Misskey власноруч.\nЯкщо використовуєте сервіси хостингу Misskey, використайте наданий пароль.\nЯкщо ви не маєте паролю, лишіть порожнім щоб продовжити. "
forgotPassword: "Я забув пароль"
fetchingAsApObject: "Отримуємо з федіверсу..."
ok: "OK"
@@ -45,6 +48,7 @@ pin: "Закріпити"
unpin: "Відкріпити"
copyContent: "Скопіювати контент"
copyLink: "Скопіювати посилання"
copyRemoteLink: "Копіювати віддалене посилання"
delete: "Видалити"
deleteAndEdit: "Видалити й редагувати"
deleteAndEditConfirm: "Ви впевнені, що хочете видалити цю нотатку та відредагувати її? Ви втратите всі реакції, поширення та відповіді на неї."
@@ -57,6 +61,7 @@ copyUserId: "Копіювати ID користувача"
copyNoteId: "блокнот ID користувача"
copyFileId: "Скопіювати ідентифікатор файлу."
searchUser: "Пошук користувачів"
searchThisUsersNotes: "Пошук нотаток користувача"
reply: "Відповісти"
loadMore: "Показати більше"
showMore: "Показати більше"
@@ -105,9 +110,11 @@ enterEmoji: "Введіть емодзі"
renote: "Поширити"
unrenote: "Відміна поширення"
renoted: "Поширити запис."
renotedToX: "Поширено до {name}"
cantRenote: "Неможливо поширити."
cantReRenote: "Поширення не можливо поширити."
quote: "Цитата"
inChannelRenote: "Поширено у канал"
pinnedNote: "Закріплений запис"
pinned: "Закріпити"
you: "Ви"
@@ -116,6 +123,7 @@ sensitive: "NSFW"
add: "Додати"
reaction: "Реакції"
reactions: "Реакції"
emojiPicker: "Вибір реакції"
reactionSettingDescription2: "Перемістити щоб змінити порядок, Клацнути мишою щоб видалити, Натиснути \"+\" щоб додати."
rememberNoteVisibility: "Пам’ятати параметри видимісті"
attachCancel: "Видалити вкладення"
@@ -289,7 +297,9 @@ folderName: "Ім'я теки"
createFolder: "Створити теку"
renameFolder: "Перейменувати теку"
deleteFolder: "Видалити теку"
folder: "Тека"
addFile: "Додати файл"
showFile: "Показати файл"
emptyDrive: "Диск порожній"
emptyFolder: "Тека порожня"
unableToDelete: "Видалення неможливе"
@@ -302,6 +312,7 @@ copyUrl: "Копіювати URL"
rename: "Перейменувати"
avatar: "Аватар"
banner: "Банер"
displayOfSensitiveMedia: "Показ чутливого медіа"
whenServerDisconnected: "Коли зв’язок із сервером втрачено"
disconnectedFromServer: "Зв’язок із сервером було перервано"
reload: "Оновити"
@@ -348,8 +359,11 @@ hcaptcha: "hCaptcha"
enableHcaptcha: "Увімкнути hCaptcha"
hcaptchaSiteKey: "Ключ сайту"
hcaptchaSecretKey: "Секретний ключ"
mcaptcha: "MCaptcha"
enableMcaptcha: "Увімкнути MCaptcha"
mcaptchaSiteKey: "Ключ сайту"
mcaptchaSecretKey: "Секретний ключ"
mcaptchaInstanceUrl: "Посилання на сервер MCaptcha"
recaptcha: "reCAPTCHA"
enableRecaptcha: "Увімкнути reCAPTCHA"
recaptchaSiteKey: "Ключ сайту"
@@ -905,6 +919,8 @@ flip: "Перевернути"
lastNDays: "Останні {n} днів"
postForm: "Створення нотатки"
information: "Інформація"
inMinutes: "х"
inDays: "д"
_chat:
invitations: "Запросити"
noHistory: "Історія порожня"

View File

@@ -1221,6 +1221,8 @@ information: "Giới thiệu"
chat: "Trò chuyện"
migrateOldSettings: "Di chuyển cài đặt cũ"
migrateOldSettings_description: "Thông thường, quá trình này diễn ra tự động, nhưng nếu vì lý do nào đó mà quá trình di chuyển không thành công, bạn có thể kích hoạt thủ công quy trình di chuyển, quá trình này sẽ ghi đè lên thông tin cấu hình hiện tại của bạn."
inMinutes: "phút"
inDays: "ngày"
_chat:
invitations: "Mời"
noHistory: "Không có dữ liệu"

View File

@@ -1313,11 +1313,12 @@ availableRoles: "可用角色"
acknowledgeNotesAndEnable: "理解注意事项后再开启。"
federationSpecified: "此服务器已开启联合白名单。只能与管理员指定的服务器通信。"
federationDisabled: "此服务器已禁用联合。无法与其它服务器上的用户通信。"
draft: "草稿"
confirmOnReact: "发送回应前需要确认"
reactAreYouSure: "要用「{emoji}」进行回应吗?"
markAsSensitiveConfirm: "要将此媒体标记为敏感吗?"
unmarkAsSensitiveConfirm: "要将此媒体解除敏感标记吗?"
preferences: "设置"
preferences: "偏好设置"
accessibility: "辅助功能"
preferencesProfile: "设置的配置"
copyPreferenceId: "复制设置 ID"
@@ -1367,6 +1368,11 @@ redisplayAllTips: "重新显示所有的提示和技巧"
hideAllTips: "隐藏所有的提示和技巧"
defaultImageCompressionLevel: "默认图像压缩等级"
defaultImageCompressionLevel_description: "较低的等级可以保持画质,但会增加文件大小。<br>较高的等级可以减少文件大小,但相对应的画质将会降低。"
inMinutes: "分"
inDays: "日"
_order:
newest: "从新到旧"
oldest: "从旧到新"
_chat:
noMessagesYet: "还没有消息"
newMessage: "新消息"
@@ -1923,7 +1929,7 @@ _role:
name: "角色名称"
description: "角色描述"
permission: "角色权限"
descriptionOfPermission: "<b>监察员</b>可以执行基本审核操作。\n<b>管理员</b>可以更改服务器的所有设置。"
descriptionOfPermission: "<b>监察员</b>可以执行基本审核操作。\n<b>管理员</b>可以更改实例的所有设置。"
assignTarget: "授权对象"
descriptionOfAssignTarget: "<b>手动</b>指手动选择谁被包括在这个角色中。\n<b>符合条件</b>指设置条件以自动包括符合条件的用户。"
manual: "手动"
@@ -1993,6 +1999,8 @@ _role:
uploadableFileTypes: "可上传的文件类型"
uploadableFileTypes_caption: "指定 MIME 类型。可用换行指定多个类型,也可以用星号(*)作为通配符。(如 image/*"
uploadableFileTypes_caption2: "文件根据文件的不同,可能无法判断其类型。若要允许此类文件,请在指定中添加 {x}。"
noteDraftLimit: "可在服务器上创建多少草稿"
watermarkAvailable: "能否使用水印功能"
_condition:
roleAssignedTo: "已分配给手动角色"
isLocal: "是本地用户"
@@ -2152,6 +2160,7 @@ _theme:
install: "安装主题"
manage: "主题管理"
code: "主题代码"
copyThemeCode: "复制主题代码"
description: "描述"
installed: "{name} 已安装"
installedThemes: "已安装的主题"
@@ -2800,6 +2809,7 @@ _fileViewer:
url: "URL"
uploadedAt: "添加日期"
attachedNotes: "附加到的帖子"
usage: "使用"
thisPageCanBeSeenFromTheAuthor: "此页只能被该文件的上传者查看。"
_externalResourceInstaller:
title: "从外部站点安装"
@@ -3103,6 +3113,7 @@ _serverSetupWizard:
text2: "为了今后也能继续开发,如果可以的话,请考虑一下捐助。"
text3: "也有面向支援者的特典!"
_uploader:
editImage: "编辑图像"
compressedToX: "压缩 {x}"
savedXPercent: "节省了 {x}% 的空间"
abortConfirm: "还有未上传的文件,要中止吗?"
@@ -3171,3 +3182,18 @@ _imageEffector:
checker: "检查"
blockNoise: "块状噪点"
tearing: "撕裂"
drafts: "草稿"
_drafts:
select: "选择草稿"
cannotCreateDraftAnymore: "已超过可创建的草稿数量。"
cannotCreateDraft: "此内容无法创建草稿。"
delete: "删除草稿"
deleteAreYouSure: "要删除草稿吗?"
noDrafts: "没有草稿"
replyTo: "回复给 {user}"
quoteOf: "对 {user} 帖子的引用"
postTo: "向 {channel} 的投稿"
saveToDraft: "保存到草稿"
restoreFromDraft: "从草稿恢复"
restore: "恢复"
listDrafts: "草稿一览"

View File

@@ -638,7 +638,7 @@ inboxUrl: "收件夾 URL"
addedRelays: "已加入的中繼器"
serviceworkerInfo: "如要使用推播通知,需要啟用此選項並設定金鑰。"
deletedNote: "已刪除的貼文"
invisibleNote: "私貼文"
invisibleNote: "私密的貼文"
enableInfiniteScroll: "啟用自動滾動頁面模式"
visibility: "可見性"
poll: "票選活動"
@@ -1313,6 +1313,7 @@ availableRoles: "可用角色"
acknowledgeNotesAndEnable: "了解注意事項後再開啟。"
federationSpecified: "此伺服器以白名單聯邦的方式運作。除了管理員指定的伺服器外,它無法與其他伺服器互動。"
federationDisabled: "此伺服器未開啟站台聯邦。無法與其他伺服器上的使用者互動。"
draft: "草稿\n"
confirmOnReact: "在做出反應前先確認"
reactAreYouSure: "用「 {emoji} 」反應嗎?"
markAsSensitiveConfirm: "要將這個媒體設定為敏感嗎?"
@@ -1367,6 +1368,11 @@ redisplayAllTips: "重新顯示所有「提示與技巧」"
hideAllTips: "隱藏所有「提示與技巧」"
defaultImageCompressionLevel: "預設的影像壓縮程度"
defaultImageCompressionLevel_description: "低的話可以保留畫質,但是會增加檔案的大小。<br>高的話可以減少檔案大小,但是會降低畫質。"
inMinutes: "分鐘"
inDays: "日"
_order:
newest: "最新的在前"
oldest: "最舊的在前"
_chat:
noMessagesYet: "尚無訊息"
newMessage: "新訊息"
@@ -1993,6 +1999,8 @@ _role:
uploadableFileTypes: "可上傳的檔案類型"
uploadableFileTypes_caption: "請指定 MIME 類型。可以用換行區隔多個類型,也可以使用星號(*作為萬用字元進行指定。例如image/*\n"
uploadableFileTypes_caption2: "有些檔案可能無法判斷其類型。若要允許這類檔案,請在指定中加入 {x}。"
noteDraftLimit: "伺服器端可建立的貼文草稿數量上限\n"
watermarkAvailable: "浮水印功能是否可用"
_condition:
roleAssignedTo: "手動指派角色完成"
isLocal: "本地使用者"
@@ -2152,6 +2160,7 @@ _theme:
install: "安裝佈景主題"
manage: "管理佈景主題"
code: "佈景主題代碼"
copyThemeCode: "複製主題代碼"
description: "描述"
installed: "{name}已安裝"
installedThemes: "已經安裝的佈景主題"
@@ -2800,6 +2809,7 @@ _fileViewer:
url: "URL"
uploadedAt: "加入日期"
attachedNotes: "含有附件的貼文"
usage: "使用情況"
thisPageCanBeSeenFromTheAuthor: "本頁面僅限上傳了這個檔案的使用者可以檢視。"
_externalResourceInstaller:
title: "從外部網站安裝"
@@ -3103,6 +3113,7 @@ _serverSetupWizard:
text2: "為了能夠繼續開發,若您願意的話,請考慮進行捐款。\n"
text3: "也有提供支援者專屬的特典!\n"
_uploader:
editImage: "編輯圖片"
compressedToX: "壓縮為 {x}"
savedXPercent: "節省了 {x}%"
abortConfirm: "有些檔案尚未上傳,您要中止嗎?"
@@ -3171,3 +3182,18 @@ _imageEffector:
checker: "棋盤格"
blockNoise: "阻擋雜訊"
tearing: "撕裂"
drafts: "草稿\n"
_drafts:
select: "選擇草槁"
cannotCreateDraftAnymore: "已超出可建立的草稿數量上限。\n"
cannotCreateDraft: "無法以此內容建立草稿。\n"
delete: "刪除草稿"
deleteAreYouSure: "確定要刪除草稿嗎?\n"
noDrafts: "沒有草稿。\n"
replyTo: "回覆給 {user}\n"
quoteOf: "引用自 {user} 的貼文\n"
postTo: "發佈到 {channel}\n"
saveToDraft: "儲存為草稿"
restoreFromDraft: "從草稿復原\n"
restore: "還原"
listDrafts: "草稿清單"

View File

@@ -1,12 +1,12 @@
{
"name": "misskey",
"version": "2025.6.4-alpha.0",
"version": "2025.8.0-alpha.1",
"codename": "nasubi",
"repository": {
"type": "git",
"url": "https://github.com/misskey-dev/misskey.git"
},
"packageManager": "pnpm@10.12.1",
"packageManager": "pnpm@10.13.1",
"workspaces": [
"packages/frontend-shared",
"packages/frontend",
@@ -52,29 +52,29 @@
"lodash": "4.17.21"
},
"dependencies": {
"cssnano": "7.0.7",
"esbuild": "0.25.5",
"cssnano": "7.1.0",
"esbuild": "0.25.6",
"execa": "9.6.0",
"fast-glob": "3.3.3",
"glob": "11.0.2",
"glob": "11.0.3",
"ignore-walk": "7.0.0",
"js-yaml": "4.1.0",
"postcss": "8.5.4",
"postcss": "8.5.6",
"tar": "7.4.3",
"terser": "5.42.0",
"terser": "5.43.1",
"typescript": "5.8.3"
},
"devDependencies": {
"@misskey-dev/eslint-plugin": "2.1.0",
"@types/node": "22.15.31",
"@typescript-eslint/eslint-plugin": "8.34.0",
"@typescript-eslint/parser": "8.34.0",
"@types/node": "22.16.4",
"@typescript-eslint/eslint-plugin": "8.37.0",
"@typescript-eslint/parser": "8.37.0",
"cross-env": "7.0.3",
"cypress": "14.4.1",
"eslint": "9.28.0",
"globals": "16.2.0",
"cypress": "14.5.2",
"eslint": "9.31.0",
"globals": "16.3.0",
"ncp": "2.0.0",
"pnpm": "10.12.1",
"pnpm": "10.13.1",
"start-server-and-test": "2.0.12"
},
"optionalDependencies": {
@@ -83,6 +83,9 @@
"pnpm": {
"overrides": {
"@aiscript-dev/aiscript-languageserver": "-"
},
"patchedDependencies": {
"typeorm": "patches/typeorm.patch"
}
}
}

View File

@@ -0,0 +1,15 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class FixAvatarUrl1750729939704 {
name = 'FixAvatarUrl1750729939704'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "user" ALTER COLUMN "avatarUrl" TYPE character varying(1024)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "user" ALTER COLUMN "avatarUrl" TYPE character varying(512)`);
}
}

View File

@@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class NoActionOnDraftRelation1752502434151 {
name = 'NoActionOnDraftRelation1752502434151'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_NOTE_DRAFT_CHANNEL_ID"`);
await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_NOTE_DRAFT_USER_ID"`);
await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_NOTE_DRAFT_RENOTE_ID"`);
await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_NOTE_DRAFT_REPLY_ID"`);
await queryRunner.query(`ALTER TABLE "note_draft" ADD CONSTRAINT "FK_e4983f28b4b18b03491536052f5" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "note_draft" DROP CONSTRAINT "FK_e4983f28b4b18b03491536052f5"`);
await queryRunner.query(`ALTER TABLE "note_draft" ADD CONSTRAINT "FK_NOTE_DRAFT_REPLY_ID" FOREIGN KEY ("replyId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "note_draft" ADD CONSTRAINT "FK_NOTE_DRAFT_RENOTE_ID" FOREIGN KEY ("renoteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "note_draft" ADD CONSTRAINT "FK_NOTE_DRAFT_USER_ID" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "note_draft" ADD CONSTRAINT "FK_NOTE_DRAFT_CHANNEL_ID" FOREIGN KEY ("channelId") REFERENCES "channel"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
}

View File

@@ -0,0 +1,79 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class MigrationCleanup1752509043847 {
name = 'MigrationCleanup1752509043847'
async up(queryRunner) {
// 1745378064470-composite-note-index.js created a index ON "note" ("userId", "id" DESC) as IDX_724b311e6f883751f261ebe378 but should be named IDX_a6f649630f55af3888e5a42919
await queryRunner.query(`ALTER INDEX "IDX_724b311e6f883751f261ebe378" RENAME TO "IDX_a6f649630f55af3888e5a42919"`);
// 1713656541000-abuse-report-notification.js generated system_webhook with hand-written SQL with CURRENT_TIMESTAMP as the default value, but its representation in TypeORM is `now()`
// see https://github.com/typeorm/typeorm/blob/f351757a15b9d2bd9d4222c69dcfd2316f46b5d1/src/driver/postgres/PostgresDriver.ts#L1575
await queryRunner.query(`ALTER TABLE "system_webhook" ALTER COLUMN "updatedAt" SET DEFAULT now()`);
// 1702718871541-ffVisibility.js defined a enum type "user_profile_followersVisibility_enum" but it should be "user_profile_followersvisibility_enum" (lowercase 'v') in typeorm
await queryRunner.query(`ALTER TYPE "public"."user_profile_followersVisibility_enum" RENAME TO "user_profile_followersvisibility_enum"`);
// 1713656541000-abuse-report-notification.js generated abuse_report_notification_recipient with hand-written SQL with CURRENT_TIMESTAMP as the default value, but its representation in TypeORM is `now()`
await queryRunner.query(`ALTER TABLE "abuse_report_notification_recipient" ALTER COLUMN "updatedAt" SET DEFAULT now()`);
// 1690796169261-play-visibility.js added visibility column to flash table but it forgot to set NOT NULL constraint
await queryRunner.query(`ALTER TABLE "flash" ALTER COLUMN "visibility" SET NOT NULL`);
// 1736686850345-createNoteDraft.js created note_draft with hand-written SQL but several types and comments are not correctly defined
await queryRunner.query(`CREATE TYPE "public"."note_draft_visibility_enum" AS ENUM('public', 'home', 'followers', 'specified')`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "id" SET DATA TYPE character varying(32)`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "replyId" SET DATA TYPE character varying(32)`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "renoteId" SET DATA TYPE character varying(32)`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "userId" SET DATA TYPE character varying(32)`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "channelId" SET DATA TYPE character varying(32)`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "fileIds" SET DATA TYPE character varying(32) array`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibleUserIds" SET DATA TYPE character varying(32) array`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibility" SET DATA TYPE "public"."note_draft_visibility_enum" USING visibility::note_draft_visibility_enum`);
await queryRunner.query(`COMMENT ON COLUMN "note_draft"."replyId" IS 'The ID of reply target.'`);
await queryRunner.query(`COMMENT ON COLUMN "note_draft"."renoteId" IS 'The ID of renote target.'`);
await queryRunner.query(`COMMENT ON COLUMN "note_draft"."userId" IS 'The ID of author.'`);
await queryRunner.query(`COMMENT ON COLUMN "note_draft"."channelId" IS 'The ID of source channel.'`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "fileIds" SET NOT NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "hasPoll" SET NOT NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "pollChoices" SET NOT NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "pollMultiple" SET NOT NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "localOnly" SET NOT NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibleUserIds" SET NOT NULL`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibleUserIds" DROP NOT NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "localOnly" DROP NOT NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "pollMultiple" DROP NOT NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "pollChoices" DROP NOT NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "hasPoll" DROP NOT NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "fileIds" DROP NOT NULL`);
await queryRunner.query(`COMMENT ON COLUMN "note_draft"."channelId" IS NULL`);
await queryRunner.query(`COMMENT ON COLUMN "note_draft"."userId" IS 'The ID of author.'`);
await queryRunner.query(`COMMENT ON COLUMN "note_draft"."renoteId" IS NULL`);
await queryRunner.query(`COMMENT ON COLUMN "note_draft"."replyId" IS NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibility" SET DATA TYPE varchar`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibleUserIds" SET DATA TYPE varchar[]`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "fileIds" SET DATA TYPE varchar[]`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "channelId" SET DATA TYPE varchar`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "userId" SET DATA TYPE varchar`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "renoteId" SET DATA TYPE varchar`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "replyId" SET DATA TYPE varchar`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "id" SET DATA TYPE varchar`);
await queryRunner.query(`DROP TYPE "public"."note_draft_visibility_enum"`);
await queryRunner.query(`ALTER TABLE "flash" ALTER COLUMN "visibility" DROP NOT NULL`);
await queryRunner.query(`ALTER TABLE "abuse_report_notification_recipient" ALTER COLUMN "updatedAt" SET DEFAULT CURRENT_TIMESTAMP`);
await queryRunner.query(`ALTER TYPE "public"."user_profile_followersvisibility_enum" RENAME TO "user_profile_followersVisibility_enum"`);
await queryRunner.query(`ALTER TABLE "system_webhook" ALTER COLUMN "updatedAt" SET DEFAULT CURRENT_TIMESTAMP`);
await queryRunner.query(`ALTER INDEX "IDX_a6f649630f55af3888e5a42919" RENAME TO "IDX_724b311e6f883751f261ebe378"`);
}
}

View File

@@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class RemoteNotesCleaning1753863104203 {
name = 'RemoteNotesCleaning1753863104203'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "enableRemoteNotesCleaning" boolean NOT NULL DEFAULT true`);
await queryRunner.query('ALTER TABLE "meta" ADD "remoteNotesCleaningMaxProcessingDurationInMinutes" integer NOT NULL DEFAULT \'60\'');
await queryRunner.query('ALTER TABLE "meta" ADD "remoteNotesCleaningExpiryDaysForEachNotes" integer NOT NULL DEFAULT \'90\'');
}
async down(queryRunner) {
await queryRunner.query('ALTER TABLE "meta" DROP COLUMN "remoteNotesCleaningExpiryDaysForEachNotes"');
await queryRunner.query('ALTER TABLE "meta" DROP COLUMN "remoteNotesCleaningMaxProcessingDurationInMinutes"');
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableRemoteNotesCleaning"`);
}
}

View File

@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class RemoveNoteConstraints1753868431598 {
name = 'RemoveNoteConstraints1753868431598'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_52ccc804d7c69037d558bac4c96"`);
await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_17cb3553c700a4985dff5a30ff5"`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_17cb3553c700a4985dff5a30ff5" FOREIGN KEY ("replyId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_52ccc804d7c69037d558bac4c96" FOREIGN KEY ("renoteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
}

View File

@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class TweakDefaultFederationSettings1754019326356 {
name = 'TweakDefaultFederationSettings1754019326356'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "federation" SET DEFAULT 'none'`);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "enableRemoteNotesCleaning" SET DEFAULT false`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "enableRemoteNotesCleaning" SET DEFAULT true`);
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "federation" SET DEFAULT 'all'`);
}
}

View File

@@ -4,7 +4,7 @@
"private": true,
"type": "module",
"engines": {
"node": "^20.10.0 || ^22.0.0"
"node": "^22.15.0"
},
"scripts": {
"start": "node ./built/boot/entry.js",
@@ -33,6 +33,7 @@
"test:fed": "pnpm jest:fed",
"test-and-coverage": "pnpm jest-and-coverage",
"test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e",
"check-migrations": "node scripts/check_migrations_clean.js",
"generate-api-json": "node ./scripts/generate_api_json.js"
},
"optionalDependencies": {

View File

@@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
// This script checks if the database migrations has been generated correctly.
import dataSource from '../ormconfig.js';
await dataSource.initialize();
const sqlInMemory = await dataSource.driver.createSchemaBuilder().log();
if (sqlInMemory.upQueries.length > 0 || sqlInMemory.downQueries.length > 0) {
console.error('There are several pending migrations. Please make sure you have generated the migrations correctly, or configured entities class correctly.');
for (const query of sqlInMemory.upQueries) {
console.error(`- ${query.query}`);
}
for (const query of sqlInMemory.downQueries) {
console.error(`- ${query.query}`);
}
process.exit(1);
} else {
console.log('All migrations are clean.');
process.exit(0);
}

View File

@@ -28,8 +28,6 @@ import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { emojiRegex } from '@/misc/emoji-regex.js';
import { NotificationService } from '@/core/NotificationService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
import { trackPromise } from '@/misc/promise-tracker.js';
const MAX_ROOM_MEMBERS = 50;
const MAX_REACTIONS_PER_MESSAGE = 100;
@@ -83,7 +81,6 @@ export class ChatService {
private chatEntityService: ChatEntityService,
private idService: IdService,
private globalEventService: GlobalEventService,
private apDeliverManagerService: ApDeliverManagerService,
private apRendererService: ApRendererService,
private queueService: QueueService,
private pushNotificationService: PushNotificationService,
@@ -239,19 +236,6 @@ export class ChatService {
}, 3000);
}
//#region AP deliver
if (this.userEntityService.isLocalUser(fromUser) && this.userEntityService.isRemoteUser(toUser)) {
(async () => {
const content = await this.apRendererService.renderChatMessage(inserted, false);
const activity = this.apRendererService.addContext(content);
const dm = this.apDeliverManagerService.createDeliverManager(fromUser, activity);
dm.addDirectRecipe(toUser);
trackPromise(dm.execute());
})();
}
//#endregion
return packedMessage;
}

View File

@@ -145,7 +145,10 @@ export class EmailService {
try {
// TODO: htmlサニタイズ
const info = await transporter.sendMail({
from: this.meta.email!,
from: this.meta.name ? {
name: this.meta.name,
address: this.meta.email!,
} : this.meta.email!,
to: to,
subject: subject,
text: text,

View File

@@ -20,6 +20,8 @@ import { CacheService } from '@/core/CacheService.js';
import { isReply } from '@/misc/is-reply.js';
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
type NoteFilter = (note: MiNote) => boolean;
type TimelineOptions = {
untilId: string | null,
sinceId: string | null,
@@ -28,7 +30,7 @@ type TimelineOptions = {
me?: { id: MiUser['id'] } | undefined | null,
useDbFallback: boolean,
redisTimelines: FanoutTimelineName[],
noteFilter?: (note: MiNote) => boolean,
noteFilter?: NoteFilter,
alwaysIncludeMyNotes?: boolean;
ignoreAuthorFromBlock?: boolean;
ignoreAuthorFromMute?: boolean;
@@ -79,7 +81,7 @@ export class FanoutTimelineEndpointService {
const shouldFallbackToDb = noteIds.length === 0 || ps.sinceId != null && ps.sinceId < oldestNoteId;
if (!shouldFallbackToDb) {
let filter = ps.noteFilter ?? (_note => true);
let filter = ps.noteFilter ?? (_note => true) as NoteFilter;
if (ps.alwaysIncludeMyNotes && ps.me) {
const me = ps.me;
@@ -145,15 +147,11 @@ export class FanoutTimelineEndpointService {
{
const parentFilter = filter;
filter = (note) => {
const noteJoined = note as MiNote & {
renoteUser: MiUser | null;
replyUser: MiUser | null;
};
if (!ps.ignoreAuthorFromUserSuspension) {
if (note.user!.isSuspended) return false;
}
if (note.userId !== note.renoteUserId && noteJoined.renoteUser?.isSuspended) return false;
if (note.userId !== note.replyUserId && noteJoined.replyUser?.isSuspended) return false;
if (note.userId !== note.renoteUserId && note.renote?.user?.isSuspended) return false;
if (note.userId !== note.replyUserId && note.reply?.user?.isSuspended) return false;
return parentFilter(note);
};
@@ -200,7 +198,7 @@ export class FanoutTimelineEndpointService {
return await ps.dbFallback(ps.untilId, ps.sinceId, ps.limit);
}
private async getAndFilterFromDb(noteIds: string[], noteFilter: (note: MiNote) => boolean, idCompare: (a: string, b: string) => number): Promise<MiNote[]> {
private async getAndFilterFromDb(noteIds: string[], noteFilter: NoteFilter, idCompare: (a: string, b: string) => number): Promise<MiNote[]> {
const query = this.notesRepository.createQueryBuilder('note')
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
.innerJoinAndSelect('note.user', 'user')

View File

@@ -4,8 +4,11 @@
*/
import { Inject, Injectable } from '@nestjs/common';
import { Brackets } from 'typeorm';
import { DI } from '@/di-symbols.js';
import { type FlashsRepository } from '@/models/_.js';
import { type FlashLikesRepository, MiUser, type FlashsRepository } from '@/models/_.js';
import { QueryService } from '@/core/QueryService.js';
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
/**
* MisskeyPlay関係のService
@@ -15,6 +18,11 @@ export class FlashService {
constructor(
@Inject(DI.flashsRepository)
private flashRepository: FlashsRepository,
@Inject(DI.flashLikesRepository)
private flashLikesRepository: FlashLikesRepository,
private queryService: QueryService,
) {
}
@@ -37,4 +45,43 @@ export class FlashService {
return await builder.getMany();
}
public async myLikes(meId: MiUser['id'], opts: { sinceId?: string, untilId?: string, sinceDate?: number, untilDate?: number, limit?: number, search?: string | null }) {
const query = this.queryService.makePaginationQuery(this.flashLikesRepository.createQueryBuilder('like'), opts.sinceId, opts.untilId, opts.sinceDate, opts.untilDate)
.andWhere('like.userId = :meId', { meId })
.leftJoinAndSelect('like.flash', 'flash');
if (opts.search != null) {
for (const word of opts.search.trim().split(' ')) {
query.andWhere(new Brackets(qb => {
qb.orWhere('flash.title ILIKE :search', { search: `%${sqlLikeEscape(word)}%` });
qb.orWhere('flash.summary ILIKE :search', { search: `%${sqlLikeEscape(word)}%` });
}));
}
}
const likes = await query
.limit(opts.limit)
.getMany();
return likes;
}
public async search(searchQuery: string, opts: { sinceId?: string, untilId?: string, sinceDate?: number, untilDate?: number, limit?: number }) {
const query = this.queryService.makePaginationQuery(this.flashRepository.createQueryBuilder('flash'), opts.sinceId, opts.untilId, opts.sinceDate, opts.untilDate)
.andWhere('flash.visibility = \'public\'');
for (const word of searchQuery.trim().split(' ')) {
query.andWhere(new Brackets(qb => {
qb.orWhere('flash.title ILIKE :search', { search: `%${sqlLikeEscape(word)}%` });
qb.orWhere('flash.summary ILIKE :search', { search: `%${sqlLikeEscape(word)}%` });
}));
}
const result = await query
.limit(opts.limit)
.getMany();
return result;
}
}

View File

@@ -421,7 +421,7 @@ export class NoteCreateService implements OnApplicationShutdown {
emojis,
userId: user.id,
localOnly: data.localOnly!,
reactionAcceptance: data.reactionAcceptance,
reactionAcceptance: data.reactionAcceptance ?? null,
visibility: data.visibility as any,
visibleUserIds: data.visibility === 'specified'
? data.visibleUsers
@@ -483,7 +483,11 @@ export class NoteCreateService implements OnApplicationShutdown {
await this.notesRepository.insert(insert);
}
return insert;
return {
...insert,
reply: data.reply ?? null,
renote: data.renote ?? null,
};
} catch (e) {
// duplicate key error
if (isDuplicateKeyValueError(e)) {

View File

@@ -62,7 +62,6 @@ export class NoteDeleteService {
*/
async delete(user: { id: MiUser['id']; uri: MiUser['uri']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote, quiet = false, deleter?: MiUser) {
const deletedAt = new Date();
const cascadingNotes = await this.findCascadingNotes(note);
if (note.replyId) {
await this.notesRepository.decrement({ id: note.replyId }, 'repliesCount', 1);
@@ -90,15 +89,6 @@ export class NoteDeleteService {
this.deliverToConcerned(user, note, content);
}
// also deliver delete activity to cascaded notes
const federatedLocalCascadingNotes = (cascadingNotes).filter(note => !note.localOnly && note.userHost == null); // filter out local-only notes
for (const cascadingNote of federatedLocalCascadingNotes) {
if (!cascadingNote.user) continue;
if (!this.userEntityService.isLocalUser(cascadingNote.user)) continue;
const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${cascadingNote.id}`), cascadingNote.user));
this.deliverToConcerned(cascadingNote.user, cascadingNote, content);
}
//#endregion
this.notesChart.update(note, false);
@@ -118,9 +108,6 @@ export class NoteDeleteService {
}
}
for (const cascadingNote of cascadingNotes) {
this.searchService.unindexNote(cascadingNote);
}
this.searchService.unindexNote(note);
await this.notesRepository.delete({
@@ -140,29 +127,6 @@ export class NoteDeleteService {
}
}
@bindThis
private async findCascadingNotes(note: MiNote): Promise<MiNote[]> {
const recursive = async (noteId: string): Promise<MiNote[]> => {
const query = this.notesRepository.createQueryBuilder('note')
.where('note.replyId = :noteId', { noteId })
.orWhere(new Brackets(q => {
q.where('note.renoteId = :noteId', { noteId })
.andWhere('note.text IS NOT NULL');
}))
.leftJoinAndSelect('note.user', 'user');
const replies = await query.getMany();
return [
replies,
...await Promise.all(replies.map(reply => recursive(reply.id))),
].flat();
};
const cascadingNotes: MiNote[] = await recursive(note.id);
return cascadingNotes;
}
@bindThis
private async getMentionedRemoteUsers(note: MiNote) {
const where = [] as any[];

View File

@@ -360,7 +360,7 @@ export class QueryService {
public generateSuspendedUserQueryForNote(q: SelectQueryBuilder<any>, excludeAuthor?: boolean): void {
if (excludeAuthor) {
const brakets = (user: string) => new Brackets(qb => qb
.where(`note.${user}Id IS NULL`)
.where(`${user}.id IS NULL`) // そもそもreplyやrenoteではない、もしくはleftjoinなどでuserが存在しなかった場合を考慮
.orWhere(`user.id = ${user}.id`)
.orWhere(`${user}.isSuspended = FALSE`));
q
@@ -368,7 +368,7 @@ export class QueryService {
.andWhere(brakets('renoteUser'));
} else {
const brakets = (user: string) => new Brackets(qb => qb
.where(`note.${user}Id IS NULL`)
.where(`${user}.id IS NULL`) // そもそもreplyやrenoteではない、もしくはleftjoinなどでuserが存在しなかった場合を考慮
.orWhere(`${user}.isSuspended = FALSE`));
q
.andWhere('user.isSuspended = FALSE')

View File

@@ -17,6 +17,7 @@ import { bindThis } from '@/decorators.js';
import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js';
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
import { type SystemWebhookPayload } from '@/core/SystemWebhookService.js';
import type { Packed } from '@/misc/json-schema.js';
import { type UserWebhookPayload } from './UserWebhookService.js';
import type {
DbJobData,
@@ -39,7 +40,6 @@ import type {
} from './QueueModule.js';
import type httpSignature from '@peertube/http-signature';
import type * as Bull from 'bullmq';
import type { Packed } from '@/misc/json-schema.js';
export const QUEUE_TYPES = [
'system',
@@ -53,6 +53,37 @@ export const QUEUE_TYPES = [
'systemWebhookDeliver',
] as const;
const REPEATABLE_SYSTEM_JOB_DEF = [{
name: 'tickCharts',
pattern: '55 * * * *',
}, {
name: 'resyncCharts',
pattern: '0 0 * * *',
}, {
name: 'cleanCharts',
pattern: '0 0 * * *',
}, {
name: 'aggregateRetention',
pattern: '0 0 * * *',
}, {
name: 'clean',
pattern: '0 0 * * *',
}, {
name: 'checkExpiredMutings',
pattern: '*/5 * * * *',
}, {
name: 'bakeBufferedReactions',
pattern: '0 0 * * *',
}, {
name: 'checkModeratorsActivity',
// 毎時30分に起動
pattern: '30 * * * *',
}, {
name: 'cleanRemoteNotes',
// 毎日午前4時に起動(最も人の少ない時間帯)
pattern: '0 4 * * *',
}];
@Injectable()
export class QueueService {
constructor(
@@ -69,61 +100,30 @@ export class QueueService {
@Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue,
@Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue,
) {
this.systemQueue.add('tickCharts', {
}, {
repeat: { pattern: '55 * * * *' },
removeOnComplete: 10,
removeOnFail: 30,
});
for (const def of REPEATABLE_SYSTEM_JOB_DEF) {
this.systemQueue.upsertJobScheduler(def.name, {
pattern: def.pattern,
}, {
name: def.name,
opts: {
// 期限ではなくcountで設定したいが、ジョブごとではなくキュー全体でカウントされるため、高頻度で実行されるジョブによって低頻度で実行されるジョブのログが消えることになる
removeOnComplete: {
age: 3600 * 24 * 7, // keep up to 7 days
},
removeOnFail: {
age: 3600 * 24 * 7, // keep up to 7 days
},
},
});
}
this.systemQueue.add('resyncCharts', {
}, {
repeat: { pattern: '0 0 * * *' },
removeOnComplete: 10,
removeOnFail: 30,
});
this.systemQueue.add('cleanCharts', {
}, {
repeat: { pattern: '0 0 * * *' },
removeOnComplete: 10,
removeOnFail: 30,
});
this.systemQueue.add('aggregateRetention', {
}, {
repeat: { pattern: '0 0 * * *' },
removeOnComplete: 10,
removeOnFail: 30,
});
this.systemQueue.add('clean', {
}, {
repeat: { pattern: '0 0 * * *' },
removeOnComplete: 10,
removeOnFail: 30,
});
this.systemQueue.add('checkExpiredMutings', {
}, {
repeat: { pattern: '*/5 * * * *' },
removeOnComplete: 10,
removeOnFail: 30,
});
this.systemQueue.add('bakeBufferedReactions', {
}, {
repeat: { pattern: '0 0 * * *' },
removeOnComplete: 10,
removeOnFail: 30,
});
this.systemQueue.add('checkModeratorsActivity', {
}, {
// 毎時30分に起動
repeat: { pattern: '30 * * * *' },
removeOnComplete: 10,
removeOnFail: 30,
// 古いバージョンで作成され現在使われなくなったrepeatableジョブをクリーンアップ
this.systemQueue.getJobSchedulers().then(schedulers => {
for (const scheduler of schedulers) {
if (!REPEATABLE_SYSTEM_JOB_DEF.some(def => def.name === scheduler.key)) {
this.systemQueue.removeJobScheduler(scheduler.key);
}
}
});
}
@@ -810,6 +810,13 @@ export class QueueService {
}
}
@bindThis
public async queueGetJobLogs(queueType: typeof QUEUE_TYPES[number], jobId: string) {
const queue = this.getQueue(queueType);
const result = await queue.getJobLogs(jobId);
return result.logs;
}
@bindThis
public async queueGetJobs(queueType: typeof QUEUE_TYPES[number], jobTypes: JobType[], search?: string) {
const RETURN_LIMIT = 100;

View File

@@ -67,6 +67,7 @@ export type RolePolicies = {
chatAvailability: 'available' | 'readonly' | 'unavailable';
uploadableFileTypes: string[];
noteDraftLimit: number;
watermarkAvailable: boolean;
};
export const DEFAULT_POLICIES: RolePolicies = {
@@ -111,6 +112,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
'audio/*',
],
noteDraftLimit: 10,
watermarkAvailable: true,
};
@Injectable()
@@ -433,6 +435,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
return [...set];
}),
noteDraftLimit: calc('noteDraftLimit', vs => Math.max(...vs)),
watermarkAvailable: calc('watermarkAvailable', vs => vs.some(v => v === true)),
};
}

View File

@@ -93,6 +93,11 @@ export class SignupService {
if (isPreserved) {
throw new Error('USED_USERNAME');
}
const hasProhibitedWords = this.utilityService.isKeyWordIncluded(username.toLowerCase(), this.meta.prohibitedWordsForNameOfUser);
if (hasProhibitedWords) {
throw new Error('USED_USERNAME');
}
}
const keyPair = await new Promise<string[]>((res, rej) =>

View File

@@ -457,36 +457,6 @@ export class ApInboxService {
}
}
@bindThis
private async chatMessage(resolver: Resolver, actor: MiRemoteUser, message: IObject): Promise<string> {
const uri = getApId(message);
if (typeof message === 'object') {
if (actor.uri !== message.attributedTo) {
return 'skip: actor.uri !== message.attributedTo';
}
if (typeof message.id === 'string') {
if (this.utilityService.extractDbHost(actor.uri) !== this.utilityService.extractDbHost(message.id)) {
return 'skip: host in actor.uri !== message.id';
}
} else {
return 'skip: message.id is not a string';
}
}
try {
await this.chatService.createMessageViaAp(message, actor, resolver);
return 'ok';
} catch (err) {
if (err instanceof StatusError && !err.isRetryable) {
return `skip ${err.statusCode}`;
} else {
throw err;
}
}
}
@bindThis
private async delete(actor: MiRemoteUser, activity: IDelete): Promise<string> {
if (actor.uri !== activity.actor) {

View File

@@ -23,7 +23,7 @@ import { MfmService, type Appender } from '@/core/MfmService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import type { MiUserKeypair } from '@/models/UserKeypair.js';
import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository, MiMeta, MiChatMessage } from '@/models/_.js';
import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository, MiMeta } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { IdService } from '@/core/IdService.js';
@@ -502,31 +502,6 @@ export class ApRendererService {
};
}
@bindThis
public async renderChatMessage(message: MiChatMessage, dive = true): Promise<IPost> {
const attributedTo = this.userEntityService.genLocalUserUri(message.fromUserId);
const file = message.fileId ? await this.driveFilesRepository.findOneBy({ id: message.fileId }) : null;
const emojis = await this.getEmojis(message.emojis);
const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji));
const tag = [
...apemojis,
];
return {
id: `${this.config.url}/chat-messages/${message.id}`,
type: 'Misskey:ChatMessage',
attributedTo,
text: message.text,
published: this.idService.parse(message.id).date.toISOString(),
to: message.toUserId,
attachment: file ? [this.renderDocument(file)] : [],
tag,
};
}
@bindThis
public async renderPerson(user: MiLocalUser) {
const id = this.userEntityService.genLocalUserUri(user.id);

View File

@@ -5,6 +5,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { EntityNotFoundError } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type { Packed } from '@/misc/json-schema.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
@@ -90,6 +91,17 @@ export class NoteDraftEntityService implements OnModuleInit {
const packedFiles = options?._hint_?.packedFiles;
const packedUsers = options?._hint_?.packedUsers;
async function nullIfEntityNotFound<T>(promise: Promise<T>): Promise<T | null> {
try {
return await promise;
} catch (err) {
if (err instanceof EntityNotFoundError) {
return null;
}
throw err;
}
}
const packed: Packed<'NoteDraft'> = await awaitAll({
id: noteDraft.id,
createdAt: this.idService.parse(noteDraft.id).date.toISOString(),
@@ -117,15 +129,15 @@ export class NoteDraftEntityService implements OnModuleInit {
} : undefined,
...(opts.detail ? {
reply: noteDraft.replyId ? this.noteEntityService.pack(noteDraft.replyId, me, {
reply: noteDraft.replyId ? nullIfEntityNotFound(this.noteEntityService.pack(noteDraft.replyId, me, {
detail: false,
skipHide: opts.skipHide,
}) : undefined,
})) : undefined,
renote: noteDraft.renoteId ? this.noteEntityService.pack(noteDraft.renoteId, me, {
renote: noteDraft.renoteId ? nullIfEntityNotFound(this.noteEntityService.pack(noteDraft.renoteId, me, {
detail: true,
skipHide: opts.skipHide,
}) : undefined,
})) : undefined,
poll: noteDraft.hasPoll ? {
choices: noteDraft.pollChoices,

View File

@@ -4,7 +4,7 @@
*/
import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import { EntityNotFoundError, In } from 'typeorm';
import { ModuleRef } from '@nestjs/core';
import { DI } from '@/di-symbols.js';
import type { Packed } from '@/misc/json-schema.js';
@@ -46,6 +46,17 @@ function getAppearNoteIds(notes: MiNote[]): Set<string> {
return appearNoteIds;
}
async function nullIfEntityNotFound<T>(promise: Promise<T>): Promise<T | null> {
try {
return await promise;
} catch (err) {
if (err instanceof EntityNotFoundError) {
return null;
}
throw err;
}
}
@Injectable()
export class NoteEntityService implements OnModuleInit {
private userEntityService: UserEntityService;
@@ -436,19 +447,21 @@ export class NoteEntityService implements OnModuleInit {
...(opts.detail ? {
clippedCount: note.clippedCount,
reply: note.replyId ? this.pack(note.reply ?? note.replyId, me, {
// そもそもJOINしていない場合はundefined、JOINしたけど存在していなかった場合はnullで区別される
reply: (note.replyId && note.reply === null) ? null : note.replyId ? nullIfEntityNotFound(this.pack(note.reply ?? note.replyId, me, {
detail: false,
skipHide: opts.skipHide,
withReactionAndUserPairCache: opts.withReactionAndUserPairCache,
_hint_: options?._hint_,
}) : undefined,
})) : undefined,
renote: note.renoteId ? this.pack(note.renote ?? note.renoteId, me, {
// そもそもJOINしていない場合はundefined、JOINしたけど存在していなかった場合はnullで区別される
renote: (note.renoteId && note.renote === null) ? null : note.renoteId ? nullIfEntityNotFound(this.pack(note.renote ?? note.renoteId, me, {
detail: true,
skipHide: opts.skipHide,
withReactionAndUserPairCache: opts.withReactionAndUserPairCache,
_hint_: options?._hint_,
}) : undefined,
})) : undefined,
poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
@@ -591,7 +604,7 @@ export class NoteEntityService implements OnModuleInit {
private findNoteOrFail(id: string): Promise<MiNote> {
return this.notesRepository.findOneOrFail({
where: { id },
relations: ['user'],
relations: ['user', 'renote', 'reply'],
});
}

View File

@@ -22,7 +22,7 @@ export class MiAbuseReportNotificationRecipient {
/**
* 有効かどうか.
*/
@Index()
@Index('IDX_abuse_report_notification_recipient_isActive')
@Column('boolean', {
default: true,
})
@@ -47,7 +47,7 @@ export class MiAbuseReportNotificationRecipient {
/**
* 通知方法.
*/
@Index()
@Index('IDX_abuse_report_notification_recipient_method')
@Column('varchar', {
length: 64,
})
@@ -56,10 +56,11 @@ export class MiAbuseReportNotificationRecipient {
/**
* 通知先のユーザID.
*/
@Index()
@Index('IDX_abuse_report_notification_recipient_userId')
@Column({
...id(),
nullable: true,
default: null,
})
public userId: MiUser['id'] | null;
@@ -75,17 +76,20 @@ export class MiAbuseReportNotificationRecipient {
/**
* 通知先のユーザプロフィール.
*/
@ManyToOne(type => MiUserProfile, {})
@ManyToOne(type => MiUserProfile, {
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'userId', referencedColumnName: 'userId', foreignKeyConstraintName: 'FK_abuse_report_notification_recipient_userId2' })
public userProfile: MiUserProfile | null;
/**
* 通知先のシステムWebhookId.
*/
@Index()
@Index('IDX_abuse_report_notification_recipient_systemWebhookId')
@Column({
...id(),
nullable: true,
default: null,
})
public systemWebhookId: string | null;
@@ -95,6 +99,6 @@ export class MiAbuseReportNotificationRecipient {
@ManyToOne(type => MiSystemWebhook, {
onDelete: 'CASCADE',
})
@JoinColumn()
@JoinColumn({ name: 'systemWebhookId', referencedColumnName: 'id', foreignKeyConstraintName: 'FK_abuse_report_notification_recipient_systemWebhookId' })
public systemWebhook: MiSystemWebhook | null;
}

View File

@@ -55,8 +55,6 @@ export class MiChatMessage {
})
public text: string | null;
// 連合用
// ローカルはnull
@Column('varchar', {
length: 512, nullable: true,
})
@@ -84,22 +82,4 @@ export class MiChatMessage {
length: 1024, array: true, default: '{}',
})
public reactions: string[];
// 連合用
@Column('varchar', {
length: 128, array: true, default: '{}',
})
public emojis: string[];
// 連合用
@Column('boolean', {
default: false,
})
public isDelivering: boolean;
// 連合用
@Column('boolean', {
default: false,
})
public isDeliverFailed: boolean;
}

View File

@@ -8,6 +8,7 @@ import { id } from './util/id.js';
@Entity('emoji')
@Index(['name', 'host'], { unique: true })
@Index('IDX_EMOJI_ROLE_IDS', { synchronize: false }) // GIN for roleIdsThatCanBeUsedThisEmojiAsReaction in production
export class MiEmoji {
@PrimaryColumn(id())
public id: string;
@@ -32,6 +33,7 @@ export class MiEmoji {
@Column('varchar', {
length: 128, nullable: true,
})
@Index('IDX_EMOJI_CATEGORY')
public category: string | null;
@Column('varchar', {

View File

@@ -59,7 +59,7 @@ export class MiMeta {
public maintainerEmail: string | null;
@Column('boolean', {
default: false,
default: true,
})
public disableRegistration: boolean;
@@ -570,7 +570,7 @@ export class MiMeta {
public bannedEmailDomains: string[];
@Column('varchar', {
length: 1024, array: true, default: '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }',
length: 1024, array: true, default: ['admin', 'administrator', 'root', 'system', 'maintainer', 'host', 'mod', 'moderator', 'owner', 'superuser', 'staff', 'auth', 'i', 'me', 'everyone', 'all', 'mention', 'mentions', 'example', 'user', 'users', 'account', 'accounts', 'official', 'help', 'helps', 'support', 'supports', 'info', 'information', 'informations', 'announce', 'announces', 'announcement', 'announcements', 'notice', 'notification', 'notifications', 'dev', 'developer', 'developers', 'tech', 'misskey'],
})
public preservedUsernames: string[];
@@ -635,7 +635,7 @@ export class MiMeta {
public urlPreviewMaximumContentLength: number;
@Column('boolean', {
default: true,
default: false,
})
public urlPreviewRequireContentLength: boolean;
@@ -648,12 +648,13 @@ export class MiMeta {
@Column('varchar', {
length: 1024,
nullable: true,
default: null,
})
public urlPreviewUserAgent: string | null;
@Column('varchar', {
length: 128,
default: 'all',
default: 'none',
})
public federation: 'all' | 'specified' | 'none';
@@ -700,6 +701,21 @@ export class MiMeta {
default: true,
})
public allowExternalApRedirect: boolean;
@Column('boolean', {
default: false,
})
public enableRemoteNotesCleaning: boolean;
@Column('integer', {
default: 60, // minutes
})
public remoteNotesCleaningMaxProcessingDurationInMinutes: number;
@Column('integer', {
default: 90, // days
})
public remoteNotesCleaningExpiryDaysForEachNotes: number;
}
export type SoftwareSuspension = {

View File

@@ -20,7 +20,8 @@ import type { MiDriveFile } from './DriveFile.js';
// You should not use `@Index({ concurrent: true })` decorator because database initialization for test will fail
// because it will always run CREATE INDEX in transaction based on decorators.
// Not appending `{ concurrent: true }` to `@Index` will not cause any problem in production,
@Index(['userId', 'id'])
@Index(['userId', 'id']) // Note: this index is ("userId", "id" DESC) in production, but not in test.
@Entity('note')
export class MiNote {
@PrimaryColumn(id())
@@ -35,7 +36,7 @@ export class MiNote {
public replyId: MiNote['id'] | null;
@ManyToOne(type => MiNote, {
onDelete: 'CASCADE',
createForeignKeyConstraints: false,
})
@JoinColumn()
public reply: MiNote | null;
@@ -49,7 +50,7 @@ export class MiNote {
public renoteId: MiNote['id'] | null;
@ManyToOne(type => MiNote, {
onDelete: 'CASCADE',
createForeignKeyConstraints: false,
})
@JoinColumn()
public renote: MiNote | null;

View File

@@ -12,11 +12,13 @@ import { MiNote } from './Note.js';
import type { MiDriveFile } from './DriveFile.js';
@Entity('note_draft')
@Index('IDX_NOTE_DRAFT_FILE_IDS', { synchronize: false }) // GIN for fileIds in production
@Index('IDX_NOTE_DRAFT_VISIBLE_USER_IDS', { synchronize: false }) // GIN for visibleUserIds in production
export class MiNoteDraft {
@PrimaryColumn(id())
public id: string;
@Index()
@Index('IDX_NOTE_DRAFT_REPLY_ID')
@Column({
...id(),
nullable: true,
@@ -24,13 +26,14 @@ export class MiNoteDraft {
})
public replyId: MiNote['id'] | null;
// There is a possibility that replyId is not null but reply is null when the reply note is deleted.
@ManyToOne(type => MiNote, {
onDelete: 'CASCADE',
createForeignKeyConstraints: false,
})
@JoinColumn()
public reply: MiNote | null;
@Index()
@Index('IDX_NOTE_DRAFT_RENOTE_ID')
@Column({
...id(),
nullable: true,
@@ -38,8 +41,9 @@ export class MiNoteDraft {
})
public renoteId: MiNote['id'] | null;
// There is a possibility that renoteId is not null but renote is null when the renote note is deleted.
@ManyToOne(type => MiNote, {
onDelete: 'CASCADE',
createForeignKeyConstraints: false,
})
@JoinColumn()
public renote: MiNote | null;
@@ -55,7 +59,7 @@ export class MiNoteDraft {
})
public cw: string | null;
@Index()
@Index('IDX_NOTE_DRAFT_USER_ID')
@Column({
...id(),
comment: 'The ID of author.',
@@ -106,7 +110,7 @@ export class MiNoteDraft {
})
public hashtag: string | null;
@Index()
@Index('IDX_NOTE_DRAFT_CHANNEL_ID')
@Column({
...id(),
nullable: true,
@@ -114,8 +118,10 @@ export class MiNoteDraft {
})
public channelId: MiChannel['id'] | null;
// There is a possibility that channelId is not null but channel is null when the channel is deleted.
// (deleting channel is not implemented so it's not happening now but may happen in the future)
@ManyToOne(type => MiChannel, {
onDelete: 'CASCADE',
createForeignKeyConstraints: false,
})
@JoinColumn()
public channel: MiChannel | null;

View File

@@ -120,7 +120,7 @@ export class MiUser {
// avatarId が null になったとしてもこれが null でない可能性があるため、このフィールドを使うときは avatarId の non-null チェックをすること
@Column('varchar', {
length: 512, nullable: true,
length: 1024, nullable: true,
})
public avatarUrl: string | null;

View File

@@ -29,7 +29,7 @@ export class MiUserProfile {
})
public location: string | null;
@Index()
// Note: There's index named IDX_de22cd2b445eee31ae51cdbe99 for SUBSTR("birthday", 6, 5)
@Column('char', {
length: 10, nullable: true,
comment: 'The birthday (YYYY-MM-DD) of the User.',

View File

@@ -51,7 +51,7 @@ export const packedFlashSchema = {
},
likedCount: {
type: 'number',
optional: false, nullable: true,
optional: false, nullable: false,
},
isLiked: {
type: 'boolean',

View File

@@ -51,11 +51,13 @@ export const packedNoteDraftSchema = {
type: 'object',
optional: true, nullable: true,
ref: 'Note',
description: 'The reply target note contents if exists. If the reply target has been deleted since the draft was created, this will be null while replyId is not null.',
},
renote: {
type: 'object',
optional: true, nullable: true,
ref: 'Note',
description: 'The renote target note contents if exists. If the renote target has been deleted since the draft was created, this will be null while renoteId is not null.',
},
visibility: {
type: 'string',

View File

@@ -313,6 +313,10 @@ export const packedRolePoliciesSchema = {
type: 'integer',
optional: false, nullable: false,
},
watermarkAvailable: {
type: 'boolean',
optional: false, nullable: false,
},
},
} as const;

View File

@@ -6,7 +6,6 @@
import { Module } from '@nestjs/common';
import { CoreModule } from '@/core/CoreModule.js';
import { GlobalModule } from '@/GlobalModule.js';
import { CheckModeratorsActivityProcessorService } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
import { QueueLoggerService } from './QueueLoggerService.js';
import { QueueProcessorService } from './QueueProcessorService.js';
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
@@ -18,6 +17,8 @@ import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMu
import { BakeBufferedReactionsProcessorService } from './processors/BakeBufferedReactionsProcessorService.js';
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
import { CleanProcessorService } from './processors/CleanProcessorService.js';
import { CheckModeratorsActivityProcessorService } from './processors/CheckModeratorsActivityProcessorService.js';
import { CleanRemoteNotesProcessorService } from './processors/CleanRemoteNotesProcessorService.js';
import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js';
import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js';
import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js';
@@ -83,6 +84,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
AggregateRetentionProcessorService,
CheckExpiredMutingsProcessorService,
CheckModeratorsActivityProcessorService,
CleanRemoteNotesProcessorService,
QueueProcessorService,
],
exports: [

View File

@@ -43,6 +43,7 @@ import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMu
import { BakeBufferedReactionsProcessorService } from './processors/BakeBufferedReactionsProcessorService.js';
import { CleanProcessorService } from './processors/CleanProcessorService.js';
import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
import { CleanRemoteNotesProcessorService } from './processors/CleanRemoteNotesProcessorService.js';
import { QueueLoggerService } from './QueueLoggerService.js';
import { QUEUE, baseWorkerOptions } from './const.js';
@@ -123,6 +124,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
private bakeBufferedReactionsProcessorService: BakeBufferedReactionsProcessorService,
private checkModeratorsActivityProcessorService: CheckModeratorsActivityProcessorService,
private cleanProcessorService: CleanProcessorService,
private cleanRemoteNotesProcessorService: CleanRemoteNotesProcessorService,
) {
this.logger = this.queueLoggerService.logger;
@@ -164,6 +166,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
case 'bakeBufferedReactions': return this.bakeBufferedReactionsProcessorService.process();
case 'checkModeratorsActivity': return this.checkModeratorsActivityProcessorService.process();
case 'clean': return this.cleanProcessorService.process();
case 'cleanRemoteNotes': return this.cleanRemoteNotesProcessorService.process(job);
default: throw new Error(`unrecognized job type ${job.name} for system`);
}
};

View File

@@ -0,0 +1,174 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { setTimeout } from 'node:timers/promises';
import { Inject, Injectable } from '@nestjs/common';
import { And, In, IsNull, LessThan, MoreThan, Not } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type { MiMeta, MiNote, NoteFavoritesRepository, NotesRepository, UserNotePiningsRepository } from '@/models/_.js';
import type Logger from '@/logger.js';
import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
@Injectable()
export class CleanRemoteNotesProcessorService {
private logger: Logger;
constructor(
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
@Inject(DI.noteFavoritesRepository)
private noteFavoritesRepository: NoteFavoritesRepository,
@Inject(DI.userNotePiningsRepository)
private userNotePiningsRepository: UserNotePiningsRepository,
private idService: IdService,
private queueLoggerService: QueueLoggerService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('clean-remote-notes');
}
@bindThis
public async process(job: Bull.Job<Record<string, unknown>>): Promise<{
deletedCount: number;
oldest: number | null;
newest: number | null;
skipped?: boolean;
}> {
if (!this.meta.enableRemoteNotesCleaning) {
this.logger.info('Remote notes cleaning is disabled, skipping...');
return {
deletedCount: 0,
oldest: null,
newest: null,
skipped: true,
};
}
this.logger.info('cleaning remote notes...');
const maxDuration = this.meta.remoteNotesCleaningMaxProcessingDurationInMinutes * 60 * 1000; // Convert minutes to milliseconds
const startAt = Date.now();
const MAX_NOTE_COUNT_PER_QUERY = 50;
const stats = {
deletedCount: 0,
oldest: null as number | null,
newest: null as number | null,
};
let cursor: MiNote['id'] = this.idService.gen(Date.now() - (1000 * 60 * 60 * 24 * this.meta.remoteNotesCleaningExpiryDaysForEachNotes));
while (true) {
const batchBeginAt = Date.now();
let notes: Pick<MiNote, 'id'>[] = await this.notesRepository.find({
where: {
id: LessThan(cursor),
userHost: Not(IsNull()),
clippedCount: 0,
renoteCount: 0,
},
take: MAX_NOTE_COUNT_PER_QUERY,
order: {
// 新しい順
// https://github.com/misskey-dev/misskey/pull/16292#issuecomment-3139376314
id: -1,
},
select: ['id'],
});
const fetchedCount = notes.length;
for (const note of notes) {
if (note.id < cursor) {
cursor = note.id;
}
}
const pinings = notes.length === 0 ? [] : await this.userNotePiningsRepository.find({
where: {
noteId: In(notes.map(note => note.id)),
},
select: ['noteId'],
});
notes = notes.filter(note => {
return !pinings.some(pining => pining.noteId === note.id);
});
const favorites = notes.length === 0 ? [] : await this.noteFavoritesRepository.find({
where: {
noteId: In(notes.map(note => note.id)),
},
select: ['noteId'],
});
notes = notes.filter(note => {
return !favorites.some(favorite => favorite.noteId === note.id);
});
const replies = notes.length === 0 ? [] : await this.notesRepository.find({
where: {
replyId: In(notes.map(note => note.id)),
userHost: IsNull(),
},
select: ['replyId'],
});
notes = notes.filter(note => {
return !replies.some(reply => reply.replyId === note.id);
});
if (notes.length > 0) {
await this.notesRepository.delete(notes.map(note => note.id));
for (const note of notes) {
const t = this.idService.parse(note.id).date.getTime();
if (stats.oldest === null || t < stats.oldest) {
stats.oldest = t;
}
if (stats.newest === null || t > stats.newest) {
stats.newest = t;
}
}
stats.deletedCount += notes.length;
}
job.log(`Deleted ${notes.length} of ${fetchedCount}; ${Date.now() - batchBeginAt}ms`);
const elapsed = Date.now() - startAt;
if (elapsed >= maxDuration) {
this.logger.info(`Reached maximum duration of ${maxDuration}ms, stopping...`);
job.log('Reached maximum duration, stopping cleaning.');
job.updateProgress(100);
break;
}
job.updateProgress((elapsed / maxDuration) * 100);
await setTimeout(1000 * 5); // Wait a moment to avoid overwhelming the db
}
this.logger.succ('cleaning of remote notes completed.');
return {
deletedCount: stats.deletedCount,
oldest: stats.oldest,
newest: stats.newest,
skipped: false,
};
}
}

View File

@@ -40,8 +40,8 @@ export class GetterService {
}
@bindThis
public async getNoteWithUser(noteId: MiNote['id']) {
const note = await this.notesRepository.findOne({ where: { id: noteId }, relations: ['user'] });
public async getNoteWithRelations(noteId: MiNote['id']) {
const note = await this.notesRepository.findOne({ where: { id: noteId }, relations: ['user', 'reply', 'renote', 'reply.user', 'renote.user'] });
if (note == null) {
throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.');

View File

@@ -129,7 +129,8 @@ export class SignupApiService {
let ticket: MiRegistrationTicket | null = null;
if (this.meta.disableRegistration) {
// テスト時はこの機構は障害となるため無効にする
if (process.env.NODE_ENV !== 'test' && this.meta.disableRegistration) {
if (invitationCode == null || typeof invitationCode !== 'string') {
reply.code(400);
return;

View File

@@ -70,6 +70,7 @@ export * as 'admin/queue/inbox-delayed' from './endpoints/admin/queue/inbox-dela
export * as 'admin/queue/retry-job' from './endpoints/admin/queue/retry-job.js';
export * as 'admin/queue/remove-job' from './endpoints/admin/queue/remove-job.js';
export * as 'admin/queue/show-job' from './endpoints/admin/queue/show-job.js';
export * as 'admin/queue/show-job-logs' from './endpoints/admin/queue/show-job-logs.js';
export * as 'admin/queue/promote-jobs' from './endpoints/admin/queue/promote-jobs.js';
export * as 'admin/queue/jobs' from './endpoints/admin/queue/jobs.js';
export * as 'admin/queue/stats' from './endpoints/admin/queue/stats.js';
@@ -168,6 +169,7 @@ export * as 'clips/update' from './endpoints/clips/update.js';
export * as 'drive' from './endpoints/drive.js';
export * as 'drive/files' from './endpoints/drive/files.js';
export * as 'drive/files/attached-notes' from './endpoints/drive/files/attached-notes.js';
export * as 'drive/files/attached-chat-messages' from './endpoints/drive/files/attached-chat-messages.js';
export * as 'drive/files/check-existence' from './endpoints/drive/files/check-existence.js';
export * as 'drive/files/create' from './endpoints/drive/files/create.js';
export * as 'drive/files/delete' from './endpoints/drive/files/delete.js';
@@ -208,6 +210,7 @@ export * as 'flash/my-likes' from './endpoints/flash/my-likes.js';
export * as 'flash/show' from './endpoints/flash/show.js';
export * as 'flash/unlike' from './endpoints/flash/unlike.js';
export * as 'flash/update' from './endpoints/flash/update.js';
export * as 'flash/search' from './endpoints/flash/search.js';
export * as 'following/create' from './endpoints/following/create.js';
export * as 'following/delete' from './endpoints/following/delete.js';
export * as 'following/invalidate' from './endpoints/following/invalidate.js';

View File

@@ -98,6 +98,8 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
state: { type: 'string', nullable: true, default: null },
reporterOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'combined' },
targetUserOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'combined' },
@@ -115,7 +117,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private queryService: QueryService,
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.abuseUserReportsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId);
const query = this.queryService.makePaginationQuery(this.abuseUserReportsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate);
switch (ps.state) {
case 'resolved': query.andWhere('report.resolved = TRUE'); break;

View File

@@ -34,6 +34,8 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
publishing: { type: 'boolean', default: null, nullable: true },
},
required: [],
@@ -48,7 +50,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private queryService: QueryService,
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.adsRepository.createQueryBuilder('ad'), ps.sinceId, ps.untilId);
const query = this.queryService.makePaginationQuery(this.adsRepository.createQueryBuilder('ad'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate);
if (ps.publishing === true) {
query.andWhere('ad.expiresAt > :now', { now: new Date() }).andWhere('ad.startsAt <= :now', { now: new Date() });
} else if (ps.publishing === false) {

View File

@@ -68,6 +68,8 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
userId: { type: 'string', format: 'misskey:id', nullable: true },
status: { type: 'string', enum: ['all', 'active', 'archived'], default: 'active' },
},
@@ -87,7 +89,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId);
const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate);
if (ps.status === 'archived') {
query.andWhere('announcement.isActive = false');

View File

@@ -71,6 +71,8 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
userId: { type: 'string', format: 'misskey:id', nullable: true },
},
required: [],

View File

@@ -34,6 +34,8 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
userId: { type: 'string', format: 'misskey:id', nullable: true },
type: { type: 'string', nullable: true, pattern: /^[a-zA-Z0-9\/\-*]+$/.toString().slice(1, -1) },
origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' },
@@ -57,7 +59,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private queryService: QueryService,
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId);
const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate);
if (ps.userId) {
query.andWhere('file.userId = :userId', { userId: ps.userId });

View File

@@ -74,6 +74,8 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
},
required: [],
} as const;
@@ -89,7 +91,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private emojiEntityService: EmojiEntityService,
) {
super(meta, paramDef, async (ps, me) => {
const q = this.queryService.makePaginationQuery(this.emojisRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId);
const q = this.queryService.makePaginationQuery(this.emojisRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate);
if (ps.host == null) {
q.andWhere('emoji.host IS NOT NULL');

View File

@@ -68,6 +68,8 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
},
required: [],
} as const;
@@ -82,7 +84,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private queryService: QueryService,
) {
super(meta, paramDef, async (ps, me) => {
const q = this.queryService.makePaginationQuery(this.emojisRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId)
const q = this.queryService.makePaginationQuery(this.emojisRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('emoji.host IS NULL');
let emojis: MiEmoji[];

View File

@@ -571,6 +571,18 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
enableRemoteNotesCleaning: {
type: 'boolean',
optional: false, nullable: false,
},
remoteNotesCleaningExpiryDaysForEachNotes: {
type: 'number',
optional: false, nullable: false,
},
remoteNotesCleaningMaxProcessingDurationInMinutes: {
type: 'number',
optional: false, nullable: false,
},
},
},
} as const;
@@ -722,6 +734,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
proxyRemoteFiles: instance.proxyRemoteFiles,
signToActivityPubGet: instance.signToActivityPubGet,
allowExternalApRedirect: instance.allowExternalApRedirect,
enableRemoteNotesCleaning: instance.enableRemoteNotesCleaning,
remoteNotesCleaningExpiryDaysForEachNotes: instance.remoteNotesCleaningExpiryDaysForEachNotes,
remoteNotesCleaningMaxProcessingDurationInMinutes: instance.remoteNotesCleaningMaxProcessingDurationInMinutes,
};
});
}

View File

@@ -0,0 +1,45 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
kind: 'read:admin:queue',
res: {
type: 'array',
optional: false, nullable: false,
items: {
optional: false, nullable: false,
type: 'string',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
queue: { type: 'string', enum: QUEUE_TYPES },
jobId: { type: 'string' },
},
required: ['queue', 'jobId'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private queueService: QueueService,
) {
super(meta, paramDef, async (ps, me) => {
return this.queueService.queueGetJobLogs(ps.queue, ps.jobId);
});
}
}

View File

@@ -49,6 +49,8 @@ export const paramDef = {
roleId: { type: 'string', format: 'misskey:id' },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
},
required: ['roleId'],
@@ -76,7 +78,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.noSuchRole);
}
const query = this.queryService.makePaginationQuery(this.roleAssignmentsRepository.createQueryBuilder('assign'), ps.sinceId, ps.untilId)
const query = this.queryService.makePaginationQuery(this.roleAssignmentsRepository.createQueryBuilder('assign'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('assign.roleId = :roleId', { roleId: role.id })
.andWhere(new Brackets(qb => {
qb

View File

@@ -9,6 +9,7 @@ import type { ModerationLogsRepository } from '@/models/_.js';
import { QueryService } from '@/core/QueryService.js';
import { DI } from '@/di-symbols.js';
import { ModerationLogEntityService } from '@/core/entities/ModerationLogEntityService.js';
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
export const meta = {
tags: ['admin'],
@@ -63,8 +64,11 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
type: { type: 'string', nullable: true },
userId: { type: 'string', format: 'misskey:id', nullable: true },
search: { type: 'string', nullable: true },
},
required: [],
} as const;
@@ -79,19 +83,24 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private queryService: QueryService,
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.moderationLogsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId);
const query = this.queryService.makePaginationQuery(this.moderationLogsRepository.createQueryBuilder('log'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate);
if (ps.type != null) {
query.andWhere('report.type = :type', { type: ps.type });
query.andWhere('log.type = :type', { type: ps.type });
}
if (ps.userId != null) {
query.andWhere('report.userId = :userId', { userId: ps.userId });
query.andWhere('log.userId = :userId', { userId: ps.userId });
}
const reports = await query.limit(ps.limit).getMany();
if (ps.search != null) {
const escapedSearch = sqlLikeEscape(ps.search);
query.andWhere('log.info::text ILIKE :search', { search: `%${escapedSearch}%` });
}
return await this.moderationLogEntityService.packMany(reports);
const logs = await query.limit(ps.limit).getMany();
return await this.moderationLogEntityService.packMany(logs);
});
}
}

View File

@@ -205,6 +205,9 @@ export const paramDef = {
proxyRemoteFiles: { type: 'boolean' },
signToActivityPubGet: { type: 'boolean' },
allowExternalApRedirect: { type: 'boolean' },
enableRemoteNotesCleaning: { type: 'boolean' },
remoteNotesCleaningExpiryDaysForEachNotes: { type: 'number' },
remoteNotesCleaningMaxProcessingDurationInMinutes: { type: 'number' },
},
required: [],
} as const;
@@ -723,6 +726,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.allowExternalApRedirect = ps.allowExternalApRedirect;
}
if (ps.enableRemoteNotesCleaning !== undefined) {
set.enableRemoteNotesCleaning = ps.enableRemoteNotesCleaning;
}
if (ps.remoteNotesCleaningExpiryDaysForEachNotes !== undefined) {
set.remoteNotesCleaningExpiryDaysForEachNotes = ps.remoteNotesCleaningExpiryDaysForEachNotes;
}
if (ps.remoteNotesCleaningMaxProcessingDurationInMinutes !== undefined) {
set.remoteNotesCleaningMaxProcessingDurationInMinutes = ps.remoteNotesCleaningMaxProcessingDurationInMinutes;
}
const before = await this.metaService.fetch(true);
await this.metaService.update(set);

View File

@@ -33,6 +33,8 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
isActive: { type: 'boolean', default: true },
},
required: [],
@@ -48,7 +50,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private announcementEntityService: AnnouncementEntityService,
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId)
const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('announcement.isActive = :isActive', { isActive: ps.isActive })
.andWhere(new Brackets(qb => {
if (me) qb.orWhere('announcement.userId = :meId', { meId: me.id });

View File

@@ -34,6 +34,8 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
},
required: [],
} as const;
@@ -48,7 +50,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private queryService: QueryService,
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.blockingsRepository.createQueryBuilder('blocking'), ps.sinceId, ps.untilId)
const query = this.queryService.makePaginationQuery(this.blockingsRepository.createQueryBuilder('blocking'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('blocking.blockerId = :meId', { meId: me.id });
const blockings = await query

View File

@@ -33,6 +33,8 @@ export const paramDef = {
properties: {
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 5 },
},
required: [],
@@ -53,8 +55,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
this.channelFollowingsRepository.createQueryBuilder(),
ps.sinceId,
ps.untilId,
null,
null,
ps.sinceDate,
ps.untilDate,
'followeeId',
)
.andWhere({ followerId: me.id });

View File

@@ -33,6 +33,8 @@ export const paramDef = {
properties: {
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 5 },
},
required: [],
@@ -48,7 +50,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private queryService: QueryService,
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId)
const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('channel.isArchived = FALSE')
.andWhere({ userId: me.id });

View File

@@ -35,6 +35,8 @@ export const paramDef = {
type: { type: 'string', enum: ['nameAndDescription', 'nameOnly'], default: 'nameAndDescription' },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 5 },
},
required: ['query'],
@@ -50,7 +52,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private queryService: QueryService,
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId)
const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('channel.isArchived = FALSE');
if (ps.query !== '') {

View File

@@ -9,6 +9,7 @@ import { DI } from '@/di-symbols.js';
import { ChatService } from '@/core/ChatService.js';
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
import { ApiError } from '@/server/api/error.js';
import { IdService } from '@/core/IdService.js';
export const meta = {
tags: ['chat'],
@@ -42,6 +43,8 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
roomId: { type: 'string', format: 'misskey:id' },
},
required: ['roomId'],
@@ -52,8 +55,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
private chatEntityService: ChatEntityService,
private chatService: ChatService,
private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
await this.chatService.checkChatAvailability(me.id, 'read');
const room = await this.chatService.findRoomById(ps.roomId);
@@ -65,7 +72,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.noSuchRoom);
}
const messages = await this.chatService.roomTimeline(room.id, ps.limit, ps.sinceId, ps.untilId);
const messages = await this.chatService.roomTimeline(room.id, ps.limit, sinceId, untilId);
this.chatService.readRoomChatMessage(me.id, room.id);

View File

@@ -10,6 +10,7 @@ import { GetterService } from '@/server/api/GetterService.js';
import { ChatService } from '@/core/ChatService.js';
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
import { ApiError } from '@/server/api/error.js';
import { IdService } from '@/core/IdService.js';
export const meta = {
tags: ['chat'],
@@ -43,6 +44,8 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
userId: { type: 'string', format: 'misskey:id' },
},
required: ['userId'],
@@ -54,8 +57,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private chatEntityService: ChatEntityService,
private chatService: ChatService,
private getterService: GetterService,
private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
await this.chatService.checkChatAvailability(me.id, 'read');
const other = await this.getterService.getUser(ps.userId).catch(err => {
@@ -63,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw err;
});
const messages = await this.chatService.userTimeline(me.id, other.id, ps.limit, ps.sinceId, ps.untilId);
const messages = await this.chatService.userTimeline(me.id, other.id, ps.limit, sinceId, untilId);
this.chatService.readUserChatMessage(me.id, other.id);

View File

@@ -9,6 +9,7 @@ import { DI } from '@/di-symbols.js';
import { ChatService } from '@/core/ChatService.js';
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
import { ApiError } from '@/server/api/error.js';
import { IdService } from '@/core/IdService.js';
export const meta = {
tags: ['chat'],
@@ -37,6 +38,8 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
},
} as const;
@@ -45,11 +48,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
private chatEntityService: ChatEntityService,
private chatService: ChatService,
private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
await this.chatService.checkChatAvailability(me.id, 'read');
const invitations = await this.chatService.getReceivedRoomInvitationsWithPagination(me.id, ps.limit, ps.sinceId, ps.untilId);
const invitations = await this.chatService.getReceivedRoomInvitationsWithPagination(me.id, ps.limit, sinceId, untilId);
return this.chatEntityService.packRoomInvitations(invitations, me);
});
}

View File

@@ -10,6 +10,7 @@ import { DI } from '@/di-symbols.js';
import { ApiError } from '@/server/api/error.js';
import { ChatService } from '@/core/ChatService.js';
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
import { IdService } from '@/core/IdService.js';
export const meta = {
tags: ['chat'],
@@ -44,6 +45,8 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
},
required: ['roomId'],
} as const;
@@ -53,8 +56,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
private chatService: ChatService,
private chatEntityService: ChatEntityService,
private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
await this.chatService.checkChatAvailability(me.id, 'read');
const room = await this.chatService.findMyRoomById(me.id, ps.roomId);
@@ -62,7 +69,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.noSuchRoom);
}
const invitations = await this.chatService.getSentRoomInvitationsWithPagination(ps.roomId, ps.limit, ps.sinceId, ps.untilId);
const invitations = await this.chatService.getSentRoomInvitationsWithPagination(ps.roomId, ps.limit, sinceId, untilId);
return this.chatEntityService.packRoomInvitations(invitations, me);
});
}

View File

@@ -9,6 +9,7 @@ import { DI } from '@/di-symbols.js';
import { ChatService } from '@/core/ChatService.js';
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
import { ApiError } from '@/server/api/error.js';
import { IdService } from '@/core/IdService.js';
export const meta = {
tags: ['chat'],
@@ -37,6 +38,8 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
},
} as const;
@@ -45,11 +48,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
private chatService: ChatService,
private chatEntityService: ChatEntityService,
private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
await this.chatService.checkChatAvailability(me.id, 'read');
const memberships = await this.chatService.getMyMemberships(me.id, ps.limit, ps.sinceId, ps.untilId);
const memberships = await this.chatService.getMyMemberships(me.id, ps.limit, sinceId, untilId);
return this.chatEntityService.packRoomMemberships(memberships, me, {
populateUser: false,

View File

@@ -9,6 +9,7 @@ import { DI } from '@/di-symbols.js';
import { ChatService } from '@/core/ChatService.js';
import { ApiError } from '@/server/api/error.js';
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
import { IdService } from '@/core/IdService.js';
export const meta = {
tags: ['chat'],
@@ -43,6 +44,8 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
},
required: ['roomId'],
} as const;
@@ -52,8 +55,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
private chatService: ChatService,
private chatEntityService: ChatEntityService,
private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
await this.chatService.checkChatAvailability(me.id, 'read');
const room = await this.chatService.findRoomById(ps.roomId);
@@ -65,7 +72,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.noSuchRoom);
}
const memberships = await this.chatService.getRoomMembershipsWithPagination(room.id, ps.limit, ps.sinceId, ps.untilId);
const memberships = await this.chatService.getRoomMembershipsWithPagination(room.id, ps.limit, sinceId, untilId);
return this.chatEntityService.packRoomMemberships(memberships, me, {
populateUser: true,

View File

@@ -9,6 +9,7 @@ import { DI } from '@/di-symbols.js';
import { ChatService } from '@/core/ChatService.js';
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
import { ApiError } from '@/server/api/error.js';
import { IdService } from '@/core/IdService.js';
export const meta = {
tags: ['chat'],
@@ -37,6 +38,8 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
},
} as const;
@@ -45,11 +48,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
private chatEntityService: ChatEntityService,
private chatService: ChatService,
private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
await this.chatService.checkChatAvailability(me.id, 'read');
const rooms = await this.chatService.getOwnedRoomsWithPagination(me.id, ps.limit, ps.sinceId, ps.untilId);
const rooms = await this.chatService.getOwnedRoomsWithPagination(me.id, ps.limit, sinceId, untilId);
return this.chatEntityService.packRooms(rooms, me);
});
}

View File

@@ -4,11 +4,13 @@
*/
import { Inject, Injectable } from '@nestjs/common';
import { Brackets } from 'typeorm';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { NotesRepository, ClipsRepository, ClipNotesRepository } from '@/models/_.js';
import { QueryService } from '@/core/QueryService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { DI } from '@/di-symbols.js';
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -44,6 +46,9 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
search: { type: 'string', minLength: 1, maxLength: 100, nullable: true },
},
required: ['clipId'],
} as const;
@@ -76,7 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.noSuchClip);
}
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.innerJoin(this.clipNotesRepository.metadata.targetName, 'clipNote', 'clipNote.noteId = note.id')
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.reply', 'reply')
@@ -95,6 +100,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
this.queryService.generateBlockedUserQueryForNotes(query, me, { noteColumn: 'renote' });
}
if (ps.search != null) {
for (const word of ps.search.trim().split(' ')) {
query.andWhere(new Brackets(qb => {
qb.orWhere('note.text ILIKE :search', { search: `%${sqlLikeEscape(word)}%` });
qb.orWhere('note.cw ILIKE :search', { search: `%${sqlLikeEscape(word)}%` });
}));
}
}
const notes = await query
.limit(ps.limit)
.getMany();

View File

@@ -34,6 +34,8 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
type: { type: 'string', nullable: true, pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1) },
sort: { type: 'string', nullable: true, enum: ['+createdAt', '-createdAt', '+name', '-name', '+size', '-size', null] },
@@ -51,7 +53,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private queryService: QueryService,
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId)
const query = this.queryService.makePaginationQuery(this.driveFilesRepository.createQueryBuilder('file'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('file.userId = :userId', { userId: me.id });
if (ps.folderId) {

View File

@@ -0,0 +1,85 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { DriveFilesRepository, ChatMessagesRepository } from '@/models/_.js';
import { QueryService } from '@/core/QueryService.js';
import { DI } from '@/di-symbols.js';
import { RoleService } from '@/core/RoleService.js';
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
import { ApiError } from '../../../error.js';
export const meta = {
tags: ['drive', 'chat'],
requireCredential: true,
kind: 'read:drive',
res: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'ChatMessage',
},
},
errors: {
noSuchFile: {
message: 'No such file.',
code: 'NO_SUCH_FILE',
id: '485ce26d-f5d2-4313-9783-e689d131eafb',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
fileId: { type: 'string', format: 'misskey:id' },
},
required: ['fileId'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
@Inject(DI.chatMessagesRepository)
private chatMessagesRepository: ChatMessagesRepository,
private chatEntityService: ChatEntityService,
private queryService: QueryService,
private roleService: RoleService,
) {
super(meta, paramDef, async (ps, me) => {
const file = await this.driveFilesRepository.findOneBy({
id: ps.fileId,
userId: await this.roleService.isModerator(me) ? undefined : me.id,
});
if (file == null) {
throw new ApiError(meta.errors.noSuchFile);
}
const query = this.queryService.makePaginationQuery(this.chatMessagesRepository.createQueryBuilder('message'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate);
query.andWhere('message.fileId = :fileId', { fileId: file.id });
const messages = await query.limit(ps.limit).getMany();
return await this.chatEntityService.packMessagesDetailed(messages, me);
});
}
}

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