Compare commits

..

3 Commits

Author SHA1 Message Date
syuilo
aba02a0dea Merge branch 'develop' into allow-suspended-access 2023-09-22 16:58:53 +09:00
syuilo
99fcd4054c Merge branch 'develop' into allow-suspended-access 2023-09-22 16:23:40 +09:00
Kagami Sascha Rosylight
c28e0abb75 feat(backend/ApiCallService): allow limited access for suspend accounts 2023-08-13 15:30:19 +02:00
267 changed files with 2021 additions and 4528 deletions

View File

@@ -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,6 +206,3 @@ signToActivityPubGet: true
# Upload or download file size limits (bytes)
#maxFileSize: 262144000
# PID File of master process
#pidFile: /tmp/misskey.pid

View File

@@ -9,7 +9,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.0.0
- run: corepack enable

View File

@@ -10,7 +10,7 @@ jobs:
check_copyright_year:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.0
- uses: actions/checkout@v4.0.0
- run: |
if [ "$(grep Copyright COPYING | sed -e 's/.*2014-\([0-9]*\) .*/\1/g')" -ne "$(date +%Y)" ]; then
echo "Please change copyright year!"

View File

@@ -13,7 +13,7 @@ jobs:
if: github.repository == 'misskey-dev/misskey'
steps:
- name: Check out the repo
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.0.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3.0.0

View File

@@ -12,7 +12,7 @@ jobs:
steps:
- name: Check out the repo
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.0.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3.0.0

View File

@@ -14,7 +14,7 @@ jobs:
env:
DOCKER_CONTENT_TRUST: 1
steps:
- uses: actions/checkout@v4.1.0
- uses: actions/checkout@v4.0.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

View File

@@ -11,7 +11,7 @@ jobs:
pnpm_install:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.0
- uses: actions/checkout@v4.0.0
with:
fetch-depth: 0
submodules: true
@@ -38,7 +38,7 @@ jobs:
- sw
- misskey-js
steps:
- uses: actions/checkout@v4.1.0
- uses: actions/checkout@v4.0.0
with:
fetch-depth: 0
submodules: true
@@ -64,7 +64,7 @@ jobs:
- backend
- misskey-js
steps:
- uses: actions/checkout@v4.1.0
- uses: actions/checkout@v4.0.0
with:
fetch-depth: 0
submodules: true

View File

@@ -53,7 +53,7 @@ jobs:
# Check out merge commit
- name: Fork based /deploy checkout
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.0.0
with:
ref: 'refs/pull/${{ github.event.client_payload.pull_request.number }}/merge'

View File

@@ -29,7 +29,7 @@ jobs:
- 56312:6379
steps:
- uses: actions/checkout@v4.1.0
- uses: actions/checkout@v4.0.0
with:
submodules: true
- name: Install pnpm

View File

@@ -16,7 +16,7 @@ jobs:
node-version: [20.5.1]
steps:
- uses: actions/checkout@v4.1.0
- uses: actions/checkout@v4.0.0
with:
submodules: true
- name: Install pnpm
@@ -68,7 +68,7 @@ jobs:
- 56312:6379
steps:
- uses: actions/checkout@v4.1.0
- uses: actions/checkout@v4.0.0
with:
submodules: true
# https://github.com/cypress-io/cypress-docker-images/issues/150

View File

@@ -21,7 +21,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.0.0
- run: corepack enable

View File

@@ -19,7 +19,7 @@ jobs:
node-version: [20.5.1]
steps:
- uses: actions/checkout@v4.1.0
- uses: actions/checkout@v4.0.0
with:
submodules: true
- name: Install pnpm

View File

@@ -12,44 +12,7 @@
-->
## 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以上が必要です
## 2023.9.0 (unreleased)
### General
- Feat: OAuth 2.0のサポート
@@ -65,10 +28,8 @@
- Feat: 二要素認証でパスキーをサポートするようになりました
- Feat: 指定したユーザーが投稿したときに通知できるようになりました
- Feat: プロフィールでのリンク検証
- Feat: モデレーションログ機能
- Feat: 通知をテストできるようになりました
- Feat: PWAのアイコンが設定できるようになりました
- Enhance: サーバー名の略称が設定できるようになりました
- Enhance: アンテナの受信ソースに指定したユーザを除外するものを追加
- Enhance: 二要素認証設定時のセキュリティを強化
- パスワード入力が必要な操作を行う際、二要素認証が有効であれば確認コードの入力も必要になりました
@@ -106,7 +67,6 @@
- Enhance: ノート詳細ページ読み込み時のパフォーマンスが向上しました
- Enhance: タイムラインでリスト/アンテナ選択時のパフォーマンスを改善
- Enhance: 「Moderation note」、「Add moderation note」をローカライズできるように
- Enhance: プラグインのソースコードを確認・コピーできるように
- Enhance: 細かなデザインの調整
- Fix: サーバー情報画面(`/instance-info/{domain}`)でブロックができないのを修正
- Fix: 未読のお知らせの「わかった」をクリック・タップしてもその場で「わかった」が消えない問題を修正
@@ -116,11 +76,6 @@
- Fix: Misskeyプラグインをインストールする際のAiScriptバージョンのチェックが0.14.0以降に対応していない問題を修正
- Fix: 他のサーバーのユーザーへ「メッセージを送信」した時の初期テキストのメンションが間違っている問題を修正
- Fix: 環境によってはMisskey Webが開けない問題を修正
- Fix: プラグインの権限リストが見れない問題を修正
- Fix: 複数の階層があるメニューで、短くタップすると正常に動かない場合がある問題を修正
- Fix: アニメーションがオフのとき、スマホで子メニューの選択ができない問題を修正
- Fix: ドロワーメニューで、親メニュー項目をマウスでホバーすると子メニューが表示されてしまう問題を修正
- Fix: AiScriptでMk:apiが外部と通信できる問題を修正
### Server
- Change: cacheRemoteFilesの初期値はfalseになりました
@@ -131,10 +86,7 @@
- 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を指定した際に検索結果に反映されるように
- Fix: 一部のfeatured noteを照会できない問題を修正
- Fix: muteがapiからのuser list timeline取得で機能しない問題を修正
@@ -155,6 +107,7 @@
### Server
- Fix: APIのオフセットが壊れていたせいで「もっと見る」でもっと見れない問題を修正
- Fix: 外部サーバーの投稿がタイムラインに表示されないことがある問題を修正
- Enhance: Add address bind config option (outgoingAddress)
## 13.14.1

View File

@@ -1140,7 +1140,6 @@ _plugin:
install: "ثبّت إضافات"
installWarn: "رجاءً لا تثبت إضافات غير موثوقة."
manage: "إدارة الإضافات"
viewSource: "اظهر المصدر"
_preferencesBackups:
createdAt: "تم إنشاؤه: {date} {time}"
updatedAt: "آخر تحديث: {date} {time}"
@@ -1556,6 +1555,3 @@ _webhookSettings:
active: "مُفعّل"
_events:
reaction: "عند التفاعل"
_moderationLogTypes:
suspend: "علِق"
resetPassword: "أعد تعيين كلمتك السرية"

View File

@@ -889,7 +889,6 @@ _plugin:
install: "প্লাগইন ইন্সটল করুন"
installWarn: "অবিশ্বস্ত প্লাগইন ইনস্টল করবেন না।"
manage: "প্লাগইন ম্যানেজ করুন"
viewSource: "উৎস দেখুন"
_registry:
scope: "স্কোপ"
key: "কী"
@@ -1333,6 +1332,3 @@ _deck:
_webhookSettings:
name: "নাম"
active: "চালু"
_moderationLogTypes:
suspend: "স্থগিত করা"
resetPassword: "পাসওয়ার্ড রিসেট করুন"

View File

@@ -479,6 +479,3 @@ _deck:
list: "Llistes"
mentions: "Mencions"
direct: "Publicacions directes"
_moderationLogTypes:
suspend: "Suspèn"
resetPassword: "Restableix la contrasenya"

View File

@@ -1492,7 +1492,6 @@ _plugin:
install: "Instalovat plugin"
installWarn: "Neinstalujte nedůvěryhodné pluginy."
manage: "Správce pluginů"
viewSource: "Zobrazit zdroj"
_preferencesBackups:
list: "Vytvořit backup"
saveNew: "Uložit novou zálohu"
@@ -2036,7 +2035,3 @@ _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"

View File

@@ -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."
poweredByMisskeyDescription: "{name} ist einer der durch die Open-Source-Plattform <b>Misskey</b> betriebenen Dienste (meist als \"Misskey-Instanz\" bezeichnet)."
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? Einige Inhalte, die diese Datei verwenden, werden auch verschwinden."
driveFileDeleteConfirm: "Möchtest du die Datei „{name}“ wirklich löschen? Sie wird in allen Inhalten, die sie verwenden, 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,7 +418,6 @@ 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"
@@ -640,7 +639,7 @@ display: "Anzeigeart"
copy: "Kopieren"
metrics: "Metriken"
overview: "Übersicht"
logs: "Protokolle"
logs: "Logs"
delayed: "Verzögert"
database: "Datenbank"
channel: "Kanäle"
@@ -711,7 +710,6 @@ lockedAccountInfo: "Auch wenn du Follow-Anfragen auf manuelle Bestätigung setzt
alwaysMarkSensitive: "Medien standardmäßig als sensibel markieren"
loadRawImages: "Anstatt Vorschaubilder immer Originalbilder anzeigen"
disableShowingAnimatedImages: "Animierte Bilder nicht abspielen"
highlightSensitiveMedia: "Sensitive Medien markieren"
verificationEmailSent: "Eine Bestätigungsmail wurde an deine Email-Adresse versendet. Besuche den dort enthaltenen Link, um die Verifizierung abzuschließen."
notSet: "Nicht konfiguriert"
emailVerified: "Email-Adresse bestätigt"
@@ -915,7 +913,7 @@ typeToConfirm: "Bitte gib zur Bestätigung {x} ein"
deleteAccount: "Benutzerkonto löschen"
document: "Dokumentation"
numberOfPageCache: "Seitencachegröße"
numberOfPageCacheDescription: "Das Erhöhen dieses Caches führt zu einer angenehmerern Benutzererfahrung, aber erhöht Last und Arbeitsspeicherauslastung auf dem Nutzergerät."
numberOfPageCacheDescription: "Das Erhöhen dieses Caches führt zu einer angenehmerern Benutzererfahrung, erhöht aber Serverlast und Arbeitsspeicherauslastung."
logoutConfirm: "Wirklich abmelden?"
lastActiveDate: "Zuletzt verwendet am"
statusbar: "Statusleiste"
@@ -1050,7 +1048,7 @@ vertical: "Vertikal"
horizontal: "Horizontal"
position: "Position"
serverRules: "Serverregeln"
pleaseConfirmBelowBeforeSignup: "Lies bitte diese Informationen und stimme ihnen vor der Registration zu."
pleaseConfirmBelowBeforeSignup: "Lies bitte Untenstehendes vor der Registration."
pleaseAgreeAllToContinue: "Zum Fortfahren muss allen obigen Feldern zugestimmt werden."
continue: "Fortfahren"
preservedUsernames: "Reservierte Benutzernamen"
@@ -1118,11 +1116,6 @@ keepScreenOn: "Bildschirm angeschaltet lassen"
verifiedLink: "Link-Besitz wurde verifiziert"
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."
@@ -1156,8 +1149,6 @@ _serverSettings:
appIconStyleRecommendation: "Da das Icon zu einem Kreis oder Quadrat zugeschnitten wird, wird ein Icon mit gefülltem Margin um den Inhalt herum empfohlen."
appIconResolutionMustBe: "Die Mindestauflösung ist {resolution}."
manifestJsonOverride: "Überschreiben von manifest.json"
shortName: "Abkürzung"
shortNameDescription: "Ein Kürzel für den Namen der Instanz, der angezeigt werden kann, falls der volle Instanzname lang ist."
_accountMigration:
moveFrom: "Von einem anderen Konto zu diesem migrieren"
moveFromSub: "Alias für ein anderes Konto erstellen"
@@ -1453,7 +1444,6 @@ _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"
@@ -1539,7 +1529,6 @@ _plugin:
install: "Plugins installieren"
installWarn: "Installiere bitte nur vertrauenswürdige Plugins."
manage: "Plugins verwalten"
viewSource: "Quelltext anzeigen"
_preferencesBackups:
list: "Erstellte Backups"
saveNew: "Neu erstellen"
@@ -1732,7 +1721,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 Code (Token) ein, der von deiner App angezeigt wird."
step3: "Gib zum Abschluss den 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."
@@ -1805,7 +1794,6 @@ _antennaSources:
homeTimeline: "Notizen von Benutzern, denen gefolgt wird"
users: "Notizen von einem oder mehreren angegebenen Benutzern"
userList: "Notizen von allen Benutzern einer Liste"
userBlacklist: "Alle Notizen abgesehen derer angegebener Benutzer"
_weekday:
sunday: "Sonntag"
monday: "Montag"
@@ -2034,7 +2022,6 @@ _notification:
notificationWillBeDisplayedLikeThis: "Benachrichtigungen sehen so aus"
_types:
all: "Alle"
note: "Neue Notizen"
follow: "Neue Follower"
mention: "Erwähnungen"
reply: "Antworten"
@@ -2104,31 +2091,3 @@ _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"

