Compare commits
102 Commits
2023.9.0-r
...
2023.9.2
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7adc8fcaf5 | ||
![]() |
5edc885c22 | ||
![]() |
e5c339b86a | ||
![]() |
d92e2b6ae0 | ||
![]() |
eb38f08e13 | ||
![]() |
f269841a83 | ||
![]() |
b55ffa2cbe | ||
![]() |
0b0e58d405 | ||
![]() |
b349d0baf8 | ||
![]() |
961f5a0caa | ||
![]() |
ac19b055c7 | ||
![]() |
eb23fd4e60 | ||
![]() |
fbab67df35 | ||
![]() |
2529830bca | ||
![]() |
c01731f091 | ||
![]() |
9771f1c435 | ||
![]() |
424bb78387 | ||
![]() |
9c448055a3 | ||
![]() |
b9da1415a5 | ||
![]() |
4216a67462 | ||
![]() |
7ce86a6196 | ||
![]() |
2438c047a7 | ||
![]() |
c106db89e1 | ||
![]() |
a388e25f3e | ||
![]() |
63c6a9bb80 | ||
![]() |
772d2432b6 | ||
![]() |
eb740e2c72 | ||
![]() |
d854942a1f | ||
![]() |
ce1218a2b2 | ||
![]() |
d860e53b67 | ||
![]() |
055464a624 | ||
![]() |
9d0c077311 | ||
![]() |
440f3144ae | ||
![]() |
5ad0906c89 | ||
![]() |
e57b536767 | ||
![]() |
2039e244c5 | ||
![]() |
bd19d75c9c | ||
![]() |
ee44f35fea | ||
![]() |
89edf8f81e | ||
![]() |
ece5469277 | ||
![]() |
576158e883 | ||
![]() |
dcaea66dbf | ||
![]() |
5318532a8d | ||
![]() |
646a8d1a54 | ||
![]() |
dc8ab01168 | ||
![]() |
f32915b515 | ||
![]() |
281369d8c5 | ||
![]() |
65aef45050 | ||
![]() |
48314a39e0 | ||
![]() |
fe570fe16b | ||
![]() |
cf573add27 | ||
![]() |
4a7f6e6de4 | ||
![]() |
00659220a5 | ||
![]() |
51546ad1ce | ||
![]() |
80d52f65eb | ||
![]() |
841e6ff901 | ||
![]() |
82a51d49a0 | ||
![]() |
30b231225c | ||
![]() |
d05563c448 | ||
![]() |
03c868b727 | ||
![]() |
8d2fb99662 | ||
![]() |
20689638db | ||
![]() |
2b561d2648 | ||
![]() |
509cea511c | ||
![]() |
72075314a8 | ||
![]() |
7a3ddc869e | ||
![]() |
eb7c65ccb3 | ||
![]() |
8e5a90589d | ||
![]() |
ed983a5baf | ||
![]() |
2ad3b1fd74 | ||
![]() |
ed53b5f9bc | ||
![]() |
19bc9c20a6 | ||
![]() |
fdf149cf52 | ||
![]() |
76c4fedb7f | ||
![]() |
c3ccec723f | ||
![]() |
a8d45d4b0d | ||
![]() |
4e24aff408 | ||
![]() |
e64a81aa1d | ||
![]() |
7093662ce5 | ||
![]() |
32c741154d | ||
![]() |
407a965c1d | ||
![]() |
de6348e8a0 | ||
![]() |
9ad57324db | ||
![]() |
94690c835e | ||
![]() |
c5d2dba28d | ||
![]() |
272e0c874f | ||
![]() |
d429f810a9 | ||
![]() |
75b28d6782 | ||
![]() |
8b1362ab03 | ||
![]() |
a096f621cf | ||
![]() |
f54a9542bb | ||
![]() |
a52bbc7c8d | ||
![]() |
59768bdf3f | ||
![]() |
1e67e9c661 | ||
![]() |
ae517a99a7 | ||
![]() |
b23a9b1a88 | ||
![]() |
5bd68aa3e0 | ||
![]() |
647ce174b3 | ||
![]() |
02c8fd9de5 | ||
![]() |
1ba49b614d | ||
![]() |
40de14415c | ||
![]() |
7c9330a02f |
@@ -30,7 +30,7 @@ url: https://example.tld/
|
||||
# The port that your Misskey server should listen on.
|
||||
port: 3000
|
||||
|
||||
# You can also use UNIX domain socket.
|
||||
# You can also use UNIX domain socket.
|
||||
# socket: /path/to/misskey.sock
|
||||
# chmodSocket: '777'
|
||||
|
||||
@@ -60,17 +60,17 @@ dbReplications: false
|
||||
# You can configure any number of replicas here
|
||||
#dbSlaves:
|
||||
# -
|
||||
# host:
|
||||
# port:
|
||||
# db:
|
||||
# user:
|
||||
# pass:
|
||||
# host:
|
||||
# port:
|
||||
# db:
|
||||
# user:
|
||||
# pass:
|
||||
# -
|
||||
# host:
|
||||
# port:
|
||||
# db:
|
||||
# user:
|
||||
# pass:
|
||||
# host:
|
||||
# port:
|
||||
# db:
|
||||
# user:
|
||||
# pass:
|
||||
|
||||
# ┌─────────────────────┐
|
||||
#───┘ Redis configuration └─────────────────────────────────────
|
||||
@@ -206,3 +206,6 @@ signToActivityPubGet: true
|
||||
|
||||
# Upload or download file size limits (bytes)
|
||||
#maxFileSize: 262144000
|
||||
|
||||
# PID File of master process
|
||||
#pidFile: /tmp/misskey.pid
|
||||
|
2
.github/workflows/api-misskey-js.yml
vendored
2
.github/workflows/api-misskey-js.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.0.0
|
||||
uses: actions/checkout@v4.1.0
|
||||
|
||||
- run: corepack enable
|
||||
|
||||
|
2
.github/workflows/check_copyright_year.yml
vendored
2
.github/workflows/check_copyright_year.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
check_copyright_year:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.0.0
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- run: |
|
||||
if [ "$(grep Copyright COPYING | sed -e 's/.*2014-\([0-9]*\) .*/\1/g')" -ne "$(date +%Y)" ]; then
|
||||
echo "Please change copyright year!"
|
||||
|
2
.github/workflows/docker-develop.yml
vendored
2
.github/workflows/docker-develop.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
if: github.repository == 'misskey-dev/misskey'
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v4.0.0
|
||||
uses: actions/checkout@v4.1.0
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
|
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v4.0.0
|
||||
uses: actions/checkout@v4.1.0
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
|
2
.github/workflows/dockle.yml
vendored
2
.github/workflows/dockle.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
env:
|
||||
DOCKER_CONTENT_TRUST: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4.0.0
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- run: |
|
||||
curl -L -o dockle.deb "https://github.com/goodwithtech/dockle/releases/download/v0.4.10/dockle_0.4.10_Linux-64bit.deb"
|
||||
sudo dpkg -i dockle.deb
|
||||
|
6
.github/workflows/lint.yml
vendored
6
.github/workflows/lint.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
pnpm_install:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.0.0
|
||||
- uses: actions/checkout@v4.1.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
- sw
|
||||
- misskey-js
|
||||
steps:
|
||||
- uses: actions/checkout@v4.0.0
|
||||
- uses: actions/checkout@v4.1.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
- backend
|
||||
- misskey-js
|
||||
steps:
|
||||
- uses: actions/checkout@v4.0.0
|
||||
- uses: actions/checkout@v4.1.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
2
.github/workflows/pr-preview-deploy.yml
vendored
2
.github/workflows/pr-preview-deploy.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
# Check out merge commit
|
||||
- name: Fork based /deploy checkout
|
||||
uses: actions/checkout@v4.0.0
|
||||
uses: actions/checkout@v4.1.0
|
||||
with:
|
||||
ref: 'refs/pull/${{ github.event.client_payload.pull_request.number }}/merge'
|
||||
|
||||
|
2
.github/workflows/test-backend.yml
vendored
2
.github/workflows/test-backend.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
- 56312:6379
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.0.0
|
||||
- uses: actions/checkout@v4.1.0
|
||||
with:
|
||||
submodules: true
|
||||
- name: Install pnpm
|
||||
|
4
.github/workflows/test-frontend.yml
vendored
4
.github/workflows/test-frontend.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
node-version: [20.5.1]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.0.0
|
||||
- uses: actions/checkout@v4.1.0
|
||||
with:
|
||||
submodules: true
|
||||
- name: Install pnpm
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
- 56312:6379
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.0.0
|
||||
- uses: actions/checkout@v4.1.0
|
||||
with:
|
||||
submodules: true
|
||||
# https://github.com/cypress-io/cypress-docker-images/issues/150
|
||||
|
2
.github/workflows/test-misskey-js.yml
vendored
2
.github/workflows/test-misskey-js.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.0.0
|
||||
uses: actions/checkout@v4.1.0
|
||||
|
||||
- run: corepack enable
|
||||
|
||||
|
2
.github/workflows/test-production.yml
vendored
2
.github/workflows/test-production.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
node-version: [20.5.1]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.0.0
|
||||
- uses: actions/checkout@v4.1.0
|
||||
with:
|
||||
submodules: true
|
||||
- name: Install pnpm
|
||||
|
46
CHANGELOG.md
46
CHANGELOG.md
@@ -12,7 +12,44 @@
|
||||
|
||||
-->
|
||||
|
||||
## 2023.9.0 (unreleased)
|
||||
## 2023.9.2
|
||||
|
||||
### General
|
||||
- Feat: ノートの編集をできるように
|
||||
- ロールで編集可否を設定可能
|
||||
- Feat: 通知を種類ごとに 全員から受け取る/フォロー中のユーザーのみ受け取る/フォロワーのみ受け取る/相互のみ受け取る/指定したリストのメンバーのみ受け取る/受け取らない から選べるように
|
||||
- Enhance: タイムラインからRenoteを除外するオプションを追加
|
||||
- Enhance: ユーザーページのノート一覧でRenoteを除外できるように
|
||||
- Enhance: タイムラインでファイルが添付されたノートのみ表示するオプションを追加
|
||||
- Enhance: モデレーションログ機能の強化
|
||||
- Enhance: 依存関係の更新
|
||||
- Enhance: ローカリゼーションの更新
|
||||
|
||||
### Client
|
||||
- Enhance: Plugin:register_post_form_actionを用いてCWを取得・変更できるように
|
||||
- Enhance: admin/ad/listにて掲載中の広告が絞り込めるように
|
||||
- Enhance: AiScriptにリモートサーバーのAPIを叩く用の関数を追加(`Mk:apiExternal`)
|
||||
|
||||
### Server
|
||||
- Enhance: MasterプロセスのPIDを書き出せるように
|
||||
- Enhance: admin/ad/createにてレスポンス200、設定した広告情報を返すように
|
||||
|
||||
## 2023.9.1
|
||||
|
||||
### General
|
||||
- Enhance: モデレーションログ機能の強化
|
||||
|
||||
### Client
|
||||
- Fix: ノートのメニューにある「詳細」ボタンの表示がログイン/ログアウト状態で統一されていない問題を修正
|
||||
|
||||
### Server
|
||||
- Fix: お知らせのページネーションが機能しない
|
||||
- Fix: 「ユーザーの新規投稿」の通知設定を切り替えるとサーバー内部エラーが出る
|
||||
|
||||
## 2023.9.0
|
||||
|
||||
### Note
|
||||
- meilisearchを使用する場合、v1.2以上が必要です
|
||||
|
||||
### General
|
||||
- Feat: OAuth 2.0のサポート
|
||||
@@ -80,6 +117,10 @@
|
||||
- Fix: 他のサーバーのユーザーへ「メッセージを送信」した時の初期テキストのメンションが間違っている問題を修正
|
||||
- Fix: 環境によってはMisskey Webが開けない問題を修正
|
||||
- Fix: プラグインの権限リストが見れない問題を修正
|
||||
- Fix: 複数の階層があるメニューで、短くタップすると正常に動かない場合がある問題を修正
|
||||
- Fix: アニメーションがオフのとき、スマホで子メニューの選択ができない問題を修正
|
||||
- Fix: ドロワーメニューで、親メニュー項目をマウスでホバーすると子メニューが表示されてしまう問題を修正
|
||||
- Fix: AiScriptでMk:apiが外部と通信できる問題を修正
|
||||
|
||||
### Server
|
||||
- Change: cacheRemoteFilesの初期値はfalseになりました
|
||||
@@ -90,6 +131,8 @@
|
||||
- Enhance: nodeinfo 2.1対応
|
||||
- Enhance: 自分へのメンション一覧を取得する際のパフォーマンスを向上
|
||||
- Enhance: Docker環境でjemallocを使用することでメモリ使用量を削減
|
||||
- Enhance: ID生成方式としてaidxを追加、かつデフォルトに
|
||||
- Enhance: Add address bind config option (outgoingAddress)
|
||||
- Fix: MK_ONLY_SERVERオプションを指定した際にクラッシュする問題を修正
|
||||
- Fix: notes/reactionsのページネーションが機能しない問題を修正
|
||||
- Fix: ノート検索 `notes/search` にてhostを指定した際に検索結果に反映されるように
|
||||
@@ -112,7 +155,6 @@
|
||||
### Server
|
||||
- Fix: APIのオフセットが壊れていたせいで「もっと見る」でもっと見れない問題を修正
|
||||
- Fix: 外部サーバーの投稿がタイムラインに表示されないことがある問題を修正
|
||||
- Enhance: Add address bind config option (outgoingAddress)
|
||||
|
||||
## 13.14.1
|
||||
|
||||
|
@@ -1556,3 +1556,6 @@ _webhookSettings:
|
||||
active: "مُفعّل"
|
||||
_events:
|
||||
reaction: "عند التفاعل"
|
||||
_moderationLogTypes:
|
||||
suspend: "علِق"
|
||||
resetPassword: "أعد تعيين كلمتك السرية"
|
||||
|
@@ -1333,3 +1333,6 @@ _deck:
|
||||
_webhookSettings:
|
||||
name: "নাম"
|
||||
active: "চালু"
|
||||
_moderationLogTypes:
|
||||
suspend: "স্থগিত করা"
|
||||
resetPassword: "পাসওয়ার্ড রিসেট করুন"
|
||||
|
@@ -479,3 +479,6 @@ _deck:
|
||||
list: "Llistes"
|
||||
mentions: "Mencions"
|
||||
direct: "Publicacions directes"
|
||||
_moderationLogTypes:
|
||||
suspend: "Suspèn"
|
||||
resetPassword: "Restableix la contrasenya"
|
||||
|
@@ -2036,3 +2036,7 @@ _webhookSettings:
|
||||
renote: "Při renotaci poznámky"
|
||||
reaction: "Při obdržení reakce"
|
||||
mention: "Při zmínce"
|
||||
_moderationLogTypes:
|
||||
suspend: "Zmrazit"
|
||||
resetPassword: "Resetovat heslo"
|
||||
createInvitation: "Vygenerovat pozvánku"
|
||||
|
@@ -2,7 +2,7 @@
|
||||
_lang_: "Deutsch"
|
||||
headlineMisskey: "Ein durch Notizen verbundenes Netzwerk"
|
||||
introMisskey: "Willkommen! Misskey ist eine dezentralisierte Open-Source Microblogging-Platform.\nVerfasse „Notizen“ um mitzuteilen, was gerade passiert oder um Ereignisse mit anderen zu teilen. 📡\nMit „Reaktionen“ kannst du außerdem schnell deine Gefühle über Notizen anderer Benutzer zum Ausdruck bringen. 👍\nEine neue Welt wartet auf dich! 🚀"
|
||||
poweredByMisskeyDescription: "{name} ist einer der durch die Open-Source-Plattform <b>Misskey</b> betriebenen Dienste (meist als \"Misskey-Instanz\" bezeichnet)."
|
||||
poweredByMisskeyDescription: "{name} ist einer der durch die Open-Source-Plattform <b>Misskey</b> betriebenen Dienste."
|
||||
monthAndDay: "{day}.{month}."
|
||||
search: "Suchen"
|
||||
notifications: "Benachrichtigungen"
|
||||
@@ -75,7 +75,7 @@ import: "Import"
|
||||
export: "Export"
|
||||
files: "Dateien"
|
||||
download: "Herunterladen"
|
||||
driveFileDeleteConfirm: "Möchtest du die Datei „{name}“ wirklich löschen? Sie wird in allen Inhalten, die sie verwenden, auch verschwinden."
|
||||
driveFileDeleteConfirm: "Möchtest du die Datei „{name}“ wirklich löschen? Einige Inhalte, die diese Datei verwenden, werden auch verschwinden."
|
||||
unfollowConfirm: "Möchtest du {name} wirklich nicht mehr folgen?"
|
||||
exportRequested: "Du hast einen Export angefragt. Dies kann etwas Zeit in Anspruch nehmen. Sobald der Export abgeschlossen ist, wird er deiner Drive hinzugefügt."
|
||||
importRequested: "Du hast einen Import angefragt. Dies kann etwas Zeit in Anspruch nehmen."
|
||||
@@ -418,6 +418,7 @@ moderator: "Moderator"
|
||||
moderation: "Moderation"
|
||||
moderationNote: "Moderationsnotiz"
|
||||
addModerationNote: "Moderationsnotiz hinzufügen"
|
||||
moderationLogs: "Moderationsprotokolle"
|
||||
nUsersMentioned: "Von {n} Benutzern erwähnt"
|
||||
securityKeyAndPasskey: "Hardware-Sicherheitsschlüssel und Passkeys"
|
||||
securityKey: "Hardware-Sicherheitsschlüssel"
|
||||
@@ -639,7 +640,7 @@ display: "Anzeigeart"
|
||||
copy: "Kopieren"
|
||||
metrics: "Metriken"
|
||||
overview: "Übersicht"
|
||||
logs: "Logs"
|
||||
logs: "Protokolle"
|
||||
delayed: "Verzögert"
|
||||
database: "Datenbank"
|
||||
channel: "Kanäle"
|
||||
@@ -1049,7 +1050,7 @@ vertical: "Vertikal"
|
||||
horizontal: "Horizontal"
|
||||
position: "Position"
|
||||
serverRules: "Serverregeln"
|
||||
pleaseConfirmBelowBeforeSignup: "Lies bitte Untenstehendes vor der Registration."
|
||||
pleaseConfirmBelowBeforeSignup: "Lies bitte diese Informationen und stimme ihnen vor der Registration zu."
|
||||
pleaseAgreeAllToContinue: "Zum Fortfahren muss allen obigen Feldern zugestimmt werden."
|
||||
continue: "Fortfahren"
|
||||
preservedUsernames: "Reservierte Benutzernamen"
|
||||
@@ -1119,6 +1120,9 @@ notifyNotes: "Über neue Notizen benachrichtigen"
|
||||
unnotifyNotes: "Nicht über neue Notizen benachrichtigen"
|
||||
authentication: "Authentifikation"
|
||||
authenticationRequiredToContinue: "Bitte authentifiziere dich, um fortzufahren"
|
||||
dateAndTime: "Zeit"
|
||||
showRenotes: "Renotes anzeigen"
|
||||
edited: "Bearbeitet"
|
||||
_announcement:
|
||||
forExistingUsers: "Nur für existierende Nutzer"
|
||||
forExistingUsersDescription: "Ist diese Option aktiviert, wird diese Ankündigung nur Nutzern angezeigt, die zum Zeitpunkt der Ankündigung bereits registriert sind. Ist sie deaktiviert, wird sie auch Nutzern, die sich nach dessen Veröffentlichung registrieren, angezeigt."
|
||||
@@ -1449,6 +1453,7 @@ _role:
|
||||
gtlAvailable: "Kann auf die globale Chronik zugreifen"
|
||||
ltlAvailable: "Kann auf die lokale Chronik zugreifen"
|
||||
canPublicNote: "Kann öffentliche Notizen erstellen"
|
||||
canEditNote: "Notizbearbeitung"
|
||||
canInvite: "Erstellung von Einladungscodes für diese Instanz"
|
||||
inviteLimit: "Maximalanzahl an Einladungen"
|
||||
inviteLimitCycle: "Zyklus des Einladungslimits"
|
||||
@@ -1727,7 +1732,7 @@ _2fa:
|
||||
step2Click: "Durch Klicken dieses QR-Codes kannst du Verifikation mit deinem Security-Token oder einer App registrieren."
|
||||
step2Uri: "Nutzt du ein Desktopprogramm, gib folgende URI eingeben"
|
||||
step3Title: "Authentifizierungsscode eingeben"
|
||||
step3: "Gib zum Abschluss den Token ein, der von deiner App angezeigt wird."
|
||||
step3: "Gib zum Abschluss den Code (Token) ein, der von deiner App angezeigt wird."
|
||||
setupCompleted: "Einrichtung abgeschlossen"
|
||||
step4: "Alle folgenden Anmeldeversuche werden ab sofort die Eingabe eines solchen Tokens benötigen."
|
||||
securityKeyNotSupported: "Dein Browser unterstützt keine Hardware-Sicherheitsschlüssel."
|
||||
@@ -2099,3 +2104,31 @@ _webhookSettings:
|
||||
renote: "Wenn du ein Renote erhältst"
|
||||
reaction: "Wenn du eine Reaktion erhältst"
|
||||
mention: "Wenn du erwähnt wirst"
|
||||
_moderationLogTypes:
|
||||
createRole: "Rolle erstellt"
|
||||
deleteRole: "Rolle gelöscht"
|
||||
updateRole: "Rolle aktualisiert"
|
||||
assignRole: "Zu Rolle zugewiesen"
|
||||
unassignRole: "Aus Rolle entfernt"
|
||||
suspend: "Gesperrt"
|
||||
unsuspend: "Entsperrt"
|
||||
addCustomEmoji: "Benutzerdefiniertes Emoji hinzugefügt"
|
||||
updateCustomEmoji: "Benutzerdefiniertes Emoji aktualisiert"
|
||||
deleteCustomEmoji: "Benutzerdefiniertes Emoji gelöscht"
|
||||
updateServerSettings: "Servereinstellungen aktualisiert"
|
||||
updateUserNote: "Moderationsnotiz aktualisiert"
|
||||
deleteDriveFile: "Datei gelöscht"
|
||||
deleteNote: "Notiz gelöscht"
|
||||
createGlobalAnnouncement: "Globale Ankündigung erstellt"
|
||||
createUserAnnouncement: "Benutzerspezifische Ankündigung erstellt"
|
||||
updateGlobalAnnouncement: "Globale Ankündigung aktualisiert"
|
||||
updateUserAnnouncement: "Benutzerspezifische Ankündigung aktualisiert"
|
||||
deleteGlobalAnnouncement: "Globale Ankündigung gelöscht"
|
||||
deleteUserAnnouncement: "Benutzerspezifische Ankündigung gelöscht"
|
||||
resetPassword: "Passwort zurückgesetzt"
|
||||
suspendRemoteInstance: "Fremde Instanz gesperrt"
|
||||
unsuspendRemoteInstance: "Fremde Instanz entsperrt"
|
||||
markSensitiveDriveFile: "Datei als sensitiv markiert"
|
||||
unmarkSensitiveDriveFile: "Datei als nicht sensitiv markiert"
|
||||
resolveAbuseReport: "Meldung bearbeitet"
|
||||
createInvitation: "Einladung erstellt"
|
||||
|
@@ -397,3 +397,5 @@ _deck:
|
||||
mentions: "Επισημάνσεις"
|
||||
_webhookSettings:
|
||||
name: "Όνομα"
|
||||
_moderationLogTypes:
|
||||
suspend: "Αποβολή"
|
||||
|
@@ -418,6 +418,7 @@ moderator: "Moderator"
|
||||
moderation: "Moderation"
|
||||
moderationNote: "Moderation note"
|
||||
addModerationNote: "Add moderation note"
|
||||
moderationLogs: "Moderation logs"
|
||||
nUsersMentioned: "Mentioned by {n} users"
|
||||
securityKeyAndPasskey: "Security- and passkeys"
|
||||
securityKey: "Security key"
|
||||
@@ -1119,6 +1120,9 @@ notifyNotes: "Notify about new notes"
|
||||
unnotifyNotes: "Stop notifying about new notes"
|
||||
authentication: "Authentication"
|
||||
authenticationRequiredToContinue: "Please authenticate to continue"
|
||||
dateAndTime: "Timestamp"
|
||||
showRenotes: "Show renotes"
|
||||
edited: "Edited"
|
||||
_announcement:
|
||||
forExistingUsers: "Existing users only"
|
||||
forExistingUsersDescription: "This announcement will only be shown to users existing at the point of publishment if enabled. If disabled, those newly signing up after it has been posted will also see it."
|
||||
@@ -1449,6 +1453,7 @@ _role:
|
||||
gtlAvailable: "Can view the global timeline"
|
||||
ltlAvailable: "Can view the local timeline"
|
||||
canPublicNote: "Can send public notes"
|
||||
canEditNote: "Note editing"
|
||||
canInvite: "Can create instance invite codes"
|
||||
inviteLimit: "Invite limit"
|
||||
inviteLimitCycle: "Invite limit cooldown"
|
||||
@@ -2099,3 +2104,31 @@ _webhookSettings:
|
||||
renote: "When renoted"
|
||||
reaction: "When receiving a reaction"
|
||||
mention: "When being mentioned"
|
||||
_moderationLogTypes:
|
||||
createRole: "Role created"
|
||||
deleteRole: "Role deleted"
|
||||
updateRole: "Role updated"
|
||||
assignRole: "Assigned to role"
|
||||
unassignRole: "Removed from role"
|
||||
suspend: "Suspended"
|
||||
unsuspend: "Unsuspended"
|
||||
addCustomEmoji: "Custom emoji added"
|
||||
updateCustomEmoji: "Custom emoji updated"
|
||||
deleteCustomEmoji: "Custom emoji deleted"
|
||||
updateServerSettings: "Server settings updated"
|
||||
updateUserNote: "Moderation note updated"
|
||||
deleteDriveFile: "File deleted"
|
||||
deleteNote: "Note deleted"
|
||||
createGlobalAnnouncement: "Global announcement created"
|
||||
createUserAnnouncement: "User announcement created"
|
||||
updateGlobalAnnouncement: "Global announcement updated"
|
||||
updateUserAnnouncement: "User announcement updated"
|
||||
deleteGlobalAnnouncement: "Global announcement deleted"
|
||||
deleteUserAnnouncement: "User announcement deleted"
|
||||
resetPassword: "Password reset"
|
||||
suspendRemoteInstance: "Remote instance suspended"
|
||||
unsuspendRemoteInstance: "Remote instance unsuspended"
|
||||
markSensitiveDriveFile: "File marked as sensitive"
|
||||
unmarkSensitiveDriveFile: "File unmarked as sensitive"
|
||||
resolveAbuseReport: "Report resolved"
|
||||
createInvitation: "Invite generated"
|
||||
|
@@ -418,6 +418,7 @@ moderator: "Moderador"
|
||||
moderation: "Moderación"
|
||||
moderationNote: "Nota de moderación"
|
||||
addModerationNote: "Añadir nota de moderación"
|
||||
moderationLogs: "Log de moderación"
|
||||
nUsersMentioned: "{n} usuarios mencionados"
|
||||
securityKeyAndPasskey: "Clave de seguridad / clave de paso"
|
||||
securityKey: "Clave de seguridad"
|
||||
@@ -710,6 +711,7 @@ lockedAccountInfo: "A menos que configures la visibilidad de tus notas como \"S
|
||||
alwaysMarkSensitive: "Marcar los medios de comunicación como contenido sensible por defecto"
|
||||
loadRawImages: "Cargar las imágenes originales en lugar de mostrar las miniaturas"
|
||||
disableShowingAnimatedImages: "No reproducir imágenes animadas"
|
||||
highlightSensitiveMedia: "Resaltar medios marcados como sensibles"
|
||||
verificationEmailSent: "Se le ha enviado un correo electrónico de confirmación. Por favor, acceda al enlace proporcionado en el correo electrónico para completar la configuración."
|
||||
notSet: "Sin especificar"
|
||||
emailVerified: "Su dirección de correo electrónico ha sido verificada."
|
||||
@@ -1109,6 +1111,16 @@ youHaveUnreadAnnouncements: "Hay anuncios sin leer"
|
||||
useSecurityKey: "Por favor, sigue las instrucciones de tu dispositivo o navegador para usar tu clave de seguridad o tu clave de paso."
|
||||
replies: "Responder"
|
||||
renotes: "Renotar"
|
||||
loadReplies: "Ver respuestas"
|
||||
loadConversation: "Ver conversación"
|
||||
pinnedList: "Lista fijada"
|
||||
keepScreenOn: "Mantener pantalla encendida"
|
||||
verifiedLink: "Propiedad del enlace verificada"
|
||||
notifyNotes: "Notificar nuevas notas"
|
||||
unnotifyNotes: "Dejar de notificar nuevas notas"
|
||||
authentication: "Autenticación"
|
||||
authenticationRequiredToContinue: "Por favor, autentifícate para continuar"
|
||||
dateAndTime: "Fecha y hora"
|
||||
_announcement:
|
||||
forExistingUsers: "Solo para usuarios registrados"
|
||||
forExistingUsersDescription: "Este anuncio solo se mostrará a aquellos usuarios registrados en el momento de su publicación. Si se deshabilita esta opción, aquellos usuarios que se registren tras su publicación también lo verán."
|
||||
@@ -1137,7 +1149,13 @@ _serverRules:
|
||||
description: "Un conjunto de reglas que serán mostradas antes del registro. Configurar un sumario de términos de servicio es recomendado."
|
||||
_serverSettings:
|
||||
iconUrl: "URL del ícono"
|
||||
appIconDescription: "Indica el icono que se va a usar cuando {host} se muestre como una app."
|
||||
appIconUsageExample: "Por ejemplo, como PWA o cuando se muestre como un marcador en la pantalla inicial del dispositivo"
|
||||
appIconStyleRecommendation: "Como el icono puede ser recortado como un cuadrado o un círculo, se recomienda un icono con un margen coloreado alrededor del contenido."
|
||||
appIconResolutionMustBe: "La resolución mínima es {resolution}."
|
||||
manifestJsonOverride: "Sobreescribir manifest.json"
|
||||
shortName: "Nombre corto"
|
||||
shortNameDescription: "Forma corta del nombre de la instancia que puede mostrarse si el nombre completo es demasiado largo."
|
||||
_accountMigration:
|
||||
moveFrom: "Trasladar de otra cuenta a ésta"
|
||||
moveFromSub: "Crear un alias para otra cuenta."
|
||||
@@ -1784,6 +1802,7 @@ _antennaSources:
|
||||
homeTimeline: "Notas de los usuarios que sigues"
|
||||
users: "Notas de un usuario o varios"
|
||||
userList: "Notas de los usuarios de una lista"
|
||||
userBlacklist: "Todas las notas excepto aquellas de uno o más usuarios especificados"
|
||||
_weekday:
|
||||
sunday: "Domingo"
|
||||
monday: "Lunes"
|
||||
@@ -1883,6 +1902,7 @@ _profile:
|
||||
metadataContent: "Contenido"
|
||||
changeAvatar: "Cambiar avatar"
|
||||
changeBanner: "Cambiar banner"
|
||||
verifiedLinkDescription: "Introduciendo una URL que contiene un enlace a tu perfil, se puede mostrar un icono de verificación de propiedad al lado del campo."
|
||||
_exportOrImport:
|
||||
allNotes: "Todas las notas"
|
||||
favoritedNotes: "Notas favoritas"
|
||||
@@ -2001,6 +2021,7 @@ _notification:
|
||||
youReceivedFollowRequest: "Has mandado una solicitud de seguimiento"
|
||||
yourFollowRequestAccepted: "Tu solicitud de seguimiento fue aceptada"
|
||||
pollEnded: "Estan disponibles los resultados de la encuesta"
|
||||
newNote: "Nueva nota"
|
||||
unreadAntennaNote: "Antena {name}"
|
||||
emptyPushNotificationMessage: "Se han actualizado las notificaciones push"
|
||||
achievementEarned: "Logro desbloqueado"
|
||||
@@ -2010,6 +2031,7 @@ _notification:
|
||||
notificationWillBeDisplayedLikeThis: "Las notificaciones tendrán este aspecto"
|
||||
_types:
|
||||
all: "Todo"
|
||||
note: "Nuevas notas"
|
||||
follow: "Siguiendo"
|
||||
mention: "Menciones"
|
||||
reply: "Respuestas"
|
||||
@@ -2079,3 +2101,31 @@ _webhookSettings:
|
||||
renote: "Cuando reciba un \"re-note\""
|
||||
reaction: "Cuando se recibe una reacción"
|
||||
mention: "Cuando hay una mención"
|
||||
_moderationLogTypes:
|
||||
createRole: "Rol creado"
|
||||
deleteRole: "Rol eliminado"
|
||||
updateRole: "Rol actualizado"
|
||||
assignRole: "Rol asignado"
|
||||
unassignRole: "Rol retirado"
|
||||
suspend: "Suspender"
|
||||
unsuspend: "Suspensión retirada"
|
||||
addCustomEmoji: "Añadido emoji personalizado"
|
||||
updateCustomEmoji: "Emoji personalizado actualizado"
|
||||
deleteCustomEmoji: "Emoji personalizado eliminado"
|
||||
updateServerSettings: "Ajustes de servidor actualizados"
|
||||
updateUserNote: "Nota de moderación actualizada"
|
||||
deleteDriveFile: "Archivo eliminado"
|
||||
deleteNote: "Nota eliminada"
|
||||
createGlobalAnnouncement: "Anuncio global creado"
|
||||
createUserAnnouncement: "Anuncio de usuario creado"
|
||||
updateGlobalAnnouncement: "Anuncio global actualizado"
|
||||
updateUserAnnouncement: "Anuncio de usuario actualizado"
|
||||
deleteGlobalAnnouncement: "Anuncio global eliminado"
|
||||
deleteUserAnnouncement: "Anuncio de usuario eliminado"
|
||||
resetPassword: "Resetear contraseña"
|
||||
suspendRemoteInstance: "Instancia remota suspendida"
|
||||
unsuspendRemoteInstance: "Suspensión de instancia remota retirada"
|
||||
markSensitiveDriveFile: "Archivo marcado como sensible"
|
||||
unmarkSensitiveDriveFile: "Archivo marcado como no sensible"
|
||||
resolveAbuseReport: "Reporte resuelto"
|
||||
createInvitation: "Generar invitación"
|
||||
|
@@ -1691,3 +1691,6 @@ _deck:
|
||||
_webhookSettings:
|
||||
name: "Nom"
|
||||
active: "Activé"
|
||||
_moderationLogTypes:
|
||||
suspend: "Suspendre"
|
||||
resetPassword: "Réinitialiser le mot de passe"
|
||||
|
@@ -1100,6 +1100,7 @@ currentAnnouncements: "Pengumuman Saat Ini"
|
||||
pastAnnouncements: "Pengumuman Terdahulu"
|
||||
replies: "Balas"
|
||||
renotes: "Renote"
|
||||
dateAndTime: "Tanggal dan Waktu"
|
||||
_initialAccountSetting:
|
||||
accountCreated: "Akun kamu telah sukses dibuat!"
|
||||
letsStartAccountSetup: "Untuk pemula, ayo atur profilmu dulu."
|
||||
@@ -2041,3 +2042,7 @@ _webhookSettings:
|
||||
renote: "Ketika direnote"
|
||||
reaction: "Ketika menerima reaksi"
|
||||
mention: "Ketika sedang disebut"
|
||||
_moderationLogTypes:
|
||||
suspend: "Tangguhkan"
|
||||
resetPassword: "Atur ulang kata sandi"
|
||||
createInvitation: "Buat kode undangan"
|
||||
|
27
locales/index.d.ts
vendored
27
locales/index.d.ts
vendored
@@ -1123,6 +1123,12 @@ export interface Locale {
|
||||
"unnotifyNotes": string;
|
||||
"authentication": string;
|
||||
"authenticationRequiredToContinue": string;
|
||||
"dateAndTime": string;
|
||||
"showRenotes": string;
|
||||
"edited": string;
|
||||
"notificationRecieveConfig": string;
|
||||
"mutualFollow": string;
|
||||
"fileAttachedOnly": string;
|
||||
"_announcement": {
|
||||
"forExistingUsers": string;
|
||||
"forExistingUsersDescription": string;
|
||||
@@ -1536,6 +1542,7 @@ export interface Locale {
|
||||
"gtlAvailable": string;
|
||||
"ltlAvailable": string;
|
||||
"canPublicNote": string;
|
||||
"canEditNote": string;
|
||||
"canInvite": string;
|
||||
"inviteLimit": string;
|
||||
"inviteLimitCycle": string;
|
||||
@@ -2250,18 +2257,36 @@ export interface Locale {
|
||||
};
|
||||
};
|
||||
"_moderationLogTypes": {
|
||||
"createRole": string;
|
||||
"deleteRole": string;
|
||||
"updateRole": string;
|
||||
"assignRole": string;
|
||||
"unassignRole": string;
|
||||
"updateRole": string;
|
||||
"suspend": string;
|
||||
"unsuspend": string;
|
||||
"addCustomEmoji": string;
|
||||
"updateCustomEmoji": string;
|
||||
"deleteCustomEmoji": string;
|
||||
"updateServerSettings": string;
|
||||
"updateUserNote": string;
|
||||
"deleteDriveFile": string;
|
||||
"deleteNote": string;
|
||||
"createGlobalAnnouncement": string;
|
||||
"createUserAnnouncement": string;
|
||||
"updateGlobalAnnouncement": string;
|
||||
"updateUserAnnouncement": string;
|
||||
"deleteGlobalAnnouncement": string;
|
||||
"deleteUserAnnouncement": string;
|
||||
"resetPassword": string;
|
||||
"suspendRemoteInstance": string;
|
||||
"unsuspendRemoteInstance": string;
|
||||
"markSensitiveDriveFile": string;
|
||||
"unmarkSensitiveDriveFile": string;
|
||||
"resolveAbuseReport": string;
|
||||
"createInvitation": string;
|
||||
"createAd": string;
|
||||
"deleteAd": string;
|
||||
"updateAd": string;
|
||||
};
|
||||
}
|
||||
declare const locales: {
|
||||
|
@@ -117,7 +117,7 @@ pinnedNote: "Nota fissata"
|
||||
pinned: "Fissa sul profilo"
|
||||
you: "Tu"
|
||||
clickToShow: "Clicca per visualizzare"
|
||||
sensitive: "Contenuto sensibile"
|
||||
sensitive: "Esplicito"
|
||||
add: "Aggiungi"
|
||||
reaction: "Reazioni"
|
||||
reactions: "Reazioni"
|
||||
@@ -125,13 +125,13 @@ reactionSetting: "Reazioni visualizzate sul pannello"
|
||||
reactionSettingDescription2: "Trascina per riorganizzare, clicca per cancellare, usa il pulsante \"+\" per aggiungere."
|
||||
rememberNoteVisibility: "Ricordare le impostazioni di visibilità delle note"
|
||||
attachCancel: "Rimuovi allegato"
|
||||
markAsSensitive: "Segna come sensibile"
|
||||
unmarkAsSensitive: "Segna come non sensibile"
|
||||
markAsSensitive: "Segna come esplicito"
|
||||
unmarkAsSensitive: "Non segnare come esplicito "
|
||||
enterFileName: "Nome del file"
|
||||
mute: "Silenzia"
|
||||
unmute: "Riattiva l'audio"
|
||||
renoteMute: "Silenzia i Rinota"
|
||||
renoteUnmute: "Non silenziare i Rinota"
|
||||
renoteMute: "Silenzia le Rinota"
|
||||
renoteUnmute: "Non silenziare le Rinota"
|
||||
block: "Blocca"
|
||||
unblock: "Sblocca"
|
||||
suspend: "Sospensione"
|
||||
@@ -148,7 +148,7 @@ editAntenna: "Modifica Antenna"
|
||||
selectWidget: "Seleziona il riquadro"
|
||||
editWidgets: "Modifica i riquadri"
|
||||
editWidgetsExit: "Conferma le modifiche"
|
||||
customEmojis: "Emoji personalizzati"
|
||||
customEmojis: "Emoji personalizzate"
|
||||
emoji: "Emoji"
|
||||
emojis: "Emoji"
|
||||
emojiName: "Nome dell'emoji"
|
||||
@@ -158,8 +158,8 @@ settingGuide: "Configurazione suggerita"
|
||||
cacheRemoteFiles: "Memorizza i file remoti nella cache"
|
||||
cacheRemoteFilesDescription: "Disabilitando questa opzione, i file remoti verranno linkati direttamente senza essere memorizzati nella cache. Sarà possibile risparmiare spazio di archiviazione sul server, ma il traffico aumenterà in quanto non verranno generate anteprime."
|
||||
youCanCleanRemoteFilesCache: "Puoi svuotare tutta la cache cliccando il bottone 🗑️ nella gestione file"
|
||||
cacheRemoteSensitiveFiles: "Memorizza nella cache i file sensibili remoti"
|
||||
cacheRemoteSensitiveFilesDescription: "Disattivando questa opzione, i file sensibili verranno caricati direttamente dall'istanza remota senza essere salvati dal server."
|
||||
cacheRemoteSensitiveFiles: "Copia nella cache locale i file espliciti remoti"
|
||||
cacheRemoteSensitiveFilesDescription: "Disattivando questa opzione, i file espliciti verranno richiesti direttamente all'istanza remota senza essere salvati nel server locale."
|
||||
flagAsBot: "Io sono un robot"
|
||||
flagAsBotDescription: "Attiva questo campo se il profilo esegue principalmente operazioni automatiche. L'attivazione segnala agli altri sviluppatori come comportarsi per evitare catene d’interazione infinite con altri bot. I sistemi interni di Misskey si adegueranno al fine di trattare questo profilo come bot."
|
||||
flagAsCat: "Sono un gatto"
|
||||
@@ -321,7 +321,7 @@ copyUrl: "Copia URL"
|
||||
rename: "Modifica nome"
|
||||
avatar: "Foto del profilo"
|
||||
banner: "Intestazione"
|
||||
displayOfSensitiveMedia: "Visibilità dei media sensibili"
|
||||
displayOfSensitiveMedia: "Visibilità dei media espliciti"
|
||||
whenServerDisconnected: "Quando la connessione col server è persa"
|
||||
disconnectedFromServer: "Il server si è disconnesso"
|
||||
reload: "Ricarica"
|
||||
@@ -418,6 +418,7 @@ moderator: "Moderatore"
|
||||
moderation: "moderazione"
|
||||
moderationNote: "Promemoria di moderazione"
|
||||
addModerationNote: "Aggiungi promemoria di moderazione"
|
||||
moderationLogs: "Cronologia di moderazione"
|
||||
nUsersMentioned: "{n} profili menzionati"
|
||||
securityKeyAndPasskey: "Chiave di sicurezza e accesso"
|
||||
securityKey: "Chiave di sicurezza"
|
||||
@@ -707,9 +708,10 @@ driveUsage: "Utilizzazione del Drive"
|
||||
noCrawle: "Rifiuta l'indicizzazione dai robot."
|
||||
noCrawleDescription: "Richiedi che i motori di ricerca non indicizzino la tua pagina di profilo, le tue note, pagine, ecc."
|
||||
lockedAccountInfo: "A meno che non imposti la visibilità delle tue note su \"Solo ai follower\", le tue note sono visibili da tutti, anche se hai configurato l'account per confermare manualmente le richieste di follow."
|
||||
alwaysMarkSensitive: "Segnare i media come sensibili per impostazione predefinita"
|
||||
alwaysMarkSensitive: "Segnare gli allegati come espliciti come opzione predefinita"
|
||||
loadRawImages: "Visualizza le intere immagini allegate invece delle miniature."
|
||||
disableShowingAnimatedImages: "Disabilita le immagini animate"
|
||||
highlightSensitiveMedia: "Evidenzia i media espliciti"
|
||||
verificationEmailSent: "Una mail di verifica è stata inviata. Si prega di accedere al collegamento per compiere la verifica."
|
||||
notSet: "Non impostato"
|
||||
emailVerified: "Il tuo indirizzo email è stato verificato"
|
||||
@@ -926,7 +928,7 @@ type: "Tipo"
|
||||
speed: "Velocità"
|
||||
slow: "Lento"
|
||||
fast: "Veloce"
|
||||
sensitiveMediaDetection: "Rilevamento dei contenuti sensibili."
|
||||
sensitiveMediaDetection: "Rilevamento dei contenuti espliciti"
|
||||
localOnly: "Soltanto locale"
|
||||
remoteOnly: "Solo remoto"
|
||||
failedToUpload: "errore di caricamento"
|
||||
@@ -989,7 +991,7 @@ thisPostMayBeAnnoying: "Questa nota potrebbe essere offensiva"
|
||||
thisPostMayBeAnnoyingHome: "Pubblica sulla timeline principale"
|
||||
thisPostMayBeAnnoyingCancel: "Annulla"
|
||||
thisPostMayBeAnnoyingIgnore: "Pubblica lo stesso"
|
||||
collapseRenotes: "Comprimi i Rinota già letti"
|
||||
collapseRenotes: "Comprimi le Rinota già viste"
|
||||
internalServerError: "Errore interno del server"
|
||||
internalServerErrorDescription: "Si è verificato un errore imprevisto all'interno del server"
|
||||
copyErrorInfo: "Copia le informazioni sull'errore"
|
||||
@@ -1006,11 +1008,11 @@ cannotBeChangedLater: "Non sarà più modificabile"
|
||||
reactionAcceptance: "Reazioni consentite"
|
||||
likeOnly: "Solo i Like"
|
||||
likeOnlyForRemote: "Solo Like remoti"
|
||||
nonSensitiveOnly: "Solamente non sensibili"
|
||||
nonSensitiveOnlyForLocalLikeOnlyForRemote: "Solamente non sensibili (solo Mi piace remoti)"
|
||||
nonSensitiveOnly: "Soltanto non espliciti"
|
||||
nonSensitiveOnlyForLocalLikeOnlyForRemote: "Soltanto non espliciti (reazioni remote)"
|
||||
rolesAssignedToMe: "I miei ruoli"
|
||||
resetPasswordConfirm: "Vuoi davvero ripristinare la password?"
|
||||
sensitiveWords: "Parole sensibili"
|
||||
sensitiveWords: "Parole esplicite"
|
||||
sensitiveWordsDescription: "Imposta automaticamente \"Home\" alla visibilità delle Note che contengono una qualsiasi parola tra queste configurate. Puoi separarle per riga."
|
||||
sensitiveWordsDescription2: "Gli spazi creano la relazione \"E\" tra parole (questo E quello). Racchiudere una parola nelle slash \"/\" la trasforma in Espressione Regolare."
|
||||
notesSearchNotAvailable: "Non è possibile cercare tra le Note."
|
||||
@@ -1116,6 +1118,10 @@ keepScreenOn: "Mantieni lo schermo acceso"
|
||||
verifiedLink: "Abbiamo confermato la validità di questo collegamento"
|
||||
notifyNotes: "Notifica nuove Note"
|
||||
unnotifyNotes: "Interrompi le notifiche di nuove Note"
|
||||
authentication: "Autenticazione"
|
||||
authenticationRequiredToContinue: "Per procedere, è richiesta l'autenticazione"
|
||||
dateAndTime: "Data e Ora"
|
||||
showRenotes: "Leggi le Rinota"
|
||||
_announcement:
|
||||
forExistingUsers: "Solo ai profili attuali"
|
||||
forExistingUsersDescription: "L'annuncio sarà visibile solo ai profili esistenti in questo momento. Se disabilitato, sarà visibile anche ai profili che verranno creati dopo la pubblicazione di questo annuncio."
|
||||
@@ -1149,6 +1155,8 @@ _serverSettings:
|
||||
appIconStyleRecommendation: "Poiché l'icona potrebbe essere ritagliata in un quadrato o in un cerchio, si raccomanda che abbia un margine colorato."
|
||||
appIconResolutionMustBe: "La risoluzione minima è {resolution}"
|
||||
manifestJsonOverride: "Sostituire il file manifest.json"
|
||||
shortName: "Abbreviazione"
|
||||
shortNameDescription: "Un'abbreviazione o un nome comune che può essere visualizzato al posto del nome ufficiale lungo del server."
|
||||
_accountMigration:
|
||||
moveFrom: "Migra un altro profilo dentro a questo"
|
||||
moveFromSub: "Crea un alias verso un altro profilo remoto"
|
||||
@@ -1478,9 +1486,9 @@ _role:
|
||||
or: "O"
|
||||
not: "NON"
|
||||
_sensitiveMediaDetection:
|
||||
description: "L'apprendimento automatico può essere utilizzato per individuare automaticamente i media sensibili da moderare. Il carico del server aumenta leggermente."
|
||||
sensitivity: "Sensibilità di rilevamento"
|
||||
sensitivityDescription: "Una minore sensibilità riduce i falsi positivi (false positività). Una maggiore sensibilità riduce le omissioni (falsi negativi)."
|
||||
description: "Utilizzare l'apprendimento automatico (machine learning) per riconoscere media espliciti e sottoporli alla moderazione. Aumenterà lievemente il carico del server."
|
||||
sensitivity: "Sensibilità del rilevamento"
|
||||
sensitivityDescription: "Abbassando la sensibilità si riducono i falsi positivi (rilevazioni errate). Aumentando la sensibilità si riduce il numero di rilevazioni mancate. (rilevazioni ignorate)."
|
||||
setSensitiveFlagAutomatically: "Impostare il flag NSFW."
|
||||
setSensitiveFlagAutomaticallyDescription: "Anche se questa impostazione è disattivata, il risultato della decisione viene conservato internamente."
|
||||
analyzeVideos: "Abilitazione dell'analisi video."
|
||||
@@ -1564,8 +1572,8 @@ _aboutMisskey:
|
||||
morePatrons: "Apprezziamo sinceramente il supporto di tante altre persone. Grazie mille! 🥰"
|
||||
patrons: "Sostenitori"
|
||||
_displayOfSensitiveMedia:
|
||||
respect: "Nascondere i media sensibili"
|
||||
ignore: "Non nascondere i media sensibili"
|
||||
respect: "Nascondere i media espliciti"
|
||||
ignore: "Non nascondere i media espliciti"
|
||||
force: "Nascondi tutti i media"
|
||||
_instanceTicker:
|
||||
none: "Nascondi"
|
||||
@@ -1795,6 +1803,7 @@ _antennaSources:
|
||||
homeTimeline: "Note dagli utenti che segui"
|
||||
users: "Note dagli utenti selezionati"
|
||||
userList: "Note dagli utenti della lista selezionata"
|
||||
userBlacklist: "Tutte le Note tranne quelle di uno o più profili specificati"
|
||||
_weekday:
|
||||
sunday: "Domenica"
|
||||
monday: "Lunedì"
|
||||
@@ -2023,7 +2032,8 @@ _notification:
|
||||
notificationWillBeDisplayedLikeThis: "La notifica apparirà così"
|
||||
_types:
|
||||
all: "Tutto"
|
||||
follow: "Novità follower"
|
||||
note: "Nuove Note"
|
||||
follow: "Nuovi profili follower"
|
||||
mention: "Menzioni"
|
||||
reply: "Risposte"
|
||||
renote: "Rinota"
|
||||
@@ -2092,3 +2102,31 @@ _webhookSettings:
|
||||
renote: "Quando la Nota è Rinotata"
|
||||
reaction: "Quando ricevo una reazione"
|
||||
mention: "Quando mi menzionano"
|
||||
_moderationLogTypes:
|
||||
createRole: "Ruolo creato"
|
||||
deleteRole: "Ruolo eliminato"
|
||||
updateRole: "Ruolo aggiornato"
|
||||
assignRole: "Ruolo assegnato"
|
||||
unassignRole: "Ruolo disassegnato"
|
||||
suspend: "Sospensione"
|
||||
unsuspend: "Sospensione rimossa"
|
||||
addCustomEmoji: "Emoji personalizzata aggiunta"
|
||||
updateCustomEmoji: "Emoji personalizzata aggiornata"
|
||||
deleteCustomEmoji: "Emoji personalizzata eliminata"
|
||||
updateServerSettings: "Impostazioni del server aggiornate"
|
||||
updateUserNote: "Promemoria di moderazione aggiornato"
|
||||
deleteDriveFile: "File da Drive eliminato"
|
||||
deleteNote: "Nota eliminata"
|
||||
createGlobalAnnouncement: "Annuncio globale creato"
|
||||
createUserAnnouncement: "Annuncio ai profili iscritti creato"
|
||||
updateGlobalAnnouncement: "Annuncio globale aggiornato"
|
||||
updateUserAnnouncement: "Annuncio ai profili iscritti aggiornato"
|
||||
deleteGlobalAnnouncement: "Annuncio globale eliminato"
|
||||
deleteUserAnnouncement: "Annuncio ai profili iscritti eliminato"
|
||||
resetPassword: "Password azzerata"
|
||||
suspendRemoteInstance: "Istanza remota sospesa"
|
||||
unsuspendRemoteInstance: "Istanza remota riattivata"
|
||||
markSensitiveDriveFile: "File nel Drive segnato come esplicito"
|
||||
unmarkSensitiveDriveFile: "File nel Drive segnato come non esplicito"
|
||||
resolveAbuseReport: "Segnalazione risolta"
|
||||
createInvitation: "Genera codice di invito"
|
||||
|
@@ -1120,6 +1120,12 @@ notifyNotes: "投稿を通知"
|
||||
unnotifyNotes: "投稿の通知を解除"
|
||||
authentication: "認証"
|
||||
authenticationRequiredToContinue: "続けるには認証を行ってください"
|
||||
dateAndTime: "日時"
|
||||
showRenotes: "リノートを表示"
|
||||
edited: "編集済み"
|
||||
notificationRecieveConfig: "通知の受信設定"
|
||||
mutualFollow: "相互フォロー"
|
||||
fileAttachedOnly: "ファイル付きのみ"
|
||||
|
||||
_announcement:
|
||||
forExistingUsers: "既存ユーザーのみ"
|
||||
@@ -1457,6 +1463,7 @@ _role:
|
||||
gtlAvailable: "グローバルタイムラインの閲覧"
|
||||
ltlAvailable: "ローカルタイムラインの閲覧"
|
||||
canPublicNote: "パブリック投稿の許可"
|
||||
canEditNote: "ノートの編集"
|
||||
canInvite: "サーバー招待コードの発行"
|
||||
inviteLimit: "招待コードの作成可能数"
|
||||
inviteLimitCycle: "招待コードの発行間隔"
|
||||
@@ -2163,15 +2170,33 @@ _webhookSettings:
|
||||
mention: "メンションされたとき"
|
||||
|
||||
_moderationLogTypes:
|
||||
createRole: "ロールを作成"
|
||||
deleteRole: "ロールを削除"
|
||||
updateRole: "ロールを更新"
|
||||
assignRole: "ロールへアサイン"
|
||||
unassignRole: "ロールのアサイン解除"
|
||||
updateRole: "ロール設定更新"
|
||||
suspend: "凍結"
|
||||
unsuspend: "凍結解除"
|
||||
addCustomEmoji: "カスタム絵文字追加"
|
||||
updateCustomEmoji: "カスタム絵文字更新"
|
||||
deleteCustomEmoji: "カスタム絵文字削除"
|
||||
updateServerSettings: "サーバー設定更新"
|
||||
updateUserNote: "モデレーションノート更新"
|
||||
deleteDriveFile: "ファイルを削除"
|
||||
deleteNote: "ノートを削除"
|
||||
createGlobalAnnouncement: "全体のお知らせを作成"
|
||||
createUserAnnouncement: "ユーザーへお知らせを作成"
|
||||
updateGlobalAnnouncement: "全体のお知らせを更新"
|
||||
updateUserAnnouncement: "ユーザーのお知らせを更新"
|
||||
deleteGlobalAnnouncement: "全体のお知らせを削除"
|
||||
deleteUserAnnouncement: "ユーザーのお知らせを削除"
|
||||
resetPassword: "パスワードをリセット"
|
||||
suspendRemoteInstance: "リモートサーバーを停止"
|
||||
unsuspendRemoteInstance: "リモートサーバーを再開"
|
||||
markSensitiveDriveFile: "ファイルをセンシティブ付与"
|
||||
unmarkSensitiveDriveFile: "ファイルをセンシティブ解除"
|
||||
resolveAbuseReport: "通報を解決"
|
||||
createInvitation: "招待コードを作成"
|
||||
createAd: "広告を作成"
|
||||
deleteAd: "広告を削除"
|
||||
updateAd: "広告を更新"
|
||||
|
@@ -1105,6 +1105,10 @@ youHaveUnreadAnnouncements: "あんたまだこのお知らせ読んどらんや
|
||||
useSecurityKey: "ブラウザまたはデバイスの言う通りに、セキュリティキーまたはパスキーを使ってや。"
|
||||
replies: "返事"
|
||||
renotes: "Renote"
|
||||
loadReplies: "返信を見るで"
|
||||
loadConversation: "会話を見るで"
|
||||
verifiedLink: "このリンク先の所有者であることが確認されたで。"
|
||||
authenticationRequiredToContinue: "続けるには認証をやってや。"
|
||||
_announcement:
|
||||
forExistingUsers: "もうおるユーザーのみ"
|
||||
forExistingUsersDescription: "有効にすると、このお知らせ作成時点でおるユーザーにのみお知らせが表示されます。無効にすると、このお知らせ作成後にアカウントを作成したユーザーにもお知らせが表示されます。"
|
||||
@@ -1133,6 +1137,11 @@ _serverRules:
|
||||
description: "新規登録前に見せる、サーバーの簡潔なルールを設定すんで。内容は使うための決め事の要約とすることを推奨するわ。"
|
||||
_serverSettings:
|
||||
iconUrl: "アイコン画像のURL"
|
||||
appIconDescription: "{host}がアプリとして表示してるんやつをアイコンを指定すんで。"
|
||||
appIconUsageExample: "PWAや、スマートフォンのホーム画面にブックマークとして追加された時など"
|
||||
appIconStyleRecommendation: "円形もしくは角丸にクロップされる場合があるさかいに、塗り潰された余白のある背景があるものが推奨されるで。"
|
||||
appIconResolutionMustBe: "解像度は必ず{resolution}である必要があるで。"
|
||||
shortNameDescription: "サーバーの名前が長い時に、代わりに表示することのできるあだ名。"
|
||||
_accountMigration:
|
||||
moveFrom: "別のアカウントからこのアカウントに引っ越す"
|
||||
moveFromSub: "別のアカウントへエイリアスを作る"
|
||||
@@ -1703,6 +1712,7 @@ _2fa:
|
||||
step2Click: "QRコードをクリックすると、今使とる端末に入っとる認証アプリとかキーリングに登録できるで。"
|
||||
step3Title: "確認コードを入れてーや"
|
||||
step3: "アプリに表示されているトークンを入力して終わりや。"
|
||||
setupCompleted: "設定が完了したで。"
|
||||
step4: "これからログインするときも、同じようにトークンを入力するんやで"
|
||||
securityKeyNotSupported: "今使とるブラウザはセキュリティキーに対応してへんのやってさ。"
|
||||
registerTOTPBeforeKey: "セキュリティキー・パスキーを登録するんやったら、まず認証アプリを設定してーな。"
|
||||
@@ -1717,6 +1727,10 @@ _2fa:
|
||||
renewTOTPConfirm: "今までの認証アプリの確認コードは使えんくなるけどええか?"
|
||||
renewTOTPOk: "もっかい設定する"
|
||||
renewTOTPCancel: "やめとく"
|
||||
checkBackupCodesBeforeCloseThisWizard: "このウィザードを閉じる前に、したのバックアップコードを確認しいや。"
|
||||
backupCodesDescription: "認証アプリが使用できんなった場合、以下のバックアップコードを使ってアカウントにアクセスできるで。これらのコードは必ず安全な場所に置いときや。各コードは一回だけ使用できるで。"
|
||||
backupCodeUsedWarning: "バックアップコードが使用されたで。認証アプリが使えなくなってるん場合、なるべく早く認証アプリを再設定しや。"
|
||||
backupCodesExhaustedWarning: "バックアップコードが全て使用されたで。認証アプリを利用できん場合、これ以上アカウントにアクセスできなくなるで。認証アプリを再登録しや。"
|
||||
_permissions:
|
||||
"read:account": "アカウントの情報を見るで"
|
||||
"write:account": "アカウントの情報を変更するで"
|
||||
@@ -1989,6 +2003,9 @@ _notification:
|
||||
unreadAntennaNote: "アンテナ {name}"
|
||||
emptyPushNotificationMessage: "プッシュ通知の更新をしといたで"
|
||||
achievementEarned: "実績を獲得しとるで"
|
||||
checkNotificationBehavior: "通知の表示を確かめるで"
|
||||
sendTestNotification: "テスト通知を送信するで"
|
||||
notificationWillBeDisplayedLikeThis: "通知はこのように表示されるで"
|
||||
_types:
|
||||
all: "すべて"
|
||||
follow: "フォロー"
|
||||
@@ -2024,6 +2041,7 @@ _deck:
|
||||
introduction2: "画面の右にある + を押して、いつでもカラムを追加できるで。"
|
||||
widgetsIntroduction: "カラムのメニューから、「ウィジェットの編集」を選んでウィジェットを追加してなー"
|
||||
useSimpleUiForNonRootPages: "非ルートページは簡易UIで表示"
|
||||
usedAsMinWidthWhenFlexible: "「幅を自動調整」が有効の場合、これが幅の最小値となるで"
|
||||
_columns:
|
||||
main: "メイン"
|
||||
widgets: "ウィジェット"
|
||||
@@ -2058,3 +2076,7 @@ _webhookSettings:
|
||||
renote: "Renoteされるとき~!"
|
||||
reaction: "ツッコミがあるとき~!"
|
||||
mention: "メンションがあるとき~!"
|
||||
_moderationLogTypes:
|
||||
suspend: "凍結"
|
||||
resetPassword: "パスワードをリセット"
|
||||
createInvitation: "招待コードを作成"
|
||||
|
@@ -2073,3 +2073,7 @@ _webhookSettings:
|
||||
renote: "누군가 내 글을 Renote했을 때"
|
||||
reaction: "누군가 내 노트에 리액션했을 때"
|
||||
mention: "누군가 나를 멘션했을 때"
|
||||
_moderationLogTypes:
|
||||
suspend: "정지"
|
||||
resetPassword: "비밀번호 재설정"
|
||||
createInvitation: "초대 코드 생성"
|
||||
|
@@ -463,3 +463,5 @@ _deck:
|
||||
mentions: "ກ່າວເຖິງ"
|
||||
_webhookSettings:
|
||||
name: "ຊື່"
|
||||
_moderationLogTypes:
|
||||
suspend: "ລະງັບ"
|
||||
|
@@ -494,3 +494,6 @@ _deck:
|
||||
mentions: "Vermeldingen"
|
||||
_webhookSettings:
|
||||
name: "Naam"
|
||||
_moderationLogTypes:
|
||||
suspend: "Opschorten"
|
||||
resetPassword: "Wachtwoord terugzetten"
|
||||
|
@@ -725,3 +725,5 @@ _deck:
|
||||
direct: "Direkte"
|
||||
_webhookSettings:
|
||||
name: "Navn"
|
||||
_moderationLogTypes:
|
||||
suspend: "Suspender"
|
||||
|
@@ -1402,3 +1402,6 @@ _webhookSettings:
|
||||
renote: "Po udostępnieniu wpisu"
|
||||
reaction: "Po otrzymaniu reakcji"
|
||||
mention: "Po zostaniu wspomnianym"
|
||||
_moderationLogTypes:
|
||||
suspend: "Zawieś"
|
||||
resetPassword: "Zresetuj hasło"
|
||||
|
@@ -1497,3 +1497,6 @@ _webhookSettings:
|
||||
follow: "Quando seguindo um usuário"
|
||||
followed: "Quando sendo seguido"
|
||||
renote: "Quando repostado"
|
||||
_moderationLogTypes:
|
||||
suspend: "Suspender"
|
||||
resetPassword: "Redefinir senha"
|
||||
|
@@ -704,3 +704,6 @@ _deck:
|
||||
mentions: "Mențiuni"
|
||||
_webhookSettings:
|
||||
name: "Nume"
|
||||
_moderationLogTypes:
|
||||
suspend: "Suspendă"
|
||||
resetPassword: "Resetează parola"
|
||||
|
@@ -1951,3 +1951,6 @@ _webhookSettings:
|
||||
createWebhook: "Создать вебхук"
|
||||
name: "Название"
|
||||
active: "Вкл."
|
||||
_moderationLogTypes:
|
||||
suspend: "Заморозить"
|
||||
resetPassword: "Сброс пароля:"
|
||||
|
@@ -1451,3 +1451,6 @@ _deck:
|
||||
_webhookSettings:
|
||||
name: "Názov"
|
||||
active: "Zapnuté"
|
||||
_moderationLogTypes:
|
||||
suspend: "Zmraziť"
|
||||
resetPassword: "Resetovať heslo"
|
||||
|
@@ -573,3 +573,6 @@ _deck:
|
||||
_webhookSettings:
|
||||
name: "Namn"
|
||||
active: "Aktiverad"
|
||||
_moderationLogTypes:
|
||||
suspend: "Suspendera"
|
||||
resetPassword: "Återställ Lösenord"
|
||||
|
@@ -416,6 +416,9 @@ totp: "แอป Authenticator"
|
||||
totpDescription: "ใช้แอปยืนยันตัวตนเพื่อป้อนรหัสผ่านแบบใช้ครั้งเดียว"
|
||||
moderator: "ผู้ควบคุม"
|
||||
moderation: "การกลั่นกรอง"
|
||||
moderationNote: "โน้ตการกลั่นกรอง"
|
||||
addModerationNote: "เพิ่มโน้ตการกลั่นกรอง"
|
||||
moderationLogs: "บันทึกการกลั่นกรอง"
|
||||
nUsersMentioned: "กล่าวถึงโดยผู้ใช้ {n} รายนี้"
|
||||
securityKeyAndPasskey: "ความปลอดภัยและรหัสผ่าน"
|
||||
securityKey: "กุญแจความปลอดภัย"
|
||||
@@ -708,6 +711,7 @@ lockedAccountInfo: "เว้นแต่ว่าคุณจะต้องต
|
||||
alwaysMarkSensitive: "ทำเครื่องหมายเป็น NSFW เป็นค่าเริ่มต้น"
|
||||
loadRawImages: "โหลดภาพต้นฉบับแทนการแสดงภาพขนาดย่อ"
|
||||
disableShowingAnimatedImages: "ไม่ต้องเล่นภาพเคลื่อนไหว"
|
||||
highlightSensitiveMedia: "ไฮไลท์สื่อที่ละเอียดอ่อน"
|
||||
verificationEmailSent: "ส่งอีเมลยืนยันแล้วนะ ได้โปรดกรุณาไปที่ลิงก์ที่รวมไว้เพื่อทำการตรวจสอบให้เสร็จสิ้น"
|
||||
notSet: "ไม่ได้ตั้งค่า"
|
||||
emailVerified: "อีเมลได้รับการยืนยันแล้ว"
|
||||
@@ -1022,6 +1026,7 @@ retryAllQueuesConfirmText: "สิ่งนี้จะเพิ่มการ
|
||||
enableChartsForRemoteUser: "สร้างแผนภูมิข้อมูลผู้ใช้ระยะไกล"
|
||||
enableChartsForFederatedInstances: "สร้างแผนภูมิข้อมูลอินสแตนซ์ระยะไกล"
|
||||
showClipButtonInNoteFooter: "เพิ่ม \"คลิป\" เพื่อบันทึกเมนูการทำงาน"
|
||||
reactionsDisplaySize: "รีแอคชั่นแสดงผลขนาด"
|
||||
noteIdOrUrl: "โน้ต ID หรือ URL"
|
||||
video: "วีดีโอ"
|
||||
videos: "วีดีโอ"
|
||||
@@ -1100,11 +1105,23 @@ iHaveReadXCarefullyAndAgree: "ฉันได้อ่านข้อควา
|
||||
dialog: "ไดอะล็อก"
|
||||
icon: "ไอคอน"
|
||||
forYou: "สำหรับคุณ"
|
||||
currentAnnouncements: "ประกาศในปัจจุบัน"
|
||||
pastAnnouncements: "ประกาศที่ผ่านมา"
|
||||
youHaveUnreadAnnouncements: "มีการประกาศที่ยังไม่ได้อ่าน"
|
||||
replies: "ตอบกลับ"
|
||||
renotes: "รีโน้ต"
|
||||
loadReplies: "แสดงการตอบกลับ"
|
||||
loadConversation: "แสดงบทสนทนา"
|
||||
pinnedList: "รายการที่ปักหมุดไว้แล้ว"
|
||||
keepScreenOn: "เปิดหน้าจอไว้"
|
||||
notifyNotes: "แจ้งเตือนเกี่ยวกับโพสต์ใหม่"
|
||||
unnotifyNotes: "หยุดการแจ้งเตือนเกี่ยวกับโน้ตใหม่"
|
||||
authentication: "การตรวจสอบสิทธิ์"
|
||||
dateAndTime: "เวลาประทับ"
|
||||
showRenotes: "แสดงรีโน้ต"
|
||||
edited: "แก้ไขแล้ว"
|
||||
_announcement:
|
||||
forExistingUsers: "ผู้ใช้งานที่มีอยู่เท่านั้น"
|
||||
forExistingUsersDescription: "การประกาศนี้จะแสดงต่อผู้ใช้ที่มีอยู่ ณ จุดที่เผยแพร่นั้นๆถ้าหากเปิดใช้งาน ถ้าหากปิดใช้งานผู้ที่กำลังสมัครใหม่หลังจากโพสต์แล้วนั้นก็จะเห็นเช่นกัน"
|
||||
needConfirmationToReadDescription: "ข้อความแจ้งแยก ถ้าหากต้องการเพื่อยืนยันว่ากำลังทำเครื่องหมายประกาศนี้ว่าอ่านแล้วจะแสดงขึ้นถ้าหากเปิดใช้งาน การประกาศนั้นจะไม่รวมอยู่ในฟังก์ชั่นว่า \"ทำเครื่องหมายทั้งหมดว่าอ่านแล้ว\""
|
||||
end: "ประกาศเก็บถาวร"
|
||||
@@ -1130,6 +1147,7 @@ _serverRules:
|
||||
description: "ชุดของกฎที่จะแสดงก่อนการลงทะเบียนเราขอแนะนำให้ตั้งค่าสรุปข้อกำหนดในการให้บริการ"
|
||||
_serverSettings:
|
||||
iconUrl: "ไอคอน URL"
|
||||
shortName: "ชื่อย่อ"
|
||||
_accountMigration:
|
||||
moveFrom: "ย้ายข้อมูลบัญชีอื่นไปยังอีกบัญชีนี้หนึ่ง"
|
||||
moveFromSub: "สร้างนามแฝงไปยังบัญชีอื่น"
|
||||
@@ -1424,6 +1442,7 @@ _role:
|
||||
gtlAvailable: "การดูไทม์ไลน์ทั่วโลก"
|
||||
ltlAvailable: "การดูไทม์ไลน์ในท้องถิ่น"
|
||||
canPublicNote: "สามารถส่งโน้ตสาธารณะ"
|
||||
canEditNote: "กำลังแก้ไขโน้ต"
|
||||
canInvite: "สร้างรหัสเชิญอินสแตนซ์"
|
||||
inviteLimit: "จำกัดการเชิญ"
|
||||
inviteLimitCycle: "จำกัดการเชิญไว้คูลดาวน์"
|
||||
@@ -1987,6 +2006,7 @@ _notification:
|
||||
youReceivedFollowRequest: "คุณมีคำขอติดตามใหม่น่ะ"
|
||||
yourFollowRequestAccepted: "คำขอติดตามของคุณได้รับการยอมรับแล้วน่ะ"
|
||||
pollEnded: "โพลสำรวจความคิดเห็นผลลัพธ์มีพร้อมใช้งาน"
|
||||
newNote: "โพสต์ใหม่"
|
||||
unreadAntennaNote: "เสาอากาศ {name}"
|
||||
emptyPushNotificationMessage: "การแจ้งเตือนแบบพุชได้รับการอัพเดทแล้ว"
|
||||
achievementEarned: "รับความสำเร็จ"
|
||||
@@ -1996,6 +2016,7 @@ _notification:
|
||||
notificationWillBeDisplayedLikeThis: "การแจ้งเตือนมีลักษณะแบบนี้"
|
||||
_types:
|
||||
all: "ทั้งหมด"
|
||||
note: "โน้ตใหม่"
|
||||
follow: "กำลังติดตาม"
|
||||
mention: "กล่าวถึง"
|
||||
reply: "ตอบกลับ"
|
||||
@@ -2065,3 +2086,21 @@ _webhookSettings:
|
||||
renote: "รีโน้ตแล้วเมื่อ"
|
||||
reaction: "เมื่อได้รับรีแอคชั่น"
|
||||
mention: "เมื่อกำลังถูกกล่าวถึง"
|
||||
_moderationLogTypes:
|
||||
createRole: "สร้างบทบาทแล้ว"
|
||||
deleteRole: "ลบบทบาทแล้ว"
|
||||
updateRole: "อัปเดตบทบาทแล้ว"
|
||||
assignRole: "ได้รับมอบหมายบทบาท"
|
||||
unassignRole: "ถอดออกจากบทบาทแล้ว"
|
||||
suspend: "ถูกระงับ"
|
||||
unsuspend: "เลิกถูกระงับ"
|
||||
addCustomEmoji: "เพิ่มอีโมจิที่กำหนดเองแล้ว"
|
||||
updateCustomEmoji: "อัปเดตอีโมจิที่กำหนดเองแล้ว"
|
||||
deleteCustomEmoji: "ลบอีโมจิที่กำหนดเองออกแล้ว"
|
||||
updateServerSettings: "อัปเดตการตั้งค่าเซิร์ฟเวอร์แล้ว"
|
||||
updateUserNote: "อัปเดตโน้ตการกลั่นกรองแล้ว"
|
||||
deleteDriveFile: "ลบไฟล์ออกแล้ว"
|
||||
deleteNote: "ลบโน้ตออกแล้ว"
|
||||
resetPassword: "รีเซ็ตรหัสผ่าน"
|
||||
resolveAbuseReport: "รายงานได้รับการแก้ไขแล้ว"
|
||||
createInvitation: "สร้างคำเชิญ"
|
||||
|
@@ -448,3 +448,6 @@ _deck:
|
||||
tl: "Zaman çizelgesi"
|
||||
list: "Listeler"
|
||||
mentions: "Bahsetmeler"
|
||||
_moderationLogTypes:
|
||||
suspend: "askıya al"
|
||||
resetPassword: "Şifre sıfırlama"
|
||||
|
@@ -1620,3 +1620,6 @@ _deck:
|
||||
_webhookSettings:
|
||||
name: "Ім'я"
|
||||
active: "Увімкнено"
|
||||
_moderationLogTypes:
|
||||
suspend: "Призупинити"
|
||||
resetPassword: "Скинути пароль"
|
||||
|
@@ -1084,3 +1084,6 @@ _webhookSettings:
|
||||
_events:
|
||||
renote: "Qayta qayd qilinganda"
|
||||
mention: "Eslanganda"
|
||||
_moderationLogTypes:
|
||||
suspend: "To'xtatish"
|
||||
resetPassword: "Parolni tiklash"
|
||||
|
@@ -1860,3 +1860,6 @@ _webhookSettings:
|
||||
_events:
|
||||
reaction: "Khi nhận được sự kiện"
|
||||
mention: "Khi có người nhắc tới bạn"
|
||||
_moderationLogTypes:
|
||||
suspend: "Vô hiệu hóa"
|
||||
resetPassword: "Đặt lại mật khẩu"
|
||||
|
@@ -418,6 +418,7 @@ moderator: "监察员"
|
||||
moderation: "管理"
|
||||
moderationNote: "管理笔记"
|
||||
addModerationNote: "添加管理笔记"
|
||||
moderationLogs: "管理日志"
|
||||
nUsersMentioned: "{n} 被提到"
|
||||
securityKeyAndPasskey: "安全密钥或 Passkey"
|
||||
securityKey: "安全密钥"
|
||||
@@ -1119,6 +1120,9 @@ notifyNotes: "打开发帖通知"
|
||||
unnotifyNotes: "关闭发帖通知"
|
||||
authentication: "验证"
|
||||
authenticationRequiredToContinue: "要继续,请先进行验证"
|
||||
dateAndTime: "日期和时间"
|
||||
showRenotes: "显示转帖"
|
||||
edited: "已编辑"
|
||||
_announcement:
|
||||
forExistingUsers: "仅限现有用户"
|
||||
forExistingUsersDescription: "若启用,该公告将仅对创建此公告时存在的用户可见。 如果禁用,则在创建此公告后注册的用户也可以看到该公告。"
|
||||
@@ -1152,6 +1156,8 @@ _serverSettings:
|
||||
appIconStyleRecommendation: "因为有可能会被裁切为圆形或者圆角矩形,建议使用边缘带有留白背景的图标。"
|
||||
appIconResolutionMustBe: "分辨率必须为 {resolution}。"
|
||||
manifestJsonOverride: "覆盖 mainfest.json"
|
||||
shortName: "简称"
|
||||
shortNameDescription: "如果服务器的正式名称很长,可以用简称或者別名来替代。"
|
||||
_accountMigration:
|
||||
moveFrom: "从别的账号迁移到此账户"
|
||||
moveFromSub: "为另一个账户建立别名"
|
||||
@@ -1447,6 +1453,7 @@ _role:
|
||||
gtlAvailable: "查看全局时间线"
|
||||
ltlAvailable: "查看本地时间线"
|
||||
canPublicNote: "允许公开发帖"
|
||||
canEditNote: "编辑帖子"
|
||||
canInvite: "发放服务器邀请码"
|
||||
inviteLimit: "可发行邀请码的数量"
|
||||
inviteLimitCycle: "邀请码的发行间隔"
|
||||
@@ -2027,6 +2034,7 @@ _notification:
|
||||
notificationWillBeDisplayedLikeThis: "通知将会这样表示"
|
||||
_types:
|
||||
all: "全部"
|
||||
note: "用户的新帖子"
|
||||
follow: "关注中"
|
||||
mention: "提及"
|
||||
reply: "回复"
|
||||
@@ -2096,3 +2104,29 @@ _webhookSettings:
|
||||
renote: "被转发时"
|
||||
reaction: "被回应时"
|
||||
mention: "被提及时"
|
||||
_moderationLogTypes:
|
||||
createRole: "创建角色"
|
||||
deleteRole: "删除角色"
|
||||
updateRole: "更新角色"
|
||||
assignRole: "分配角色"
|
||||
unassignRole: "取消分配角色"
|
||||
suspend: "冻结"
|
||||
unsuspend: "解除冻结"
|
||||
addCustomEmoji: "添加自定义表情符号"
|
||||
updateCustomEmoji: "更新自定义表情符号"
|
||||
deleteCustomEmoji: "删除自定义表情符号"
|
||||
updateServerSettings: "更新服务器设置"
|
||||
updateUserNote: "更新管理笔记"
|
||||
deleteDriveFile: "删除文件"
|
||||
deleteNote: "删除帖子"
|
||||
createGlobalAnnouncement: "创建全体通知"
|
||||
createUserAnnouncement: "创建用户通知"
|
||||
updateGlobalAnnouncement: "更新全体通知"
|
||||
updateUserAnnouncement: "更新用户通知"
|
||||
deleteGlobalAnnouncement: "删除全体通知"
|
||||
deleteUserAnnouncement: "删除用户通知"
|
||||
resetPassword: "重置密码"
|
||||
markSensitiveDriveFile: "标记网盘文件为敏感媒体"
|
||||
unmarkSensitiveDriveFile: "取消标记网盘文件为敏感媒体"
|
||||
resolveAbuseReport: "处理举报"
|
||||
createInvitation: "发行邀请码"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
_lang_: "繁體中文"
|
||||
headlineMisskey: "貼文連繫網絡"
|
||||
_lang_: "繁體中文(台灣)"
|
||||
headlineMisskey: "貼文連繫網路"
|
||||
introMisskey: "歡迎!Misskey 是一個開放原始碼且去中心化的社群網路服務。\n發布「貼文」向身邊的人分享您的想法!📡\n利用「反應」表達您對貼文的感覺!👍\n讓我們一起探索新的世界吧!🚀"
|
||||
poweredByMisskeyDescription: "{name}是開放原始碼平臺 <b>Misskey</b> 的伺服器之一。"
|
||||
monthAndDay: "{month} 月 {day} 日"
|
||||
@@ -15,7 +15,7 @@ gotIt: "知道了"
|
||||
cancel: "取消"
|
||||
noThankYou: "現在不要"
|
||||
enterUsername: "輸入使用者名稱"
|
||||
renotedBy: "{user} 轉發"
|
||||
renotedBy: "{user} 轉發了"
|
||||
noNotes: "無貼文"
|
||||
noNotifications: "沒有通知"
|
||||
instance: "伺服器"
|
||||
@@ -45,7 +45,7 @@ pin: "置頂"
|
||||
unpin: "取消置頂"
|
||||
copyContent: "複製內容"
|
||||
copyLink: "複製連結"
|
||||
copyLinkRenote: "複製轉貼連結"
|
||||
copyLinkRenote: "複製轉發的連結"
|
||||
delete: "刪除"
|
||||
deleteAndEdit: "刪除並編輯"
|
||||
deleteAndEditConfirm: "要刪除並再次編輯嗎?此貼文的所有反應、轉發和回覆也將會消失。"
|
||||
@@ -56,7 +56,7 @@ copyRSS: "複製RSS"
|
||||
copyUsername: "複製使用者名稱"
|
||||
copyUserId: "複製使用者 ID"
|
||||
copyNoteId: "複製貼文 ID"
|
||||
copyFileId: "複製檔案ID"
|
||||
copyFileId: "複製檔案 ID"
|
||||
copyFolderId: "複製資料夾ID"
|
||||
copyProfileUrl: "複製個人資料網址"
|
||||
searchUser: "搜尋使用者"
|
||||
@@ -75,9 +75,9 @@ import: "匯入"
|
||||
export: "匯出"
|
||||
files: "檔案"
|
||||
download: "下載"
|
||||
driveFileDeleteConfirm: "確定要刪除檔案「{name}」嗎?使用此附件的貼文也會跟著消失。\n"
|
||||
driveFileDeleteConfirm: "確定要刪除檔案「{name}」嗎?使用此檔案的貼文也會跟著被刪除。"
|
||||
unfollowConfirm: "確定要取消追隨{name}嗎?"
|
||||
exportRequested: "已請求匯出。這可能會花一點時間。匯出的檔案將會被放到雲端裡。"
|
||||
exportRequested: "已請求匯出。這可能會花一點時間。匯出的檔案將會被放到雲端硬碟裡。"
|
||||
importRequested: "已請求匯入。這可能會花一點時間。"
|
||||
lists: "清單"
|
||||
noLists: "你沒有任何清單"
|
||||
@@ -107,7 +107,7 @@ followRequestPending: "追隨許可待批准"
|
||||
enterEmoji: "輸入表情符號"
|
||||
renote: "轉發"
|
||||
unrenote: "取消轉發"
|
||||
renoted: "轉發成功"
|
||||
renoted: "轉發成功。"
|
||||
cantRenote: "無法轉發此貼文。"
|
||||
cantReRenote: "無法轉發之前已經轉發過的內容。"
|
||||
quote: "引用"
|
||||
@@ -138,8 +138,8 @@ suspend: "凍結"
|
||||
unsuspend: "解除凍結"
|
||||
blockConfirm: "確定要封鎖此使用者嗎?"
|
||||
unblockConfirm: "確定要解除封鎖此使用者嗎?"
|
||||
suspendConfirm: "確定凍結此帳戶?"
|
||||
unsuspendConfirm: "確定解凍此帳戶?"
|
||||
suspendConfirm: "確定凍結此使用者?"
|
||||
unsuspendConfirm: "確定解凍此使用者?"
|
||||
selectList: "選擇清單"
|
||||
editList: "編輯清單"
|
||||
selectChannel: "選擇頻道"
|
||||
@@ -152,20 +152,20 @@ customEmojis: "自訂表情符號"
|
||||
emoji: "表情符號"
|
||||
emojis: "表情符號"
|
||||
emojiName: "表情符號名稱"
|
||||
emojiUrl: "表情符號URL"
|
||||
emojiUrl: "表情符號 URL"
|
||||
addEmoji: "新增表情符號"
|
||||
settingGuide: "推薦設定"
|
||||
cacheRemoteFiles: "快取遠端檔案"
|
||||
cacheRemoteFilesDescription: "禁用此設定會停止建立遠端檔案快取,從而節省伺服器儲存空間,但會因從遠端讀取資料而增加網路數據用量。"
|
||||
youCanCleanRemoteFilesCache: "按檔案管理的🗑️按鈕,將快取全部刪除。"
|
||||
cacheRemoteFilesDescription: "啟用此設定後,遠端檔案會被快取在本伺服器的儲存空間中。雖然顯示圖片會變快,但會消耗較多伺服器的儲存空間。至於要快取遠端使用者到什麼程度,是依照角色的雲端硬碟容量而定。當超過這個限制時,從較舊的檔案開始自快取中刪除並改為連結。關閉這個設定時,遠端檔案從一開始就維持連結的方式,但建議將 default.yml 的 proxyRemoteFiles 設為 true,以便產生圖片的縮圖並保護使用者的隱私,。"
|
||||
youCanCleanRemoteFilesCache: "按檔案管理的🗑️按鈕,可將快取全部刪除。"
|
||||
cacheRemoteSensitiveFiles: "快取遠端的敏感檔案"
|
||||
cacheRemoteSensitiveFilesDescription: "若停用這個設定,則不會快取遠端的敏感檔案,而是直接連結。"
|
||||
flagAsBot: "此使用者是機器人"
|
||||
flagAsBotDescription: "標記本帳戶由程式控制,防止其他程式與本帳戶產生無限互動的行為。"
|
||||
flagAsBotDescription: "如果本帳戶是由程式控制,請啟用此選項。啟用後,會作為標示幫助其他開發者防止機器人之間產生無限互動的行為,並會調整Misskey內部系統將本帳戶識別為機器人"
|
||||
flagAsCat: "此帳戶是一隻貓,喵~~~!!!"
|
||||
flagAsCatDescription: "如果想將本帳戶標示為一隻貓,請開啟此標示"
|
||||
flagShowTimelineReplies: "在時間軸上顯示貼文的回覆"
|
||||
flagShowTimelineRepliesDescription: "啟用時,時間軸除了顯示使用者的貼文以外,還會顯示使用者對其他貼文的回覆。"
|
||||
flagShowTimelineRepliesDescription: "啟用後,時間軸除了顯示使用者的貼文以外,還會顯示使用者對其他貼文的回覆。"
|
||||
autoAcceptFollowed: "自動允許來自追隨中使用者的追隨請求"
|
||||
addAccount: "新增帳戶"
|
||||
reloadAccountsList: "更新帳戶清單的資訊"
|
||||
@@ -184,7 +184,7 @@ host: "主機"
|
||||
selectUser: "選取使用者"
|
||||
recipient: "收件人"
|
||||
annotation: "註解"
|
||||
federation: "聯邦宇宙"
|
||||
federation: "站台聯邦"
|
||||
instances: "伺服器"
|
||||
registeredAt: "初次觀測"
|
||||
latestRequestReceivedAt: "上次收到的請求"
|
||||
@@ -418,12 +418,13 @@ moderator: "審查員"
|
||||
moderation: "審查"
|
||||
moderationNote: "管理筆記"
|
||||
addModerationNote: "新增管理筆記"
|
||||
nUsersMentioned: "被提及到 {n} 次"
|
||||
moderationLogs: "管理日誌"
|
||||
nUsersMentioned: "被 {n} 個人提及"
|
||||
securityKeyAndPasskey: "安全金鑰、Passkey"
|
||||
securityKey: "安全金鑰"
|
||||
lastUsed: "上次使用"
|
||||
lastUsedAt: "上次使用:{t}"
|
||||
unregister: "註銷帳戶"
|
||||
unregister: "註銷"
|
||||
passwordLessLogin: "設置無密碼登入"
|
||||
passwordLessLoginDescription: "不使用密碼,以安全金鑰或 Passkey 登入"
|
||||
resetPassword: "重設密碼"
|
||||
@@ -508,8 +509,8 @@ promote: "推廣"
|
||||
numberOfDays: "有效天數"
|
||||
hideThisNote: "隱藏此貼文"
|
||||
showFeaturedNotesInTimeline: "在時間軸上顯示熱門推薦"
|
||||
objectStorage: "對象存儲"
|
||||
useObjectStorage: "使用對象存儲"
|
||||
objectStorage: "物件儲存"
|
||||
useObjectStorage: "使用物件儲存"
|
||||
objectStorageBaseUrl: "Base URL"
|
||||
objectStorageBaseUrlDesc: "用於引用的 URL。如果您使用的是 CDN 或反向代理,請指定其 URL,例如 S3(https://<bucket>.s3.amazonaws.com)、GCS(https://storage.googleapis.com/<bucket>)。"
|
||||
objectStorageBucket: "儲存空間(Bucket)"
|
||||
@@ -572,7 +573,7 @@ tokenRevokedDescription: "登入權杖失效,請重新登入。"
|
||||
accountDeleted: "帳戶已被刪除"
|
||||
accountDeletedDescription: "這個帳戶已被刪除。"
|
||||
menu: "選單"
|
||||
divider: "分割線"
|
||||
divider: "分隔線"
|
||||
addItem: "新增項目"
|
||||
rearrange: "排序方式"
|
||||
relays: "中繼"
|
||||
@@ -581,7 +582,7 @@ inboxUrl: "收件夾URL"
|
||||
addedRelays: "已加入的中繼"
|
||||
serviceworkerInfo: "您需要啟用推送通知。"
|
||||
deletedNote: "已刪除的貼文"
|
||||
invisibleNote: "隱藏的貼文"
|
||||
invisibleNote: "私密的貼文"
|
||||
enableInfiniteScroll: "啟用自動滾動頁面模式"
|
||||
visibility: "可見性"
|
||||
poll: "投票"
|
||||
@@ -1119,6 +1120,8 @@ notifyNotes: "開啟貼文通知"
|
||||
unnotifyNotes: "關閉貼文通知"
|
||||
authentication: "驗證"
|
||||
authenticationRequiredToContinue: "請於繼續前完成驗證"
|
||||
dateAndTime: "日期與時間"
|
||||
showRenotes: "顯示轉發貼文"
|
||||
_announcement:
|
||||
forExistingUsers: "僅限既有的使用者"
|
||||
forExistingUsersDescription: "啟用代表僅向現存使用者顯示;停用代表張貼後註冊的新使用者也會看到。"
|
||||
@@ -1449,6 +1452,7 @@ _role:
|
||||
gtlAvailable: "瀏覽全域時間軸"
|
||||
ltlAvailable: "瀏覽本地時間軸"
|
||||
canPublicNote: "允許公開貼文"
|
||||
canEditNote: "允許編輯貼文"
|
||||
canInvite: "發行實例邀請碼"
|
||||
inviteLimit: "可建立邀請碼的數量"
|
||||
inviteLimitCycle: "邀請碼的發放間隔"
|
||||
@@ -1574,8 +1578,8 @@ _displayOfSensitiveMedia:
|
||||
force: "隱藏所有檔案"
|
||||
_instanceTicker:
|
||||
none: "隱藏"
|
||||
remote: "向遠端使用者顯示"
|
||||
always: "總是顯示"
|
||||
remote: "只顯示遠端使用者"
|
||||
always: "一律顯示"
|
||||
_serverDisconnectedBehavior:
|
||||
reload: "自動重載"
|
||||
dialog: "彈出式警告"
|
||||
@@ -1608,7 +1612,7 @@ _wordMute:
|
||||
mutedNotes: "已靜音的貼文"
|
||||
_instanceMute:
|
||||
instanceMuteDescription: "包括對被靜音實例上的使用者的回覆,被設定的實例上所有貼文及轉發都會被靜音。"
|
||||
instanceMuteDescription2: "換行以分隔"
|
||||
instanceMuteDescription2: "設定時以換行進行分隔"
|
||||
title: "將隱藏被設定的實例貼文。"
|
||||
heading: "將實例靜音"
|
||||
_theme:
|
||||
@@ -1661,7 +1665,7 @@ _theme:
|
||||
mentionMe: "提到了我"
|
||||
renote: "轉發貼文"
|
||||
modalBg: "對話框背景"
|
||||
divider: "分割線"
|
||||
divider: "分隔線"
|
||||
scrollbarHandle: "捲動條"
|
||||
scrollbarHandleHover: "捲動條(懸浮)"
|
||||
dateLabelFg: "日期標籤文字"
|
||||
@@ -2099,3 +2103,31 @@ _webhookSettings:
|
||||
renote: "當被轉發時"
|
||||
reaction: "當獲得反應時"
|
||||
mention: "當被提到時"
|
||||
_moderationLogTypes:
|
||||
createRole: "新增角色"
|
||||
deleteRole: "刪除角色 "
|
||||
updateRole: "更新角色設定"
|
||||
assignRole: "指派角色"
|
||||
unassignRole: "撤銷角色"
|
||||
suspend: "凍結"
|
||||
unsuspend: "解除凍結"
|
||||
addCustomEmoji: "新增自訂表情符號"
|
||||
updateCustomEmoji: "更新自訂表情符號"
|
||||
deleteCustomEmoji: "刪除自訂表情符號"
|
||||
updateServerSettings: "更新伺服器設定"
|
||||
updateUserNote: "更新管理筆記"
|
||||
deleteDriveFile: "刪除檔案"
|
||||
deleteNote: "刪除貼文"
|
||||
createGlobalAnnouncement: "建立全網通知"
|
||||
createUserAnnouncement: "建立使用者通知"
|
||||
updateGlobalAnnouncement: "更新全部的公告"
|
||||
updateUserAnnouncement: "更新使用者的公告"
|
||||
deleteGlobalAnnouncement: "刪除全部的公告"
|
||||
deleteUserAnnouncement: "刪除使用者的公告"
|
||||
resetPassword: "重設密碼"
|
||||
suspendRemoteInstance: "封鎖遠端伺服器"
|
||||
unsuspendRemoteInstance: "解除封鎖遠端伺服器"
|
||||
markSensitiveDriveFile: "標記為敏感檔案"
|
||||
unmarkSensitiveDriveFile: "撤銷標記為敏感檔案"
|
||||
resolveAbuseReport: "解決檢舉"
|
||||
createInvitation: "建立邀請碼"
|
||||
|
12
package.json
12
package.json
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "2023.9.0-rc.2",
|
||||
"version": "2023.9.2",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/misskey-dev/misskey.git"
|
||||
},
|
||||
"packageManager": "pnpm@8.7.6",
|
||||
"packageManager": "pnpm@8.8.0",
|
||||
"workspaces": [
|
||||
"packages/frontend",
|
||||
"packages/backend",
|
||||
@@ -46,15 +46,15 @@
|
||||
"execa": "8.0.1",
|
||||
"cssnano": "6.0.1",
|
||||
"js-yaml": "4.1.0",
|
||||
"postcss": "8.4.30",
|
||||
"postcss": "8.4.31",
|
||||
"terser": "5.20.0",
|
||||
"typescript": "5.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "6.7.2",
|
||||
"@typescript-eslint/parser": "6.7.2",
|
||||
"@typescript-eslint/eslint-plugin": "6.7.3",
|
||||
"@typescript-eslint/parser": "6.7.3",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "13.2.0",
|
||||
"cypress": "13.3.0",
|
||||
"eslint": "8.50.0",
|
||||
"start-server-and-test": "2.0.1"
|
||||
},
|
||||
|
@@ -0,0 +1,21 @@
|
||||
export class MutingNotificationTypes1695605508898 {
|
||||
name = 'MutingNotificationTypes1695605508898'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum" RENAME TO "user_profile_mutingnotificationtypes_enum_old"`);
|
||||
await queryRunner.query(`CREATE TYPE "public"."user_profile_mutingnotificationtypes_enum" AS ENUM('note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app', 'test', 'pollVote', 'groupInvited')`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" DROP DEFAULT`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" TYPE "public"."user_profile_mutingnotificationtypes_enum"[] USING "mutingNotificationTypes"::"text"::"public"."user_profile_mutingnotificationtypes_enum"[]`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'`);
|
||||
await queryRunner.query(`DROP TYPE "public"."user_profile_mutingnotificationtypes_enum_old"`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`CREATE TYPE "public"."user_profile_mutingnotificationtypes_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'achievementEarned', 'app')`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" DROP DEFAULT`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" TYPE "public"."user_profile_mutingnotificationtypes_enum_old"[] USING "mutingNotificationTypes"::"text"::"public"."user_profile_mutingnotificationtypes_enum_old"[]`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'`);
|
||||
await queryRunner.query(`DROP TYPE "public"."user_profile_mutingnotificationtypes_enum"`);
|
||||
await queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum_old" RENAME TO "user_profile_mutingnotificationtypes_enum"`);
|
||||
}
|
||||
}
|
11
packages/backend/migration/1695901659683-note-updated-at.js
Normal file
11
packages/backend/migration/1695901659683-note-updated-at.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export class NoteUpdatedAt1695901659683 {
|
||||
name = 'NoteUpdatedAt1695901659683'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "note" ADD "updatedAt" TIMESTAMP WITH TIME ZONE`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "updatedAt"`);
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class NotificationRecieveConfig1695944637565 {
|
||||
name = 'NotificationRecieveConfig1695944637565'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "mutingNotificationTypes"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ADD "notificationRecieveConfig" jsonb NOT NULL DEFAULT '{}'`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "notificationRecieveConfig"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ADD "mutingNotificationTypes" "public"."user_profile_notificationrecieveconfig_enum" array NOT NULL DEFAULT '{}'`);
|
||||
}
|
||||
}
|
@@ -39,7 +39,7 @@
|
||||
"@swc/core-win32-x64-msvc": "1.3.56",
|
||||
"@tensorflow/tfjs": "4.4.0",
|
||||
"@tensorflow/tfjs-node": "4.4.0",
|
||||
"bufferutil": "^4.0.7",
|
||||
"bufferutil": "4.0.7",
|
||||
"slacc-android-arm-eabi": "0.0.10",
|
||||
"slacc-android-arm64": "0.0.10",
|
||||
"slacc-darwin-arm64": "0.0.10",
|
||||
@@ -53,7 +53,7 @@
|
||||
"slacc-linux-x64-musl": "0.0.10",
|
||||
"slacc-win32-arm64-msvc": "0.0.10",
|
||||
"slacc-win32-x64-msvc": "0.0.10",
|
||||
"utf-8-validate": "^6.0.3"
|
||||
"utf-8-validate": "6.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.412.0",
|
||||
@@ -68,17 +68,17 @@
|
||||
"@fastify/cors": "8.4.0",
|
||||
"@fastify/express": "2.3.0",
|
||||
"@fastify/http-proxy": "9.2.1",
|
||||
"@fastify/multipart": "7.7.3",
|
||||
"@fastify/multipart": "8.0.0",
|
||||
"@fastify/static": "6.11.2",
|
||||
"@fastify/view": "8.2.0",
|
||||
"@nestjs/common": "10.2.6",
|
||||
"@nestjs/core": "10.2.6",
|
||||
"@nestjs/testing": "10.2.6",
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@simplewebauthn/server": "8.1.1",
|
||||
"@simplewebauthn/server": "8.2.0",
|
||||
"@sinonjs/fake-timers": "11.1.0",
|
||||
"@swc/cli": "0.1.62",
|
||||
"@swc/core": "1.3.87",
|
||||
"@swc/core": "1.3.90",
|
||||
"accepts": "1.3.8",
|
||||
"ajv": "8.12.0",
|
||||
"archiver": "6.0.1",
|
||||
@@ -115,7 +115,7 @@
|
||||
"json5": "2.2.3",
|
||||
"jsonld": "8.3.1",
|
||||
"jsrsasign": "10.8.6",
|
||||
"meilisearch": "0.34.2",
|
||||
"meilisearch": "0.35.0",
|
||||
"mfm-js": "0.23.3",
|
||||
"microformats-parser": "1.5.2",
|
||||
"mime-types": "2.1.35",
|
||||
@@ -155,7 +155,7 @@
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"summaly": "github:misskey-dev/summaly",
|
||||
"systeminformation": "5.21.8",
|
||||
"systeminformation": "5.21.9",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tmp": "0.2.1",
|
||||
"tsc-alias": "1.8.8",
|
||||
@@ -187,33 +187,33 @@
|
||||
"@types/jsdom": "21.1.3",
|
||||
"@types/jsonld": "1.5.10",
|
||||
"@types/jsrsasign": "10.5.9",
|
||||
"@types/mime-types": "2.1.1",
|
||||
"@types/ms": "0.7.31",
|
||||
"@types/node": "20.6.3",
|
||||
"@types/mime-types": "2.1.2",
|
||||
"@types/ms": "0.7.32",
|
||||
"@types/node": "20.7.1",
|
||||
"@types/node-fetch": "3.0.3",
|
||||
"@types/nodemailer": "6.4.10",
|
||||
"@types/nodemailer": "6.4.11",
|
||||
"@types/oauth": "0.9.2",
|
||||
"@types/oauth2orize": "1.11.1",
|
||||
"@types/oauth2orize-pkce": "0.1.0",
|
||||
"@types/pg": "8.10.2",
|
||||
"@types/pug": "2.0.6",
|
||||
"@types/pg": "8.10.3",
|
||||
"@types/pug": "2.0.7",
|
||||
"@types/punycode": "2.1.0",
|
||||
"@types/qrcode": "1.5.2",
|
||||
"@types/random-seed": "0.3.3",
|
||||
"@types/ratelimiter": "3.4.4",
|
||||
"@types/rename": "1.0.4",
|
||||
"@types/sanitize-html": "2.9.0",
|
||||
"@types/semver": "7.5.2",
|
||||
"@types/rename": "1.0.5",
|
||||
"@types/sanitize-html": "2.9.1",
|
||||
"@types/semver": "7.5.3",
|
||||
"@types/sharp": "0.32.0",
|
||||
"@types/simple-oauth2": "5.0.4",
|
||||
"@types/sinonjs__fake-timers": "8.1.2",
|
||||
"@types/simple-oauth2": "5.0.5",
|
||||
"@types/sinonjs__fake-timers": "8.1.3",
|
||||
"@types/tinycolor2": "1.4.4",
|
||||
"@types/tmp": "0.2.4",
|
||||
"@types/vary": "1.1.0",
|
||||
"@types/web-push": "3.6.0",
|
||||
"@types/ws": "8.5.5",
|
||||
"@typescript-eslint/eslint-plugin": "6.7.2",
|
||||
"@typescript-eslint/parser": "6.7.2",
|
||||
"@types/vary": "1.1.1",
|
||||
"@types/web-push": "3.6.1",
|
||||
"@types/ws": "8.5.6",
|
||||
"@typescript-eslint/eslint-plugin": "6.7.3",
|
||||
"@typescript-eslint/parser": "6.7.3",
|
||||
"aws-sdk-client-mock": "3.0.0",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.50.0",
|
||||
|
@@ -63,6 +63,7 @@ export async function masterMain() {
|
||||
showNodejsVersion();
|
||||
config = loadConfigBoot();
|
||||
//await connectDb();
|
||||
if (config.pidFile) fs.writeFileSync(config.pidFile, process.pid.toString());
|
||||
} catch (e) {
|
||||
bootLogger.error('Fatal error occurred during initialization', null, true);
|
||||
process.exit(1);
|
||||
|
@@ -89,6 +89,7 @@ type Source = {
|
||||
perChannelMaxNoteCacheCount?: number;
|
||||
perUserNotificationsMaxCount?: number;
|
||||
deactivateAntennaThreshold?: number;
|
||||
pidFile: string;
|
||||
};
|
||||
|
||||
export type Config = {
|
||||
@@ -163,6 +164,7 @@ export type Config = {
|
||||
perChannelMaxNoteCacheCount: number;
|
||||
perUserNotificationsMaxCount: number;
|
||||
deactivateAntennaThreshold: number;
|
||||
pidFile: string;
|
||||
};
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
@@ -255,6 +257,7 @@ export function loadConfig(): Config {
|
||||
perChannelMaxNoteCacheCount: config.perChannelMaxNoteCacheCount ?? 1000,
|
||||
perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 300,
|
||||
deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7),
|
||||
pidFile: config.pidFile,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Brackets } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import type { AnnouncementReadsRepository, AnnouncementsRepository, MiAnnouncement, MiAnnouncementRead } from '@/models/_.js';
|
||||
import type { AnnouncementReadsRepository, AnnouncementsRepository, MiAnnouncement, MiAnnouncementRead, UsersRepository } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { Packed } from '@/misc/json-schema.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
@@ -23,6 +23,9 @@ export class AnnouncementService {
|
||||
@Inject(DI.announcementReadsRepository)
|
||||
private announcementReadsRepository: AnnouncementReadsRepository,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
private idService: IdService,
|
||||
private globalEventService: GlobalEventService,
|
||||
private moderationLogService: ModerationLogService,
|
||||
@@ -60,7 +63,7 @@ export class AnnouncementService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async create(values: Partial<MiAnnouncement>, moderator: MiUser): Promise<{ raw: MiAnnouncement; packed: Packed<'Announcement'> }> {
|
||||
public async create(values: Partial<MiAnnouncement>, moderator?: MiUser): Promise<{ raw: MiAnnouncement; packed: Packed<'Announcement'> }> {
|
||||
const announcement = await this.announcementsRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
createdAt: new Date(),
|
||||
@@ -82,20 +85,27 @@ export class AnnouncementService {
|
||||
announcement: packed,
|
||||
});
|
||||
|
||||
this.moderationLogService.log(moderator, 'createUserAnnouncement', {
|
||||
announcementId: announcement.id,
|
||||
announcement: announcement,
|
||||
userId: values.userId,
|
||||
});
|
||||
if (moderator) {
|
||||
const user = await this.usersRepository.findOneByOrFail({ id: values.userId });
|
||||
this.moderationLogService.log(moderator, 'createUserAnnouncement', {
|
||||
announcementId: announcement.id,
|
||||
announcement: announcement,
|
||||
userId: values.userId,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.globalEventService.publishBroadcastStream('announcementCreated', {
|
||||
announcement: packed,
|
||||
});
|
||||
|
||||
this.moderationLogService.log(moderator, 'createGlobalAnnouncement', {
|
||||
announcementId: announcement.id,
|
||||
announcement: announcement,
|
||||
});
|
||||
if (moderator) {
|
||||
this.moderationLogService.log(moderator, 'createGlobalAnnouncement', {
|
||||
announcementId: announcement.id,
|
||||
announcement: announcement,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -104,6 +114,63 @@ export class AnnouncementService {
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async update(announcement: MiAnnouncement, values: Partial<MiAnnouncement>, moderator?: MiUser): Promise<void> {
|
||||
await this.announcementsRepository.update(announcement.id, {
|
||||
updatedAt: new Date(),
|
||||
title: values.title,
|
||||
text: values.text,
|
||||
/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 空の文字列の場合、nullを渡すようにするため */
|
||||
imageUrl: values.imageUrl || null,
|
||||
display: values.display,
|
||||
icon: values.icon,
|
||||
forExistingUsers: values.forExistingUsers,
|
||||
needConfirmationToRead: values.needConfirmationToRead,
|
||||
isActive: values.isActive,
|
||||
});
|
||||
|
||||
const after = await this.announcementsRepository.findOneByOrFail({ id: announcement.id });
|
||||
|
||||
if (moderator) {
|
||||
if (announcement.userId) {
|
||||
const user = await this.usersRepository.findOneByOrFail({ id: announcement.userId });
|
||||
this.moderationLogService.log(moderator, 'updateUserAnnouncement', {
|
||||
announcementId: announcement.id,
|
||||
before: announcement,
|
||||
after: after,
|
||||
userId: announcement.userId,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
} else {
|
||||
this.moderationLogService.log(moderator, 'updateGlobalAnnouncement', {
|
||||
announcementId: announcement.id,
|
||||
before: announcement,
|
||||
after: after,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async delete(announcement: MiAnnouncement, moderator?: MiUser): Promise<void> {
|
||||
await this.announcementsRepository.delete(announcement.id);
|
||||
|
||||
if (moderator) {
|
||||
if (announcement.userId) {
|
||||
this.moderationLogService.log(moderator, 'deleteUserAnnouncement', {
|
||||
announcementId: announcement.id,
|
||||
announcement: announcement,
|
||||
});
|
||||
} else {
|
||||
this.moderationLogService.log(moderator, 'deleteGlobalAnnouncement', {
|
||||
announcementId: announcement.id,
|
||||
announcement: announcement,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async read(user: MiUser, announcementId: MiAnnouncement['id']): Promise<void> {
|
||||
try {
|
||||
|
@@ -15,7 +15,7 @@ import { DI } from '@/di-symbols.js';
|
||||
import type { AntennasRepository, UserListJoiningsRepository } from '@/models/_.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { StreamMessages } from '@/server/api/stream/types.js';
|
||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
@@ -50,7 +50,7 @@ export class AntennaService implements OnApplicationShutdown {
|
||||
const obj = JSON.parse(data);
|
||||
|
||||
if (obj.channel === 'internal') {
|
||||
const { type, body } = obj.message as StreamMessages['internal']['payload'];
|
||||
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
|
||||
switch (type) {
|
||||
case 'antennaCreated':
|
||||
this.antennas.push({
|
||||
|
@@ -11,7 +11,7 @@ import type { MiLocalUser, MiUser } from '@/models/User.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { StreamMessages } from '@/server/api/stream/types.js';
|
||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
@@ -160,7 +160,7 @@ export class CacheService implements OnApplicationShutdown {
|
||||
const obj = JSON.parse(data);
|
||||
|
||||
if (obj.channel === 'internal') {
|
||||
const { type, body } = obj.message as StreamMessages['internal']['payload'];
|
||||
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
|
||||
switch (type) {
|
||||
case 'userChangeSuspendedState':
|
||||
case 'remoteUserUpdated': {
|
||||
|
@@ -12,12 +12,13 @@ import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||
import type { MiEmoji } from '@/models/Emoji.js';
|
||||
import type { EmojisRepository, MiRole } from '@/models/_.js';
|
||||
import type { EmojisRepository, MiRole, MiUser } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { query } from '@/misc/prelude/url.js';
|
||||
import type { Serialized } from '@/server/api/stream/types.js';
|
||||
import type { Serialized } from '@/types.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/;
|
||||
|
||||
@@ -36,6 +37,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||
private utilityService: UtilityService,
|
||||
private idService: IdService,
|
||||
private emojiEntityService: EmojiEntityService,
|
||||
private moderationLogService: ModerationLogService,
|
||||
private globalEventService: GlobalEventService,
|
||||
) {
|
||||
this.cache = new MemoryKVCache<MiEmoji | null>(1000 * 60 * 60 * 12);
|
||||
@@ -66,7 +68,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||
isSensitive: boolean;
|
||||
localOnly: boolean;
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][];
|
||||
}): Promise<MiEmoji> {
|
||||
}, moderator?: MiUser): Promise<MiEmoji> {
|
||||
const emoji = await this.emojisRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
updatedAt: new Date(),
|
||||
@@ -89,6 +91,13 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||
this.globalEventService.publishBroadcastStream('emojiAdded', {
|
||||
emoji: await this.emojiEntityService.packDetailed(emoji.id),
|
||||
});
|
||||
|
||||
if (moderator) {
|
||||
this.moderationLogService.log(moderator, 'addCustomEmoji', {
|
||||
emojiId: emoji.id,
|
||||
emoji: emoji,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return emoji;
|
||||
@@ -104,7 +113,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||
isSensitive?: boolean;
|
||||
localOnly?: boolean;
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction?: MiRole['id'][];
|
||||
}): Promise<void> {
|
||||
}, moderator?: MiUser): Promise<void> {
|
||||
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
|
||||
const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() });
|
||||
if (sameNameEmoji != null && sameNameEmoji.id !== id) throw new Error('name already exists');
|
||||
@@ -125,11 +134,11 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||
|
||||
this.localEmojisCache.refresh();
|
||||
|
||||
const updated = await this.emojiEntityService.packDetailed(emoji.id);
|
||||
const packed = await this.emojiEntityService.packDetailed(emoji.id);
|
||||
|
||||
if (emoji.name === data.name) {
|
||||
this.globalEventService.publishBroadcastStream('emojiUpdated', {
|
||||
emojis: [updated],
|
||||
emojis: [packed],
|
||||
});
|
||||
} else {
|
||||
this.globalEventService.publishBroadcastStream('emojiDeleted', {
|
||||
@@ -137,7 +146,16 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||
});
|
||||
|
||||
this.globalEventService.publishBroadcastStream('emojiAdded', {
|
||||
emoji: updated,
|
||||
emoji: packed,
|
||||
});
|
||||
}
|
||||
|
||||
if (moderator) {
|
||||
const updated = await this.emojisRepository.findOneByOrFail({ id: id });
|
||||
this.moderationLogService.log(moderator, 'updateCustomEmoji', {
|
||||
emojiId: emoji.id,
|
||||
before: emoji,
|
||||
after: updated,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -231,7 +249,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async delete(id: MiEmoji['id']) {
|
||||
public async delete(id: MiEmoji['id'], moderator?: MiUser) {
|
||||
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
|
||||
|
||||
await this.emojisRepository.delete(emoji.id);
|
||||
@@ -241,16 +259,30 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||
this.globalEventService.publishBroadcastStream('emojiDeleted', {
|
||||
emojis: [await this.emojiEntityService.packDetailed(emoji)],
|
||||
});
|
||||
|
||||
if (moderator) {
|
||||
this.moderationLogService.log(moderator, 'deleteCustomEmoji', {
|
||||
emojiId: emoji.id,
|
||||
emoji: emoji,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async deleteBulk(ids: MiEmoji['id'][]) {
|
||||
public async deleteBulk(ids: MiEmoji['id'][], moderator?: MiUser) {
|
||||
const emojis = await this.emojisRepository.findBy({
|
||||
id: In(ids),
|
||||
});
|
||||
|
||||
for (const emoji of emojis) {
|
||||
await this.emojisRepository.delete(emoji.id);
|
||||
|
||||
if (moderator) {
|
||||
this.moderationLogService.log(moderator, 'deleteCustomEmoji', {
|
||||
emojiId: emoji.id,
|
||||
emoji: emoji,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.localEmojisCache.refresh();
|
||||
|
@@ -87,6 +87,9 @@ type UploadFromUrlArgs = {
|
||||
|
||||
@Injectable()
|
||||
export class DriveService {
|
||||
public static NoSuchFolderError = class extends Error {};
|
||||
public static InvalidFileNameError = class extends Error {};
|
||||
public static CannotUnmarkSensitiveError = class extends Error {};
|
||||
private registerLogger: Logger;
|
||||
private downloaderLogger: Logger;
|
||||
private deleteLogger: Logger;
|
||||
@@ -649,6 +652,62 @@ export class DriveService {
|
||||
return file;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async updateFile(file: MiDriveFile, values: Partial<MiDriveFile>, updater: MiUser) {
|
||||
const alwaysMarkNsfw = (await this.roleService.getUserPolicies(file.userId)).alwaysMarkNsfw;
|
||||
|
||||
if (values.name && !this.driveFileEntityService.validateFileName(file.name)) {
|
||||
throw new DriveService.InvalidFileNameError();
|
||||
}
|
||||
|
||||
if (values.isSensitive !== undefined && values.isSensitive !== file.isSensitive && alwaysMarkNsfw && !values.isSensitive) {
|
||||
throw new DriveService.CannotUnmarkSensitiveError();
|
||||
}
|
||||
|
||||
if (values.folderId != null) {
|
||||
const folder = await this.driveFoldersRepository.findOneBy({
|
||||
id: values.folderId,
|
||||
userId: file.userId!,
|
||||
});
|
||||
|
||||
if (folder == null) {
|
||||
throw new DriveService.NoSuchFolderError();
|
||||
}
|
||||
}
|
||||
|
||||
await this.driveFilesRepository.update(file.id, values);
|
||||
|
||||
const fileObj = await this.driveFileEntityService.pack(file.id, { self: true });
|
||||
|
||||
// Publish fileUpdated event
|
||||
if (file.userId) {
|
||||
this.globalEventService.publishDriveStream(file.userId, 'fileUpdated', fileObj);
|
||||
}
|
||||
|
||||
if (await this.roleService.isModerator(updater) && (file.userId !== updater.id)) {
|
||||
if (values.isSensitive !== undefined && values.isSensitive !== file.isSensitive) {
|
||||
const user = file.userId ? await this.usersRepository.findOneByOrFail({ id: file.userId }) : null;
|
||||
if (values.isSensitive) {
|
||||
this.moderationLogService.log(updater, 'markSensitiveDriveFile', {
|
||||
fileId: file.id,
|
||||
fileUserId: file.userId,
|
||||
fileUserUsername: user?.username ?? null,
|
||||
fileUserHost: user?.host ?? null,
|
||||
});
|
||||
} else {
|
||||
this.moderationLogService.log(updater, 'unmarkSensitiveDriveFile', {
|
||||
fileId: file.id,
|
||||
fileUserId: file.userId,
|
||||
fileUserUsername: user?.username ?? null,
|
||||
fileUserHost: user?.host ?? null,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fileObj;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async deleteFile(file: MiDriveFile, isExpired = false, deleter?: MiUser) {
|
||||
if (file.storedInternal) {
|
||||
@@ -741,9 +800,12 @@ export class DriveService {
|
||||
}
|
||||
|
||||
if (deleter && await this.roleService.isModerator(deleter) && (file.userId !== deleter.id)) {
|
||||
const user = file.userId ? await this.usersRepository.findOneByOrFail({ id: file.userId }) : null;
|
||||
this.moderationLogService.log(deleter, 'deleteDriveFile', {
|
||||
fileId: file.id,
|
||||
fileUserId: file.userId,
|
||||
fileUserUsername: user?.username ?? null,
|
||||
fileUserHost: user?.host ?? null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -5,27 +5,254 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import type { MiChannel } from '@/models/Channel.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import type { MiUserProfile } from '@/models/UserProfile.js';
|
||||
import type { MiNote } from '@/models/Note.js';
|
||||
import type { MiUserList } from '@/models/UserList.js';
|
||||
import type { MiAntenna } from '@/models/Antenna.js';
|
||||
import type {
|
||||
StreamChannels,
|
||||
AdminStreamTypes,
|
||||
AntennaStreamTypes,
|
||||
BroadcastTypes,
|
||||
DriveStreamTypes,
|
||||
InternalStreamTypes,
|
||||
MainStreamTypes,
|
||||
NoteStreamTypes,
|
||||
UserListStreamTypes,
|
||||
RoleTimelineStreamTypes,
|
||||
} from '@/server/api/stream/types.js';
|
||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||
import type { MiDriveFolder } from '@/models/DriveFolder.js';
|
||||
import type { MiUserList } from '@/models/UserList.js';
|
||||
import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
|
||||
import type { MiSignin } from '@/models/Signin.js';
|
||||
import type { MiPage } from '@/models/Page.js';
|
||||
import type { MiWebhook } from '@/models/Webhook.js';
|
||||
import type { MiMeta } from '@/models/Meta.js';
|
||||
import { MiRole, MiRoleAssignment } from '@/models/_.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { MiRole } from '@/models/_.js';
|
||||
import { Serialized } from '@/types.js';
|
||||
import type Emitter from 'strict-event-emitter-types';
|
||||
import type { EventEmitter } from 'events';
|
||||
|
||||
//#region Stream type-body definitions
|
||||
export interface BroadcastTypes {
|
||||
emojiAdded: {
|
||||
emoji: Packed<'EmojiDetailed'>;
|
||||
};
|
||||
emojiUpdated: {
|
||||
emojis: Packed<'EmojiDetailed'>[];
|
||||
};
|
||||
emojiDeleted: {
|
||||
emojis: {
|
||||
id?: string;
|
||||
name: string;
|
||||
[other: string]: any;
|
||||
}[];
|
||||
};
|
||||
announcementCreated: {
|
||||
announcement: Packed<'Announcement'>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MainEventTypes {
|
||||
notification: Packed<'Notification'>;
|
||||
mention: Packed<'Note'>;
|
||||
reply: Packed<'Note'>;
|
||||
renote: Packed<'Note'>;
|
||||
follow: Packed<'UserDetailedNotMe'>;
|
||||
followed: Packed<'User'>;
|
||||
unfollow: Packed<'User'>;
|
||||
meUpdated: Packed<'User'>;
|
||||
pageEvent: {
|
||||
pageId: MiPage['id'];
|
||||
event: string;
|
||||
var: any;
|
||||
userId: MiUser['id'];
|
||||
user: Packed<'User'>;
|
||||
};
|
||||
urlUploadFinished: {
|
||||
marker?: string | null;
|
||||
file: Packed<'DriveFile'>;
|
||||
};
|
||||
readAllNotifications: undefined;
|
||||
unreadNotification: Packed<'Notification'>;
|
||||
unreadMention: MiNote['id'];
|
||||
readAllUnreadMentions: undefined;
|
||||
unreadSpecifiedNote: MiNote['id'];
|
||||
readAllUnreadSpecifiedNotes: undefined;
|
||||
readAllAntennas: undefined;
|
||||
unreadAntenna: MiAntenna;
|
||||
readAllAnnouncements: undefined;
|
||||
myTokenRegenerated: undefined;
|
||||
signin: MiSignin;
|
||||
registryUpdated: {
|
||||
scope?: string[];
|
||||
key: string;
|
||||
value: any | null;
|
||||
};
|
||||
driveFileCreated: Packed<'DriveFile'>;
|
||||
readAntenna: MiAntenna;
|
||||
receiveFollowRequest: Packed<'User'>;
|
||||
announcementCreated: {
|
||||
announcement: Packed<'Announcement'>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DriveEventTypes {
|
||||
fileCreated: Packed<'DriveFile'>;
|
||||
fileDeleted: MiDriveFile['id'];
|
||||
fileUpdated: Packed<'DriveFile'>;
|
||||
folderCreated: Packed<'DriveFolder'>;
|
||||
folderDeleted: MiDriveFolder['id'];
|
||||
folderUpdated: Packed<'DriveFolder'>;
|
||||
}
|
||||
|
||||
export interface NoteEventTypes {
|
||||
pollVoted: {
|
||||
choice: number;
|
||||
userId: MiUser['id'];
|
||||
};
|
||||
deleted: {
|
||||
deletedAt: Date;
|
||||
};
|
||||
updated: {
|
||||
cw: string | null;
|
||||
text: string;
|
||||
};
|
||||
reacted: {
|
||||
reaction: string;
|
||||
emoji?: {
|
||||
name: string;
|
||||
url: string;
|
||||
} | null;
|
||||
userId: MiUser['id'];
|
||||
};
|
||||
unreacted: {
|
||||
reaction: string;
|
||||
userId: MiUser['id'];
|
||||
};
|
||||
}
|
||||
type NoteStreamEventTypes = {
|
||||
[key in keyof NoteEventTypes]: {
|
||||
id: MiNote['id'];
|
||||
body: NoteEventTypes[key];
|
||||
};
|
||||
};
|
||||
|
||||
export interface UserListEventTypes {
|
||||
userAdded: Packed<'User'>;
|
||||
userRemoved: Packed<'User'>;
|
||||
}
|
||||
|
||||
export interface AntennaEventTypes {
|
||||
note: MiNote;
|
||||
}
|
||||
|
||||
export interface RoleTimelineEventTypes {
|
||||
note: Packed<'Note'>;
|
||||
}
|
||||
|
||||
export interface AdminEventTypes {
|
||||
newAbuseUserReport: {
|
||||
id: MiAbuseUserReport['id'];
|
||||
targetUserId: MiUser['id'],
|
||||
reporterId: MiUser['id'],
|
||||
comment: string;
|
||||
};
|
||||
}
|
||||
//#endregion
|
||||
|
||||
// 辞書(interface or type)から{ type, body }ユニオンを定義
|
||||
// https://stackoverflow.com/questions/49311989/can-i-infer-the-type-of-a-value-using-extends-keyof-type
|
||||
// VS Codeの展開を防止するためにEvents型を定義
|
||||
type Events<T extends object> = { [K in keyof T]: { type: K; body: T[K]; } };
|
||||
type EventUnionFromDictionary<
|
||||
T extends object,
|
||||
U = Events<T>
|
||||
> = U[keyof U];
|
||||
|
||||
type SerializedAll<T> = {
|
||||
[K in keyof T]: Serialized<T[K]>;
|
||||
};
|
||||
|
||||
export interface InternalEventTypes {
|
||||
userChangeSuspendedState: { id: MiUser['id']; isSuspended: MiUser['isSuspended']; };
|
||||
userTokenRegenerated: { id: MiUser['id']; oldToken: string; newToken: string; };
|
||||
remoteUserUpdated: { id: MiUser['id']; };
|
||||
follow: { followerId: MiUser['id']; followeeId: MiUser['id']; };
|
||||
unfollow: { followerId: MiUser['id']; followeeId: MiUser['id']; };
|
||||
blockingCreated: { blockerId: MiUser['id']; blockeeId: MiUser['id']; };
|
||||
blockingDeleted: { blockerId: MiUser['id']; blockeeId: MiUser['id']; };
|
||||
policiesUpdated: MiRole['policies'];
|
||||
roleCreated: MiRole;
|
||||
roleDeleted: MiRole;
|
||||
roleUpdated: MiRole;
|
||||
userRoleAssigned: MiRoleAssignment;
|
||||
userRoleUnassigned: MiRoleAssignment;
|
||||
webhookCreated: MiWebhook;
|
||||
webhookDeleted: MiWebhook;
|
||||
webhookUpdated: MiWebhook;
|
||||
antennaCreated: MiAntenna;
|
||||
antennaDeleted: MiAntenna;
|
||||
antennaUpdated: MiAntenna;
|
||||
metaUpdated: MiMeta;
|
||||
followChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
|
||||
unfollowChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
|
||||
updateUserProfile: MiUserProfile;
|
||||
mute: { muterId: MiUser['id']; muteeId: MiUser['id']; };
|
||||
unmute: { muterId: MiUser['id']; muteeId: MiUser['id']; };
|
||||
userListMemberAdded: { userListId: MiUserList['id']; memberId: MiUser['id']; };
|
||||
userListMemberRemoved: { userListId: MiUserList['id']; memberId: MiUser['id']; };
|
||||
}
|
||||
|
||||
// name/messages(spec) pairs dictionary
|
||||
export type GlobalEvents = {
|
||||
internal: {
|
||||
name: 'internal';
|
||||
payload: EventUnionFromDictionary<SerializedAll<InternalEventTypes>>;
|
||||
};
|
||||
broadcast: {
|
||||
name: 'broadcast';
|
||||
payload: EventUnionFromDictionary<SerializedAll<BroadcastTypes>>;
|
||||
};
|
||||
main: {
|
||||
name: `mainStream:${MiUser['id']}`;
|
||||
payload: EventUnionFromDictionary<SerializedAll<MainEventTypes>>;
|
||||
};
|
||||
drive: {
|
||||
name: `driveStream:${MiUser['id']}`;
|
||||
payload: EventUnionFromDictionary<SerializedAll<DriveEventTypes>>;
|
||||
};
|
||||
note: {
|
||||
name: `noteStream:${MiNote['id']}`;
|
||||
payload: EventUnionFromDictionary<SerializedAll<NoteStreamEventTypes>>;
|
||||
};
|
||||
userList: {
|
||||
name: `userListStream:${MiUserList['id']}`;
|
||||
payload: EventUnionFromDictionary<SerializedAll<UserListEventTypes>>;
|
||||
};
|
||||
roleTimeline: {
|
||||
name: `roleTimelineStream:${MiRole['id']}`;
|
||||
payload: EventUnionFromDictionary<SerializedAll<RoleTimelineEventTypes>>;
|
||||
};
|
||||
antenna: {
|
||||
name: `antennaStream:${MiAntenna['id']}`;
|
||||
payload: EventUnionFromDictionary<SerializedAll<AntennaEventTypes>>;
|
||||
};
|
||||
admin: {
|
||||
name: `adminStream:${MiUser['id']}`;
|
||||
payload: EventUnionFromDictionary<SerializedAll<AdminEventTypes>>;
|
||||
};
|
||||
notes: {
|
||||
name: 'notesStream';
|
||||
payload: Serialized<Packed<'Note'>>;
|
||||
};
|
||||
};
|
||||
|
||||
// API event definitions
|
||||
// ストリームごとのEmitterの辞書を用意
|
||||
type EventEmitterDictionary = { [x in keyof GlobalEvents]: Emitter.default<EventEmitter, { [y in GlobalEvents[x]['name']]: (e: GlobalEvents[x]['payload']) => void }> };
|
||||
// 共用体型を交差型にする型 https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection
|
||||
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
||||
// Emitter辞書から共用体型を作り、UnionToIntersectionで交差型にする
|
||||
export type StreamEventEmitter = UnionToIntersection<EventEmitterDictionary[keyof GlobalEvents]>;
|
||||
// { [y in name]: (e: spec) => void }をまとめてその交差型をEmitterにかけるとts(2590)にひっかかる
|
||||
|
||||
// provide stream channels union
|
||||
export type StreamChannels = GlobalEvents[keyof GlobalEvents]['name'];
|
||||
|
||||
@Injectable()
|
||||
export class GlobalEventService {
|
||||
@@ -51,7 +278,7 @@ export class GlobalEventService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishInternalEvent<K extends keyof InternalStreamTypes>(type: K, value?: InternalStreamTypes[K]): void {
|
||||
public publishInternalEvent<K extends keyof InternalEventTypes>(type: K, value?: InternalEventTypes[K]): void {
|
||||
this.publish('internal', type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
@@ -61,17 +288,17 @@ export class GlobalEventService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishMainStream<K extends keyof MainStreamTypes>(userId: MiUser['id'], type: K, value?: MainStreamTypes[K]): void {
|
||||
public publishMainStream<K extends keyof MainEventTypes>(userId: MiUser['id'], type: K, value?: MainEventTypes[K]): void {
|
||||
this.publish(`mainStream:${userId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishDriveStream<K extends keyof DriveStreamTypes>(userId: MiUser['id'], type: K, value?: DriveStreamTypes[K]): void {
|
||||
public publishDriveStream<K extends keyof DriveEventTypes>(userId: MiUser['id'], type: K, value?: DriveEventTypes[K]): void {
|
||||
this.publish(`driveStream:${userId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishNoteStream<K extends keyof NoteStreamTypes>(noteId: MiNote['id'], type: K, value?: NoteStreamTypes[K]): void {
|
||||
public publishNoteStream<K extends keyof NoteEventTypes>(noteId: MiNote['id'], type: K, value?: NoteEventTypes[K]): void {
|
||||
this.publish(`noteStream:${noteId}`, type, {
|
||||
id: noteId,
|
||||
body: value,
|
||||
@@ -79,17 +306,17 @@ export class GlobalEventService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishUserListStream<K extends keyof UserListStreamTypes>(listId: MiUserList['id'], type: K, value?: UserListStreamTypes[K]): void {
|
||||
public publishUserListStream<K extends keyof UserListEventTypes>(listId: MiUserList['id'], type: K, value?: UserListEventTypes[K]): void {
|
||||
this.publish(`userListStream:${listId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishAntennaStream<K extends keyof AntennaStreamTypes>(antennaId: MiAntenna['id'], type: K, value?: AntennaStreamTypes[K]): void {
|
||||
public publishAntennaStream<K extends keyof AntennaEventTypes>(antennaId: MiAntenna['id'], type: K, value?: AntennaEventTypes[K]): void {
|
||||
this.publish(`antennaStream:${antennaId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishRoleTimelineStream<K extends keyof RoleTimelineStreamTypes>(roleId: MiRole['id'], type: K, value?: RoleTimelineStreamTypes[K]): void {
|
||||
public publishRoleTimelineStream<K extends keyof RoleTimelineEventTypes>(roleId: MiRole['id'], type: K, value?: RoleTimelineEventTypes[K]): void {
|
||||
this.publish(`roleTimelineStream:${roleId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
@@ -99,7 +326,7 @@ export class GlobalEventService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishAdminStream<K extends keyof AdminStreamTypes>(userId: MiUser['id'], type: K, value?: AdminStreamTypes[K]): void {
|
||||
public publishAdminStream<K extends keyof AdminEventTypes>(userId: MiUser['id'], type: K, value?: AdminEventTypes[K]): void {
|
||||
this.publish(`adminStream:${userId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ import { DI } from '@/di-symbols.js';
|
||||
import { MiMeta } from '@/models/Meta.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { StreamMessages } from '@/server/api/stream/types.js';
|
||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
@@ -46,7 +46,7 @@ export class MetaService implements OnApplicationShutdown {
|
||||
const obj = JSON.parse(data);
|
||||
|
||||
if (obj.channel === 'internal') {
|
||||
const { type, body } = obj.message as StreamMessages['internal']['payload'];
|
||||
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
|
||||
switch (type) {
|
||||
case 'metaUpdated': {
|
||||
this.cache = body;
|
||||
|
@@ -110,9 +110,8 @@ class NotificationManager {
|
||||
// 通知される側のユーザーが通知する側のユーザーをミュートしていない限りは通知する
|
||||
if (!mentioneesMutedUserIds.includes(this.notifier.id)) {
|
||||
this.notificationService.createNotification(x.target, x.reason, {
|
||||
notifierId: this.notifier.id,
|
||||
noteId: this.note.id,
|
||||
});
|
||||
}, this.notifier.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -515,9 +514,8 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||
}).then(followings => {
|
||||
for (const following of followings) {
|
||||
this.notificationService.createNotification(following.followerId, 'note', {
|
||||
notifierId: user.id,
|
||||
noteId: note.id,
|
||||
});
|
||||
}, user.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -135,9 +135,12 @@ export class NoteDeleteService {
|
||||
});
|
||||
|
||||
if (deleter && (note.userId !== deleter.id)) {
|
||||
const user = await this.usersRepository.findOneByOrFail({ id: note.userId });
|
||||
this.moderationLogService.log(deleter, 'deleteNote', {
|
||||
noteId: note.id,
|
||||
noteUserId: note.userId,
|
||||
noteUserUsername: user.username,
|
||||
noteUserHost: user.host,
|
||||
note: note,
|
||||
});
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@ import { NotificationEntityService } from '@/core/entities/NotificationEntitySer
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { UserListService } from '@/core/UserListService.js';
|
||||
|
||||
@Injectable()
|
||||
export class NotificationService implements OnApplicationShutdown {
|
||||
@@ -38,6 +39,7 @@ export class NotificationService implements OnApplicationShutdown {
|
||||
private globalEventService: GlobalEventService,
|
||||
private pushNotificationService: PushNotificationService,
|
||||
private cacheService: CacheService,
|
||||
private userListService: UserListService,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -74,27 +76,56 @@ export class NotificationService implements OnApplicationShutdown {
|
||||
public async createNotification(
|
||||
notifieeId: MiUser['id'],
|
||||
type: MiNotification['type'],
|
||||
data: Partial<MiNotification>,
|
||||
data: Omit<Partial<MiNotification>, 'notifierId'>,
|
||||
notifierId?: MiUser['id'] | null,
|
||||
): Promise<MiNotification | null> {
|
||||
const profile = await this.cacheService.userProfileCache.fetch(notifieeId);
|
||||
const isMuted = profile.mutingNotificationTypes.includes(type);
|
||||
if (isMuted) return null;
|
||||
const recieveConfig = profile.notificationRecieveConfig[type];
|
||||
if (recieveConfig?.type === 'never') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data.notifierId) {
|
||||
if (notifieeId === data.notifierId) {
|
||||
if (notifierId) {
|
||||
if (notifieeId === notifierId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const mutings = await this.cacheService.userMutingsCache.fetch(notifieeId);
|
||||
if (mutings.has(data.notifierId)) {
|
||||
if (mutings.has(notifierId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (recieveConfig?.type === 'following') {
|
||||
const isFollowing = await this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => followings.has(notifierId));
|
||||
if (!isFollowing) {
|
||||
return null;
|
||||
}
|
||||
} else if (recieveConfig?.type === 'follower') {
|
||||
const isFollower = await this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => followings.has(notifieeId));
|
||||
if (!isFollower) {
|
||||
return null;
|
||||
}
|
||||
} else if (recieveConfig?.type === 'mutualFollow') {
|
||||
const [isFollowing, isFollower] = await Promise.all([
|
||||
this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => followings.has(notifierId)),
|
||||
this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => followings.has(notifieeId)),
|
||||
]);
|
||||
if (!isFollowing && !isFollower) {
|
||||
return null;
|
||||
}
|
||||
} else if (recieveConfig?.type === 'list') {
|
||||
const isMember = await this.userListService.membersCache.fetch(recieveConfig.userListId).then(members => members.has(notifierId));
|
||||
if (!isMember) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const notification = {
|
||||
id: this.idService.genId(),
|
||||
createdAt: new Date(),
|
||||
type: type,
|
||||
notifierId: notifierId,
|
||||
...data,
|
||||
} as MiNotification;
|
||||
|
||||
@@ -117,8 +148,8 @@ export class NotificationService implements OnApplicationShutdown {
|
||||
this.globalEventService.publishMainStream(notifieeId, 'unreadNotification', packed);
|
||||
this.pushNotificationService.pushNotification(notifieeId, 'notification', packed);
|
||||
|
||||
if (type === 'follow') this.emailNotificationFollow(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! }));
|
||||
if (type === 'receiveFollowRequest') this.emailNotificationReceiveFollowRequest(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! }));
|
||||
if (type === 'follow') this.emailNotificationFollow(notifieeId, await this.usersRepository.findOneByOrFail({ id: notifierId! }));
|
||||
if (type === 'receiveFollowRequest') this.emailNotificationReceiveFollowRequest(notifieeId, await this.usersRepository.findOneByOrFail({ id: notifierId! }));
|
||||
}, () => { /* aborted, ignore it */ });
|
||||
|
||||
return notification;
|
||||
|
@@ -219,10 +219,9 @@ export class ReactionService {
|
||||
// リアクションされたユーザーがローカルユーザーなら通知を作成
|
||||
if (note.userHost === null) {
|
||||
this.notificationService.createNotification(note.userId, 'reaction', {
|
||||
notifierId: user.id,
|
||||
noteId: note.id,
|
||||
reaction: reaction,
|
||||
});
|
||||
}, user.id);
|
||||
}
|
||||
|
||||
//#region 配信
|
||||
|
@@ -15,7 +15,7 @@ import { MetaService } from '@/core/MetaService.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import type { RoleCondFormulaValue } from '@/models/Role.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { StreamMessages } from '@/server/api/stream/types.js';
|
||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
@@ -26,6 +26,7 @@ export type RolePolicies = {
|
||||
gtlAvailable: boolean;
|
||||
ltlAvailable: boolean;
|
||||
canPublicNote: boolean;
|
||||
canEditNote: boolean;
|
||||
canInvite: boolean;
|
||||
inviteLimit: number;
|
||||
inviteLimitCycle: number;
|
||||
@@ -50,6 +51,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
||||
gtlAvailable: true,
|
||||
ltlAvailable: true,
|
||||
canPublicNote: true,
|
||||
canEditNote: true,
|
||||
canInvite: false,
|
||||
inviteLimit: 0,
|
||||
inviteLimitCycle: 60 * 24 * 7,
|
||||
@@ -114,7 +116,7 @@ export class RoleService implements OnApplicationShutdown {
|
||||
const obj = JSON.parse(data);
|
||||
|
||||
if (obj.channel === 'internal') {
|
||||
const { type, body } = obj.message as StreamMessages['internal']['payload'];
|
||||
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
|
||||
switch (type) {
|
||||
case 'roleCreated': {
|
||||
const cached = this.rolesCache.get();
|
||||
@@ -294,6 +296,7 @@ export class RoleService implements OnApplicationShutdown {
|
||||
gtlAvailable: calc('gtlAvailable', vs => vs.some(v => v === true)),
|
||||
ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)),
|
||||
canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)),
|
||||
canEditNote: calc('canEditNote', vs => vs.some(v => v === true)),
|
||||
canInvite: calc('canInvite', vs => vs.some(v => v === true)),
|
||||
inviteLimit: calc('inviteLimit', vs => Math.max(...vs)),
|
||||
inviteLimitCycle: calc('inviteLimitCycle', vs => Math.max(...vs)),
|
||||
@@ -412,10 +415,13 @@ export class RoleService implements OnApplicationShutdown {
|
||||
this.globalEventService.publishInternalEvent('userRoleAssigned', created);
|
||||
|
||||
if (moderator) {
|
||||
const user = await this.usersRepository.findOneByOrFail({ id: userId });
|
||||
this.moderationLogService.log(moderator, 'assignRole', {
|
||||
roleId: roleId,
|
||||
roleName: role.name,
|
||||
userId: userId,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
expiresAt: expiresAt ? expiresAt.toISOString() : null,
|
||||
});
|
||||
}
|
||||
@@ -445,11 +451,16 @@ export class RoleService implements OnApplicationShutdown {
|
||||
this.globalEventService.publishInternalEvent('userRoleUnassigned', existing);
|
||||
|
||||
if (moderator) {
|
||||
const role = await this.rolesRepository.findOneByOrFail({ id: roleId });
|
||||
const [user, role] = await Promise.all([
|
||||
this.usersRepository.findOneByOrFail({ id: userId }),
|
||||
this.rolesRepository.findOneByOrFail({ id: roleId }),
|
||||
]);
|
||||
this.moderationLogService.log(moderator, 'unassignRole', {
|
||||
roleId: roleId,
|
||||
roleName: role.name,
|
||||
userId: userId,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -473,6 +484,42 @@ export class RoleService implements OnApplicationShutdown {
|
||||
redisPipeline.exec();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async create(values: Partial<MiRole>, moderator?: MiUser): Promise<MiRole> {
|
||||
const date = new Date();
|
||||
const created = await this.rolesRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
createdAt: date,
|
||||
updatedAt: date,
|
||||
lastUsedAt: date,
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
color: values.color,
|
||||
iconUrl: values.iconUrl,
|
||||
target: values.target,
|
||||
condFormula: values.condFormula,
|
||||
isPublic: values.isPublic,
|
||||
isAdministrator: values.isAdministrator,
|
||||
isModerator: values.isModerator,
|
||||
isExplorable: values.isExplorable,
|
||||
asBadge: values.asBadge,
|
||||
canEditMembersByModerator: values.canEditMembersByModerator,
|
||||
displayOrder: values.displayOrder,
|
||||
policies: values.policies,
|
||||
}).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
this.globalEventService.publishInternalEvent('roleCreated', created);
|
||||
|
||||
if (moderator) {
|
||||
this.moderationLogService.log(moderator, 'createRole', {
|
||||
roleId: created.id,
|
||||
role: created,
|
||||
});
|
||||
}
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async update(role: MiRole, params: Partial<MiRole>, moderator?: MiUser): Promise<void> {
|
||||
const date = new Date();
|
||||
@@ -493,6 +540,19 @@ export class RoleService implements OnApplicationShutdown {
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async delete(role: MiRole, moderator?: MiUser): Promise<void> {
|
||||
await this.rolesRepository.delete({ id: role.id });
|
||||
this.globalEventService.publishInternalEvent('roleDeleted', role);
|
||||
|
||||
if (moderator) {
|
||||
this.moderationLogService.log(moderator, 'deleteRole', {
|
||||
roleId: role.id,
|
||||
role: role,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public dispose(): void {
|
||||
this.redisForSub.off('message', this.onMessage);
|
||||
|
@@ -230,8 +230,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||
|
||||
// 通知を作成
|
||||
this.notificationService.createNotification(follower.id, 'followRequestAccepted', {
|
||||
notifierId: followee.id,
|
||||
});
|
||||
}, followee.id);
|
||||
}
|
||||
|
||||
if (alreadyFollowed) return;
|
||||
@@ -304,8 +303,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||
|
||||
// 通知を作成
|
||||
this.notificationService.createNotification(followee.id, 'follow', {
|
||||
notifierId: follower.id,
|
||||
});
|
||||
}, follower.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,9 +486,8 @@ export class UserFollowingService implements OnModuleInit {
|
||||
|
||||
// 通知を作成
|
||||
this.notificationService.createNotification(followee.id, 'receiveFollowRequest', {
|
||||
notifierId: follower.id,
|
||||
followRequestId: followRequest.id,
|
||||
});
|
||||
}, follower.id);
|
||||
}
|
||||
|
||||
if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
|
||||
|
@@ -3,7 +3,8 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import type { UserListJoiningsRepository } from '@/models/_.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import type { MiUserList } from '@/models/UserList.js';
|
||||
@@ -16,12 +17,22 @@ import { ProxyAccountService } from '@/core/ProxyAccountService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { RedisKVCache } from '@/misc/cache.js';
|
||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
|
||||
@Injectable()
|
||||
export class UserListService {
|
||||
export class UserListService implements OnApplicationShutdown {
|
||||
public static TooManyUsersError = class extends Error {};
|
||||
|
||||
public membersCache: RedisKVCache<Set<string>>;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.redis)
|
||||
private redisClient: Redis.Redis,
|
||||
|
||||
@Inject(DI.redisForSub)
|
||||
private redisForSub: Redis.Redis,
|
||||
|
||||
@Inject(DI.userListJoiningsRepository)
|
||||
private userListJoiningsRepository: UserListJoiningsRepository,
|
||||
|
||||
@@ -32,10 +43,48 @@ export class UserListService {
|
||||
private proxyAccountService: ProxyAccountService,
|
||||
private queueService: QueueService,
|
||||
) {
|
||||
this.membersCache = new RedisKVCache<Set<string>>(this.redisClient, 'userListMembers', {
|
||||
lifetime: 1000 * 60 * 30, // 30m
|
||||
memoryCacheLifetime: 1000 * 60, // 1m
|
||||
fetcher: (key) => this.userListJoiningsRepository.find({ where: { userListId: key }, select: ['userId'] }).then(xs => new Set(xs.map(x => x.userId))),
|
||||
toRedisConverter: (value) => JSON.stringify(Array.from(value)),
|
||||
fromRedisConverter: (value) => new Set(JSON.parse(value)),
|
||||
});
|
||||
|
||||
this.redisForSub.on('message', this.onMessage);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async push(target: MiUser, list: MiUserList, me: MiUser) {
|
||||
private async onMessage(_: string, data: string): Promise<void> {
|
||||
const obj = JSON.parse(data);
|
||||
|
||||
if (obj.channel === 'internal') {
|
||||
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
|
||||
switch (type) {
|
||||
case 'userListMemberAdded': {
|
||||
const { userListId, memberId } = body;
|
||||
const members = await this.membersCache.get(userListId);
|
||||
if (members) {
|
||||
members.add(memberId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'userListMemberRemoved': {
|
||||
const { userListId, memberId } = body;
|
||||
const members = await this.membersCache.get(userListId);
|
||||
if (members) {
|
||||
members.delete(memberId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async addMember(target: MiUser, list: MiUserList, me: MiUser) {
|
||||
const currentCount = await this.userListJoiningsRepository.countBy({
|
||||
userListId: list.id,
|
||||
});
|
||||
@@ -50,6 +99,7 @@ export class UserListService {
|
||||
userListId: list.id,
|
||||
} as MiUserListJoining);
|
||||
|
||||
this.globalEventService.publishInternalEvent('userListMemberAdded', { userListId: list.id, memberId: target.id });
|
||||
this.globalEventService.publishUserListStream(list.id, 'userAdded', await this.userEntityService.pack(target));
|
||||
|
||||
// このインスタンス内にこのリモートユーザーをフォローしているユーザーがいなくても投稿を受け取るためにダミーのユーザーがフォローしたということにする
|
||||
@@ -60,4 +110,26 @@ export class UserListService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async removeMember(target: MiUser, list: MiUserList) {
|
||||
await this.userListJoiningsRepository.delete({
|
||||
userId: target.id,
|
||||
userListId: list.id,
|
||||
});
|
||||
|
||||
this.globalEventService.publishInternalEvent('userListMemberRemoved', { userListId: list.id, memberId: target.id });
|
||||
this.globalEventService.publishUserListStream(list.id, 'userRemoved', await this.userEntityService.pack(target));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public dispose(): void {
|
||||
this.redisForSub.off('message', this.onMessage);
|
||||
this.membersCache.dispose();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ import type { WebhooksRepository } from '@/models/_.js';
|
||||
import type { MiWebhook } from '@/models/Webhook.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { StreamMessages } from '@/server/api/stream/types.js';
|
||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
@@ -45,7 +45,7 @@ export class WebhookService implements OnApplicationShutdown {
|
||||
const obj = JSON.parse(data);
|
||||
|
||||
if (obj.channel === 'internal') {
|
||||
const { type, body } = obj.message as StreamMessages['internal']['payload'];
|
||||
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
|
||||
switch (type) {
|
||||
case 'webhookCreated':
|
||||
if (body.active) {
|
||||
|
@@ -308,6 +308,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||
const packed: Packed<'Note'> = await awaitAll({
|
||||
id: note.id,
|
||||
createdAt: note.createdAt.toISOString(),
|
||||
updatedAt: note.updatedAt ? note.updatedAt.toISOString() : undefined,
|
||||
userId: note.userId,
|
||||
user: this.userEntityService.pack(note.user ?? note.userId, me, {
|
||||
detail: false,
|
||||
|
@@ -452,7 +452,7 @@ export class UserEntityService implements OnModuleInit {
|
||||
hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id),
|
||||
mutedWords: profile!.mutedWords,
|
||||
mutedInstances: profile!.mutedInstances,
|
||||
mutingNotificationTypes: profile!.mutingNotificationTypes,
|
||||
notificationRecieveConfig: profile!.notificationRecieveConfig,
|
||||
emailNotificationTypes: profile!.emailNotificationTypes,
|
||||
achievements: profile!.achievements,
|
||||
loggedInDays: profile!.loggedInDates.length,
|
||||
|
@@ -24,6 +24,11 @@ export class MiNote {
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
default: null,
|
||||
})
|
||||
public updatedAt: Date | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
|
@@ -8,6 +8,7 @@ import { obsoleteNotificationTypes, ffVisibility, notificationTypes } from '@/ty
|
||||
import { id } from './util/id.js';
|
||||
import { MiUser } from './User.js';
|
||||
import { MiPage } from './Page.js';
|
||||
import { MiUserList } from './UserList.js';
|
||||
|
||||
// TODO: このテーブルで管理している情報すべてレジストリで管理するようにしても良いかも
|
||||
// ただ、「emailVerified が true なユーザーを find する」のようなクエリは書けなくなるからウーン
|
||||
@@ -222,16 +223,25 @@ export class MiUserProfile {
|
||||
})
|
||||
public mutedInstances: string[];
|
||||
|
||||
@Column('enum', {
|
||||
enum: [
|
||||
...notificationTypes,
|
||||
// マイグレーションで削除が困難なので古いenumは残しておく
|
||||
...obsoleteNotificationTypes,
|
||||
],
|
||||
array: true,
|
||||
default: [],
|
||||
@Column('jsonb', {
|
||||
default: {},
|
||||
})
|
||||
public mutingNotificationTypes: typeof notificationTypes[number][];
|
||||
public notificationRecieveConfig: {
|
||||
[notificationType in typeof notificationTypes[number]]?: {
|
||||
type: 'all';
|
||||
} | {
|
||||
type: 'never';
|
||||
} | {
|
||||
type: 'following';
|
||||
} | {
|
||||
type: 'follower';
|
||||
} | {
|
||||
type: 'mutualFollow';
|
||||
} | {
|
||||
type: 'list';
|
||||
userListId: MiUserList['id'];
|
||||
};
|
||||
};
|
||||
|
||||
@Column('varchar', {
|
||||
length: 32, array: true, default: '{}',
|
||||
|
@@ -17,6 +17,11 @@ export const packedNoteSchema = {
|
||||
optional: false, nullable: false,
|
||||
format: 'date-time',
|
||||
},
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
format: 'date-time',
|
||||
},
|
||||
deletedAt: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
@@ -142,7 +147,7 @@ export const packedNoteSchema = {
|
||||
isSensitive: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@@ -387,13 +387,9 @@ export const packedMeDetailedOnlySchema = {
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
},
|
||||
mutingNotificationTypes: {
|
||||
type: 'array',
|
||||
nullable: true, optional: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
notificationRecieveConfig: {
|
||||
type: 'object',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
emailNotificationTypes: {
|
||||
type: 'array',
|
||||
|
@@ -101,7 +101,7 @@ export class ImportUserListsProcessorService {
|
||||
|
||||
if (await this.userListJoiningsRepository.findOneBy({ userListId: list!.id, userId: target.id }) != null) continue;
|
||||
|
||||
this.userListService.push(target, list!, user);
|
||||
this.userListService.addMember(target, list!, user);
|
||||
} catch (e) {
|
||||
this.logger.warn(`Error in line:${linenum} ${e}`);
|
||||
}
|
||||
|
@@ -258,6 +258,7 @@ import * as ep___notes_clips from './endpoints/notes/clips.js';
|
||||
import * as ep___notes_conversation from './endpoints/notes/conversation.js';
|
||||
import * as ep___notes_create from './endpoints/notes/create.js';
|
||||
import * as ep___notes_delete from './endpoints/notes/delete.js';
|
||||
import * as ep___notes_update from './endpoints/notes/update.js';
|
||||
import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
|
||||
import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
|
||||
import * as ep___notes_featured from './endpoints/notes/featured.js';
|
||||
@@ -606,6 +607,7 @@ const $notes_clips: Provider = { provide: 'ep:notes/clips', useClass: ep___notes
|
||||
const $notes_conversation: Provider = { provide: 'ep:notes/conversation', useClass: ep___notes_conversation.default };
|
||||
const $notes_create: Provider = { provide: 'ep:notes/create', useClass: ep___notes_create.default };
|
||||
const $notes_delete: Provider = { provide: 'ep:notes/delete', useClass: ep___notes_delete.default };
|
||||
const $notes_update: Provider = { provide: 'ep:notes/update', useClass: ep___notes_update.default };
|
||||
const $notes_favorites_create: Provider = { provide: 'ep:notes/favorites/create', useClass: ep___notes_favorites_create.default };
|
||||
const $notes_favorites_delete: Provider = { provide: 'ep:notes/favorites/delete', useClass: ep___notes_favorites_delete.default };
|
||||
const $notes_featured: Provider = { provide: 'ep:notes/featured', useClass: ep___notes_featured.default };
|
||||
@@ -958,6 +960,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$notes_conversation,
|
||||
$notes_create,
|
||||
$notes_delete,
|
||||
$notes_update,
|
||||
$notes_favorites_create,
|
||||
$notes_favorites_delete,
|
||||
$notes_featured,
|
||||
@@ -1304,6 +1307,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$notes_conversation,
|
||||
$notes_create,
|
||||
$notes_delete,
|
||||
$notes_update,
|
||||
$notes_favorites_create,
|
||||
$notes_favorites_delete,
|
||||
$notes_featured,
|
||||
|
@@ -258,6 +258,7 @@ import * as ep___notes_clips from './endpoints/notes/clips.js';
|
||||
import * as ep___notes_conversation from './endpoints/notes/conversation.js';
|
||||
import * as ep___notes_create from './endpoints/notes/create.js';
|
||||
import * as ep___notes_delete from './endpoints/notes/delete.js';
|
||||
import * as ep___notes_update from './endpoints/notes/update.js';
|
||||
import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
|
||||
import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
|
||||
import * as ep___notes_featured from './endpoints/notes/featured.js';
|
||||
@@ -604,6 +605,7 @@ const eps = [
|
||||
['notes/conversation', ep___notes_conversation],
|
||||
['notes/create', ep___notes_create],
|
||||
['notes/delete', ep___notes_delete],
|
||||
['notes/update', ep___notes_update],
|
||||
['notes/favorites/create', ep___notes_favorites_create],
|
||||
['notes/favorites/delete', ep___notes_favorites_delete],
|
||||
['notes/featured', ep___notes_featured],
|
||||
|
@@ -8,6 +8,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { AdsRepository } from '@/models/_.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
@@ -39,9 +40,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private adsRepository: AdsRepository,
|
||||
|
||||
private idService: IdService,
|
||||
private moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.adsRepository.insert({
|
||||
const ad = await this.adsRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
createdAt: new Date(),
|
||||
expiresAt: new Date(ps.expiresAt),
|
||||
@@ -53,7 +55,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
ratio: ps.ratio,
|
||||
place: ps.place,
|
||||
memo: ps.memo,
|
||||
}).then(r => this.adsRepository.findOneByOrFail({ id: r.identifiers[0].id }));
|
||||
|
||||
this.moderationLogService.log(me, 'createAd', {
|
||||
adId: ad.id,
|
||||
ad: ad,
|
||||
});
|
||||
|
||||
return ad;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { AdsRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -37,6 +38,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
constructor(
|
||||
@Inject(DI.adsRepository)
|
||||
private adsRepository: AdsRepository,
|
||||
|
||||
private moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const ad = await this.adsRepository.findOneBy({ id: ps.id });
|
||||
@@ -44,6 +47,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
if (ad == null) throw new ApiError(meta.errors.noSuchAd);
|
||||
|
||||
await this.adsRepository.delete(ad.id);
|
||||
|
||||
this.moderationLogService.log(me, 'deleteAd', {
|
||||
adId: ad.id,
|
||||
ad: ad,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
publishing: { type: 'boolean', default: false },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
@@ -36,6 +37,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.adsRepository.createQueryBuilder('ad'), ps.sinceId, ps.untilId);
|
||||
if (ps.publishing) {
|
||||
query.andWhere('ad.expiresAt > :now', { now: new Date() }).andWhere('ad.startsAt <= :now', { now: new Date() });
|
||||
}
|
||||
const ads = await query.limit(ps.limit).getMany();
|
||||
|
||||
return ads;
|
||||
|
@@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { AdsRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -46,6 +47,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
constructor(
|
||||
@Inject(DI.adsRepository)
|
||||
private adsRepository: AdsRepository,
|
||||
|
||||
private moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const ad = await this.adsRepository.findOneBy({ id: ps.id });
|
||||
@@ -63,6 +66,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
startsAt: new Date(ps.startsAt),
|
||||
dayOfWeek: ps.dayOfWeek,
|
||||
});
|
||||
|
||||
const updatedAd = await this.adsRepository.findOneByOrFail({ id: ad.id });
|
||||
|
||||
this.moderationLogService.log(me, 'updateAd', {
|
||||
adId: ad.id,
|
||||
before: ad,
|
||||
after: updatedAd,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { AnnouncementsRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { AnnouncementService } from '@/core/AnnouncementService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -37,13 +38,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
constructor(
|
||||
@Inject(DI.announcementsRepository)
|
||||
private announcementsRepository: AnnouncementsRepository,
|
||||
|
||||
private announcementService: AnnouncementService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const announcement = await this.announcementsRepository.findOneBy({ id: ps.id });
|
||||
|
||||
if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
|
||||
|
||||
await this.announcementsRepository.delete(announcement.id);
|
||||
await this.announcementService.delete(announcement, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { AnnouncementsRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { AnnouncementService } from '@/core/AnnouncementService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -45,13 +46,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
constructor(
|
||||
@Inject(DI.announcementsRepository)
|
||||
private announcementsRepository: AnnouncementsRepository,
|
||||
|
||||
private announcementService: AnnouncementService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const announcement = await this.announcementsRepository.findOneBy({ id: ps.id });
|
||||
|
||||
if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
|
||||
|
||||
await this.announcementsRepository.update(announcement.id, {
|
||||
await this.announcementService.update(announcement, {
|
||||
updatedAt: new Date(),
|
||||
title: ps.title,
|
||||
text: ps.text,
|
||||
@@ -62,7 +65,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
forExistingUsers: ps.forExistingUsers,
|
||||
needConfirmationToRead: ps.needConfirmationToRead,
|
||||
isActive: ps.isActive,
|
||||
});
|
||||
}, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,6 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { DriveFilesRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
@@ -61,7 +60,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private customEmojiService: CustomEmojiService,
|
||||
|
||||
private emojiEntityService: EmojiEntityService,
|
||||
private moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
@@ -77,11 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
isSensitive: ps.isSensitive ?? false,
|
||||
localOnly: ps.localOnly ?? false,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [],
|
||||
});
|
||||
|
||||
this.moderationLogService.log(me, 'addCustomEmoji', {
|
||||
emojiId: emoji.id,
|
||||
});
|
||||
}, me);
|
||||
|
||||
return this.emojiEntityService.packDetailed(emoji);
|
||||
});
|
||||
|
@@ -30,7 +30,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private customEmojiService: CustomEmojiService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.customEmojiService.deleteBulk(ps.ids);
|
||||
await this.customEmojiService.deleteBulk(ps.ids, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -36,7 +36,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private customEmojiService: CustomEmojiService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.customEmojiService.delete(ps.id);
|
||||
await this.customEmojiService.delete(ps.id, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -84,7 +84,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
isSensitive: ps.isSensitive,
|
||||
localOnly: ps.localOnly,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||
});
|
||||
}, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ import type { InstancesRepository } from '@/models/_.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
@@ -34,6 +35,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
|
||||
private utilityService: UtilityService,
|
||||
private federatedInstanceService: FederatedInstanceService,
|
||||
private moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const instance = await this.instancesRepository.findOneBy({ host: this.utilityService.toPuny(ps.host) });
|
||||
@@ -42,9 +44,23 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
throw new Error('instance not found');
|
||||
}
|
||||
|
||||
this.federatedInstanceService.update(instance.id, {
|
||||
await this.federatedInstanceService.update(instance.id, {
|
||||
isSuspended: ps.isSuspended,
|
||||
});
|
||||
|
||||
if (instance.isSuspended !== ps.isSuspended) {
|
||||
if (ps.isSuspended) {
|
||||
this.moderationLogService.log(me, 'suspendRemoteInstance', {
|
||||
id: instance.id,
|
||||
host: instance.host,
|
||||
});
|
||||
} else {
|
||||
this.moderationLogService.log(me, 'unsuspendRemoteInstance', {
|
||||
id: instance.id,
|
||||
host: instance.host,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ import { InviteCodeEntityService } from '@/core/entities/InviteCodeEntityService
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { generateInviteCode } from '@/misc/generate-invite-code.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -60,6 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
|
||||
private inviteCodeEntityService: InviteCodeEntityService,
|
||||
private idService: IdService,
|
||||
private moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
if (ps.expiresAt && isNaN(Date.parse(ps.expiresAt))) {
|
||||
@@ -78,6 +80,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
}
|
||||
|
||||
const tickets = await Promise.all(ticketsPromises);
|
||||
|
||||
this.moderationLogService.log(me, 'createInvitation', {
|
||||
invitations: tickets,
|
||||
});
|
||||
|
||||
return await this.inviteCodeEntityService.packMany(tickets, me);
|
||||
});
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
@@ -46,8 +47,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
|
||||
@Inject(DI.userProfilesRepository)
|
||||
private userProfilesRepository: UserProfilesRepository,
|
||||
|
||||
private moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps) => {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const user = await this.usersRepository.findOneBy({ id: ps.userId });
|
||||
|
||||
if (user == null) {
|
||||
@@ -69,6 +72,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
password: hash,
|
||||
});
|
||||
|
||||
this.moderationLogService.log(me, 'resetPassword', {
|
||||
userId: user.id,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
|
||||
return {
|
||||
password: passwd,
|
||||
};
|
||||
|
@@ -10,6 +10,7 @@ import { InstanceActorService } from '@/core/InstanceActorService.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
@@ -41,6 +42,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private queueService: QueueService,
|
||||
private instanceActorService: InstanceActorService,
|
||||
private apRendererService: ApRendererService,
|
||||
private moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const report = await this.abuseUserReportsRepository.findOneBy({ id: ps.reportId });
|
||||
@@ -61,6 +63,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
assigneeId: me.id,
|
||||
forwarded: ps.forward && report.targetUserHost != null,
|
||||
});
|
||||
|
||||
this.moderationLogService.log(me, 'resolveAbuseReport', {
|
||||
reportId: report.id,
|
||||
report: report,
|
||||
forwarded: ps.forward && report.targetUserHost != null,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -5,11 +5,8 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { RolesRepository } from '@/models/_.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { RoleEntityService } from '@/core/entities/RoleEntityService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin', 'role'],
|
||||
@@ -58,37 +55,11 @@ export const paramDef = {
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.rolesRepository)
|
||||
private rolesRepository: RolesRepository,
|
||||
|
||||
private globalEventService: GlobalEventService,
|
||||
private idService: IdService,
|
||||
private roleEntityService: RoleEntityService,
|
||||
private roleService: RoleService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const date = new Date();
|
||||
const created = await this.rolesRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
createdAt: date,
|
||||
updatedAt: date,
|
||||
lastUsedAt: date,
|
||||
name: ps.name,
|
||||
description: ps.description,
|
||||
color: ps.color,
|
||||
iconUrl: ps.iconUrl,
|
||||
target: ps.target,
|
||||
condFormula: ps.condFormula,
|
||||
isPublic: ps.isPublic,
|
||||
isAdministrator: ps.isAdministrator,
|
||||
isModerator: ps.isModerator,
|
||||
isExplorable: ps.isExplorable,
|
||||
asBadge: ps.asBadge,
|
||||
canEditMembersByModerator: ps.canEditMembersByModerator,
|
||||
displayOrder: ps.displayOrder,
|
||||
policies: ps.policies,
|
||||
}).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
this.globalEventService.publishInternalEvent('roleCreated', created);
|
||||
const created = await this.roleService.create(ps, me);
|
||||
|
||||
return await this.roleEntityService.pack(created, me);
|
||||
});
|
||||
|
@@ -6,9 +6,9 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { RolesRepository } from '@/models/_.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin', 'role'],
|
||||
@@ -41,17 +41,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
@Inject(DI.rolesRepository)
|
||||
private rolesRepository: RolesRepository,
|
||||
|
||||
private globalEventService: GlobalEventService,
|
||||
private roleService: RoleService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps) => {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const role = await this.rolesRepository.findOneBy({ id: ps.roleId });
|
||||
if (role == null) {
|
||||
throw new ApiError(meta.errors.noSuchRole);
|
||||
}
|
||||
await this.rolesRepository.delete({
|
||||
id: ps.roleId,
|
||||
});
|
||||
this.globalEventService.publishInternalEvent('roleDeleted', role);
|
||||
await this.roleService.delete(role, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -79,9 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
throw new ApiError(meta.errors.noSuchRole);
|
||||
}
|
||||
|
||||
const date = new Date();
|
||||
await this.roleService.update(role, {
|
||||
updatedAt: date,
|
||||
name: ps.name,
|
||||
description: ps.description,
|
||||
color: ps.color,
|
||||
|
@@ -81,7 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
receiveAnnouncementEmail: profile.receiveAnnouncementEmail,
|
||||
mutedWords: profile.mutedWords,
|
||||
mutedInstances: profile.mutedInstances,
|
||||
mutingNotificationTypes: profile.mutingNotificationTypes,
|
||||
notificationRecieveConfig: profile.notificationRecieveConfig,
|
||||
isModerator: isModerator,
|
||||
isSilenced: isSilenced,
|
||||
isSuspended: user.isSuspended,
|
||||
|
@@ -61,7 +61,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
});
|
||||
|
||||
this.moderationLogService.log(me, 'suspend', {
|
||||
targetId: user.id,
|
||||
userId: user.id,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
|
||||
(async () => {
|
||||
|
@@ -46,7 +46,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
});
|
||||
|
||||
this.moderationLogService.log(me, 'unsuspend', {
|
||||
targetId: user.id,
|
||||
userId: user.id,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
|
||||
this.userSuspendService.doPostUnsuspend(user);
|
||||
|
@@ -51,6 +51,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
|
||||
this.moderationLogService.log(me, 'updateUserNote', {
|
||||
userId: user.id,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
before: currentProfile.moderationNote,
|
||||
after: ps.text,
|
||||
});
|
||||
|
@@ -52,7 +52,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId)
|
||||
.where('announcement.isActive = :isActive', { isActive: ps.isActive })
|
||||
.andWhere('announcement.isActive = :isActive', { isActive: ps.isActive })
|
||||
.andWhere(new Brackets(qb => {
|
||||
if (me) qb.orWhere('announcement.userId = :meId', { meId: me.id });
|
||||
qb.orWhere('announcement.userId IS NULL');
|
||||
|
@@ -4,12 +4,11 @@
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { DriveFilesRepository, DriveFoldersRepository } from '@/models/_.js';
|
||||
import type { DriveFilesRepository } from '@/models/_.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { DriveService } from '@/core/DriveService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -77,16 +76,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
@Inject(DI.driveFilesRepository)
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
@Inject(DI.driveFoldersRepository)
|
||||
private driveFoldersRepository: DriveFoldersRepository,
|
||||
|
||||
private driveFileEntityService: DriveFileEntityService,
|
||||
private driveService: DriveService,
|
||||
private roleService: RoleService,
|
||||
private globalEventService: GlobalEventService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
const alwaysMarkNsfw = (await this.roleService.getUserPolicies(me.id)).alwaysMarkNsfw;
|
||||
if (file == null) {
|
||||
throw new ApiError(meta.errors.noSuchFile);
|
||||
}
|
||||
@@ -95,49 +89,28 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
throw new ApiError(meta.errors.accessDenied);
|
||||
}
|
||||
|
||||
if (ps.name) file.name = ps.name;
|
||||
if (!this.driveFileEntityService.validateFileName(file.name)) {
|
||||
throw new ApiError(meta.errors.invalidFileName);
|
||||
}
|
||||
let packedFile;
|
||||
|
||||
if (ps.comment !== undefined) file.comment = ps.comment;
|
||||
|
||||
if (ps.isSensitive !== undefined && ps.isSensitive !== file.isSensitive && alwaysMarkNsfw && !ps.isSensitive) {
|
||||
throw new ApiError(meta.errors.restrictedByRole);
|
||||
}
|
||||
|
||||
if (ps.isSensitive !== undefined) file.isSensitive = ps.isSensitive;
|
||||
|
||||
if (ps.folderId !== undefined) {
|
||||
if (ps.folderId === null) {
|
||||
file.folderId = null;
|
||||
try {
|
||||
packedFile = await this.driveService.updateFile(file, {
|
||||
folderId: ps.folderId,
|
||||
name: ps.name,
|
||||
isSensitive: ps.isSensitive,
|
||||
comment: ps.comment,
|
||||
}, me);
|
||||
} catch (e) {
|
||||
if (e instanceof DriveService.InvalidFileNameError) {
|
||||
throw new ApiError(meta.errors.invalidFileName);
|
||||
} else if (e instanceof DriveService.NoSuchFolderError) {
|
||||
throw new ApiError(meta.errors.noSuchFolder);
|
||||
} else if (e instanceof DriveService.CannotUnmarkSensitiveError) {
|
||||
throw new ApiError(meta.errors.restrictedByRole);
|
||||
} else {
|
||||
const folder = await this.driveFoldersRepository.findOneBy({
|
||||
id: ps.folderId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
if (folder == null) {
|
||||
throw new ApiError(meta.errors.noSuchFolder);
|
||||
}
|
||||
|
||||
file.folderId = folder.id;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
await this.driveFilesRepository.update(file.id, {
|
||||
name: file.name,
|
||||
comment: file.comment,
|
||||
folderId: file.folderId,
|
||||
isSensitive: file.isSensitive,
|
||||
});
|
||||
|
||||
const fileObj = await this.driveFileEntityService.pack(file, { self: true });
|
||||
|
||||
// Publish fileUpdated event
|
||||
this.globalEventService.publishDriveStream(me.id, 'fileUpdated', fileObj);
|
||||
|
||||
return fileObj;
|
||||
return packedFile;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -75,6 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
summary: ps.summary,
|
||||
script: ps.script,
|
||||
permissions: ps.permissions,
|
||||
visibility: ps.visibility,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -165,9 +165,7 @@ export const paramDef = {
|
||||
mutedInstances: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
mutingNotificationTypes: { type: 'array', items: {
|
||||
type: 'string', enum: notificationTypes,
|
||||
} },
|
||||
notificationRecieveConfig: { type: 'object' },
|
||||
emailNotificationTypes: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
@@ -248,7 +246,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
profileUpdates.enableWordMute = ps.mutedWords.length > 0;
|
||||
}
|
||||
if (ps.mutedInstances !== undefined) profileUpdates.mutedInstances = ps.mutedInstances;
|
||||
if (ps.mutingNotificationTypes !== undefined) profileUpdates.mutingNotificationTypes = ps.mutingNotificationTypes as typeof notificationTypes[number][];
|
||||
if (ps.notificationRecieveConfig !== undefined) profileUpdates.notificationRecieveConfig = ps.notificationRecieveConfig;
|
||||
if (typeof ps.isLocked === 'boolean') updates.isLocked = ps.isLocked;
|
||||
if (typeof ps.isExplorable === 'boolean') updates.isExplorable = ps.isExplorable;
|
||||
if (typeof ps.hideOnlineStatus === 'boolean') updates.hideOnlineStatus = ps.hideOnlineStatus;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user