View File

@@ -397,5 +397,3 @@ _deck:
mentions: "Επισημάνσεις"
_webhookSettings:
name: "Όνομα"
_moderationLogTypes:
suspend: "Αποβολή"

View File

@@ -418,7 +418,6 @@ 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"
@@ -711,7 +710,6 @@ lockedAccountInfo: "Unless you set your note visiblity to \"Followers only\", yo
alwaysMarkSensitive: "Mark as sensitive by default"
loadRawImages: "Load original images instead of showing thumbnails"
disableShowingAnimatedImages: "Don't play animated images"
highlightSensitiveMedia: "Highlight sensitive media"
verificationEmailSent: "A verification email has been sent. Please follow the included link to complete verification."
notSet: "Not set"
emailVerified: "Email has been verified"
@@ -915,7 +913,7 @@ typeToConfirm: "Please enter {x} to confirm"
deleteAccount: "Delete account"
document: "Documentation"
numberOfPageCache: "Number of cached pages"
numberOfPageCacheDescription: "Increasing this number will improve convenience for but cause more load as more memory usage on the user's device."
numberOfPageCacheDescription: "Increasing this number will improve convenience for users but cause more server load as well as more memory to be used."
logoutConfirm: "Really log out?"
lastActiveDate: "Last used at"
statusbar: "Status bar"
@@ -1118,11 +1116,6 @@ keepScreenOn: "Keep screen on"
verifiedLink: "Link ownership has been verified"
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."
@@ -1156,8 +1149,6 @@ _serverSettings:
appIconStyleRecommendation: "As the icon may be cropped to a square or circle, an icon with colored margin around the content is recommended."
appIconResolutionMustBe: "The minimum resolution is {resolution}."
manifestJsonOverride: "manifest.json Override"
shortName: "Short name"
shortNameDescription: "A shorthand for the instance's name that can be displayed if the full official name is long."
_accountMigration:
moveFrom: "Migrate another account to this one"
moveFromSub: "Create alias to another account"
@@ -1453,7 +1444,6 @@ _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"
@@ -1539,7 +1529,6 @@ _plugin:
install: "Install plugins"
installWarn: "Please do not install untrustworthy plugins."
manage: "Manage plugins"
viewSource: "View source"
_preferencesBackups:
list: "Created backups"
saveNew: "Save new backup"
@@ -1805,7 +1794,6 @@ _antennaSources:
homeTimeline: "Notes from followed users"
users: "Notes from specific users"
userList: "Notes from a specified list of users"
userBlacklist: "All notes except for those of one or more specified users"
_weekday:
sunday: "Sunday"
monday: "Monday"
@@ -2034,7 +2022,6 @@ _notification:
notificationWillBeDisplayedLikeThis: "Notifications look like this"
_types:
all: "All"
note: "New notes"
follow: "New followers"
mention: "Mentions"
reply: "Replies"
@@ -2104,31 +2091,3 @@ _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"

View File

@@ -418,7 +418,6 @@ 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"
@@ -711,7 +710,6 @@ 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."
@@ -1111,16 +1109,6 @@ 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."
@@ -1149,13 +1137,7 @@ _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."
@@ -1536,7 +1518,6 @@ _plugin:
install: "Instalar plugins"
installWarn: "Por favor no instale plugins que no son de confianza"
manage: "Gestionar plugins"
viewSource: "Ver la fuente"
_preferencesBackups:
list: "Respaldos creados"
saveNew: "Guardar nuevo respaldo"
@@ -1802,7 +1783,6 @@ _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"
@@ -1902,7 +1882,6 @@ _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"
@@ -2021,7 +2000,6 @@ _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"
@@ -2031,7 +2009,6 @@ _notification:
notificationWillBeDisplayedLikeThis: "Las notificaciones tendrán este aspecto"
_types:
all: "Todo"
note: "Nuevas notas"
follow: "Siguiendo"
mention: "Menciones"
reply: "Respuestas"
@@ -2101,31 +2078,3 @@ _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"

View File

@@ -272,7 +272,6 @@ startMessaging: "Commencer à discuter"
nUsersRead: "Lu par {n} personnes"
agreeTo: "Jaccepte {0}"
agree: "Accepter"
agreeBelow: "Jaccepte ce qui suit"
basicNotesBeforeCreateAccount: "Notes importantes"
termsOfService: "Conditions d'utilisation"
start: "Commencer"
@@ -407,7 +406,6 @@ aboutMisskey: "À propos de Misskey"
administrator: "Administrateur"
token: "Jeton"
2fa: "Authentification à deux facteurs"
setupOf2fa: "Configuration de lauthentification à deux facteurs"
totp: "Application d'authentification"
totpDescription: "Entrez un mot de passe à usage unique à l'aide d'une application d'authentification"
moderator: "Modérateur·rice·s"
@@ -415,7 +413,6 @@ moderation: "Modérations"
moderationNote: "Note de modération"
addModerationNote: "Ajouter une note de modération"
nUsersMentioned: "{n} utilisateur·rice·s mentionné·e·s"
securityKeyAndPasskey: "Sécurité et clés de sécurité"
securityKey: "Clé de sécurité"
lastUsed: "Dernier utilisé"
lastUsedAt: "Dernière utilisation : {t}"
@@ -800,7 +797,6 @@ popularPosts: "Les plus consultées"
shareWithNote: "Partager dans une note"
ads: "Publicité"
expiration: "Échéance"
startingperiod: "Commencer"
memo: "Pense-bête"
priority: "Priorité"
high: "Haute"
@@ -962,7 +958,6 @@ internalServerError: "Erreur interne du serveur"
copyErrorInfo: "Copier les détails de lerreur"
exploreOtherServers: "Trouver une autre instance"
disableFederationOk: "Désactiver"
likeOnly: "Les favoris uniquement"
license: "Licence"
video: "Vidéo"
videos: "Vidéos"
@@ -983,7 +978,6 @@ horizontal: "Latéral"
serverRules: "Règles du serveur"
archive: "Archive"
youFollowing: "Abonné·e"
options: "Options"
later: "Plus tard"
goToMisskey: "Retour vers Misskey"
expirationDate: "Date dexpiration"
@@ -996,24 +990,12 @@ icon: "Avatar"
forYou: "Pour vous"
replies: "Répondre"
renotes: "Renoter"
loadReplies: "Inclure les réponses"
pinnedList: "Liste épinglée"
notifyNotes: "Notifier à propos des nouvelles notes"
authentication: "Authentification"
authenticationRequiredToContinue: "Veuillez vous authentifier pour continuer"
_announcement:
readConfirmTitle: "Marquer comme lu ?"
_initialAccountSetting:
profileSetting: "Paramètres du profil"
privacySetting: "Paramètres de confidentialité"
initialAccountSettingCompleted: "Configuration du profil terminée avec succès !"
ifYouNeedLearnMore: "Si vous voulez en savoir plus comment utiliser {name}(Misskey), veuillez visiter {link}."
skipAreYouSure: "Désirez-vous ignorer la configuration du profile ?"
_serverSettings:
iconUrl: "URL de licône"
_accountMigration:
moveFrom: "Migrer un autre compte vers le présent compte"
moveFromSub: "Créer un alias vers un autre compte"
moveToLabel: "Compte vers lequel vous migrez :"
startMigration: "Migrer"
movedTo: "Compte vers lequel vous migrez :"
@@ -1070,33 +1052,20 @@ _achievements:
_login1000:
flavor: "Merci d'utiliser Misskey !"
_profileFilled:
title: "Bien préparé"
description: "Configuration de votre profil"
_markedAsCat:
title: "Je suis un chat"
description: "Rendre votre compte comme un chat"
flavor: "Je n'ai pas encore de nom"
_following1:
title: "Vous suivez votre premier utilisateur·rice"
_following50:
title: "Beaucoup d'amis"
_followers10:
title: "Abonnez-moi !"
_followers100:
title: "Populaire"
_followers500:
title: "Tour radio"
_followers1000:
title: "Influenceur·euse"
_iLoveMisskey:
title: "Jadore Misskey"
description: "Publication « J❤ #Misskey »"
flavor: "L'équipe de développement de Misskey apprécie vraiment votre aide !"
_foundTreasure:
title: "Chasse au trésor"
description: "Vous avez trouvé le trésor caché"
_client30min:
title: "Pause bien méritée"
_postedAtLateNight:
flavor: "Cest lheure daller au lit."
_postedAt0min0sec:
@@ -1105,45 +1074,18 @@ _achievements:
flavor: "Tic tac, tic tac, tic tac, ding !"
_viewInstanceChart:
title: "Analyste"
_outputHelloWorldOnScratchpad:
title: "Bonjour tout le monde !"
_open3windows:
title: "Multi-fenêtres"
_driveFolderCircularReference:
title: "Référence circulaire"
_setNameToSyuilo:
description: "Vous avez spécifié « syuilo » comme nom"
_passedSinceAccountCreated1:
title: "Premier anniversaire"
_passedSinceAccountCreated2:
title: "Second anniversaire"
_passedSinceAccountCreated3:
title: "3ème anniversaire"
_loggedInOnBirthday:
title: "Joyeux Anniversaire !"
description: "Vous vous êtes connecté à la date de votre anniversaire"
_loggedInOnNewYearsDay:
title: "Bonne année !"
_cookieClicked:
flavor: "Attendez une minute, vous êtes sur le mauvais site web ?"
_brainDiver:
flavor: "Misskey-Misskey La-Tu-Ma"
_role:
new: "Nouveau rôle"
edit: "Modifier le rôle"
name: "Nom du rôle"
description: "Description du rôle"
permission: "Rôle et autorisations"
assignTarget: "Attribuer"
condition: "Condition"
isPublic: "Rôle public"
options: "Options"
policies: "Stratégies"
baseRole: "Modèle de rôle"
useBaseValue: "Utiliser la valeur du modèle de rôle"
chooseRoleToAssign: "Sélectionner le rôle à assigner"
iconUrl: "URL de licône"
displayOrder: "Classement"
priority: "Priorité"
_priority:
low: "Basse"
@@ -1202,7 +1144,6 @@ _plugin:
install: "Installation de plugin"
installWarn: "Ninstallez que des extensions provenant de sources de confiance."
manage: "Gestion des plugins"
viewSource: "Afficher la source"
_preferencesBackups:
list: "Sauvegardes créées"
saveNew: "Nouvelle sauvegarde"
@@ -1389,7 +1330,6 @@ _2fa:
securityKeyNotSupported: "Votre navigateur ne prend pas en charge les clés de sécurité."
securityKeyInfo: "Vous pouvez configurer l'authentification WebAuthN pour sécuriser davantage le processus de connexion grâce à une clé de sécurité matérielle qui prend en charge FIDO2, ou bien en configurant l'authentification par empreinte digitale ou par code PIN sur votre appareil."
securityKeyName: "Nom de la clé"
removeKey: "Supprimer la clé de sécurité"
removeKeyConfirm: "Voulez-vous supprimer {name} ?"
renewTOTPOk: "Reconfigurer"
renewTOTPCancel: "Pas maintenant"
@@ -1691,6 +1631,3 @@ _deck:
_webhookSettings:
name: "Nom"
active: "Activé"
_moderationLogTypes:
suspend: "Suspendre"
resetPassword: "Réinitialiser le mot de passe"

View File

@@ -1100,7 +1100,6 @@ 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."
@@ -1497,7 +1496,6 @@ _plugin:
install: "Memasang plugin"
installWarn: "Mohon jangan memasang plugin yang tidak dapat dipercayai."
manage: "Manajemen plugin"
viewSource: "Lihat sumber"
_preferencesBackups:
list: "Cadangan yang dibuat"
saveNew: "Simpan cadangan baru"
@@ -2042,7 +2040,3 @@ _webhookSettings:
renote: "Ketika direnote"
reaction: "Ketika menerima reaksi"
mention: "Ketika sedang disebut"
_moderationLogTypes:
suspend: "Tangguhkan"
resetPassword: "Atur ulang kata sandi"
createInvitation: "Buat kode undangan"

44
locales/index.d.ts vendored
View File

@@ -421,7 +421,6 @@ export interface Locale {
"moderation": string;
"moderationNote": string;
"addModerationNote": string;
"moderationLogs": string;
"nUsersMentioned": string;
"securityKeyAndPasskey": string;
"securityKey": string;
@@ -1123,12 +1122,6 @@ 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;
@@ -1165,8 +1158,6 @@ export interface Locale {
"appIconStyleRecommendation": string;
"appIconResolutionMustBe": string;
"manifestJsonOverride": string;
"shortName": string;
"shortNameDescription": string;
};
"_accountMigration": {
"moveFrom": string;
@@ -1542,7 +1533,6 @@ export interface Locale {
"gtlAvailable": string;
"ltlAvailable": string;
"canPublicNote": string;
"canEditNote": string;
"canInvite": string;
"inviteLimit": string;
"inviteLimitCycle": string;
@@ -1642,7 +1632,6 @@ export interface Locale {
"install": string;
"installWarn": string;
"manage": string;
"viewSource": string;
};
"_preferencesBackups": {
"list": string;
@@ -2176,7 +2165,6 @@ export interface Locale {
"notificationWillBeDisplayedLikeThis": string;
"_types": {
"all": string;
"note": string;
"follow": string;
"mention": string;
"reply": string;
@@ -2256,38 +2244,6 @@ export interface Locale {
"mention": string;
};
};
"_moderationLogTypes": {
"createRole": string;
"deleteRole": string;
"updateRole": string;
"assignRole": string;
"unassignRole": 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: {
[lang: string]: Locale;

View File

@@ -117,7 +117,7 @@ pinnedNote: "Nota fissata"
pinned: "Fissa sul profilo"
you: "Tu"
clickToShow: "Clicca per visualizzare"
sensitive: "Esplicito"
sensitive: "Contenuto sensibile"
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 esplicito"
unmarkAsSensitive: "Non segnare come esplicito "
markAsSensitive: "Segna come sensibile"
unmarkAsSensitive: "Segna come non sensibile"
enterFileName: "Nome del file"
mute: "Silenzia"
unmute: "Riattiva l'audio"
renoteMute: "Silenzia le Rinota"
renoteUnmute: "Non silenziare le Rinota"
renoteMute: "Silenzia i Rinota"
renoteUnmute: "Non silenziare i 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 personalizzate"
customEmojis: "Emoji personalizzati"
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: "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."
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."
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 dinterazione 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 espliciti"
displayOfSensitiveMedia: "Visibilità dei media sensibili"
whenServerDisconnected: "Quando la connessione col server è persa"
disconnectedFromServer: "Il server si è disconnesso"
reload: "Ricarica"
@@ -418,7 +418,6 @@ 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"
@@ -708,10 +707,9 @@ 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 gli allegati come espliciti come opzione predefinita"
alwaysMarkSensitive: "Segnare i media come sensibili per impostazione 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"
@@ -928,7 +926,7 @@ type: "Tipo"
speed: "Velocità"
slow: "Lento"
fast: "Veloce"
sensitiveMediaDetection: "Rilevamento dei contenuti espliciti"
sensitiveMediaDetection: "Rilevamento dei contenuti sensibili."
localOnly: "Soltanto locale"
remoteOnly: "Solo remoto"
failedToUpload: "errore di caricamento"
@@ -991,7 +989,7 @@ thisPostMayBeAnnoying: "Questa nota potrebbe essere offensiva"
thisPostMayBeAnnoyingHome: "Pubblica sulla timeline principale"
thisPostMayBeAnnoyingCancel: "Annulla"
thisPostMayBeAnnoyingIgnore: "Pubblica lo stesso"
collapseRenotes: "Comprimi le Rinota già viste"
collapseRenotes: "Comprimi i Rinota già letti"
internalServerError: "Errore interno del server"
internalServerErrorDescription: "Si è verificato un errore imprevisto all'interno del server"
copyErrorInfo: "Copia le informazioni sull'errore"
@@ -1008,11 +1006,11 @@ cannotBeChangedLater: "Non sarà più modificabile"
reactionAcceptance: "Reazioni consentite"
likeOnly: "Solo i Like"
likeOnlyForRemote: "Solo Like remoti"
nonSensitiveOnly: "Soltanto non espliciti"
nonSensitiveOnlyForLocalLikeOnlyForRemote: "Soltanto non espliciti (reazioni remote)"
nonSensitiveOnly: "Solamente non sensibili"
nonSensitiveOnlyForLocalLikeOnlyForRemote: "Solamente non sensibili (solo Mi piace remoti)"
rolesAssignedToMe: "I miei ruoli"
resetPasswordConfirm: "Vuoi davvero ripristinare la password?"
sensitiveWords: "Parole esplicite"
sensitiveWords: "Parole sensibili"
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."
@@ -1118,10 +1116,6 @@ 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."
@@ -1155,8 +1149,6 @@ _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"
@@ -1486,9 +1478,9 @@ _role:
or: "O"
not: "NON"
_sensitiveMediaDetection:
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)."
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)."
setSensitiveFlagAutomatically: "Impostare il flag NSFW."
setSensitiveFlagAutomaticallyDescription: "Anche se questa impostazione è disattivata, il risultato della decisione viene conservato internamente."
analyzeVideos: "Abilitazione dell'analisi video."
@@ -1537,7 +1529,6 @@ _plugin:
install: "Installa estensioni"
installWarn: "Si prega di installare soltanto estensioni che provengono da fonti affidabili."
manage: "Gestisci estensioni"
viewSource: "Visualizza sorgente"
_preferencesBackups:
list: "Elenco di impostazioni salvate in precedenza"
saveNew: "Nuovo salvataggio"
@@ -1572,8 +1563,8 @@ _aboutMisskey:
morePatrons: "Apprezziamo sinceramente il supporto di tante altre persone. Grazie mille! 🥰"
patrons: "Sostenitori"
_displayOfSensitiveMedia:
respect: "Nascondere i media espliciti"
ignore: "Non nascondere i media espliciti"
respect: "Nascondere i media sensibili"
ignore: "Non nascondere i media sensibili"
force: "Nascondi tutti i media"
_instanceTicker:
none: "Nascondi"
@@ -1803,7 +1794,6 @@ _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ì"
@@ -2032,8 +2022,7 @@ _notification:
notificationWillBeDisplayedLikeThis: "La notifica apparirà così"
_types:
all: "Tutto"
note: "Nuove Note"
follow: "Nuovi profili follower"
follow: "Novità follower"
mention: "Menzioni"
reply: "Risposte"
renote: "Rinota"
@@ -2102,31 +2091,3 @@ _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"

View File

@@ -418,7 +418,6 @@ moderator: "モデレーター"
moderation: "モデレーション"
moderationNote: "モデレーションノート"
addModerationNote: "モデレーションノートを追加する"
moderationLogs: "モデログ"
nUsersMentioned: "{n}人が投稿"
securityKeyAndPasskey: "セキュリティキー・パスキー"
securityKey: "セキュリティキー"
@@ -1120,12 +1119,6 @@ notifyNotes: "投稿を通知"
unnotifyNotes: "投稿の通知を解除"
authentication: "認証"
authenticationRequiredToContinue: "続けるには認証を行ってください"
dateAndTime: "日時"
showRenotes: "リノートを表示"
edited: "編集済み"
notificationRecieveConfig: "通知の受信設定"
mutualFollow: "相互フォロー"
fileAttachedOnly: "ファイル付きのみ"
_announcement:
forExistingUsers: "既存ユーザーのみ"
@@ -1163,8 +1156,6 @@ _serverSettings:
appIconStyleRecommendation: "円形もしくは角丸にクロップされる場合があるため、塗り潰された余白のある背景を持つことが推奨されます。"
appIconResolutionMustBe: "解像度は必ず{resolution}である必要があります。"
manifestJsonOverride: "manifest.jsonのオーバーライド"
shortName: "略称"
shortNameDescription: "サーバーの正式名称が長い場合に、代わりに表示することのできる略称や通称。"
_accountMigration:
moveFrom: "別のアカウントからこのアカウントに移行"
@@ -1463,7 +1454,6 @@ _role:
gtlAvailable: "グローバルタイムラインの閲覧"
ltlAvailable: "ローカルタイムラインの閲覧"
canPublicNote: "パブリック投稿の許可"
canEditNote: "ノートの編集"
canInvite: "サーバー招待コードの発行"
inviteLimit: "招待コードの作成可能数"
inviteLimitCycle: "招待コードの発行間隔"
@@ -1559,7 +1549,6 @@ _plugin:
install: "プラグインのインストール"
installWarn: "信頼できないプラグインはインストールしないでください。"
manage: "プラグインの管理"
viewSource: "ソースを表示"
_preferencesBackups:
list: "作成したバックアップ"
@@ -2091,7 +2080,6 @@ _notification:
_types:
all: "すべて"
note: "ユーザーの新規投稿"
follow: "フォロー"
mention: "メンション"
reply: "リプライ"
@@ -2168,35 +2156,3 @@ _webhookSettings:
renote: "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: "招待コードを作成"
createAd: "広告を作成"
deleteAd: "広告を削除"
updateAd: "広告を更新"

View File

@@ -1105,10 +1105,6 @@ youHaveUnreadAnnouncements: "あんたまだこのお知らせ読んどらんや
useSecurityKey: "ブラウザまたはデバイスの言う通りに、セキュリティキーまたはパスキーを使ってや。"
replies: "返事"
renotes: "Renote"
loadReplies: "返信を見るで"
loadConversation: "会話を見るで"
verifiedLink: "このリンク先の所有者であることが確認されたで。"
authenticationRequiredToContinue: "続けるには認証をやってや。"
_announcement:
forExistingUsers: "もうおるユーザーのみ"
forExistingUsersDescription: "有効にすると、このお知らせ作成時点でおるユーザーにのみお知らせが表示されます。無効にすると、このお知らせ作成後にアカウントを作成したユーザーにもお知らせが表示されます。"
@@ -1137,11 +1133,6 @@ _serverRules:
description: "新規登録前に見せる、サーバーの簡潔なルールを設定すんで。内容は使うための決め事の要約とすることを推奨するわ。"
_serverSettings:
iconUrl: "アイコン画像のURL"
appIconDescription: "{host}がアプリとして表示してるんやつをアイコンを指定すんで。"
appIconUsageExample: "PWAや、スマートフォンのホーム画面にブックマークとして追加された時など"
appIconStyleRecommendation: "円形もしくは角丸にクロップされる場合があるさかいに、塗り潰された余白のある背景があるものが推奨されるで。"
appIconResolutionMustBe: "解像度は必ず{resolution}である必要があるで。"
shortNameDescription: "サーバーの名前が長い時に、代わりに表示することのできるあだ名。"
_accountMigration:
moveFrom: "別のアカウントからこのアカウントに引っ越す"
moveFromSub: "別のアカウントへエイリアスを作る"
@@ -1519,7 +1510,6 @@ _plugin:
install: "プラグインのインストール"
installWarn: "信頼できへんプラグインはインストールせんとってな"
manage: "プラグインの管理"
viewSource: "ソースを表示"
_preferencesBackups:
list: "作ったバックアップ"
saveNew: "新しく保存"
@@ -1712,7 +1702,6 @@ _2fa:
step2Click: "QRコードをクリックすると、今使とる端末に入っとる認証アプリとかキーリングに登録できるで。"
step3Title: "確認コードを入れてーや"
step3: "アプリに表示されているトークンを入力して終わりや。"
setupCompleted: "設定が完了したで。"
step4: "これからログインするときも、同じようにトークンを入力するんやで"
securityKeyNotSupported: "今使とるブラウザはセキュリティキーに対応してへんのやってさ。"
registerTOTPBeforeKey: "セキュリティキー・パスキーを登録するんやったら、まず認証アプリを設定してーな。"
@@ -1727,10 +1716,6 @@ _2fa:
renewTOTPConfirm: "今までの認証アプリの確認コードは使えんくなるけどええか?"
renewTOTPOk: "もっかい設定する"
renewTOTPCancel: "やめとく"
checkBackupCodesBeforeCloseThisWizard: "このウィザードを閉じる前に、したのバックアップコードを確認しいや。"
backupCodesDescription: "認証アプリが使用できんなった場合、以下のバックアップコードを使ってアカウントにアクセスできるで。これらのコードは必ず安全な場所に置いときや。各コードは一回だけ使用できるで。"
backupCodeUsedWarning: "バックアップコードが使用されたで。認証アプリが使えなくなってるん場合、なるべく早く認証アプリを再設定しや。"
backupCodesExhaustedWarning: "バックアップコードが全て使用されたで。認証アプリを利用できん場合、これ以上アカウントにアクセスできなくなるで。認証アプリを再登録しや。"
_permissions:
"read:account": "アカウントの情報を見るで"
"write:account": "アカウントの情報を変更するで"
@@ -2003,9 +1988,6 @@ _notification:
unreadAntennaNote: "アンテナ {name}"
emptyPushNotificationMessage: "プッシュ通知の更新をしといたで"
achievementEarned: "実績を獲得しとるで"
checkNotificationBehavior: "通知の表示を確かめるで"
sendTestNotification: "テスト通知を送信するで"
notificationWillBeDisplayedLikeThis: "通知はこのように表示されるで"
_types:
all: "すべて"
follow: "フォロー"
@@ -2041,7 +2023,6 @@ _deck:
introduction2: "画面の右にある + を押して、いつでもカラムを追加できるで。"
widgetsIntroduction: "カラムのメニューから、「ウィジェットの編集」を選んでウィジェットを追加してなー"
useSimpleUiForNonRootPages: "非ルートページは簡易UIで表示"
usedAsMinWidthWhenFlexible: "「幅を自動調整」が有効の場合、これが幅の最小値となるで"
_columns:
main: "メイン"
widgets: "ウィジェット"
@@ -2076,7 +2057,3 @@ _webhookSettings:
renote: "Renoteされるとき"
reaction: "ツッコミがあるとき~!"
mention: "メンションがあるとき~!"
_moderationLogTypes:
suspend: "凍結"
resetPassword: "パスワードをリセット"
createInvitation: "招待コードを作成"

View File

@@ -1512,7 +1512,6 @@ _plugin:
install: "플러그인 설치"
installWarn: "신뢰할 수 없는 플러그인은 설치하지 않는 것이 좋습니다."
manage: "플러그인 관리"
viewSource: "소스 보기"
_preferencesBackups:
list: "생성한 백업"
saveNew: "새 백업 만들기"
@@ -2073,7 +2072,3 @@ _webhookSettings:
renote: "누군가 내 글을 Renote했을 때"
reaction: "누군가 내 노트에 리액션했을 때"
mention: "누군가 나를 멘션했을 때"
_moderationLogTypes:
suspend: "정지"
resetPassword: "비밀번호 재설정"
createInvitation: "초대 코드 생성"

View File

@@ -463,5 +463,3 @@ _deck:
mentions: "ກ່າວເຖິງ"
_webhookSettings:
name: "ຊື່"
_moderationLogTypes:
suspend: "ລະງັບ"

View File

@@ -494,6 +494,3 @@ _deck:
mentions: "Vermeldingen"
_webhookSettings:
name: "Naam"
_moderationLogTypes:
suspend: "Opschorten"
resetPassword: "Wachtwoord terugzetten"

View File

@@ -725,5 +725,3 @@ _deck:
direct: "Direkte"
_webhookSettings:
name: "Navn"
_moderationLogTypes:
suspend: "Suspender"

View File

@@ -925,7 +925,6 @@ _plugin:
install: "Zainstaluj wtyczki"
installWarn: "Nie instaluj niezaufanych wtyczek."
manage: "Zarządzanie wtyczkami"
viewSource: "Zobacz źródło"
_preferencesBackups:
list: "Utworzone kopie zapasowe"
saveNew: "Zapisz nową kopię zapasową"
@@ -1402,6 +1401,3 @@ _webhookSettings:
renote: "Po udostępnieniu wpisu"
reaction: "Po otrzymaniu reakcji"
mention: "Po zostaniu wspomnianym"
_moderationLogTypes:
suspend: "Zawieś"
resetPassword: "Zresetuj hasło"

View File

@@ -1497,6 +1497,3 @@ _webhookSettings:
follow: "Quando seguindo um usuário"
followed: "Quando sendo seguido"
renote: "Quando repostado"
_moderationLogTypes:
suspend: "Suspender"
resetPassword: "Redefinir senha"

View File

@@ -704,6 +704,3 @@ _deck:
mentions: "Mențiuni"
_webhookSettings:
name: "Nume"
_moderationLogTypes:
suspend: "Suspendă"
resetPassword: "Resetează parola"

View File

@@ -1427,7 +1427,6 @@ _plugin:
install: "Установка расширений"
installWarn: "Пожалуйста, не устанавливайте расширения, которым не доверяете."
manage: "Управление расширениями"
viewSource: "Просмотр исходника"
_preferencesBackups:
list: "Существующие резервные копии"
saveNew: "Создать резервную копию"
@@ -1951,6 +1950,3 @@ _webhookSettings:
createWebhook: "Создать вебхук"
name: "Название"
active: "Вкл."
_moderationLogTypes:
suspend: "Заморозить"
resetPassword: "Сброс пароля:"

View File

@@ -978,7 +978,6 @@ _plugin:
install: "Inštalova pluginy"
installWarn: "Prosím neinštalujte nedôveryhodné pluginy."
manage: "Spravovanie pluginov"
viewSource: "Ukázať zdroj"
_preferencesBackups:
list: "Vytvorené zálohy"
saveNew: "Uložiť novú"
@@ -1451,6 +1450,3 @@ _deck:
_webhookSettings:
name: "Názov"
active: "Zapnuté"
_moderationLogTypes:
suspend: "Zmraziť"
resetPassword: "Resetovať heslo"

View File

@@ -573,6 +573,3 @@ _deck:
_webhookSettings:
name: "Namn"
active: "Aktiverad"
_moderationLogTypes:
suspend: "Suspendera"
resetPassword: "Återställ Lösenord"

View File

@@ -416,9 +416,6 @@ totp: "แอป Authenticator"
totpDescription: "ใช้แอปยืนยันตัวตนเพื่อป้อนรหัสผ่านแบบใช้ครั้งเดียว"
moderator: "ผู้ควบคุม"
moderation: "การกลั่นกรอง"
moderationNote: "โน้ตการกลั่นกรอง"
addModerationNote: "เพิ่มโน้ตการกลั่นกรอง"
moderationLogs: "บันทึกการกลั่นกรอง"
nUsersMentioned: "กล่าวถึงโดยผู้ใช้ {n} รายนี้"
securityKeyAndPasskey: "ความปลอดภัยและรหัสผ่าน"
securityKey: "กุญแจความปลอดภัย"
@@ -711,7 +708,6 @@ lockedAccountInfo: "เว้นแต่ว่าคุณจะต้องต
alwaysMarkSensitive: "ทำเครื่องหมายเป็น NSFW เป็นค่าเริ่มต้น"
loadRawImages: "โหลดภาพต้นฉบับแทนการแสดงภาพขนาดย่อ"
disableShowingAnimatedImages: "ไม่ต้องเล่นภาพเคลื่อนไหว"
highlightSensitiveMedia: "ไฮไลท์สื่อที่ละเอียดอ่อน"
verificationEmailSent: "ส่งอีเมลยืนยันแล้วนะ ได้โปรดกรุณาไปที่ลิงก์ที่รวมไว้เพื่อทำการตรวจสอบให้เสร็จสิ้น"
notSet: "ไม่ได้ตั้งค่า"
emailVerified: "อีเมลได้รับการยืนยันแล้ว"
@@ -1026,7 +1022,6 @@ retryAllQueuesConfirmText: "สิ่งนี้จะเพิ่มการ
enableChartsForRemoteUser: "สร้างแผนภูมิข้อมูลผู้ใช้ระยะไกล"
enableChartsForFederatedInstances: "สร้างแผนภูมิข้อมูลอินสแตนซ์ระยะไกล"
showClipButtonInNoteFooter: "เพิ่ม \"คลิป\" เพื่อบันทึกเมนูการทำงาน"
reactionsDisplaySize: "รีแอคชั่นแสดงผลขนาด"
noteIdOrUrl: "โน้ต ID หรือ URL"
video: "วีดีโอ"
videos: "วีดีโอ"
@@ -1105,23 +1100,11 @@ iHaveReadXCarefullyAndAgree: "ฉันได้อ่านข้อควา
dialog: "ไดอะล็อก"
icon: "ไอคอน"
forYou: "สำหรับคุณ"
currentAnnouncements: "ประกาศในปัจจุบัน"
pastAnnouncements: "ประกาศที่ผ่านมา"
youHaveUnreadAnnouncements: "มีการประกาศที่ยังไม่ได้อ่าน"
replies: "ตอบกลับ"
renotes: "รีโน้ต"
loadReplies: "แสดงการตอบกลับ"
loadConversation: "แสดงบทสนทนา"
pinnedList: "รายการที่ปักหมุดไว้แล้ว"
keepScreenOn: "เปิดหน้าจอไว้"
notifyNotes: "แจ้งเตือนเกี่ยวกับโพสต์ใหม่"
unnotifyNotes: "หยุดการแจ้งเตือนเกี่ยวกับโน้ตใหม่"
authentication: "การตรวจสอบสิทธิ์"
dateAndTime: "เวลาประทับ"
showRenotes: "แสดงรีโน้ต"
edited: "แก้ไขแล้ว"
_announcement:
forExistingUsers: "ผู้ใช้งานที่มีอยู่เท่านั้น"
forExistingUsersDescription: "การประกาศนี้จะแสดงต่อผู้ใช้ที่มีอยู่ ณ จุดที่เผยแพร่นั้นๆถ้าหากเปิดใช้งาน ถ้าหากปิดใช้งานผู้ที่กำลังสมัครใหม่หลังจากโพสต์แล้วนั้นก็จะเห็นเช่นกัน"
needConfirmationToReadDescription: "ข้อความแจ้งแยก ถ้าหากต้องการเพื่อยืนยันว่ากำลังทำเครื่องหมายประกาศนี้ว่าอ่านแล้วจะแสดงขึ้นถ้าหากเปิดใช้งาน การประกาศนั้นจะไม่รวมอยู่ในฟังก์ชั่นว่า \"ทำเครื่องหมายทั้งหมดว่าอ่านแล้ว\""
end: "ประกาศเก็บถาวร"
@@ -1147,7 +1130,6 @@ _serverRules:
description: "ชุดของกฎที่จะแสดงก่อนการลงทะเบียนเราขอแนะนำให้ตั้งค่าสรุปข้อกำหนดในการให้บริการ"
_serverSettings:
iconUrl: "ไอคอน URL"
shortName: "ชื่อย่อ"
_accountMigration:
moveFrom: "ย้ายข้อมูลบัญชีอื่นไปยังอีกบัญชีนี้หนึ่ง"
moveFromSub: "สร้างนามแฝงไปยังบัญชีอื่น"
@@ -1442,7 +1424,6 @@ _role:
gtlAvailable: "การดูไทม์ไลน์ทั่วโลก"
ltlAvailable: "การดูไทม์ไลน์ในท้องถิ่น"
canPublicNote: "สามารถส่งโน้ตสาธารณะ"
canEditNote: "กำลังแก้ไขโน้ต"
canInvite: "สร้างรหัสเชิญอินสแตนซ์"
inviteLimit: "จำกัดการเชิญ"
inviteLimitCycle: "จำกัดการเชิญไว้คูลดาวน์"
@@ -1528,7 +1509,6 @@ _plugin:
install: "ติดตั้งปลั๊กอิน"
installWarn: "กรุณาอย่าติดตั้งปลั๊กอินที่ไม่น่าเชื่อถือนะคะ"
manage: "จัดการปลั๊กอิน"
viewSource: "ดูต้นฉบับ"
_preferencesBackups:
list: "สร้างการสำรองข้อมูล"
saveNew: "บันทึกใหม่"
@@ -2006,7 +1986,6 @@ _notification:
youReceivedFollowRequest: "คุณมีคำขอติดตามใหม่น่ะ"
yourFollowRequestAccepted: "คำขอติดตามของคุณได้รับการยอมรับแล้วน่ะ"
pollEnded: "โพลสำรวจความคิดเห็นผลลัพธ์มีพร้อมใช้งาน"
newNote: "โพสต์ใหม่"
unreadAntennaNote: "เสาอากาศ {name}"
emptyPushNotificationMessage: "การแจ้งเตือนแบบพุชได้รับการอัพเดทแล้ว"
achievementEarned: "รับความสำเร็จ"
@@ -2016,7 +1995,6 @@ _notification:
notificationWillBeDisplayedLikeThis: "การแจ้งเตือนมีลักษณะแบบนี้"
_types:
all: "ทั้งหมด"
note: "โน้ตใหม่"
follow: "กำลังติดตาม"
mention: "กล่าวถึง"
reply: "ตอบกลับ"
@@ -2086,21 +2064,3 @@ _webhookSettings:
renote: "รีโน้ตแล้วเมื่อ"
reaction: "เมื่อได้รับรีแอคชั่น"
mention: "เมื่อกำลังถูกกล่าวถึง"
_moderationLogTypes:
createRole: "สร้างบทบาทแล้ว"
deleteRole: "ลบบทบาทแล้ว"
updateRole: "อัปเดตบทบาทแล้ว"
assignRole: "ได้รับมอบหมายบทบาท"
unassignRole: "ถอดออกจากบทบาทแล้ว"
suspend: "ถูกระงับ"
unsuspend: "เลิกถูกระงับ"
addCustomEmoji: "เพิ่มอีโมจิที่กำหนดเองแล้ว"
updateCustomEmoji: "อัปเดตอีโมจิที่กำหนดเองแล้ว"
deleteCustomEmoji: "ลบอีโมจิที่กำหนดเองออกแล้ว"
updateServerSettings: "อัปเดตการตั้งค่าเซิร์ฟเวอร์แล้ว"
updateUserNote: "อัปเดตโน้ตการกลั่นกรองแล้ว"
deleteDriveFile: "ลบไฟล์ออกแล้ว"
deleteNote: "ลบโน้ตออกแล้ว"
resetPassword: "รีเซ็ตรหัสผ่าน"
resolveAbuseReport: "รายงานได้รับการแก้ไขแล้ว"
createInvitation: "สร้างคำเชิญ"

View File

@@ -448,6 +448,3 @@ _deck:
tl: "Zaman çizelgesi"
list: "Listeler"
mentions: "Bahsetmeler"
_moderationLogTypes:
suspend: "askıya al"
resetPassword: "Şifre sıfırlama"

View File

@@ -1180,7 +1180,6 @@ _plugin:
install: "Встановити плагін"
installWarn: "Будь ласка, не встановлюйте плагінів, яким ви не довіряєте."
manage: "Керування плагінами"
viewSource: "Переглянути вихідний код"
_preferencesBackups:
list: "Створені бекапи"
saveNew: "Зберегти як новий"
@@ -1620,6 +1619,3 @@ _deck:
_webhookSettings:
name: "Ім'я"
active: "Увімкнено"
_moderationLogTypes:
suspend: "Призупинити"
resetPassword: "Скинути пароль"

View File

@@ -1084,6 +1084,3 @@ _webhookSettings:
_events:
renote: "Qayta qayd qilinganda"
mention: "Eslanganda"
_moderationLogTypes:
suspend: "To'xtatish"
resetPassword: "Parolni tiklash"

View File

@@ -1343,7 +1343,6 @@ _plugin:
install: "Cài đặt tiện ích"
installWarn: "Vui lòng không cài đặt những tiện ích đáng ngờ."
manage: "Quản lý plugin"
viewSource: "Xem mã nguồn"
_preferencesBackups:
list: "Tạo sao lưu"
saveNew: "Lưu bản sao lưu"
@@ -1860,6 +1859,3 @@ _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"

View File

@@ -418,7 +418,6 @@ moderator: "监察员"
moderation: "管理"
moderationNote: "管理笔记"
addModerationNote: "添加管理笔记"
moderationLogs: "管理日志"
nUsersMentioned: "{n} 被提到"
securityKeyAndPasskey: "安全密钥或 Passkey"
securityKey: "安全密钥"
@@ -711,7 +710,6 @@ lockedAccountInfo: "即使启用该功能,只要您不将帖子可见范围设
alwaysMarkSensitive: "默认将媒体文件标记为敏感内容"
loadRawImages: "添加附件图像的缩略图时使用原始图像质量"
disableShowingAnimatedImages: "不播放动画"
highlightSensitiveMedia: "高亮显示敏感媒体"
verificationEmailSent: "已发送确认电子邮件。请访问电子邮件中的链接以完成设置。"
notSet: "未设置"
emailVerified: "电子邮件地址已验证"
@@ -1118,11 +1116,6 @@ keepScreenOn: "保持设备屏幕开启"
verifiedLink: "已验证的链接"
notifyNotes: "打开发帖通知"
unnotifyNotes: "关闭发帖通知"
authentication: "验证"
authenticationRequiredToContinue: "要继续,请先进行验证"
dateAndTime: "日期和时间"
showRenotes: "显示转帖"
edited: "已编辑"
_announcement:
forExistingUsers: "仅限现有用户"
forExistingUsersDescription: "若启用,该公告将仅对创建此公告时存在的用户可见。 如果禁用,则在创建此公告后注册的用户也可以看到该公告。"
@@ -1156,8 +1149,6 @@ _serverSettings:
appIconStyleRecommendation: "因为有可能会被裁切为圆形或者圆角矩形,建议使用边缘带有留白背景的图标。"
appIconResolutionMustBe: "分辨率必须为 {resolution}。"
manifestJsonOverride: "覆盖 mainfest.json"
shortName: "简称"
shortNameDescription: "如果服务器的正式名称很长,可以用简称或者別名来替代。"
_accountMigration:
moveFrom: "从别的账号迁移到此账户"
moveFromSub: "为另一个账户建立别名"
@@ -1453,7 +1444,6 @@ _role:
gtlAvailable: "查看全局时间线"
ltlAvailable: "查看本地时间线"
canPublicNote: "允许公开发帖"
canEditNote: "编辑帖子"
canInvite: "发放服务器邀请码"
inviteLimit: "可发行邀请码的数量"
inviteLimitCycle: "邀请码的发行间隔"
@@ -1539,7 +1529,6 @@ _plugin:
install: "安装插件"
installWarn: "请不要安装不可信的插件。"
manage: "管理插件..."
viewSource: "查看源代码"
_preferencesBackups:
list: "已创建的备份"
saveNew: "另存为"
@@ -1805,7 +1794,6 @@ _antennaSources:
homeTimeline: "已关注用户的帖子"
users: "来自指定用户的帖子"
userList: "来自指定列表中的帖子"
userBlacklist: "除掉已选择用户后所有的帖子"
_weekday:
sunday: "星期日"
monday: "星期一"
@@ -2034,7 +2022,6 @@ _notification:
notificationWillBeDisplayedLikeThis: "通知将会这样表示"
_types:
all: "全部"
note: "用户的新帖子"
follow: "关注中"
mention: "提及"
reply: "回复"
@@ -2104,29 +2091,3 @@ _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: "发行邀请码"

View File

@@ -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}」嗎?使用此檔案的貼文也會跟著被刪除。"
driveFileDeleteConfirm: "確定要刪除檔案「{name}」嗎?使用此附件的貼文也會跟著消失。\n"
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: "用此設定後,遠端檔案會被快取在本伺服器儲存空間中。雖然顯示圖片會變快,但會消耗較多伺服器的儲存空間。至於要快取遠端使用者到什麼程度,是依照角色的雲端硬碟容量而定。當超過這個限制時,從較舊的檔案開始自快取中刪除並改為連結。關閉這個設定時,遠端檔案從一開始就維持連結的方式,但建議將 default.yml 的 proxyRemoteFiles 設為 true以便產生圖片的縮圖並保護使用者的隱私。"
youCanCleanRemoteFilesCache: "按檔案管理的🗑️按鈕,將快取全部刪除。"
cacheRemoteFilesDescription: "用此設定會停止建立遠端檔案快取,從而節省伺服器儲存空間,但會因從遠端讀取資料而增加網路數據用量。"
youCanCleanRemoteFilesCache: "按檔案管理的🗑️按鈕,將快取全部刪除。"
cacheRemoteSensitiveFiles: "快取遠端的敏感檔案"
cacheRemoteSensitiveFilesDescription: "若停用這個設定,則不會快取遠端的敏感檔案,而是直接連結。"
flagAsBot: "此使用者是機器人"
flagAsBotDescription: "如果本帳戶由程式控制,請啟用此選項。啟用後會作為標示幫助其他開發者防止機器人之間產生無限互動的行為並會調整Misskey內部系統將本帳戶識別為機器人"
flagAsBotDescription: "標記本帳戶由程式控制,防止其他程式與本帳戶產生無限互動的行為。"
flagAsCat: "此帳戶是一隻貓,喵~~~!!!"
flagAsCatDescription: "如果想將本帳戶標示為一隻貓,請開啟此標示"
flagShowTimelineReplies: "在時間軸上顯示貼文的回覆"
flagShowTimelineRepliesDescription: "啟用,時間軸除了顯示使用者的貼文以外,還會顯示使用者對其他貼文的回覆。"
flagShowTimelineRepliesDescription: "啟用,時間軸除了顯示使用者的貼文以外,還會顯示使用者對其他貼文的回覆。"
autoAcceptFollowed: "自動允許來自追隨中使用者的追隨請求"
addAccount: "新增帳戶"
reloadAccountsList: "更新帳戶清單的資訊"
@@ -184,7 +184,7 @@ host: "主機"
selectUser: "選取使用者"
recipient: "收件人"
annotation: "註解"
federation: "站台聯邦"
federation: "聯邦宇宙"
instances: "伺服器"
registeredAt: "初次觀測"
latestRequestReceivedAt: "上次收到的請求"
@@ -321,7 +321,7 @@ copyUrl: "複製URL"
rename: "重新命名"
avatar: "大頭貼"
banner: "橫幅"
displayOfSensitiveMedia: "敏感檔案的顯示"
displayOfSensitiveMedia: "顯示敏感媒體"
whenServerDisconnected: "與伺服器的連接中斷時"
disconnectedFromServer: "與伺服器中斷連線"
reload: "重新整理"
@@ -418,13 +418,12 @@ moderator: "審查員"
moderation: "審查"
moderationNote: "管理筆記"
addModerationNote: "新增管理筆記"
moderationLogs: "管理日誌"
nUsersMentioned: "被 {n} 個人提及"
nUsersMentioned: "被提及到 {n} 次"
securityKeyAndPasskey: "安全金鑰、Passkey"
securityKey: "安全金鑰"
lastUsed: "上次使用"
lastUsedAt: "上次使用:{t}"
unregister: "註銷"
unregister: "註銷帳戶"
passwordLessLogin: "設置無密碼登入"
passwordLessLoginDescription: "不使用密碼,以安全金鑰或 Passkey 登入"
resetPassword: "重設密碼"
@@ -491,7 +490,7 @@ createAccount: "建立帳戶"
existingAccount: "現有帳戶"
regenerate: "再次生成"
fontSize: "字體大小"
mediaListWithOneImageAppearance: "只有一張圖片時的檔案列表高度"
mediaListWithOneImageAppearance: "只有一張圖片時的媒體列表高度"
limitTo: "上限為 {x}"
noFollowRequests: "沒有追隨您的請求"
openImageInNewTab: "於新分頁中開啟圖片"
@@ -509,8 +508,8 @@ promote: "推廣"
numberOfDays: "有效天數"
hideThisNote: "隱藏此貼文"
showFeaturedNotesInTimeline: "在時間軸上顯示熱門推薦"
objectStorage: "物件儲存"
useObjectStorage: "使用物件儲存"
objectStorage: "對象存儲"
useObjectStorage: "使用對象存儲"
objectStorageBaseUrl: "Base URL"
objectStorageBaseUrlDesc: "用於引用的 URL。如果您使用的是 CDN 或反向代理,請指定其 URL例如 S3https://<bucket>.s3.amazonaws.com、GCShttps://storage.googleapis.com/<bucket>)。"
objectStorageBucket: "儲存空間Bucket"
@@ -573,7 +572,7 @@ tokenRevokedDescription: "登入權杖失效,請重新登入。"
accountDeleted: "帳戶已被刪除"
accountDeletedDescription: "這個帳戶已被刪除。"
menu: "選單"
divider: "分線"
divider: "分線"
addItem: "新增項目"
rearrange: "排序方式"
relays: "中繼"
@@ -582,7 +581,7 @@ inboxUrl: "收件夾URL"
addedRelays: "已加入的中繼"
serviceworkerInfo: "您需要啟用推送通知。"
deletedNote: "已刪除的貼文"
invisibleNote: "私密的貼文"
invisibleNote: "隱藏的貼文"
enableInfiniteScroll: "啟用自動滾動頁面模式"
visibility: "可見性"
poll: "投票"
@@ -708,10 +707,9 @@ driveUsage: "雲端硬碟使用量"
noCrawle: "拒絕搜尋引擎索引"
noCrawleDescription: "要求網路搜尋引擎不要索引你的個人資料頁、貼文及頁面等。"
lockedAccountInfo: "即使你通過了追隨者請求,除非你將貼文的可見性設定為 「追隨者」,否則任何人都能看見你的貼文。"
alwaysMarkSensitive: "預設標記檔案為敏感內容"
alwaysMarkSensitive: "預設將多媒體標記為敏感內容"
loadRawImages: "以原始圖檔顯示附件圖檔的縮圖"
disableShowingAnimatedImages: "不播放動態圖檔"
highlightSensitiveMedia: "強調敏感標記"
verificationEmailSent: "已發送驗證電子郵件。請點擊進入電子郵件中的鏈接完成驗證。"
notSet: "未設定"
emailVerified: "已成功驗證您的電郵"
@@ -928,7 +926,7 @@ type: "類型"
speed: "速度"
slow: "慢"
fast: "快"
sensitiveMediaDetection: "敏感檔案的檢測"
sensitiveMediaDetection: "敏感性媒體的檢測"
localOnly: "僅限本地"
remoteOnly: "僅限遠端"
failedToUpload: "上傳失敗"
@@ -937,7 +935,7 @@ cannotUploadBecauseNoFreeSpace: "由於雲端硬碟沒有可用空間,因此
cannotUploadBecauseExceedsFileSizeLimit: "由於超過了檔案大小的限制,無法上傳。"
beta: "測試版"
enableAutoSensitive: "自動 NSFW 判定"
enableAutoSensitiveDescription: "如果可用,它將使用機器學習技術判斷檔案是否需要標記為敏感。即使關閉此功能,也可能會依實例規則而自動啟用。"
enableAutoSensitiveDescription: "如果可用,它將使用機器學習技術判斷多媒體內容是否需要標記 NSFW。即使關閉此功能,也可能會依實例規則而自動啟用。"
activeEmailValidationDescription: "積極驗證使用者的電郵地址,以判斷它是否可以通訊。關閉此選項代表只會檢查地址是否符合格式。"
navbar: "導覽列"
shuffle: "隨機"
@@ -1118,10 +1116,6 @@ keepScreenOn: "保持設備螢幕開啟"
verifiedLink: "已驗證連結"
notifyNotes: "開啟貼文通知"
unnotifyNotes: "關閉貼文通知"
authentication: "驗證"
authenticationRequiredToContinue: "請於繼續前完成驗證"
dateAndTime: "日期與時間"
showRenotes: "顯示轉發貼文"
_announcement:
forExistingUsers: "僅限既有的使用者"
forExistingUsersDescription: "啟用代表僅向現存使用者顯示;停用代表張貼後註冊的新使用者也會看到。"
@@ -1155,8 +1149,6 @@ _serverSettings:
appIconStyleRecommendation: "因為可能會裁剪成圓形或圓角,所以建議用單色填滿邊框及背景。"
appIconResolutionMustBe: "解析度必須為 {resolution}。"
manifestJsonOverride: "覆寫 manifest.json"
shortName: "簡稱"
shortNameDescription: "如果伺服器的正式名稱很長,可用簡稱或通稱代替。"
_accountMigration:
moveFrom: "從其他帳戶遷移到這個帳戶"
moveFromSub: "為另一個帳戶建立別名"
@@ -1452,7 +1444,6 @@ _role:
gtlAvailable: "瀏覽全域時間軸"
ltlAvailable: "瀏覽本地時間軸"
canPublicNote: "允許公開貼文"
canEditNote: "允許編輯貼文"
canInvite: "發行實例邀請碼"
inviteLimit: "可建立邀請碼的數量"
inviteLimitCycle: "邀請碼的發放間隔"
@@ -1487,7 +1478,7 @@ _role:
or: "~或~"
not: "~否"
_sensitiveMediaDetection:
description: "您可以使用機器學習自動檢測敏感檔案以便審查。這會稍微增加伺服器負荷。"
description: "您可以使用機器學習自動檢測敏感媒體並將其用於審查。 伺服器負荷會稍微增加。"
sensitivity: "檢測敏感度"
sensitivityDescription: "敏感度低時,誤檢測(偽陽性)會減少。敏感度高時,漏檢(偽陰性)會減少。"
setSensitiveFlagAutomatically: "設定 NSFW 標籤"
@@ -1538,7 +1529,6 @@ _plugin:
install: "安裝外掛組件"
installWarn: "請不要安裝來源不明的外掛。"
manage: "管理外掛"
viewSource: "檢視原始碼"
_preferencesBackups:
list: "已備份的設定檔"
saveNew: "另存新檔"
@@ -1573,13 +1563,13 @@ _aboutMisskey:
morePatrons: "還有許許多多幫助我們的其他人,非常感謝你們。 🥰"
patrons: "贊助者"
_displayOfSensitiveMedia:
respect: "隱藏敏感檔案"
ignore: "顯示敏感檔案"
force: "隱藏所有檔案"
respect: "隱藏被標記為敏感的多媒體內容"
ignore: "不隱藏被標記為敏感的多媒體內容"
force: "隱藏所有多媒體內容"
_instanceTicker:
none: "隱藏"
remote: "只顯示遠端使用者"
always: "一律顯示"
remote: "遠端使用者顯示"
always: "總是顯示"
_serverDisconnectedBehavior:
reload: "自動重載"
dialog: "彈出式警告"
@@ -1612,7 +1602,7 @@ _wordMute:
mutedNotes: "已靜音的貼文"
_instanceMute:
instanceMuteDescription: "包括對被靜音實例上的使用者的回覆,被設定的實例上所有貼文及轉發都會被靜音。"
instanceMuteDescription2: "設定時以換行進行分隔"
instanceMuteDescription2: "換行以分隔"
title: "將隱藏被設定的實例貼文。"
heading: "將實例靜音"
_theme:
@@ -1665,7 +1655,7 @@ _theme:
mentionMe: "提到了我"
renote: "轉發貼文"
modalBg: "對話框背景"
divider: "分線"
divider: "分線"
scrollbarHandle: "捲動條"
scrollbarHandleHover: "捲動條(懸浮)"
dateLabelFg: "日期標籤文字"
@@ -1804,7 +1794,6 @@ _antennaSources:
homeTimeline: "來自已追隨使用者的貼文"
users: "來自特定使用者的貼文"
userList: "來自特定清單中的貼文"
userBlacklist: "除指定使用者外的所有貼文"
_weekday:
sunday: "週日"
monday: "週一"
@@ -2033,7 +2022,6 @@ _notification:
notificationWillBeDisplayedLikeThis: "通知會以這樣的方式顯示"
_types:
all: "全部 "
note: "使用者的最新貼文"
follow: "追隨中"
mention: "提及"
reply: "回覆"
@@ -2103,31 +2091,3 @@ _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: "建立邀請碼"

View File

@@ -1,12 +1,12 @@
{
"name": "misskey",
"version": "2023.9.2",
"version": "2023.9.0-beta.10",
"codename": "nasubi",
"repository": {
"type": "git",
"url": "https://github.com/misskey-dev/misskey.git"
},
"packageManager": "pnpm@8.8.0",
"packageManager": "pnpm@8.7.6",
"workspaces": [
"packages/frontend",
"packages/backend",
@@ -46,16 +46,16 @@
"execa": "8.0.1",
"cssnano": "6.0.1",
"js-yaml": "4.1.0",
"postcss": "8.4.31",
"postcss": "8.4.30",
"terser": "5.20.0",
"typescript": "5.2.2"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "6.7.3",
"@typescript-eslint/parser": "6.7.3",
"@typescript-eslint/eslint-plugin": "6.7.2",
"@typescript-eslint/parser": "6.7.2",
"cross-env": "7.0.3",
"cypress": "13.3.0",
"eslint": "8.50.0",
"cypress": "13.2.0",
"eslint": "8.49.0",
"start-server-and-test": "2.0.1"
},
"optionalDependencies": {

View File

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

View File

@@ -1,21 +0,0 @@
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"`);
}
}

View File

@@ -1,11 +0,0 @@
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"`);
}
}

View File

@@ -1,18 +0,0 @@
/*
* 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 '{}'`);
}
}

View File

@@ -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": "8.0.0",
"@fastify/multipart": "7.7.3",
"@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",
"@nestjs/common": "10.2.5",
"@nestjs/core": "10.2.5",
"@nestjs/testing": "10.2.5",
"@peertube/http-signature": "1.7.0",
"@simplewebauthn/server": "8.2.0",
"@simplewebauthn/server": "8.1.1",
"@sinonjs/fake-timers": "11.1.0",
"@swc/cli": "0.1.62",
"@swc/core": "1.3.90",
"@swc/core": "1.3.87",
"accepts": "1.3.8",
"ajv": "8.12.0",
"archiver": "6.0.1",
@@ -86,7 +86,7 @@
"bcryptjs": "2.4.3",
"blurhash": "2.0.5",
"body-parser": "1.20.2",
"bullmq": "4.11.4",
"bullmq": "4.11.3",
"cacheable-lookup": "7.0.0",
"cbor": "9.0.1",
"chalk": "5.3.0",
@@ -115,7 +115,7 @@
"json5": "2.2.3",
"jsonld": "8.3.1",
"jsrsasign": "10.8.6",
"meilisearch": "0.35.0",
"meilisearch": "0.34.2",
"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.9",
"systeminformation": "5.21.8",
"tinycolor2": "1.6.0",
"tmp": "0.2.1",
"tsc-alias": "1.8.8",
@@ -174,7 +174,7 @@
"@simplewebauthn/typescript-types": "8.0.0",
"@swc/jest": "0.2.29",
"@types/accepts": "1.3.5",
"@types/archiver": "5.3.3",
"@types/archiver": "5.3.2",
"@types/bcryptjs": "2.4.4",
"@types/body-parser": "1.19.3",
"@types/cbor": "6.0.0",
@@ -187,36 +187,36 @@
"@types/jsdom": "21.1.3",
"@types/jsonld": "1.5.10",
"@types/jsrsasign": "10.5.9",
"@types/mime-types": "2.1.2",
"@types/ms": "0.7.32",
"@types/node": "20.7.1",
"@types/mime-types": "2.1.1",
"@types/ms": "0.7.31",
"@types/node": "20.6.3",
"@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.11",
"@types/nodemailer": "6.4.10",
"@types/oauth": "0.9.2",
"@types/oauth2orize": "1.11.1",
"@types/oauth2orize-pkce": "0.1.0",
"@types/pg": "8.10.3",
"@types/pug": "2.0.7",
"@types/pg": "8.10.2",
"@types/pug": "2.0.6",
"@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.5",
"@types/sanitize-html": "2.9.1",
"@types/semver": "7.5.3",
"@types/rename": "1.0.4",
"@types/sanitize-html": "2.9.0",
"@types/semver": "7.5.2",
"@types/sharp": "0.32.0",
"@types/simple-oauth2": "5.0.5",
"@types/sinonjs__fake-timers": "8.1.3",
"@types/simple-oauth2": "5.0.4",
"@types/sinonjs__fake-timers": "8.1.2",
"@types/tinycolor2": "1.4.4",
"@types/tmp": "0.2.4",
"@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",
"@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",
"aws-sdk-client-mock": "3.0.0",
"cross-env": "7.0.3",
"eslint": "8.50.0",
"eslint": "8.49.0",
"eslint-plugin-import": "2.28.1",
"execa": "8.0.1",
"jest": "29.7.0",

View File

@@ -63,7 +63,6 @@ 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);

View File

@@ -89,7 +89,6 @@ type Source = {
perChannelMaxNoteCacheCount?: number;
perUserNotificationsMaxCount?: number;
deactivateAntennaThreshold?: number;
pidFile: string;
};
export type Config = {
@@ -164,7 +163,6 @@ export type Config = {
perChannelMaxNoteCacheCount: number;
perUserNotificationsMaxCount: number;
deactivateAntennaThreshold: number;
pidFile: string;
};
const _filename = fileURLToPath(import.meta.url);
@@ -257,7 +255,6 @@ export function loadConfig(): Config {
perChannelMaxNoteCacheCount: config.perChannelMaxNoteCacheCount ?? 1000,
perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 300,
deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7),
pidFile: config.pidFile,
};
}

View File

@@ -7,12 +7,11 @@ 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, UsersRepository } from '@/models/_.js';
import type { AnnouncementReadsRepository, AnnouncementsRepository, MiAnnouncement, MiAnnouncementRead } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import { Packed } from '@/misc/json-schema.js';
import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
@Injectable()
export class AnnouncementService {
@@ -23,12 +22,8 @@ export class AnnouncementService {
@Inject(DI.announcementReadsRepository)
private announcementReadsRepository: AnnouncementReadsRepository,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
private idService: IdService,
private globalEventService: GlobalEventService,
private moderationLogService: ModerationLogService,
) {
}
@@ -63,7 +58,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>): Promise<{ raw: MiAnnouncement; packed: Packed<'Announcement'> }> {
const announcement = await this.announcementsRepository.insert({
id: this.idService.genId(),
createdAt: new Date(),
@@ -84,28 +79,10 @@ export class AnnouncementService {
this.globalEventService.publishMainStream(values.userId, 'announcementCreated', {
announcement: packed,
});
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,
});
if (moderator) {
this.moderationLogService.log(moderator, 'createGlobalAnnouncement', {
announcementId: announcement.id,
announcement: announcement,
});
}
}
return {
@@ -114,63 +91,6 @@ 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 {

View File

@@ -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 type { GlobalEvents } from '@/core/GlobalEventService.js';
import { StreamMessages } from '@/server/api/stream/types.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 GlobalEvents['internal']['payload'];
const { type, body } = obj.message as StreamMessages['internal']['payload'];
switch (type) {
case 'antennaCreated':
this.antennas.push({

View File

@@ -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 type { GlobalEvents } from '@/core/GlobalEventService.js';
import { StreamMessages } from '@/server/api/stream/types.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 GlobalEvents['internal']['payload'];
const { type, body } = obj.message as StreamMessages['internal']['payload'];
switch (type) {
case 'userChangeSuspendedState':
case 'remoteUserUpdated': {

View File

@@ -12,13 +12,12 @@ 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, MiUser } from '@/models/_.js';
import type { EmojisRepository, MiRole } 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 '@/types.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import type { Serialized } from '@/server/api/stream/types.js';
const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/;
@@ -37,7 +36,6 @@ 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);
@@ -68,7 +66,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
isSensitive: boolean;
localOnly: boolean;
roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][];
}, moderator?: MiUser): Promise<MiEmoji> {
}): Promise<MiEmoji> {
const emoji = await this.emojisRepository.insert({
id: this.idService.genId(),
updatedAt: new Date(),
@@ -91,13 +89,6 @@ 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;
@@ -113,7 +104,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
isSensitive?: boolean;
localOnly?: boolean;
roleIdsThatCanBeUsedThisEmojiAsReaction?: MiRole['id'][];
}, moderator?: MiUser): Promise<void> {
}): 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');
@@ -134,11 +125,11 @@ export class CustomEmojiService implements OnApplicationShutdown {
this.localEmojisCache.refresh();
const packed = await this.emojiEntityService.packDetailed(emoji.id);
const updated = await this.emojiEntityService.packDetailed(emoji.id);
if (emoji.name === data.name) {
this.globalEventService.publishBroadcastStream('emojiUpdated', {
emojis: [packed],
emojis: [updated],
});
} else {
this.globalEventService.publishBroadcastStream('emojiDeleted', {
@@ -146,16 +137,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
});
this.globalEventService.publishBroadcastStream('emojiAdded', {
emoji: packed,
});
}
if (moderator) {
const updated = await this.emojisRepository.findOneByOrFail({ id: id });
this.moderationLogService.log(moderator, 'updateCustomEmoji', {
emojiId: emoji.id,
before: emoji,
after: updated,
emoji: updated,
});
}
}
@@ -249,7 +231,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
}
@bindThis
public async delete(id: MiEmoji['id'], moderator?: MiUser) {
public async delete(id: MiEmoji['id']) {
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
await this.emojisRepository.delete(emoji.id);
@@ -259,30 +241,16 @@ 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'][], moderator?: MiUser) {
public async deleteBulk(ids: MiEmoji['id'][]) {
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();

View File

@@ -42,7 +42,6 @@ import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import { correctFilename } from '@/misc/correct-filename.js';
import { isMimeImage } from '@/misc/is-mime-image.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
type AddFileArgs = {
/** User who wish to add file */
@@ -87,9 +86,6 @@ 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;
@@ -123,7 +119,6 @@ export class DriveService {
private globalEventService: GlobalEventService,
private queueService: QueueService,
private roleService: RoleService,
private moderationLogService: ModerationLogService,
private driveChart: DriveChart,
private perUserDriveChart: PerUserDriveChart,
private instanceChart: InstanceChart,
@@ -653,63 +648,7 @@ export class DriveService {
}
@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) {
public async deleteFile(file: MiDriveFile, isExpired = false) {
if (file.storedInternal) {
this.internalStorageService.del(file.accessKey!);
@@ -732,11 +671,11 @@ export class DriveService {
}
}
this.deletePostProcess(file, isExpired, deleter);
this.deletePostProcess(file, isExpired);
}
@bindThis
public async deleteFileSync(file: MiDriveFile, isExpired = false, deleter?: MiUser) {
public async deleteFileSync(file: MiDriveFile, isExpired = false) {
if (file.storedInternal) {
this.internalStorageService.del(file.accessKey!);
@@ -763,11 +702,11 @@ export class DriveService {
await Promise.all(promises);
}
this.deletePostProcess(file, isExpired, deleter);
this.deletePostProcess(file, isExpired);
}
@bindThis
private async deletePostProcess(file: MiDriveFile, isExpired = false, deleter?: MiUser) {
private async deletePostProcess(file: MiDriveFile, isExpired = false) {
// リモートファイル期限切れ削除後は直リンクにする
if (isExpired && file.userHost !== null && file.uri != null) {
this.driveFilesRepository.update(file.id, {
@@ -794,20 +733,6 @@ export class DriveService {
this.instanceChart.updateDrive(file, false);
}
}
if (file.userId) {
this.globalEventService.publishDriveStream(file.userId, 'fileDeleted', file.id);
}
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,
});
}
}
@bindThis

View File

@@ -5,254 +5,27 @@
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 { MiAntenna } from '@/models/Antenna.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 { 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 { Packed } from '@/misc/json-schema.js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { bindThis } from '@/decorators.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'];
import { MiRole } from '@/models/_.js';
@Injectable()
export class GlobalEventService {
@@ -278,7 +51,7 @@ export class GlobalEventService {
}
@bindThis
public publishInternalEvent<K extends keyof InternalEventTypes>(type: K, value?: InternalEventTypes[K]): void {
public publishInternalEvent<K extends keyof InternalStreamTypes>(type: K, value?: InternalStreamTypes[K]): void {
this.publish('internal', type, typeof value === 'undefined' ? null : value);
}
@@ -288,17 +61,17 @@ export class GlobalEventService {
}
@bindThis
public publishMainStream<K extends keyof MainEventTypes>(userId: MiUser['id'], type: K, value?: MainEventTypes[K]): void {
public publishMainStream<K extends keyof MainStreamTypes>(userId: MiUser['id'], type: K, value?: MainStreamTypes[K]): void {
this.publish(`mainStream:${userId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis
public publishDriveStream<K extends keyof DriveEventTypes>(userId: MiUser['id'], type: K, value?: DriveEventTypes[K]): void {
public publishDriveStream<K extends keyof DriveStreamTypes>(userId: MiUser['id'], type: K, value?: DriveStreamTypes[K]): void {
this.publish(`driveStream:${userId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis
public publishNoteStream<K extends keyof NoteEventTypes>(noteId: MiNote['id'], type: K, value?: NoteEventTypes[K]): void {
public publishNoteStream<K extends keyof NoteStreamTypes>(noteId: MiNote['id'], type: K, value?: NoteStreamTypes[K]): void {
this.publish(`noteStream:${noteId}`, type, {
id: noteId,
body: value,
@@ -306,17 +79,17 @@ export class GlobalEventService {
}
@bindThis
public publishUserListStream<K extends keyof UserListEventTypes>(listId: MiUserList['id'], type: K, value?: UserListEventTypes[K]): void {
public publishUserListStream<K extends keyof UserListStreamTypes>(listId: MiUserList['id'], type: K, value?: UserListStreamTypes[K]): void {
this.publish(`userListStream:${listId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis
public publishAntennaStream<K extends keyof AntennaEventTypes>(antennaId: MiAntenna['id'], type: K, value?: AntennaEventTypes[K]): void {
public publishAntennaStream<K extends keyof AntennaStreamTypes>(antennaId: MiAntenna['id'], type: K, value?: AntennaStreamTypes[K]): void {
this.publish(`antennaStream:${antennaId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis
public publishRoleTimelineStream<K extends keyof RoleTimelineEventTypes>(roleId: MiRole['id'], type: K, value?: RoleTimelineEventTypes[K]): void {
public publishRoleTimelineStream<K extends keyof RoleTimelineStreamTypes>(roleId: MiRole['id'], type: K, value?: RoleTimelineStreamTypes[K]): void {
this.publish(`roleTimelineStream:${roleId}`, type, typeof value === 'undefined' ? null : value);
}
@@ -326,7 +99,7 @@ export class GlobalEventService {
}
@bindThis
public publishAdminStream<K extends keyof AdminEventTypes>(userId: MiUser['id'], type: K, value?: AdminEventTypes[K]): void {
public publishAdminStream<K extends keyof AdminStreamTypes>(userId: MiUser['id'], type: K, value?: AdminStreamTypes[K]): void {
this.publish(`adminStream:${userId}`, type, typeof value === 'undefined' ? null : value);
}
}

View File

@@ -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 type { GlobalEvents } from '@/core/GlobalEventService.js';
import { StreamMessages } from '@/server/api/stream/types.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 GlobalEvents['internal']['payload'];
const { type, body } = obj.message as StreamMessages['internal']['payload'];
switch (type) {
case 'metaUpdated': {
this.cache = body;

View File

@@ -9,7 +9,6 @@ import type { ModerationLogsRepository } from '@/models/_.js';
import type { MiUser } from '@/models/User.js';
import { IdService } from '@/core/IdService.js';
import { bindThis } from '@/decorators.js';
import { ModerationLogPayloads, moderationLogTypes } from '@/types.js';
@Injectable()
export class ModerationLogService {
@@ -22,13 +21,13 @@ export class ModerationLogService {
}
@bindThis
public async log<T extends typeof moderationLogTypes[number]>(moderator: { id: MiUser['id'] }, type: T, info?: ModerationLogPayloads[T]) {
public async insertModerationLog(moderator: { id: MiUser['id'] }, type: string, info?: Record<string, any>) {
await this.moderationLogsRepository.insert({
id: this.idService.genId(),
createdAt: new Date(),
userId: moderator.id,
type: type,
info: (info as any) ?? {},
info: info ?? {},
});
}
}

View File

@@ -110,8 +110,9 @@ 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);
});
}
}
}
@@ -514,8 +515,9 @@ 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);
});
}
});
}

View File

@@ -23,7 +23,6 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js';
import { SearchService } from '@/core/SearchService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
@Injectable()
export class NoteDeleteService {
@@ -49,7 +48,6 @@ export class NoteDeleteService {
private apDeliverManagerService: ApDeliverManagerService,
private metaService: MetaService,
private searchService: SearchService,
private moderationLogService: ModerationLogService,
private notesChart: NotesChart,
private perUserNotesChart: PerUserNotesChart,
private instanceChart: InstanceChart,
@@ -60,7 +58,7 @@ export class NoteDeleteService {
* @param user 投稿者
* @param note 投稿
*/
async delete(user: { id: MiUser['id']; uri: MiUser['uri']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote, quiet = false, deleter?: MiUser) {
async delete(user: { id: MiUser['id']; uri: MiUser['uri']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote, quiet = false) {
const deletedAt = new Date();
const cascadingNotes = await this.findCascadingNotes(note);
@@ -133,17 +131,6 @@ export class NoteDeleteService {
id: note.id,
userId: user.id,
});
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,
});
}
}
@bindThis

View File

@@ -18,7 +18,6 @@ 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 {
@@ -39,7 +38,6 @@ export class NotificationService implements OnApplicationShutdown {
private globalEventService: GlobalEventService,
private pushNotificationService: PushNotificationService,
private cacheService: CacheService,
private userListService: UserListService,
) {
}
@@ -76,56 +74,27 @@ export class NotificationService implements OnApplicationShutdown {
public async createNotification(
notifieeId: MiUser['id'],
type: MiNotification['type'],
data: Omit<Partial<MiNotification>, 'notifierId'>,
notifierId?: MiUser['id'] | null,
data: Partial<MiNotification>,
): Promise<MiNotification | null> {
const profile = await this.cacheService.userProfileCache.fetch(notifieeId);
const recieveConfig = profile.notificationRecieveConfig[type];
if (recieveConfig?.type === 'never') {
return null;
}
const isMuted = profile.mutingNotificationTypes.includes(type);
if (isMuted) return null;
if (notifierId) {
if (notifieeId === notifierId) {
if (data.notifierId) {
if (notifieeId === data.notifierId) {
return null;
}
const mutings = await this.cacheService.userMutingsCache.fetch(notifieeId);
if (mutings.has(notifierId)) {
if (mutings.has(data.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;
@@ -148,8 +117,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: notifierId! }));
if (type === 'receiveFollowRequest') this.emailNotificationReceiveFollowRequest(notifieeId, await this.usersRepository.findOneByOrFail({ id: notifierId! }));
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! }));
}, () => { /* aborted, ignore it */ });
return notification;

View File

@@ -219,9 +219,10 @@ export class ReactionService {
// リアクションされたユーザーがローカルユーザーなら通知を作成
if (note.userHost === null) {
this.notificationService.createNotification(note.userId, 'reaction', {
notifierId: user.id,
noteId: note.id,
reaction: reaction,
}, user.id);
});
}
//#region 配信

View File

@@ -15,10 +15,9 @@ 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 type { GlobalEvents } from '@/core/GlobalEventService.js';
import { StreamMessages } from '@/server/api/stream/types.js';
import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import type { Packed } from '@/misc/json-schema.js';
import type { OnApplicationShutdown } from '@nestjs/common';
@@ -26,7 +25,6 @@ export type RolePolicies = {
gtlAvailable: boolean;
ltlAvailable: boolean;
canPublicNote: boolean;
canEditNote: boolean;
canInvite: boolean;
inviteLimit: number;
inviteLimitCycle: number;
@@ -51,7 +49,6 @@ export const DEFAULT_POLICIES: RolePolicies = {
gtlAvailable: true,
ltlAvailable: true,
canPublicNote: true,
canEditNote: true,
canInvite: false,
inviteLimit: 0,
inviteLimitCycle: 60 * 24 * 7,
@@ -101,7 +98,6 @@ export class RoleService implements OnApplicationShutdown {
private userEntityService: UserEntityService,
private globalEventService: GlobalEventService,
private idService: IdService,
private moderationLogService: ModerationLogService,
) {
//this.onMessage = this.onMessage.bind(this);
@@ -116,7 +112,7 @@ export class RoleService implements OnApplicationShutdown {
const obj = JSON.parse(data);
if (obj.channel === 'internal') {
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
const { type, body } = obj.message as StreamMessages['internal']['payload'];
switch (type) {
case 'roleCreated': {
const cached = this.rolesCache.get();
@@ -296,7 +292,6 @@ 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)),
@@ -379,11 +374,9 @@ export class RoleService implements OnApplicationShutdown {
}
@bindThis
public async assign(userId: MiUser['id'], roleId: MiRole['id'], expiresAt: Date | null = null, moderator?: MiUser): Promise<void> {
public async assign(userId: MiUser['id'], roleId: MiRole['id'], expiresAt: Date | null = null): Promise<void> {
const now = new Date();
const role = await this.rolesRepository.findOneByOrFail({ id: roleId });
const existing = await this.roleAssignmentsRepository.findOneBy({
roleId: roleId,
userId: userId,
@@ -413,22 +406,10 @@ 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,
});
}
}
@bindThis
public async unassign(userId: MiUser['id'], roleId: MiRole['id'], moderator?: MiUser): Promise<void> {
public async unassign(userId: MiUser['id'], roleId: MiRole['id']): Promise<void> {
const now = new Date();
const existing = await this.roleAssignmentsRepository.findOneBy({ roleId, userId });
@@ -449,20 +430,6 @@ export class RoleService implements OnApplicationShutdown {
});
this.globalEventService.publishInternalEvent('userRoleUnassigned', existing);
if (moderator) {
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,
});
}
}
@bindThis
@@ -484,75 +451,6 @@ 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();
await this.rolesRepository.update(role.id, {
updatedAt: date,
...params,
});
const updated = await this.rolesRepository.findOneByOrFail({ id: role.id });
this.globalEventService.publishInternalEvent('roleUpdated', updated);
if (moderator) {
this.moderationLogService.log(moderator, 'updateRole', {
roleId: role.id,
before: role,
after: updated,
});
}
}
@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);

View File

@@ -230,7 +230,8 @@ export class UserFollowingService implements OnModuleInit {
// 通知を作成
this.notificationService.createNotification(follower.id, 'followRequestAccepted', {
}, followee.id);
notifierId: followee.id,
});
}
if (alreadyFollowed) return;
@@ -303,7 +304,8 @@ export class UserFollowingService implements OnModuleInit {
// 通知を作成
this.notificationService.createNotification(followee.id, 'follow', {
}, follower.id);
notifierId: follower.id,
});
}
}
@@ -486,8 +488,9 @@ 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)) {

View File

@@ -3,8 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import * as Redis from 'ioredis';
import { Inject, Injectable } from '@nestjs/common';
import type { UserListJoiningsRepository } from '@/models/_.js';
import type { MiUser } from '@/models/User.js';
import type { MiUserList } from '@/models/UserList.js';
@@ -17,22 +16,12 @@ 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 implements OnApplicationShutdown {
export class UserListService {
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,
@@ -43,48 +32,10 @@ export class UserListService implements OnApplicationShutdown {
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
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) {
public async push(target: MiUser, list: MiUserList, me: MiUser) {
const currentCount = await this.userListJoiningsRepository.countBy({
userListId: list.id,
});
@@ -99,7 +50,6 @@ export class UserListService implements OnApplicationShutdown {
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));
// このインスタンス内にこのリモートユーザーをフォローしているユーザーがいなくても投稿を受け取るためにダミーのユーザーがフォローしたということにする
@@ -110,26 +60,4 @@ export class UserListService implements OnApplicationShutdown {
}
}
}
@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();
}
}

View File

@@ -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 type { GlobalEvents } from '@/core/GlobalEventService.js';
import { StreamMessages } from '@/server/api/stream/types.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 GlobalEvents['internal']['payload'];
const { type, body } = obj.message as StreamMessages['internal']['payload'];
switch (type) {
case 'webhookCreated':
if (body.active) {

View File

@@ -308,7 +308,6 @@ 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,

View File

@@ -452,7 +452,7 @@ export class UserEntityService implements OnModuleInit {
hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id),
mutedWords: profile!.mutedWords,
mutedInstances: profile!.mutedInstances,
notificationRecieveConfig: profile!.notificationRecieveConfig,
mutingNotificationTypes: profile!.mutingNotificationTypes,
emailNotificationTypes: profile!.emailNotificationTypes,
achievements: profile!.achievements,
loggedInDays: profile!.loggedInDates.length,

View File

@@ -20,11 +20,6 @@ export class MiMeta {
})
public name: string | null;
@Column('varchar', {
length: 64, nullable: true,
})
public shortName: string | null;
@Column('varchar', {
length: 1024, nullable: true,
})

View File

@@ -24,11 +24,6 @@ export class MiNote {
})
public createdAt: Date;
@Column('timestamp with time zone', {
default: null,
})
public updatedAt: Date | null;
@Index()
@Column({
...id(),

View File

@@ -8,7 +8,6 @@ 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 する」のようなクエリは書けなくなるからウーン
@@ -223,25 +222,16 @@ export class MiUserProfile {
})
public mutedInstances: string[];
@Column('jsonb', {
default: {},
@Column('enum', {
enum: [
...notificationTypes,
// マイグレーションで削除が困難なので古いenumは残しておく
...obsoleteNotificationTypes,
],
array: true,
default: [],
})
public notificationRecieveConfig: {
[notificationType in typeof notificationTypes[number]]?: {
type: 'all';
} | {
type: 'never';
} | {
type: 'following';
} | {
type: 'follower';
} | {
type: 'mutualFollow';
} | {
type: 'list';
userListId: MiUserList['id'];
};
};
public mutingNotificationTypes: typeof notificationTypes[number][];
@Column('varchar', {
length: 32, array: true, default: '{}',

View File

@@ -17,11 +17,6 @@ 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,
@@ -147,7 +142,7 @@ export const packedNoteSchema = {
isSensitive: {
type: 'boolean',
optional: true, nullable: false,
},
}
},
},
},

View File

@@ -387,9 +387,13 @@ export const packedMeDetailedOnlySchema = {
nullable: false, optional: false,
},
},
notificationRecieveConfig: {
type: 'object',
nullable: false, optional: false,
mutingNotificationTypes: {
type: 'array',
nullable: true, optional: false,
items: {
type: 'string',
nullable: false, optional: false,
},
},
emailNotificationTypes: {
type: 'array',

View File

@@ -101,7 +101,7 @@ export class ImportUserListsProcessorService {
if (await this.userListJoiningsRepository.findOneBy({ userListId: list!.id, userId: target.id }) != null) continue;
this.userListService.addMember(target, list!, user);
this.userListService.push(target, list!, user);
} catch (e) {
this.logger.warn(`Error in line:${linenum} ${e}`);
}

View File

@@ -276,17 +276,10 @@ export class ApiCallService implements OnApplicationShutdown {
id: '1384574d-a912-4b81-8601-c7b1c4085df1',
httpStatusCode: 401,
});
} else if (user!.isSuspended) {
throw new ApiError({
message: 'Your account has been suspended.',
code: 'YOUR_ACCOUNT_SUSPENDED',
kind: 'permission',
id: 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370',
});
}
}
if (ep.meta.prohibitMoved) {
if (ep.meta.prohibitDeactivated) {
if (user?.movedToUri) {
throw new ApiError({
message: 'You have moved your account.',
@@ -295,6 +288,14 @@ export class ApiCallService implements OnApplicationShutdown {
id: '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31',
});
}
if (user?.isSuspended) {
throw new ApiError({
message: 'Your account has been suspended.',
code: 'YOUR_ACCOUNT_SUSPENDED',
kind: 'permission',
id: 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370',
});
}
}
if ((ep.meta.requireModerator || ep.meta.requireAdmin) && !user!.isRoot) {

View File

@@ -258,7 +258,6 @@ 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';
@@ -607,7 +606,6 @@ 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 };
@@ -960,7 +958,6 @@ 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,
@@ -1307,7 +1304,6 @@ 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,

View File

@@ -258,7 +258,6 @@ 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';
@@ -605,7 +604,6 @@ 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],
@@ -737,7 +735,7 @@ export interface IEndpointMeta {
* 引っ越し済みのユーザーによるリクエストを禁止するか
* 省略した場合は false として解釈されます。
*/
readonly prohibitMoved?: boolean;
readonly prohibitDeactivated?: boolean;
/**
* エンドポイントのリミテーションに関するやつ

View File

@@ -8,7 +8,6 @@ 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'],
@@ -40,10 +39,9 @@ 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) => {
const ad = await this.adsRepository.insert({
await this.adsRepository.insert({
id: this.idService.genId(),
createdAt: new Date(),
expiresAt: new Date(ps.expiresAt),
@@ -55,14 +53,7 @@ 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;
});
}
}

View File

@@ -7,7 +7,6 @@ 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 = {
@@ -38,8 +37,6 @@ 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 });
@@ -47,11 +44,6 @@ 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,
});
});
}
}

View File

@@ -22,7 +22,6 @@ 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;
@@ -37,9 +36,6 @@ 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;

View File

@@ -7,7 +7,6 @@ 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 = {
@@ -47,8 +46,6 @@ 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 });
@@ -66,14 +63,6 @@ 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,
});
});
}
}

View File

@@ -81,7 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
forExistingUsers: ps.forExistingUsers,
needConfirmationToRead: ps.needConfirmationToRead,
userId: ps.userId,
}, me);
});
return packed;
});

View File

@@ -7,7 +7,6 @@ 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 = {
@@ -38,15 +37,13 @@ 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.announcementService.delete(announcement, me);
await this.announcementsRepository.delete(announcement.id);
});
}
}

View File

@@ -7,7 +7,6 @@ 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 = {
@@ -46,15 +45,13 @@ 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.announcementService.update(announcement, {
await this.announcementsRepository.update(announcement.id, {
updatedAt: new Date(),
title: ps.title,
text: ps.text,
@@ -65,7 +62,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
forExistingUsers: ps.forExistingUsers,
needConfirmationToRead: ps.needConfirmationToRead,
isActive: ps.isActive,
}, me);
});
});
}
}

View File

@@ -8,6 +8,7 @@ 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';
@@ -60,6 +61,7 @@ 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 });
@@ -75,7 +77,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
isSensitive: ps.isSensitive ?? false,
localOnly: ps.localOnly ?? false,
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [],
}, me);
});
this.moderationLogService.insertModerationLog(me, 'addEmoji', {
emojiId: emoji.id,
});
return this.emojiEntityService.packDetailed(emoji);
});

View File

@@ -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, me);
await this.customEmojiService.deleteBulk(ps.ids);
});
}
}

View File

@@ -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, me);
await this.customEmojiService.delete(ps.id);
});
}
}

View File

@@ -84,7 +84,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
isSensitive: ps.isSensitive,
localOnly: ps.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction,
}, me);
});
});
}
}

View File

@@ -9,7 +9,6 @@ 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'],
@@ -35,7 +34,6 @@ 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) });
@@ -44,23 +42,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new Error('instance not found');
}
await this.federatedInstanceService.update(instance.id, {
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,
});
}
}
});
}
}

View File

@@ -10,7 +10,6 @@ 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 = {
@@ -61,7 +60,6 @@ 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))) {
@@ -80,11 +78,6 @@ 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);
});
}

View File

@@ -321,7 +321,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
maintainerEmail: instance.maintainerEmail,
version: this.config.version,
name: instance.name,
shortName: instance.shortName,
uri: this.config.url,
description: instance.description,
langs: instance.langs,

View File

@@ -30,7 +30,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
super(meta, paramDef, async (ps, me) => {
this.queueService.destroy();
this.moderationLogService.log(me, 'clearQueue');
this.moderationLogService.insertModerationLog(me, 'clearQueue');
});
}
}

View File

@@ -70,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
break;
}
this.moderationLogService.log(me, 'promoteQueue');
this.moderationLogService.insertModerationLog(me, 'promoteQueue');
});
}
}

View File

@@ -9,7 +9,6 @@ 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'],
@@ -47,10 +46,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
private moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
super(meta, paramDef, async (ps) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId });
if (user == null) {
@@ -72,12 +69,6 @@ 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,
};

View File

@@ -10,7 +10,6 @@ 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'],
@@ -42,7 +41,6 @@ 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 });
@@ -63,12 +61,6 @@ 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,
});
});
}
}

View File

@@ -83,7 +83,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
return;
}
await this.roleService.assign(user.id, role.id, ps.expiresAt ? new Date(ps.expiresAt) : null, me);
await this.roleService.assign(user.id, role.id, ps.expiresAt ? new Date(ps.expiresAt) : null);
});
}
}

View File

@@ -5,8 +5,11 @@
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'],
@@ -55,11 +58,37 @@ 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 created = await this.roleService.create(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);
return await this.roleEntityService.pack(created, me);
});

View File

@@ -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,14 +41,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.rolesRepository)
private rolesRepository: RolesRepository,
private roleService: RoleService,
private globalEventService: GlobalEventService,
) {
super(meta, paramDef, async (ps, me) => {
super(meta, paramDef, async (ps) => {
const role = await this.rolesRepository.findOneBy({ id: ps.roleId });
if (role == null) {
throw new ApiError(meta.errors.noSuchRole);
}
await this.roleService.delete(role, me);
await this.rolesRepository.delete({
id: ps.roleId,
});
this.globalEventService.publishInternalEvent('roleDeleted', role);
});
}
}

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