diff --git a/CHANGELOG.md b/CHANGELOG.md
index 66b2d50dc08c1fcdab6a4c3907b9cf1f1964bd3b..9b42370b50b4ef2c62732c05364cfd68d3f15aca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,35 @@
 
 -->
 
+## 13.10.3
+
+### General
+- コンディショナルロールの条件に「投稿数が~以下」「投稿数が~以上」を追加
+- リアクション非対応AP実装からのLikeアクティビティの解釈を👍から♥に
+
+### Client
+- クリップボタンをノートアクションに追加できるように
+- センシティブワードの一覧にピン留めユーザーのIDが表示される問題を修正
+
+### Server
+- リモートユーザーのチャート生成を無効にするオプションを追加
+- リモートサーバーのチャート生成を無効にするオプションを追加
+- ドライブのチャートはローカルユーザーのみ生成するように
+- 空のアンテナが作成できるのを修正
+
+## 13.10.2
+
+### Server
+- 絵文字を編集すると保存できないことがある問題を修正
+
+### Client
+- ドライブファイルのメニューが正常に動作しない問題を修正
+
+## 13.10.1
+
+### Client
+- Misskey PlayのPlayボタンを押した時にエラーが発生する問題を修正
+
 ## 13.10.0
 
 ### General
@@ -22,15 +51,19 @@
 - ロールの並び順を設定可能に
 - カスタム絵文字にライセンス情報を付与できるように
 - 指定した文字列を含む投稿の公開範囲をホームにできるように
+- 使われてないアンテナは自動停止されるように
 
 ### Client
 - 設定から自分のロールを確認できるように
 - 広告一覧ページを追加
+- ドライブクリーナーを追加
 - DM作成時にメンションも含むように
 - フォロー申請のボタンのデザインを改善
 - 付箋ウィジェットの高さを設定可能に
 - APオブジェクトを入力してフェッチする機能とユーザーやノートの検索機能を分離
 - ナビゲーションバーの項目に「プロフィール」を追加できるように
+- ナビゲーションバーのカスタマイズをドラッグ&ドロップで行えるように
+- ジョブキューの再試行をワンクリックでできるように
 - AiScriptを0.13.1に更新
 - oEmbedをサポートしているウェブサイトのプレビューができるように
 	- YouTubeをoEmbedでロードし、プレビューで共有ボタンを押すとOSの共有画面がでるように
@@ -42,6 +75,7 @@
 - Safariでプラグインが複数ある場合に正常に読み込まれない問題を修正
 - Bookwyrmのユーザーのプロフィールページで「リモートで表示」をタップしても反応がない問題を修正
 - 非ログイン時の「Misskeyについて」の表示を修正
+- PC版にて「設定」「コントロールパネル」のリンクを2度以上続けてクリックした際に空白のページが表示される問題を修正
 
 ### Server
 - OpenAPIエンドポイントを復旧
@@ -59,6 +93,7 @@
 - リテンション分析が上手く機能しないことがあるのを修正
 - 空のアンテナが作成できないように修正
 - 特定の条件で通報が見れない問題を修正
+- 絵文字の名前に任意の文字が使用できる問題を修正
 
 ## 13.9.2 (2023/03/06)
 
diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml
index 5254b20ef0a66409dfff354ef0d646980b15f822..c2910b90cd61b8ea38a0ba1cb73033d435df508f 100644
--- a/locales/ar-SA.yml
+++ b/locales/ar-SA.yml
@@ -545,7 +545,6 @@ tokenRequested: "منح حق الوصول إلى الحساب"
 pluginTokenRequestedDescription: "ستتمكن الإضافة من استخدام هذه الأذونات."
 notificationType: "أنواع الإشعارات"
 edit: "التعديل"
-useStarForReactionFallback: "استخدم ★ كبديل إذا كان التفاعل مجهولًا"
 emailServer: "خادم البريد الإلكتروني"
 emailConfigInfo: "يستخدم لتأكيد عنوان بريدك الإلكتروني ولإعادة تعيين كلمة المرور إن نسيتها."
 email: "البريد الإلكتروني "
@@ -1275,3 +1274,7 @@ _deck:
     channel: "القنوات"
     mentions: "الإشارات"
     direct: "مباشرة"
+_webhookSettings:
+  name: "الإسم"
+  active: "مفعّل"
+
diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml
index 49b76b8ab356678fa218afa81dd927bae445c857..40af5a33260840ad0839b069739ecd44d84ca279 100644
--- a/locales/bn-BD.yml
+++ b/locales/bn-BD.yml
@@ -562,7 +562,6 @@ tokenRequested: "অ্যাকাউন্টে অ্যাক্সেস 
 pluginTokenRequestedDescription: "এই প্লাগইনটি এখানে দেওয়া অনুমুতিসমূহ ব্যাবহার করবে"
 notificationType: "বিজ্ঞপ্তির ধরন"
 edit: "সম্পাদনা"
-useStarForReactionFallback: "রিঅ্যাকশনের ইমোজি না জানলে ★ ব্যবহার করুন"
 emailServer: "ইমেইল সার্ভার"
 enableEmail: "ইমেইল বিতরণ চালু করুন"
 emailConfigInfo: "আপনার ইমেল ঠিকানা নিশ্চিত করতে এবং আপনার পাসওয়ার্ড পুনরায় সেট করতে ব্যবহৃত হয়"
@@ -1354,3 +1353,7 @@ _deck:
     channel: "চ্যানেলগুলি"
     mentions: "উল্লেখসমূহ"
     direct: "ডাইরেক্ট নোটগুলি"
+_webhookSettings:
+  name: "নাম"
+  active: "চালু"
+
diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index 2b1168f7803f5c53fc88f7b78e0cfb140e8ebde3..bc9e66249390fabd230ce07b27358f42d405ecb2 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -460,3 +460,4 @@ _deck:
     list: "Llistes"
     mentions: "Mencions"
     direct: "Publicacions directes"
+
diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml
index 7f665895b90f49ed434b0868d868f59e732d0ad9..4b59192474a7b703ce411eed3c04072238ec7c96 100644
--- a/locales/cs-CZ.yml
+++ b/locales/cs-CZ.yml
@@ -776,3 +776,7 @@ _deck:
     list: "Seznamy"
     channel: "Kanály"
     mentions: "Zmínění"
+_webhookSettings:
+  name: "Jméno"
+  active: "Zapnuto"
+
diff --git a/locales/da-DK.yml b/locales/da-DK.yml
index 08c15ed092fc217aec263728d823d1f62a0fb88f..d1fbec9f6791cea281989c9cb2a38ef4c6c44588 100644
--- a/locales/da-DK.yml
+++ b/locales/da-DK.yml
@@ -1,2 +1,3 @@
 ---
 _lang_: "Dansk"
+
diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index 08808ea6a4fb60c4befae07df833b780e4a533f6..f6c28fbded87bb63f78a2de003cfd210b4224490 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -594,7 +594,6 @@ tokenRequested: "Zugriff zum Benutzerkonto gewähren"
 pluginTokenRequestedDescription: "Dieses Plugin wird die hier konfigurierten Berechtigungen verwenden können."
 notificationType: "Art der Benachrichtigung"
 edit: "Bearbeiten"
-useStarForReactionFallback: "Verwende ★ falls das Reaktions-Emoji unbekannt ist"
 emailServer: "Email-Server"
 enableEmail: "Email-Versand aktivieren"
 emailConfigInfo: "Zur Email-Bestätigung bei Registrierung oder zum Zurücksetzen des Passworts verwendet"
@@ -977,6 +976,10 @@ notesSearchNotAvailable: "Die Notizsuche ist nicht verfügbar."
 license: "Lizenz"
 unfavoriteConfirm: "Wirklich aus Favoriten entfernen?"
 myClips: "Meine Clips"
+drivecleaner: "Drive-Reiniger"
+retryAllQueuesNow: "Sofort Warteschlangen erneut ausführen"
+retryAllQueuesConfirmTitle: "Wirklich erneut versuchen?"
+retryAllQueuesConfirmText: "Dies wird zu einer temporären Erhöhung der Serverlast führen."
 _achievements:
   earnedAt: "Freigeschaltet am"
   _types:
@@ -1273,6 +1276,8 @@ _role:
     followersMoreThanOrEq: "Hat X oder mehr Follower"
     followingLessThanOrEq: "Folgt X oder weniger Benutzern"
     followingMoreThanOrEq: "Folgt X oder mehr Benutzern"
+    notesLessThanOrEq: "Beitragszahl ist kleiner-gleich"
+    notesMoreThanOrEq: "Beitragszahl ist größer-gleich"
     and: "UND-Bedingung"
     or: "ODER-Bedingung"
     not: "NICHT-Bedingung"
@@ -1868,3 +1873,10 @@ _dialog:
 _disabledTimeline:
   title: "Chronik deaktiviert"
   description: "Mit deinen jetzigen Rollen ist diese Chronik nicht verfügbar."
+_drivecleaner:
+  orderBySizeDesc: "Absteigende Dateigrößen"
+  orderByCreatedAtAsc: "Aufsteigendes Erstelldatum"
+_webhookSettings:
+  name: "Name"
+  active: "Aktiviert"
+
diff --git a/locales/el-GR.yml b/locales/el-GR.yml
index 0721ba6e999bdd409e02038fa8e865599abfbc37..634e36c29eedc26ff845fdc2b4fd2d34eb17dc94 100644
--- a/locales/el-GR.yml
+++ b/locales/el-GR.yml
@@ -392,3 +392,6 @@ _deck:
     antenna: "Αντένες"
     list: "Λίστα"
     mentions: "Επισημάνσεις"
+_webhookSettings:
+  name: "Όνομα"
+
diff --git a/locales/en-US.yml b/locales/en-US.yml
index 9e018ce2acdb92972b9a625db8c19a644485b7c1..6489919e8a7d65e7a309cfa5995756679a9ce575 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -530,7 +530,7 @@ nothing: "There's nothing to see here"
 installedDate: "Authorized at"
 lastUsedDate: "Last used at"
 state: "State"
-sort: "Sort"
+sort: "Sorting order"
 ascendingOrder: "Ascending"
 descendingOrder: "Descending"
 scratchpad: "Scratchpad"
@@ -594,7 +594,6 @@ tokenRequested: "Grant access to account"
 pluginTokenRequestedDescription: "This plugin will be able to use the permissions set here."
 notificationType: "Notification type"
 edit: "Edit"
-useStarForReactionFallback: "Use ★ as fallback if the reaction emoji is unknown"
 emailServer: "Email server"
 enableEmail: "Enable email distribution"
 emailConfigInfo: "Used to confirm your email during sign-up or if you forget your password"
@@ -977,6 +976,13 @@ notesSearchNotAvailable: "Note search is unavailable."
 license: "License"
 unfavoriteConfirm: "Really remove from favorites?"
 myClips: "My clips"
+drivecleaner: "Drive Cleaner"
+retryAllQueuesNow: "Retry running all queues"
+retryAllQueuesConfirmTitle: "Really retry all?"
+retryAllQueuesConfirmText: "This will temporarily increase the server load."
+enableChartsForRemoteUser: "Generate remote user data charts"
+enableChartsForFederatedInstances: "Generate remote instance data charts"
+showClipButtonInNoteFooter: "Add \"Clip\" to note action menu"
 _achievements:
   earnedAt: "Unlocked at"
   _types:
@@ -1273,6 +1279,8 @@ _role:
     followersMoreThanOrEq: "Has X or more followers"
     followingLessThanOrEq: "Follows X or fewer accounts"
     followingMoreThanOrEq: "Follows X or more accounts"
+    notesLessThanOrEq: "Post count is less than/equal to"
+    notesMoreThanOrEq: "Post count is greater than/equal to"
     and: "AND-Condition"
     or: "OR-Condition"
     not: "NOT-Condition"
@@ -1868,3 +1876,21 @@ _dialog:
 _disabledTimeline:
   title: "Timeline disabled"
   description: "You cannot use this timeline under your current roles."
+_drivecleaner:
+  orderBySizeDesc: "Descending Filesizes"
+  orderByCreatedAtAsc: "Ascending Dates"
+_webhookSettings:
+  createWebhook: "Create Webhook"
+  name: "Name"
+  secret: "Secret"
+  events: "Webhook Events"
+  active: "Enabled"
+  _events:
+    follow: "When following a user"
+    followed: "When being followed"
+    note: "When posting a note"
+    reply: "When receiving a reply"
+    renote: "When renoted"
+    reaction: "When receiving a reaction"
+    mention: "When being mentioned"
+
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index 3e73e4c5ea9e379c2aa7e650ef92d006144616ea..70327c5eac11ea05a35032b579ca7777d666cf73 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -594,7 +594,6 @@ tokenRequested: "Permiso de acceso a la cuenta"
 pluginTokenRequestedDescription: "Este plugin podrá usar los permisos descritos aquí"
 notificationType: "Tipo de notificación"
 edit: "Editar"
-useStarForReactionFallback: "En caso de que los emojis de reacciones no sean claros, usar en su lugar una estrella"
 emailServer: "Servidor de correo"
 enableEmail: "Activar el envío de correos electrónicos"
 emailConfigInfo: "Usar en caso de validación de correo electrónico y pedido de contraseña"
@@ -973,6 +972,14 @@ rolesAssignedToMe: "Roles asignados a mí"
 resetPasswordConfirm: "¿Realmente quieres cambiar la contraseña?"
 sensitiveWords: "Palabras sensibles"
 sensitiveWordsDescription: "La visibilidad de todas las notas que contienen cualquiera de las palabras configuradas serán puestas en \"Inicio\" automáticamente. Puedes enumerás varias separándolas con saltos de línea"
+notesSearchNotAvailable: "No se puede buscar una nota"
+license: "Licencia"
+unfavoriteConfirm: "¿Desea quitar de favoritos?"
+myClips: "Mis clips"
+drivecleaner: "Limpiador del Drive"
+retryAllQueuesNow: "Reintentar inmediatamente todas las colas"
+retryAllQueuesConfirmTitle: "Desea ¿reintentar inmediatamente todas las colas?"
+retryAllQueuesConfirmText: "La carga del servidor está incrementándose temporalmente "
 _achievements:
   earnedAt: "Desbloqueado el"
   _types:
@@ -1864,3 +1871,10 @@ _dialog:
 _disabledTimeline:
   title: "Línea de tiempo deshabilitada"
   description: "No puedes usar esta línea de tiempo con tus roles actuales."
+_drivecleaner:
+  orderBySizeDesc: "Más grandes"
+  orderByCreatedAtAsc: "Más antiguos"
+_webhookSettings:
+  name: "Nombre"
+  active: "Activado"
+
diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml
index f9b8939e8b2ebd60aa265dabdeeb5751ea6f1b5b..11573e0ce0034cfc5755257115e9bb62e4487527 100644
--- a/locales/fr-FR.yml
+++ b/locales/fr-FR.yml
@@ -575,7 +575,6 @@ tokenRequested: "Autoriser l'accès au compte"
 pluginTokenRequestedDescription: "Ce plugin pourra utiliser les autorisations définies ici."
 notificationType: "Type de notifications"
 edit: "Editer"
-useStarForReactionFallback: "Utiliser ★ comme alternative si l’émoji de réaction est inconnu"
 emailServer: "Serveur mail"
 enableEmail: "Activer la distribution de courriel"
 emailConfigInfo: "Utilisé pour confirmer votre adresse de courriel et la réinitialisation de votre mot de passe en cas d’oubli."
@@ -1468,3 +1467,7 @@ _deck:
     channel: "Canaux"
     mentions: "Mentions"
     direct: "Direct"
+_webhookSettings:
+  name: "Nom"
+  active: "Activé"
+
diff --git a/locales/hr-HR.yml b/locales/hr-HR.yml
index ed97d539c095cf1413af30cc23dea272095b97dd..cd21505a47e530a967e3c44bd2a772d1b8d08bd7 100644
--- a/locales/hr-HR.yml
+++ b/locales/hr-HR.yml
@@ -1 +1,2 @@
 ---
+
diff --git a/locales/ht-HT.yml b/locales/ht-HT.yml
index ed97d539c095cf1413af30cc23dea272095b97dd..cd21505a47e530a967e3c44bd2a772d1b8d08bd7 100644
--- a/locales/ht-HT.yml
+++ b/locales/ht-HT.yml
@@ -1 +1,2 @@
 ---
+
diff --git a/locales/id-ID.yml b/locales/id-ID.yml
index 5d74cf5389a22f52e8da2b7c53363abe55f04616..e5a057477a238fe35c96670fc0ba304d3969644d 100644
--- a/locales/id-ID.yml
+++ b/locales/id-ID.yml
@@ -579,7 +579,6 @@ tokenRequested: "Berikan ijin akses ke akun"
 pluginTokenRequestedDescription: "Plugin ini dapat menggunakan setelan ijin disini."
 notificationType: "Jenis pemberitahuan"
 edit: "Sunting"
-useStarForReactionFallback: "Gunakan ★ sebagai fallback jika reaksi emoji tidak diketahui"
 emailServer: "Peladen surel"
 enableEmail: "Nyalakan distribusi surel"
 emailConfigInfo: "Digunakan untuk mengonfirmasi surel kamu disaat mendaftar dan lupa kata sandi"
@@ -1804,3 +1803,7 @@ _deck:
     channel: "Kanal"
     mentions: "Sebutan"
     direct: "Langsung"
+_webhookSettings:
+  name: "Nama"
+  active: "Aktif"
+
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index 44499fa3dd9cf01e286943430718ad297983169a..ddd1e5e90a5e7225055d6cd976d1443938252b81 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -565,8 +565,8 @@ enableInfiniteScroll: "Abilita scorrimento infinito"
 visibility: "Visibilità"
 poll: "Sondaggio"
 useCw: "Nascondere media"
-enablePlayer: "Apri in lettore video"
-disablePlayer: "Chiudi il lettore"
+enablePlayer: "Visualizza"
+disablePlayer: "Chiudi"
 expandTweet: "Espandi tweet"
 themeEditor: "Editor di temi"
 description: "Descrizione"
@@ -594,7 +594,6 @@ tokenRequested: "Autorizza accesso al profilo"
 pluginTokenRequestedDescription: "Il plugin potrà utilizzare le autorizzazioni impostate qui."
 notificationType: "Tipo di notifiche"
 edit: "Modifica"
-useStarForReactionFallback: "Se è sconosciuto l'emoji di reazione, usare la ★ come alternativa."
 emailServer: "Server email"
 enableEmail: "Abilita consegna email"
 emailConfigInfo: "Utilizzato per verificare il tuo indirizzo di posta elettronica e per reimpostare la tua password"
@@ -977,6 +976,10 @@ notesSearchNotAvailable: "Non è possibile cercare tra le Note."
 license: "Licenza"
 unfavoriteConfirm: "Vuoi davvero rimuovere la preferenza?"
 myClips: "Le mie Clip"
+drivecleaner: "Drive cleaner"
+retryAllQueuesNow: "Ritenta di consumare tutte le code"
+retryAllQueuesConfirmTitle: "Vuoi ritentare adesso?"
+retryAllQueuesConfirmText: "Potrebbe sovraccaricare il server temporaneamente."
 _achievements:
   earnedAt: "Data di conseguimento"
   _types:
@@ -1868,3 +1871,10 @@ _dialog:
 _disabledTimeline:
   title: "Timeline disabilitata"
   description: "Il tuo ruolo non ha i permessi per accedere a questa timeline"
+_drivecleaner:
+  orderBySizeDesc: "Dal più grande al più piccolo"
+  orderByCreatedAtAsc: "Dal più vecchio al più recente"
+_webhookSettings:
+  name: "Nome"
+  active: "Attivo"
+
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index c4e86fc64ab2426c5a4b2da558d64d296e223687..cf4ede30b62977ef54566e9f8523baeb8c71217f 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -460,7 +460,7 @@ aboutX: "{x}について"
 emojiStyle: "絵文字のスタイル"
 native: "ネイティブ"
 disableDrawer: "メニューをドロワーで表示しない"
-showNoteActionsOnlyHover: "ノートの操作部をホバー時のみ表示する"
+showNoteActionsOnlyHover: "ノートのアクションをホバー時のみ表示する"
 noHistory: "履歴はありません"
 signinHistory: "ログイン履歴"
 enableAdvancedMfm: "高度なMFMを有効にする"
@@ -594,7 +594,6 @@ tokenRequested: "アカウントへのアクセス許可"
 pluginTokenRequestedDescription: "このプラグインはここで設定した権限を行使できるようになります。"
 notificationType: "通知の種類"
 edit: "編集"
-useStarForReactionFallback: "リアクション絵文字が不明な場合、代わりに★を使う"
 emailServer: "メールサーバー"
 enableEmail: "メール配信機能を有効化する"
 emailConfigInfo: "メールアドレスの確認やパスワードリセットの際に使います"
@@ -977,6 +976,13 @@ notesSearchNotAvailable: "ノート検索は利用できません。"
 license: "ライセンス"
 unfavoriteConfirm: "お気に入り解除しますか?"
 myClips: "自分のクリップ"
+drivecleaner: "ドライブクリーナー"
+retryAllQueuesNow: "すべてのキューを今すぐ再試行"
+retryAllQueuesConfirmTitle: "今すぐ再試行しますか?"
+retryAllQueuesConfirmText: "一時的にサーバーの負荷が増大することがあります。"
+enableChartsForRemoteUser: "リモートユーザーのチャートを生成"
+enableChartsForFederatedInstances: "リモートサーバーのチャートを生成"
+showClipButtonInNoteFooter: "ノートのアクションにクリップを追加"
 
 _achievements:
   earnedAt: "獲得日時"
@@ -1275,6 +1281,8 @@ _role:
     followersMoreThanOrEq: "フォロワー数が~以上"
     followingLessThanOrEq: "フォロー数が~以下"
     followingMoreThanOrEq: "フォロー数が~以上"
+    notesLessThanOrEq: "投稿数が~以下"
+    notesMoreThanOrEq: "投稿数が~以上"
     and: "~かつ~"
     or: "~または~"
     not: "~ではない"
@@ -1922,3 +1930,23 @@ _dialog:
 _disabledTimeline:
   title: "無効化されたタイムライン"
   description: "現在のロールでは、このタイムラインを使用することはできません。"
+
+_drivecleaner:
+  orderBySizeDesc: "サイズが大きい順"
+  orderByCreatedAtAsc: "追加日が古い順"
+
+_webhookSettings:
+  createWebhook: "Webhookを作成"
+  name: "名前"
+  secret: "シークレット"
+  events: "Webhookを実行するタイミング"
+  active: "有効"
+  _events:
+    follow: "フォローしたとき"
+    followed: "フォローされたとき"
+    note: "ノートを投稿したとき"
+    reply: "返信されたとき"
+    renote: "Renoteされたとき"
+    reaction: "リアクションがあったとき"
+    mention: "メンションされたとき"
+
diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml
index bd9ae46d34e66d76b046a98490d0f40dc1280dc7..5b1b312b780f754164fae75d5211fa6d6202b454 100644
--- a/locales/ja-KS.yml
+++ b/locales/ja-KS.yml
@@ -594,7 +594,6 @@ tokenRequested: "アカウントへのアクセス許してやったらどうや
 pluginTokenRequestedDescription: "このプラグインはここで設定した権限を使えるようになるで。"
 notificationType: "通知の種類"
 edit: "編集"
-useStarForReactionFallback: "リアクションがようわからん場合、★を使う"
 emailServer: "メールサーバー"
 enableEmail: "メール配信を受け取る"
 emailConfigInfo: "メールアドレスの確認とかパスワードリセットの時に使うで"
@@ -977,6 +976,13 @@ notesSearchNotAvailable: "ノート検索は使われへんで。"
 license: "ライセンス"
 unfavoriteConfirm: "ほんまに気に入らんの?"
 myClips: "自分のクリップ"
+drivecleaner: "ドライブキレイキレイ"
+retryAllQueuesNow: "キューを全部もっかいやり直す"
+retryAllQueuesConfirmTitle: "もっかいやってみるか?"
+retryAllQueuesConfirmText: "一時的にサーバー重なるかもしれへんで。"
+enableChartsForRemoteUser: "リモートユーザーのチャートを作る"
+enableChartsForFederatedInstances: "リモートサーバーのチャートを作る"
+showClipButtonInNoteFooter: "ノートのアクションにクリップを追加"
 _achievements:
   earnedAt: "貰った日ぃ"
   _types:
@@ -1273,6 +1279,8 @@ _role:
     followersMoreThanOrEq: "フォロワー数が~以上"
     followingLessThanOrEq: "フォロー数が~以下"
     followingMoreThanOrEq: "フォロー数が~以上"
+    notesLessThanOrEq: "投稿数が~以下しかない"
+    notesMoreThanOrEq: "投稿を~以上しとる"
     and: "~かつ~"
     or: "~または~"
     not: "~ではない"
@@ -1868,3 +1876,21 @@ _dialog:
 _disabledTimeline:
   title: "使われへんタイムライン"
   description: "あんたの今のロールやったら、このタイムラインは使われへんで。"
+_drivecleaner:
+  orderBySizeDesc: "サイズのでかい順"
+  orderByCreatedAtAsc: "追加日の古い順"
+_webhookSettings:
+  createWebhook: "Webhookをつくる"
+  name: "名前"
+  secret: "シークレット"
+  events: "Webhookを投げるタイミング"
+  active: "有効"
+  _events:
+    follow: "フォローしたとき~!"
+    followed: "フォローもらったとき~!"
+    note: "ノートを投稿したとき~!"
+    reply: "返信があるとき~!"
+    renote: "Renoteされるとき~!"
+    reaction: "リアクションがあるとき~!"
+    mention: "メンションがあるとき~!"
+
diff --git a/locales/jbo-EN.yml b/locales/jbo-EN.yml
index ed97d539c095cf1413af30cc23dea272095b97dd..cd21505a47e530a967e3c44bd2a772d1b8d08bd7 100644
--- a/locales/jbo-EN.yml
+++ b/locales/jbo-EN.yml
@@ -1 +1,2 @@
 ---
+
diff --git a/locales/kab-KAB.yml b/locales/kab-KAB.yml
index 18fd8f5a58d1651258fd7ce54959ab49cc9f1b60..8b43041e4c66fbcef8145a99ac166f3958f0b1b1 100644
--- a/locales/kab-KAB.yml
+++ b/locales/kab-KAB.yml
@@ -103,3 +103,4 @@ _deck:
   _columns:
     notifications: "Ilɣuyen"
     list: "Tibdarin"
+
diff --git a/locales/kn-IN.yml b/locales/kn-IN.yml
index ef66f3fbd234b5c028500b7a71a678b71dbb7d31..63a75302a159b51226438bd1002274169438d6ae 100644
--- a/locales/kn-IN.yml
+++ b/locales/kn-IN.yml
@@ -83,3 +83,4 @@ _deck:
     notifications: "ಅಧಿಸೂಚನೆಗಳು"
     tl: "ಸಮಯಸಾಲು"
     mentions: "ಹೆಸರಿಸಿದ"
+
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index e52d619f8c65a43fff7819180d58a2b0386d5bce..31b4200c2e79f97dd5ad70c9d630de9facb28e51 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -592,7 +592,6 @@ tokenRequested: "계정 접근 허용"
 pluginTokenRequestedDescription: "이 플러그인은 여기서 설정한 권한을 사용할 수 있게 됩니다."
 notificationType: "알림 유형"
 edit: "편집"
-useStarForReactionFallback: "알 수 없는 리액션 이모지 대신 ★ 사용"
 emailServer: "메일 서버"
 enableEmail: "이메일 송신 기능 활성화"
 emailConfigInfo: "가입 시 메일 주소 확인이나 비밀번호 초기화 시에 사용합니다."
@@ -1849,3 +1848,7 @@ _deck:
 _dialog:
   charactersExceeded: "최대 글자수를 초과하였습니다! 현재 {current} / 최대 {min}"
   charactersBelow: "최소 글자수 미만입니다! 현재 {current} / 최소 {min}"
+_webhookSettings:
+  name: "이름"
+  active: "활성화"
+
diff --git a/locales/lo-LA.yml b/locales/lo-LA.yml
index 9c1a48c67ccfb14a6c778c6932833735606db957..5736fa67a7b678a0a171984958ed843525c1dc5f 100644
--- a/locales/lo-LA.yml
+++ b/locales/lo-LA.yml
@@ -368,3 +368,4 @@ _deck:
     list: "ລາຍການ"
     channel: "ຊ່ອງ"
     mentions: "ກ່າວເຖິງ"
+
diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml
index 3d33b5227e6b6956065901b1a9072ebe29537d64..efbb83c70ffe2c6439e2096a62fc34c9e25304f7 100644
--- a/locales/nl-NL.yml
+++ b/locales/nl-NL.yml
@@ -483,3 +483,6 @@ _deck:
     antenna: "Antennes"
     list: "Lijsten"
     mentions: "Vermeldingen"
+_webhookSettings:
+  name: "Naam"
+
diff --git a/locales/no-NO.yml b/locales/no-NO.yml
index 83e189b9cf1c78ce3d50251015448b388294ddff..36a0a2e0e3c7177665f4f9fa0a335f61a69e5513 100644
--- a/locales/no-NO.yml
+++ b/locales/no-NO.yml
@@ -1,2 +1,3 @@
 ---
 _lang_: "Norsk Bokmål"
+
diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml
index 1dc818d45919c776f31d5c01355ee182d87f312a..cc0bbb1fac376db8ce9e98c1756d2302f4de81de 100644
--- a/locales/pl-PL.yml
+++ b/locales/pl-PL.yml
@@ -564,7 +564,6 @@ tokenRequested: "Przydziel dostęp do konta"
 pluginTokenRequestedDescription: "Ta wtyczka będzie mogła korzystać z ustawionych tu uprawnień."
 notificationType: "Rodzaj powiadomień"
 edit: "Edytuj"
-useStarForReactionFallback: "Użyj ★ jako zapasowego emoji, gdy emoji reakcji jest nieznane"
 emailServer: "Serwer poczty e-mail"
 enableEmail: "Włącz dostarczanie wiadomości e-mail"
 emailConfigInfo: "Wykorzystywany do potwierdzenia adresu e-mail w trakcie rejestracji, lub gdy zapomnisz hasła"
@@ -1358,3 +1357,7 @@ _deck:
     channel: "Kanały"
     mentions: "Wspomnienia"
     direct: "Bezpośredni"
+_webhookSettings:
+  name: "Nazwa"
+  active: "WÅ‚aczono"
+
diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml
index 40b4aee7e6adbb188d0e5179bc12e59e3d2f0016..870ad501507f55d3b8c8be3f370b035e6405cfb2 100644
--- a/locales/pt-PT.yml
+++ b/locales/pt-PT.yml
@@ -555,3 +555,6 @@ _deck:
     list: "Listas"
     mentions: "Menções"
     direct: "Notas diretas"
+_webhookSettings:
+  name: "Nome"
+
diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml
index 10cb085f3fa13727a9122b6fc37bacd5fa264494..89f8afac9a3104b61121f338c6137257244ce591 100644
--- a/locales/ro-RO.yml
+++ b/locales/ro-RO.yml
@@ -561,7 +561,6 @@ tokenRequested: "Acordă acces la cont"
 pluginTokenRequestedDescription: "Acest plugin va putea să folosească permisiunile setate aici."
 notificationType: "Tipul notificării"
 edit: "Editează"
-useStarForReactionFallback: "Folosește ★ ca fallback dacă emoji-ul este necunoscut"
 emailServer: "Server email"
 enableEmail: "Activează distribuția de emailuri"
 emailConfigInfo: "Folosit pentru a confirma emailul tău în timpul logări dacă îți uiți parola"
@@ -702,3 +701,6 @@ _deck:
     list: "Liste"
     channel: "Canale"
     mentions: "Mențiuni"
+_webhookSettings:
+  name: "Nume"
+
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index 81ea01179b7a92ac544df6cbbb0b46ad14ede272..6a1756f8a2151a83cf6ba16b7df91e159b6c1813 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -585,7 +585,6 @@ tokenRequested: "Открыть доступ к учётной записи"
 pluginTokenRequestedDescription: "Это расширение сможет пользоваться разрешениями, установленными здесь."
 notificationType: "Тип уведомления"
 edit: "Изменить"
-useStarForReactionFallback: "Ставить ★ в качестве реакции вместо неизвестного эмодзи"
 emailServer: "Сервер электронной почты"
 enableEmail: "Включить обмен электронной почтой"
 emailConfigInfo: "Используется для подтверждения адреса электронной почты и сброса пароля."
@@ -1837,3 +1836,7 @@ _deck:
 _dialog:
   charactersExceeded: "Превышено максимальное количество символов! У вас {current} / из   {max}"
   charactersBelow: "Это ниже минимального количества символов! У вас {current} / из {min}"
+_webhookSettings:
+  name: "Название"
+  active: "Вкл."
+
diff --git a/locales/si-LK.yml b/locales/si-LK.yml
index ed97d539c095cf1413af30cc23dea272095b97dd..cd21505a47e530a967e3c44bd2a772d1b8d08bd7 100644
--- a/locales/si-LK.yml
+++ b/locales/si-LK.yml
@@ -1 +1,2 @@
 ---
+
diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml
index d4be5540b8fce14e6638e1055601b6cca2d29f4f..ff6075b7038408736127c7128cd3d14407703a08 100644
--- a/locales/sk-SK.yml
+++ b/locales/sk-SK.yml
@@ -586,7 +586,6 @@ tokenRequested: "Povoliť prístup k účtu"
 pluginTokenRequestedDescription: "Tento plugin bude môcť používať oprávnenia nastavené tu."
 notificationType: "Typ oznámenia"
 edit: "Upraviť"
-useStarForReactionFallback: "Použiť ★ keď emoji reakcie nie je známe"
 emailServer: "Email server"
 enableEmail: "Zapnúť email"
 emailConfigInfo: "Používa sa na overenie emaily pri registrácii alebo pri zabudnutí hesla"
@@ -1475,3 +1474,7 @@ _deck:
     channel: "Kanály"
     mentions: "Zmienky"
     direct: "Priame poznámky"
+_webhookSettings:
+  name: "Názov"
+  active: "Zapnuté"
+
diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml
index 5e66df207639c2b198b794930070f27030bf1dd3..6ea5f77c21310b162b409840170cc98674281a69 100644
--- a/locales/sv-SE.yml
+++ b/locales/sv-SE.yml
@@ -442,3 +442,6 @@ _deck:
     antenna: "Antenner"
     list: "Listor"
     mentions: "Omnämningar"
+_webhookSettings:
+  active: "Aktiverad"
+
diff --git a/locales/th-TH.yml b/locales/th-TH.yml
index cf33e6642bb23f84acee2a82d9d1eb8f3057dd0e..a8b47843980c6324941712e5c80847e83c6676f3 100644
--- a/locales/th-TH.yml
+++ b/locales/th-TH.yml
@@ -544,6 +544,8 @@ userSuspended: "ผู้ใช้รายนี้ถูกระงับก
 userSilenced: "ผู้ใช้รายนี้กำลังถูกปิดกั้น"
 yourAccountSuspendedTitle: "บัญชีนี้นั้นถูกระงับ"
 yourAccountSuspendedDescription: "บัญชีนี้ถูกระงับ เนื่องจากละเมิดข้อกำหนดในการให้บริการของเซิร์ฟเวอร์หรืออาจจะละเมิดหลักเกณฑ์ชุมชน หรือ อาจจะโดนร้องเรียนเรื่องการละเมิดลิขสิทธิ์และอื่นๆอย่างต่อเนื่องซ้ำๆ หากคุณคิดว่าไม่ได้ทำผิดจริงๆหรือตัดสินผิดพลาด ได้โปรดกรุณาติดต่อผู้ดูแลระบบหากคุณต้องการทราบเหตุผลโดยละเอียดเพิ่มเติม และขอความกรุณาอย่าสร้างบัญชีใหม่"
+tokenRevoked: "โทเค็นไม่ถูกต้อง"
+accountDeleted: "ลบบัญชีแล้ว"
 menu: "เมนู"
 divider: "ตัวแบ่ง"
 addItem: "เพิ่มรายการ"
@@ -587,7 +589,6 @@ tokenRequested: "ให้สิทธิ์การเข้าถึงบั
 pluginTokenRequestedDescription: "ปลั๊กอินนี้จะสามารถใช้การอนุญาตที่ตั้งค่าไว้ที่นี่นะ"
 notificationType: "ประเภทการแจ้งเตือน"
 edit: "แก้ไข"
-useStarForReactionFallback: "ใช้ ★ เป็นทางเลือกแทนถ้าหากไม่ทราบอิโมจิ"
 emailServer: "อีเมล์เซิร์ฟเวอร์"
 enableEmail: "เปิดใช้งานการกระจายอีเมล"
 emailConfigInfo: "ใช้เพื่อยืนยันอีเมลของคุณระหว่างการสมัครหรือถ้าหากคุณลืมรหัสผ่าน"
@@ -959,6 +960,18 @@ invitationRequiredToRegister: "อินสแตนซ์นี้เป็น
 emailNotSupported: "อินสแตนซ์นี้ไม่รองรับการส่งอีเมลนะค่ะ"
 postToTheChannel: "โพสต์ลงช่อง"
 cannotBeChangedLater: "สิ่งนี้ไม่สามารถเปลี่ยนแปลงได้ในภายหลังนะ"
+likeOnly: "ที่ชอบเท่านั้น"
+resetPasswordConfirm: "รีเซ็ตรหัสผ่านของคุณจริงๆหรอ?"
+sensitiveWords: "คำที่ละเอียดอ่อน"
+sensitiveWordsDescription: "การเปิดเผยโน้ตทั้งหมดที่มีคำที่กำหนดค่าไว้จะถูกตั้งค่าเป็น \"หน้าแรก\" โดยอัตโนมัติ คุณยังสามารถแสดงหลายรายการได้โดยแยกรายการโดยใช้ตัวแบ่งบรรทัดได้นะ"
+notesSearchNotAvailable: "การค้นหาโน้ตไม่พร้อมใช้งานนะค่ะ"
+license: "ใบอนุญาต"
+unfavoriteConfirm: "ลบออกจากรายการโปรดแน่ใจหรอ?"
+myClips: "คลิปของฉัน"
+drivecleaner: "ทำความสะอาดไดรฟ์"
+retryAllQueuesNow: "ลองเรียกใช้คิวทั้งหมดอีกครั้ง"
+retryAllQueuesConfirmTitle: "ลองใหม่ทั้งหมดจริงๆหรอแน่ใจนะ?"
+retryAllQueuesConfirmText: "สิ่งนี้จะเพิ่มการโหลดเซิร์ฟเวอร์ชั่วคราวนะ"
 _achievements:
   earnedAt: "ได้รับเมื่อ"
   _types:
@@ -1218,6 +1231,8 @@ _role:
   iconUrl: "ไอคอน URL"
   asBadge: "แสดงเป็นตรา"
   descriptionOfAsBadge: "ไอคอนของบทบาทนี้จะปรากฏถัดจากชื่อผู้ใช้ของผู้ใช้งานด้วยบทบาทนี้ถ้าหากเปิดใช้งาน"
+  displayOrder: "ตำแหน่ง"
+  descriptionOfDisplayOrder: "ยิ่งตัวเลขสูง ตำแหน่ง UI ก็ยิ่งสูงขึ้นนะ"
   canEditMembersByModerator: "อนุญาตให้ผู้ดูแลแก้ไขสมาชิก"
   descriptionOfCanEditMembersByModerator: "เมื่อเปิดใช้ ผู้ดูแลนอกเหนือจากผู้ดูแลระบบแล้ว จะสามารถกำหนดและยกเลิกการมอบหมายบทบาทนี้ให้กับผู้ใช้ได้ เมื่อปิด เฉพาะผู้ดูแลระบบเท่านั้นที่จะสามารถกำหนดผู้ใช้ได้นะ"
   priority: "ลำดับความสำคัญ"
@@ -1243,6 +1258,7 @@ _role:
     rateLimitFactor: "ขีดจำกัดอัตรา"
     descriptionOfRateLimitFactor: "ขีดจํากัดอัตราที่ต่ำกว่ามีข้อจํากัดน้อยกว่าข้อจํากัดที่สูงกว่า"
     canHideAds: "ซ่อนโฆษณา"
+    canSearchNotes: "การใช้การค้นหาโน้ต"
   _condition:
     isLocal: "ผู้ใช้ภายใน"
     isRemote: "ผู้ใช้ระยะไกล"
@@ -1844,3 +1860,13 @@ _deck:
 _dialog:
   charactersExceeded: "คุณกำลังมีตัวอักขระเกินขีดจำกัดสูงสุดแล้วนะ! ปัจจุบันอยู่ที่ {current} จาก {max}"
   charactersBelow: "คุณกำลังใช้อักขระต่ำกว่าขีดจำกัดขั้นต่ำเลยนะ! ปัจจุบันอยู่ที่ {current} จาก {min}"
+_disabledTimeline:
+  title: "ปิดใช้งานไทม์ไลน์"
+  description: "คุณไม่สามารถใช้ไทม์ไลน์นี้ภายใต้บทบาทปัจจุบันของคุณได้"
+_drivecleaner:
+  orderBySizeDesc: "ขนาดไฟล์จากมากไปหาน้อย"
+  orderByCreatedAtAsc: "วันที่จากน้อยไปหามาก"
+_webhookSettings:
+  name: "ชื่อ"
+  active: "เปิดใช้งาน"
+
diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml
index 7bd8188a48f5be6cd209a619c07481d1fe35fca3..0f53dbafcbd639c437bdbcaf54f29c5b3f37fe93 100644
--- a/locales/tr-TR.yml
+++ b/locales/tr-TR.yml
@@ -60,3 +60,4 @@ _deck:
   _columns:
     notifications: "Bildirim"
     tl: "Zaman çizelgesi"
+
diff --git a/locales/ug-CN.yml b/locales/ug-CN.yml
index 65ef84125907178fa6b7d801d4f33b981c97e0a7..5b825d7bf39da1a52814a3d828f4dda49e4879b2 100644
--- a/locales/ug-CN.yml
+++ b/locales/ug-CN.yml
@@ -2,3 +2,4 @@
 _lang_: "ياپونچە"
 search: "ئىزدەش"
 searchByGoogle: "ئىزدەش"
+
diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml
index 56e3f024a1a86e66d75bf07cec1bb99208c91d12..7b2ee6d891c5aac554d1be862a7f7ac35d3c1ab0 100644
--- a/locales/uk-UA.yml
+++ b/locales/uk-UA.yml
@@ -576,7 +576,6 @@ tokenRequested: "Надати доступ до акаунту"
 pluginTokenRequestedDescription: "Цей плагін зможе використовувати дозволи які тут вказані."
 notificationType: "Тип сповіщення"
 edit: "Редагувати"
-useStarForReactionFallback: "Використовувати ★ як запасний варіант, якщо емодзі реакції невідомий"
 emailServer: "Email сервер"
 enableEmail: "Увімкнути функцію доставки пошти"
 emailConfigInfo: "Використовується для підтвердження електронної пошти підчас реєстрації, а також для відновлення паролю."
@@ -1639,3 +1638,7 @@ _deck:
     channel: "Канали"
     mentions: "Згадки"
     direct: "Особисте"
+_webhookSettings:
+  name: "Ім'я"
+  active: "Увімкнено"
+
diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml
index ce36de03db4b2af703e4b4d2465029a599fa1253..f81445473293108c95ef26dc35b8f312bf19fdf7 100644
--- a/locales/vi-VN.yml
+++ b/locales/vi-VN.yml
@@ -585,7 +585,6 @@ tokenRequested: "Cấp quyền truy cập vào tài khoản"
 pluginTokenRequestedDescription: "Plugin này sẽ có thể sử dụng các quyền được đặt ở đây."
 notificationType: "Loại thông báo"
 edit: "Sá»­a"
-useStarForReactionFallback: "Dùng ★ nếu emoji biểu cảm không có"
 emailServer: "Email máy chủ"
 enableEmail: "Bật phân phối email"
 emailConfigInfo: "Được dùng để xác minh email của bạn lúc đăng ký hoặc nếu bạn quên mật khẩu của mình"
@@ -1705,3 +1704,7 @@ _deck:
 _dialog:
   charactersExceeded: "Bạn nhắn quá giới hạn ký tự!! Hiện nay {current} / giới hạn {max}"
   charactersBelow: "Bạn nhắn quá ít tối thiểu ký tự!! Hiện nay {current} / Tối thiểu {min}"
+_webhookSettings:
+  name: "Tên"
+  active: "Đã bật"
+
diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml
index 73b36f8ec2c67295388d120a940912e0525e4610..c687c47a17920e10f0f647de99bfb6a28f7ccecd 100644
--- a/locales/zh-CN.yml
+++ b/locales/zh-CN.yml
@@ -594,7 +594,6 @@ tokenRequested: "允许访问账户"
 pluginTokenRequestedDescription: "此插件将能够拥有此处设置的权限"
 notificationType: "通知类型"
 edit: "编辑"
-useStarForReactionFallback: "如果回应的是未知表情符号,则使用★作为代替"
 emailServer: "邮件服务器"
 enableEmail: "启用发送邮件功能"
 emailConfigInfo: "用于确认电子邮件和密码重置"
@@ -977,6 +976,10 @@ notesSearchNotAvailable: "帖子检索不可用"
 license: "许可信息"
 unfavoriteConfirm: "确定要取消收藏吗?"
 myClips: "我的便签"
+drivecleaner: "网盘整理"
+retryAllQueuesNow: "立刻重试所有队列"
+retryAllQueuesConfirmTitle: "要再尝试一次吗?"
+retryAllQueuesConfirmText: "可能会使服务器负荷在一定时间内增加"
 _achievements:
   earnedAt: "达成时间"
   _types:
@@ -1868,3 +1871,10 @@ _dialog:
 _disabledTimeline:
   title: "时间线已禁用"
   description: "您不能在当前角色使用时间线。"
+_drivecleaner:
+  orderBySizeDesc: "按大小降序排列"
+  orderByCreatedAtAsc: "按添加日期降序排列"
+_webhookSettings:
+  name: "名称"
+  active: "已启用"
+
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index 6109bdbeec6c418806f420c5f6b1b6a08bba84f9..e031a88f4b978f5ee4b4a7af02dbc061f88663b3 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -531,8 +531,8 @@ installedDate: "安裝時間"
 lastUsedDate: "最後上線日期"
 state: "狀態"
 sort: "排序"
-ascendingOrder: "遞增"
-descendingOrder: "遞減"
+ascendingOrder: "昇冪"
+descendingOrder: "降冪"
 scratchpad: "暫存記憶體"
 scratchpadDescription: "AiScript控制台為AiScript提供了實驗環境。您可以在此編寫、執行和確認代碼與Misskey互動的结果。"
 output: "輸出"
@@ -594,7 +594,6 @@ tokenRequested: "允許存取帳戶"
 pluginTokenRequestedDescription: "此外掛將擁有在此設定的權限。"
 notificationType: "通知形式"
 edit: "編輯"
-useStarForReactionFallback: "以★代替未知的表情符號"
 emailServer: "電郵伺服器"
 enableEmail: "啟用發送電郵功能"
 emailConfigInfo: "用於確認電郵地址及密碼重置"
@@ -678,8 +677,8 @@ sentReactionsCount: "反應發送次數"
 receivedReactionsCount: "收到反應次數"
 pollVotesCount: "已統計的投票數"
 pollVotedCount: "已投票數"
-yes: "確定"
-no: "取消"
+yes: "是"
+no: "否"
 driveFilesCount: "雲端硬碟檔案數量"
 driveUsage: "雲端硬碟使用量"
 noCrawle: "拒絕搜尋引擎索引"
@@ -973,6 +972,14 @@ rolesAssignedToMe: "指派給自己的角色"
 resetPasswordConfirm: "重設密碼?"
 sensitiveWords: "敏感詞"
 sensitiveWordsDescription: "將含有設定詞彙的貼文可見性設為發送至首頁。可以用換行來進行複數的設定。"
+notesSearchNotAvailable: "無法使用搜尋貼文功能。"
+license: "授權"
+unfavoriteConfirm: "要取消收錄我的最愛嗎?"
+myClips: "我的摘錄"
+drivecleaner: "雲端硬碟清掃器"
+retryAllQueuesNow: "立刻重試所有佇列"
+retryAllQueuesConfirmTitle: "要現在重試嗎?"
+retryAllQueuesConfirmText: "伺服器的負荷可能會暫時增加。"
 _achievements:
   earnedAt: "獲得日期"
   _types:
@@ -1498,7 +1505,7 @@ _time:
 _tutorial:
   title: "Misskey使用方法"
   step1_1: "歡迎!"
-  step1_2: "此為「時間軸」頁面,它會按照時間順序顯示你「追隨」的人發出的「貼文」"
+  step1_2: "此為「時間軸」頁面,它會按照時間順序顯示你「追隨」的人發出的「貼文」。"
   step1_3: "由於你沒有發佈任何貼文,也沒有追隨任何人,所以你的時間軸目前是空的。"
   step2_1: "在發文或追隨其他人之前先讓我們設定一下個人資料吧。"
   step2_2: "提供一些關於自己的資訊來讓其他人更有追隨你的意願。"
@@ -1864,3 +1871,10 @@ _dialog:
 _disabledTimeline:
   title: "停用的時間軸"
   description: "目前的角色無法使用這個時間軸。"
+_drivecleaner:
+  orderBySizeDesc: "檔案由大到小"
+  orderByCreatedAtAsc: "依照加入的日期順序"
+_webhookSettings:
+  name: "名稱"
+  active: "已啟用"
+
diff --git a/package.json b/package.json
index f68608911cde4855fce9bc964719336a1d6a214b..e4cf9c85d219ad60db3ad52647267c554179d64c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "13.10.0",
+	"version": "13.10.3",
 	"codename": "nasubi",
 	"repository": {
 		"type": "git",
diff --git a/packages/backend/migration/1679309757174-antenna-active.js b/packages/backend/migration/1679309757174-antenna-active.js
new file mode 100644
index 0000000000000000000000000000000000000000..69e845c1421febd42818e8080a6f26b9d5b76811
--- /dev/null
+++ b/packages/backend/migration/1679309757174-antenna-active.js
@@ -0,0 +1,17 @@
+export class antennaActive1679309757174 {
+    name = 'antennaActive1679309757174'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "antenna" ADD "lastUsedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT 'now'`);
+        await queryRunner.query(`ALTER TABLE "antenna" ADD "isActive" boolean NOT NULL DEFAULT true`);
+        await queryRunner.query(`CREATE INDEX "IDX_084c2abb8948ef59a37dce6ac1" ON "antenna" ("lastUsedAt") `);
+        await queryRunner.query(`CREATE INDEX "IDX_36ef5192a1ce55ed0e40aa4db5" ON "antenna" ("isActive") `);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`DROP INDEX "public"."IDX_36ef5192a1ce55ed0e40aa4db5"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_084c2abb8948ef59a37dce6ac1"`);
+        await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "isActive"`);
+        await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "lastUsedAt"`);
+    }
+}
diff --git a/packages/backend/migration/1679639483253-enableChartsForRemoteUser.js b/packages/backend/migration/1679639483253-enableChartsForRemoteUser.js
new file mode 100644
index 0000000000000000000000000000000000000000..42faab74664ea0bd397d0dc6a5744be7c6abe6be
--- /dev/null
+++ b/packages/backend/migration/1679639483253-enableChartsForRemoteUser.js
@@ -0,0 +1,11 @@
+export class enableChartsForRemoteUser1679639483253 {
+    name = 'enableChartsForRemoteUser1679639483253'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ADD "enableChartsForRemoteUser" boolean NOT NULL DEFAULT true`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableChartsForRemoteUser"`);
+    }
+}
diff --git a/packages/backend/migration/1679651580149-cleanup.js b/packages/backend/migration/1679651580149-cleanup.js
new file mode 100644
index 0000000000000000000000000000000000000000..1f00f3cc1fadecae829091242056cab56d484da6
--- /dev/null
+++ b/packages/backend/migration/1679651580149-cleanup.js
@@ -0,0 +1,11 @@
+export class cleanup1679651580149 {
+    name = 'cleanup1679651580149'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "useStarForReactionFallback"`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ADD "useStarForReactionFallback" boolean NOT NULL DEFAULT false`);
+    }
+}
diff --git a/packages/backend/migration/1679652081809-enableChartsForFederatedInstances.js b/packages/backend/migration/1679652081809-enableChartsForFederatedInstances.js
new file mode 100644
index 0000000000000000000000000000000000000000..0733339841c447da254e3d2d6892a9e4f46d6e8e
--- /dev/null
+++ b/packages/backend/migration/1679652081809-enableChartsForFederatedInstances.js
@@ -0,0 +1,11 @@
+export class enableChartsForFederatedInstances1679652081809 {
+    name = 'enableChartsForFederatedInstances1679652081809'
+
+    async up(queryRunner) {
+			await queryRunner.query(`ALTER TABLE "meta" ADD "enableChartsForFederatedInstances" boolean NOT NULL DEFAULT true`);
+	}
+
+	async down(queryRunner) {
+			await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableChartsForFederatedInstances"`);
+	}
+}
diff --git a/packages/backend/package.json b/packages/backend/package.json
index a6fe2a07a8f8ec034b18dc8c1adcd6e1a18f49b9..162acd9f805dd98f546b98487e4552169ca9bb98 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -37,6 +37,9 @@
 		"@tensorflow/tfjs-node": "4.2.0"
 	},
 	"dependencies": {
+		"@aws-sdk/client-s3": "^3.294.0",
+		"@aws-sdk/lib-storage": "^3.294.0",
+		"@aws-sdk/node-http-handler": "^3.292.0",
 		"@bull-board/api": "5.0.0",
 		"@bull-board/fastify": "5.0.0",
 		"@bull-board/ui": "5.0.0",
@@ -59,7 +62,6 @@
 		"ajv": "8.12.0",
 		"archiver": "5.3.1",
 		"autwh": "0.1.0",
-		"aws-sdk": "2.1318.0",
 		"bcryptjs": "2.4.3",
 		"blurhash": "2.0.5",
 		"bull": "4.10.4",
@@ -190,6 +192,7 @@
 		"@types/ws": "8.5.4",
 		"@typescript-eslint/eslint-plugin": "5.54.1",
 		"@typescript-eslint/parser": "5.54.1",
+		"aws-sdk-client-mock": "^2.1.1",
 		"cross-env": "7.0.3",
 		"eslint": "8.35.0",
 		"eslint-plugin-import": "2.27.5",
diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts
index 35fbb53e81acc914b7e5adeb3cf92be0a1a86a0c..aaa26a832173ef497d829f533e2078e95b97e88b 100644
--- a/packages/backend/src/core/AntennaService.ts
+++ b/packages/backend/src/core/AntennaService.ts
@@ -71,12 +71,14 @@ export class AntennaService implements OnApplicationShutdown {
 					this.antennas.push({
 						...body,
 						createdAt: new Date(body.createdAt),
+						lastUsedAt: new Date(body.lastUsedAt),
 					});
 					break;
 				case 'antennaUpdated':
 					this.antennas[this.antennas.findIndex(a => a.id === body.id)] = {
 						...body,
 						createdAt: new Date(body.createdAt),
+						lastUsedAt: new Date(body.lastUsedAt),
 					};
 					break;
 				case 'antennaDeleted':
@@ -217,7 +219,9 @@ export class AntennaService implements OnApplicationShutdown {
 	@bindThis
 	public async getAntennas() {
 		if (!this.antennasFetched) {
-			this.antennas = await this.antennasRepository.find();
+			this.antennas = await this.antennasRepository.findBy({
+				isActive: true,
+			});
 			this.antennasFetched = true;
 		}
 	
diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts
index b404848d7db04e9c2c6a340cc66db3351f2c9244..a62854c61c6ddec9e4644e4ea12b8172b4f4173d 100644
--- a/packages/backend/src/core/CustomEmojiService.ts
+++ b/packages/backend/src/core/CustomEmojiService.ts
@@ -8,7 +8,7 @@ import type { DriveFile } from '@/models/entities/DriveFile.js';
 import type { Emoji } from '@/models/entities/Emoji.js';
 import type { EmojisRepository, Note } from '@/models/index.js';
 import { bindThis } from '@/decorators.js';
-import { Cache } from '@/misc/cache.js';
+import { KVCache } from '@/misc/cache.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import type { Config } from '@/config.js';
 import { ReactionService } from '@/core/ReactionService.js';
@@ -16,7 +16,7 @@ import { query } from '@/misc/prelude/url.js';
 
 @Injectable()
 export class CustomEmojiService {
-	private cache: Cache<Emoji | null>;
+	private cache: KVCache<Emoji | null>;
 
 	constructor(
 		@Inject(DI.config)
@@ -34,7 +34,7 @@ export class CustomEmojiService {
 		private globalEventService: GlobalEventService,
 		private reactionService: ReactionService,
 	) {
-		this.cache = new Cache<Emoji | null>(1000 * 60 * 60 * 12);
+		this.cache = new KVCache<Emoji | null>(1000 * 60 * 60 * 12);
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts
index f1e93d6dd9d5019b341c373dedf2a98ac68c6551..c6258474ec055654ea571e05d12c3c61ccc15f1f 100644
--- a/packages/backend/src/core/DriveService.ts
+++ b/packages/backend/src/core/DriveService.ts
@@ -4,6 +4,7 @@ import { v4 as uuid } from 'uuid';
 import sharp from 'sharp';
 import { sharpBmp } from 'sharp-read-bmp';
 import { IsNull } from 'typeorm';
+import { DeleteObjectCommandInput, PutObjectCommandInput, NoSuchKey } from '@aws-sdk/client-s3';
 import { DI } from '@/di-symbols.js';
 import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/index.js';
 import type { Config } from '@/config.js';
@@ -36,7 +37,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 type S3 from 'aws-sdk/clients/s3.js';
 
 type AddFileArgs = {
 	/** User who wish to add file */
@@ -81,6 +81,7 @@ type UploadFromUrlArgs = {
 export class DriveService {
 	private registerLogger: Logger;
 	private downloaderLogger: Logger;
+	private deleteLogger: Logger;
 
 	constructor(
 		@Inject(DI.config)
@@ -118,6 +119,7 @@ export class DriveService {
 		const logger = new Logger('drive', 'blue');
 		this.registerLogger = logger.createSubLogger('register', 'yellow');
 		this.downloaderLogger = logger.createSubLogger('downloader');
+		this.deleteLogger = logger.createSubLogger('delete');
 	}
 
 	/***
@@ -368,7 +370,7 @@ export class DriveService {
 			Body: stream,
 			ContentType: type,
 			CacheControl: 'max-age=31536000, immutable',
-		} as S3.PutObjectRequest;
+		} as PutObjectCommandInput;
 
 		if (filename) params.ContentDisposition = contentDisposition(
 			'inline',
@@ -378,21 +380,16 @@ export class DriveService {
 		);
 		if (meta.objectStorageSetPublicRead) params.ACL = 'public-read';
 
-		const s3 = this.s3Service.getS3(meta);
-
-		const upload = s3.upload(params, {
-			partSize: s3.endpoint.hostname === 'storage.googleapis.com' ? 500 * 1024 * 1024 : 8 * 1024 * 1024,
-		});
-
-		await upload.promise()
+		await this.s3Service.upload(meta, params)
 			.then(
 				result => {
-					if (result) {
+					if ('Bucket' in result) { // CompleteMultipartUploadCommandOutput
 						this.registerLogger.debug(`Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`);
-					} else {
-						this.registerLogger.error(`Upload Result Empty: key = ${key}, filename = ${filename}`);
+					} else { // AbortMultipartUploadCommandOutput
+						this.registerLogger.error(`Upload Result Aborted: key = ${key}, filename = ${filename}`);
 					}
-				},
+				})
+			.catch(
 				err => {
 					this.registerLogger.error(`Upload Failed: key = ${key}, filename = ${filename}`, err);
 				},
@@ -528,10 +525,10 @@ export class DriveService {
 		};
 
 		const properties: {
-		width?: number;
-		height?: number;
-		orientation?: number;
-	} = {};
+			width?: number;
+			height?: number;
+			orientation?: number;
+		} = {};
 
 		if (info.width) {
 			properties['width'] = info.width;
@@ -616,17 +613,20 @@ export class DriveService {
 
 		if (user) {
 			this.driveFileEntityService.pack(file, { self: true }).then(packedFile => {
-			// Publish driveFileCreated event
+				// Publish driveFileCreated event
 				this.globalEventService.publishMainStream(user.id, 'driveFileCreated', packedFile);
 				this.globalEventService.publishDriveStream(user.id, 'fileCreated', packedFile);
 			});
 		}
 
-		// 統計を更新
 		this.driveChart.update(file, true);
-		this.perUserDriveChart.update(file, true);
-		if (file.userHost !== null) {
-			this.instanceChart.updateDrive(file, true);
+		if (file.userHost == null) {
+			// ローカルユーザーのみ
+			this.perUserDriveChart.update(file, true);
+		} else {
+			if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
+				this.instanceChart.updateDrive(file, true);
+			}
 		}
 
 		return file;
@@ -692,7 +692,7 @@ export class DriveService {
 
 	@bindThis
 	private async deletePostProcess(file: DriveFile, isExpired = false) {
-	// リモートファイル期限切れ削除後は直リンクにする
+		// リモートファイル期限切れ削除後は直リンクにする
 		if (isExpired && file.userHost !== null && file.uri != null) {
 			this.driveFilesRepository.update(file.id, {
 				isLink: true,
@@ -709,33 +709,36 @@ export class DriveService {
 			this.driveFilesRepository.delete(file.id);
 		}
 
-		// 統計を更新
 		this.driveChart.update(file, false);
-		this.perUserDriveChart.update(file, false);
-		if (file.userHost !== null) {
-			this.instanceChart.updateDrive(file, false);
+		if (file.userHost == null) {
+			// ローカルユーザーのみ
+			this.perUserDriveChart.update(file, false);
+		} else {
+			if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
+				this.instanceChart.updateDrive(file, false);
+			}
 		}
 	}
 
 	@bindThis
 	public async deleteObjectStorageFile(key: string) {
 		const meta = await this.metaService.fetch();
-
-		const s3 = this.s3Service.getS3(meta);
-
 		try {
-			await s3.deleteObject({
-				Bucket: meta.objectStorageBucket!,
+			const param = {
+				Bucket: meta.objectStorageBucket,
 				Key: key,
-			}).promise();
+			} as DeleteObjectCommandInput;
+
+			await this.s3Service.delete(meta, param);
 		} catch (err: any) {
-			if (err.code === 'NoSuchKey') {
-				console.warn(`The object storage had no such key to delete: ${key}. Skipping this.`, err);
+			if (err.name === 'NoSuchKey') {
+				this.deleteLogger.warn(`The object storage had no such key to delete: ${key}. Skipping this.`, err as Error);
 				return;
+			} else {
+				throw new Error(`Failed to delete the file from the object storage with the given key: ${key}`, {
+					cause: err,
+				});
 			}
-			throw new Error(`Failed to delete the file from the object storage with the given key: ${key}`, {
-				cause: err,
-			});
 		}
 	}
 
diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts
index e83b037dd7cb8b79fd76ab30f07f4da203757197..b85791e43f12f37969cf08f1265eee6db957646f 100644
--- a/packages/backend/src/core/FederatedInstanceService.ts
+++ b/packages/backend/src/core/FederatedInstanceService.ts
@@ -1,7 +1,7 @@
 import { Inject, Injectable } from '@nestjs/common';
 import type { InstancesRepository } from '@/models/index.js';
 import type { Instance } from '@/models/entities/Instance.js';
-import { Cache } from '@/misc/cache.js';
+import { KVCache } from '@/misc/cache.js';
 import { IdService } from '@/core/IdService.js';
 import { DI } from '@/di-symbols.js';
 import { UtilityService } from '@/core/UtilityService.js';
@@ -9,7 +9,7 @@ import { bindThis } from '@/decorators.js';
 
 @Injectable()
 export class FederatedInstanceService {
-	private cache: Cache<Instance>;
+	private cache: KVCache<Instance>;
 
 	constructor(
 		@Inject(DI.instancesRepository)
@@ -18,7 +18,7 @@ export class FederatedInstanceService {
 		private utilityService: UtilityService,
 		private idService: IdService,
 	) {
-		this.cache = new Cache<Instance>(1000 * 60 * 60);
+		this.cache = new KVCache<Instance>(1000 * 60 * 60);
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/InstanceActorService.ts b/packages/backend/src/core/InstanceActorService.ts
index ee9ae0733fbc8e46f671d5fc0c7e820fa0f4ced8..ef87051a74045b54904901a2cffeebc1dfe8b1a2 100644
--- a/packages/backend/src/core/InstanceActorService.ts
+++ b/packages/backend/src/core/InstanceActorService.ts
@@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import { IsNull } from 'typeorm';
 import type { LocalUser } from '@/models/entities/User.js';
 import type { UsersRepository } from '@/models/index.js';
-import { Cache } from '@/misc/cache.js';
+import { KVCache } from '@/misc/cache.js';
 import { DI } from '@/di-symbols.js';
 import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
 import { bindThis } from '@/decorators.js';
@@ -11,7 +11,7 @@ const ACTOR_USERNAME = 'instance.actor' as const;
 
 @Injectable()
 export class InstanceActorService {
-	private cache: Cache<LocalUser>;
+	private cache: KVCache<LocalUser>;
 
 	constructor(
 		@Inject(DI.usersRepository)
@@ -19,7 +19,7 @@ export class InstanceActorService {
 
 		private createSystemUserService: CreateSystemUserService,
 	) {
-		this.cache = new Cache<LocalUser>(Infinity);
+		this.cache = new KVCache<LocalUser>(Infinity);
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 2fc2a3d54fa4655fda4991179d70842f67037908..7d080537610c0bef6b88c0f8104e6bff9c6b0dfd 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -19,7 +19,7 @@ import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js
 import { checkWordMute } from '@/misc/check-word-mute.js';
 import type { Channel } from '@/models/entities/Channel.js';
 import { normalizeForSearch } from '@/misc/normalize-for-search.js';
-import { Cache } from '@/misc/cache.js';
+import { KVCache } from '@/misc/cache.js';
 import type { UserProfile } from '@/models/entities/UserProfile.js';
 import { RelayService } from '@/core/RelayService.js';
 import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
@@ -46,7 +46,7 @@ import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
 import { RoleService } from '@/core/RoleService.js';
 import { MetaService } from '@/core/MetaService.js';
 
-const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
+const mutedWordsCache = new KVCache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
 
 type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
 
@@ -435,15 +435,20 @@ export class NoteCreateService implements OnApplicationShutdown {
 		createdAt: User['createdAt'];
 		isBot: User['isBot'];
 	}, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) {
-		// 統計を更新
+		const meta = await this.metaService.fetch();
+
 		this.notesChart.update(note, true);
-		this.perUserNotesChart.update(user, note, true);
+		if (meta.enableChartsForRemoteUser || (user.host == null)) {
+			this.perUserNotesChart.update(user, note, true);
+		}
 
 		// Register host
 		if (this.userEntityService.isRemoteUser(user)) {
-			this.federatedInstanceService.fetch(user.host).then(i => {
+			this.federatedInstanceService.fetch(user.host).then(async i => {
 				this.instancesRepository.increment({ id: i.id }, 'notesCount', 1);
-				this.instanceChart.updateNote(i.host, note, true);
+				if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
+					this.instanceChart.updateNote(i.host, note, true);
+				}
 			});
 		}
 
diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts
index 571b62552336de22d12ee721f22373c0659c812b..dd878f7bba0a48aee4dd37df9eb61ef7e0a5d426 100644
--- a/packages/backend/src/core/NoteDeleteService.ts
+++ b/packages/backend/src/core/NoteDeleteService.ts
@@ -16,6 +16,7 @@ import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerServ
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { bindThis } from '@/decorators.js';
+import { MetaService } from '@/core/MetaService.js';
 
 @Injectable()
 export class NoteDeleteService {
@@ -39,6 +40,7 @@ export class NoteDeleteService {
 		private federatedInstanceService: FederatedInstanceService,
 		private apRendererService: ApRendererService,
 		private apDeliverManagerService: ApDeliverManagerService,
+		private metaService: MetaService,
 		private notesChart: NotesChart,
 		private perUserNotesChart: PerUserNotesChart,
 		private instanceChart: InstanceChart,
@@ -95,14 +97,19 @@ export class NoteDeleteService {
 			}
 			//#endregion
 
-			// 統計を更新
+			const meta = await this.metaService.fetch();
+
 			this.notesChart.update(note, false);
-			this.perUserNotesChart.update(user, note, false);
+			if (meta.enableChartsForRemoteUser || (user.host == null)) {
+				this.perUserNotesChart.update(user, note, false);
+			}
 
 			if (this.userEntityService.isRemoteUser(user)) {
-				this.federatedInstanceService.fetch(user.host).then(i => {
+				this.federatedInstanceService.fetch(user.host).then(async i => {
 					this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1);
-					this.instanceChart.updateNote(i.host, note, false);
+					if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
+						this.instanceChart.updateNote(i.host, note, false);
+					}
 				});
 			}
 		}
diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts
index 271ba79176886172dfef98004ee85e7f9bf7262e..b3aea878d6c4f5bba748815fd83a0dccc012c286 100644
--- a/packages/backend/src/core/ReactionService.ts
+++ b/packages/backend/src/core/ReactionService.ts
@@ -21,6 +21,8 @@ import { bindThis } from '@/decorators.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import { UserBlockingService } from '@/core/UserBlockingService.js';
 
+const FALLBACK = '❤';
+
 const legacies: Record<string, string> = {
 	'like': '👍',
 	'love': '❤', // ここに記述する場合は異体字セレクタを入れない
@@ -147,7 +149,11 @@ export class ReactionService {
 			.where('id = :id', { id: note.id })
 			.execute();
 
-		this.perUserReactionsChart.update(user, note);
+		const meta = await this.metaService.fetch();
+
+		if (meta.enableChartsForRemoteUser || (user.host == null)) {
+			this.perUserReactionsChart.update(user, note);
+		}
 
 		// カスタム絵文字リアクションだったら絵文字情報も送る
 		const decodedReaction = this.decodeReaction(reaction);
@@ -251,12 +257,6 @@ export class ReactionService {
 		//#endregion
 	}
 
-	@bindThis
-	public async getFallbackReaction(): Promise<string> {
-		const meta = await this.metaService.fetch();
-		return meta.useStarForReactionFallback ? '⭐' : '👍';
-	}
-
 	@bindThis
 	public convertLegacyReactions(reactions: Record<string, number>) {
 		const _reactions = {} as Record<string, number>;
@@ -290,7 +290,7 @@ export class ReactionService {
 
 	@bindThis
 	public async toDbReaction(reaction?: string | null, reacterHost?: string | null): Promise<string> {
-		if (reaction == null) return await this.getFallbackReaction();
+		if (reaction == null) return FALLBACK;
 
 		reacterHost = this.utilityService.toPunyNullable(reacterHost);
 
@@ -318,7 +318,7 @@ export class ReactionService {
 			if (emoji) return reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`;
 		}
 
-		return await this.getFallbackReaction();
+		return FALLBACK;
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts
index 86f983cc780e18c1a0ab73ff9ef13b92b917b800..4537f1b81a53bb76c6b054067e1f3a272c1a2165 100644
--- a/packages/backend/src/core/RelayService.ts
+++ b/packages/backend/src/core/RelayService.ts
@@ -3,7 +3,7 @@ import { IsNull } from 'typeorm';
 import type { LocalUser, User } from '@/models/entities/User.js';
 import type { RelaysRepository, UsersRepository } from '@/models/index.js';
 import { IdService } from '@/core/IdService.js';
-import { Cache } from '@/misc/cache.js';
+import { KVCache } from '@/misc/cache.js';
 import type { Relay } from '@/models/entities/Relay.js';
 import { QueueService } from '@/core/QueueService.js';
 import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
@@ -16,7 +16,7 @@ const ACTOR_USERNAME = 'relay.actor' as const;
 
 @Injectable()
 export class RelayService {
-	private relaysCache: Cache<Relay[]>;
+	private relaysCache: KVCache<Relay[]>;
 
 	constructor(
 		@Inject(DI.usersRepository)
@@ -30,7 +30,7 @@ export class RelayService {
 		private createSystemUserService: CreateSystemUserService,
 		private apRendererService: ApRendererService,
 	) {
-		this.relaysCache = new Cache<Relay[]>(1000 * 60 * 10);
+		this.relaysCache = new KVCache<Relay[]>(1000 * 60 * 10);
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts
index 4775196c6fdd7df616e992c10566e10395180f41..7b63e43cb186e18b25c431fced9dbadfc038515c 100644
--- a/packages/backend/src/core/RoleService.ts
+++ b/packages/backend/src/core/RoleService.ts
@@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import Redis from 'ioredis';
 import { In } from 'typeorm';
 import type { Role, RoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/index.js';
-import { Cache } from '@/misc/cache.js';
+import { KVCache } from '@/misc/cache.js';
 import type { User } from '@/models/entities/User.js';
 import { DI } from '@/di-symbols.js';
 import { bindThis } from '@/decorators.js';
@@ -57,8 +57,8 @@ export const DEFAULT_POLICIES: RolePolicies = {
 
 @Injectable()
 export class RoleService implements OnApplicationShutdown {
-	private rolesCache: Cache<Role[]>;
-	private roleAssignmentByUserIdCache: Cache<RoleAssignment[]>;
+	private rolesCache: KVCache<Role[]>;
+	private roleAssignmentByUserIdCache: KVCache<RoleAssignment[]>;
 
 	public static AlreadyAssignedError = class extends Error {};
 	public static NotAssignedError = class extends Error {};
@@ -84,8 +84,8 @@ export class RoleService implements OnApplicationShutdown {
 	) {
 		//this.onMessage = this.onMessage.bind(this);
 
-		this.rolesCache = new Cache<Role[]>(Infinity);
-		this.roleAssignmentByUserIdCache = new Cache<RoleAssignment[]>(Infinity);
+		this.rolesCache = new KVCache<Role[]>(Infinity);
+		this.roleAssignmentByUserIdCache = new KVCache<RoleAssignment[]>(Infinity);
 
 		this.redisSubscriber.on('message', this.onMessage);
 	}
@@ -192,6 +192,12 @@ export class RoleService implements OnApplicationShutdown {
 				case 'followingMoreThanOrEq': {
 					return user.followingCount >= value.value;
 				}
+				case 'notesLessThanOrEq': {
+					return user.notesCount <= value.value;
+				}
+				case 'notesMoreThanOrEq': {
+					return user.notesCount >= value.value;
+				}
 				default:
 					return false;
 			}
diff --git a/packages/backend/src/core/S3Service.ts b/packages/backend/src/core/S3Service.ts
index cc8f95081314a0086ece2892baadea365f9374ec..629278d9157c9d6eca6aa47734c17f6d6e243df2 100644
--- a/packages/backend/src/core/S3Service.ts
+++ b/packages/backend/src/core/S3Service.ts
@@ -1,11 +1,16 @@
 import { URL } from 'node:url';
+import * as http from 'node:http';
+import * as https from 'node:https';
 import { Inject, Injectable } from '@nestjs/common';
-import S3 from 'aws-sdk/clients/s3.js';
+import { DeleteObjectCommand, S3Client } from '@aws-sdk/client-s3';
+import { Upload } from '@aws-sdk/lib-storage';
+import { NodeHttpHandler, NodeHttpHandlerOptions } from '@aws-sdk/node-http-handler';
 import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
 import type { Meta } from '@/models/entities/Meta.js';
 import { HttpRequestService } from '@/core/HttpRequestService.js';
 import { bindThis } from '@/decorators.js';
+import type { DeleteObjectCommandInput, PutObjectCommandInput } from '@aws-sdk/client-s3';
 
 @Injectable()
 export class S3Service {
@@ -18,25 +23,47 @@ export class S3Service {
 	}
 
 	@bindThis
-	public getS3(meta: Meta) {
+	public getS3Client(meta: Meta): S3Client {
 		const u = meta.objectStorageEndpoint
-			? `${meta.objectStorageUseSSL ? 'https://' : 'http://'}${meta.objectStorageEndpoint}`
-			: `${meta.objectStorageUseSSL ? 'https://' : 'http://'}example.net`;
+			? `${meta.objectStorageUseSSL ? 'https' : 'http'}://${meta.objectStorageEndpoint}`
+			: `${meta.objectStorageUseSSL ? 'https' : 'http'}://example.net`; // dummy url to select http(s) agent
 
-		return new S3({
-			endpoint: meta.objectStorageEndpoint && meta.objectStorageEndpoint.length > 0
-				? meta.objectStorageEndpoint
-				: undefined,
-			accessKeyId: meta.objectStorageAccessKey!,
-			secretAccessKey: meta.objectStorageSecretKey!,
+		const agent = this.httpRequestService.getAgentByUrl(new URL(u), !meta.objectStorageUseProxy);
+		const handlerOption: NodeHttpHandlerOptions = {};
+		if (meta.objectStorageUseSSL) {
+			handlerOption.httpsAgent = agent as https.Agent;
+		} else {
+			handlerOption.httpAgent = agent as http.Agent;
+		}
+
+		return new S3Client({
+			endpoint: meta.objectStorageEndpoint ? u : undefined,
+			credentials: (meta.objectStorageAccessKey !== null && meta.objectStorageSecretKey !== null) ? {
+				accessKeyId: meta.objectStorageAccessKey,
+				secretAccessKey: meta.objectStorageSecretKey,
+			} : undefined,
 			region: meta.objectStorageRegion ?? undefined,
-			sslEnabled: meta.objectStorageUseSSL,
-			s3ForcePathStyle: !meta.objectStorageEndpoint	// AWS with endPoint omitted
-				? false
-				: meta.objectStorageS3ForcePathStyle,
-			httpOptions: {
-				agent: this.httpRequestService.getAgentByUrl(new URL(u), !meta.objectStorageUseProxy),
-			},
+			tls: meta.objectStorageUseSSL,
+			forcePathStyle: meta.objectStorageEndpoint ? meta.objectStorageS3ForcePathStyle : false, // AWS with endPoint omitted
+			requestHandler: new NodeHttpHandler(handlerOption),
 		});
 	}
+
+	@bindThis
+	public async upload(meta: Meta, input: PutObjectCommandInput) {
+		const client = this.getS3Client(meta);
+		return new Upload({
+			client,
+			params: input,
+			partSize: (client.config.endpoint && (await client.config.endpoint()).hostname === 'storage.googleapis.com')
+				? 500 * 1024 * 1024
+				: 8 * 1024 * 1024,
+		}).done();
+	}
+
+	@bindThis
+	public delete(meta: Meta, input: DeleteObjectCommandInput) {
+		const client = this.getS3Client(meta);
+		return client.send(new DeleteObjectCommand(input));
+	}
 }
diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts
index 92408da3429495b21824f3fb67fafecd0a0c841a..33b51537a668650c19b0260a0a2c9cf181e027bc 100644
--- a/packages/backend/src/core/UserBlockingService.ts
+++ b/packages/backend/src/core/UserBlockingService.ts
@@ -15,7 +15,7 @@ import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
 import { LoggerService } from '@/core/LoggerService.js';
 import { WebhookService } from '@/core/WebhookService.js';
 import { bindThis } from '@/decorators.js';
-import { Cache } from '@/misc/cache.js';
+import { KVCache } from '@/misc/cache.js';
 import { StreamMessages } from '@/server/api/stream/types.js';
 
 @Injectable()
@@ -23,7 +23,7 @@ export class UserBlockingService implements OnApplicationShutdown {
 	private logger: Logger;
 
 	// キーがユーザーIDで、値がそのユーザーがブロックしているユーザーのIDのリストなキャッシュ
-	private blockingsByUserIdCache: Cache<User['id'][]>;
+	private blockingsByUserIdCache: KVCache<User['id'][]>;
 
 	constructor(
 		@Inject(DI.redisSubscriber)
@@ -58,7 +58,7 @@ export class UserBlockingService implements OnApplicationShutdown {
 	) {
 		this.logger = this.loggerService.getLogger('user-block');
 
-		this.blockingsByUserIdCache = new Cache<User['id'][]>(Infinity);
+		this.blockingsByUserIdCache = new KVCache<User['id'][]>(Infinity);
 
 		this.redisSubscriber.on('message', this.onMessage);
 	}
diff --git a/packages/backend/src/core/UserCacheService.ts b/packages/backend/src/core/UserCacheService.ts
index fc383d1c0861299927a7fc9ddd35a4cdb9ec2df3..631eb44062ccb65d1cc5a2c32cbe54db33a36b79 100644
--- a/packages/backend/src/core/UserCacheService.ts
+++ b/packages/backend/src/core/UserCacheService.ts
@@ -1,7 +1,7 @@
 import { Inject, Injectable } from '@nestjs/common';
 import Redis from 'ioredis';
 import type { UsersRepository } from '@/models/index.js';
-import { Cache } from '@/misc/cache.js';
+import { KVCache } from '@/misc/cache.js';
 import type { LocalUser, User } from '@/models/entities/User.js';
 import { DI } from '@/di-symbols.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
@@ -11,10 +11,10 @@ import type { OnApplicationShutdown } from '@nestjs/common';
 
 @Injectable()
 export class UserCacheService implements OnApplicationShutdown {
-	public userByIdCache: Cache<User>;
-	public localUserByNativeTokenCache: Cache<LocalUser | null>;
-	public localUserByIdCache: Cache<LocalUser>;
-	public uriPersonCache: Cache<User | null>;
+	public userByIdCache: KVCache<User>;
+	public localUserByNativeTokenCache: KVCache<LocalUser | null>;
+	public localUserByIdCache: KVCache<LocalUser>;
+	public uriPersonCache: KVCache<User | null>;
 
 	constructor(
 		@Inject(DI.redisSubscriber)
@@ -27,10 +27,10 @@ export class UserCacheService implements OnApplicationShutdown {
 	) {
 		//this.onMessage = this.onMessage.bind(this);
 
-		this.userByIdCache = new Cache<User>(Infinity);
-		this.localUserByNativeTokenCache = new Cache<LocalUser | null>(Infinity);
-		this.localUserByIdCache = new Cache<LocalUser>(Infinity);
-		this.uriPersonCache = new Cache<User | null>(Infinity);
+		this.userByIdCache = new KVCache<User>(Infinity);
+		this.localUserByNativeTokenCache = new KVCache<LocalUser | null>(Infinity);
+		this.localUserByIdCache = new KVCache<LocalUser>(Infinity);
+		this.uriPersonCache = new KVCache<User | null>(Infinity);
 
 		this.redisSubscriber.on('message', this.onMessage);
 	}
diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts
index 1c8550435300dee37717f5644e93ca25eb234e57..b51b553c7082e3fd0a39f418d22d71e62c8fb25f 100644
--- a/packages/backend/src/core/UserFollowingService.ts
+++ b/packages/backend/src/core/UserFollowingService.ts
@@ -17,6 +17,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
 import { bindThis } from '@/decorators.js';
 import { UserBlockingService } from '@/core/UserBlockingService.js';
+import { MetaService } from '@/core/MetaService.js';
 import Logger from '../logger.js';
 
 const logger = new Logger('following/create');
@@ -57,6 +58,7 @@ export class UserFollowingService {
 		private idService: IdService,
 		private queueService: QueueService,
 		private globalEventService: GlobalEventService,
+		private metaService: MetaService,
 		private notificationService: NotificationService,
 		private federatedInstanceService: FederatedInstanceService,
 		private webhookService: WebhookService,
@@ -200,14 +202,18 @@ export class UserFollowingService {
 
 		//#region Update instance stats
 		if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
-			this.federatedInstanceService.fetch(follower.host).then(i => {
+			this.federatedInstanceService.fetch(follower.host).then(async i => {
 				this.instancesRepository.increment({ id: i.id }, 'followingCount', 1);
-				this.instanceChart.updateFollowing(i.host, true);
+				if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
+					this.instanceChart.updateFollowing(i.host, true);
+				}
 			});
 		} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
-			this.federatedInstanceService.fetch(followee.host).then(i => {
+			this.federatedInstanceService.fetch(followee.host).then(async i => {
 				this.instancesRepository.increment({ id: i.id }, 'followersCount', 1);
-				this.instanceChart.updateFollowers(i.host, true);
+				if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
+					this.instanceChart.updateFollowers(i.host, true);
+				}
 			});
 		}
 		//#endregion
@@ -320,14 +326,18 @@ export class UserFollowingService {
 
 		//#region Update instance stats
 		if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
-			this.federatedInstanceService.fetch(follower.host).then(i => {
+			this.federatedInstanceService.fetch(follower.host).then(async i => {
 				this.instancesRepository.decrement({ id: i.id }, 'followingCount', 1);
-				this.instanceChart.updateFollowing(i.host, false);
+				if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
+					this.instanceChart.updateFollowing(i.host, false);
+				}
 			});
 		} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
-			this.federatedInstanceService.fetch(followee.host).then(i => {
+			this.federatedInstanceService.fetch(followee.host).then(async i => {
 				this.instancesRepository.decrement({ id: i.id }, 'followersCount', 1);
-				this.instanceChart.updateFollowers(i.host, false);
+				if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
+					this.instanceChart.updateFollowers(i.host, false);
+				}
 			});
 		}
 		//#endregion
diff --git a/packages/backend/src/core/UserKeypairStoreService.ts b/packages/backend/src/core/UserKeypairStoreService.ts
index 1d3cc87c8db25f6d9b3f0eb76b6051b16c5da948..61c9293f86cd3305d0fcb4c9e557aabf817d93a1 100644
--- a/packages/backend/src/core/UserKeypairStoreService.ts
+++ b/packages/backend/src/core/UserKeypairStoreService.ts
@@ -1,20 +1,20 @@
 import { Inject, Injectable } from '@nestjs/common';
 import type { User } from '@/models/entities/User.js';
 import type { UserKeypairsRepository } from '@/models/index.js';
-import { Cache } from '@/misc/cache.js';
+import { KVCache } from '@/misc/cache.js';
 import type { UserKeypair } from '@/models/entities/UserKeypair.js';
 import { DI } from '@/di-symbols.js';
 import { bindThis } from '@/decorators.js';
 
 @Injectable()
 export class UserKeypairStoreService {
-	private cache: Cache<UserKeypair>;
+	private cache: KVCache<UserKeypair>;
 
 	constructor(
 		@Inject(DI.userKeypairsRepository)
 		private userKeypairsRepository: UserKeypairsRepository,
 	) {
-		this.cache = new Cache<UserKeypair>(Infinity);
+		this.cache = new KVCache<UserKeypair>(Infinity);
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts
index d0a4ad7a7581dc239d36a292a79a8aebe8e73856..c3b3875613f854ccd2b728f60020963f44def4cc 100644
--- a/packages/backend/src/core/activitypub/ApDbResolverService.ts
+++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts
@@ -3,7 +3,7 @@ import escapeRegexp from 'escape-regexp';
 import { DI } from '@/di-symbols.js';
 import type { NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js';
 import type { Config } from '@/config.js';
-import { Cache } from '@/misc/cache.js';
+import { KVCache } from '@/misc/cache.js';
 import type { UserPublickey } from '@/models/entities/UserPublickey.js';
 import { UserCacheService } from '@/core/UserCacheService.js';
 import type { Note } from '@/models/entities/Note.js';
@@ -31,8 +31,8 @@ export type UriParseResult = {
 
 @Injectable()
 export class ApDbResolverService {
-	private publicKeyCache: Cache<UserPublickey | null>;
-	private publicKeyByUserIdCache: Cache<UserPublickey | null>;
+	private publicKeyCache: KVCache<UserPublickey | null>;
+	private publicKeyByUserIdCache: KVCache<UserPublickey | null>;
 
 	constructor(
 		@Inject(DI.config)
@@ -50,8 +50,8 @@ export class ApDbResolverService {
 		private userCacheService: UserCacheService,
 		private apPersonService: ApPersonService,
 	) {
-		this.publicKeyCache = new Cache<UserPublickey | null>(Infinity);
-		this.publicKeyByUserIdCache = new Cache<UserPublickey | null>(Infinity);
+		this.publicKeyCache = new KVCache<UserPublickey | null>(Infinity);
+		this.publicKeyByUserIdCache = new KVCache<UserPublickey | null>(Infinity);
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts
index d06958da0cb4071eb5c9d17e41cd68adfab14a7f..41f7eafa41cf35f724de42ac6b7f9b568f1691cf 100644
--- a/packages/backend/src/core/activitypub/models/ApPersonService.ts
+++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts
@@ -30,6 +30,7 @@ import { StatusError } from '@/misc/status-error.js';
 import type { UtilityService } from '@/core/UtilityService.js';
 import type { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { bindThis } from '@/decorators.js';
+import { MetaService } from '@/core/MetaService.js';
 import { getApId, getApType, getOneApHrefNullable, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js';
 import { extractApHashtags } from './tag.js';
 import type { OnModuleInit } from '@nestjs/common';
@@ -50,6 +51,7 @@ export class ApPersonService implements OnModuleInit {
 	private userEntityService: UserEntityService;
 	private idService: IdService;
 	private globalEventService: GlobalEventService;
+	private metaService: MetaService;
 	private federatedInstanceService: FederatedInstanceService;
 	private fetchInstanceMetadataService: FetchInstanceMetadataService;
 	private userCacheService: UserCacheService;
@@ -92,6 +94,7 @@ export class ApPersonService implements OnModuleInit {
 		//private userEntityService: UserEntityService,
 		//private idService: IdService,
 		//private globalEventService: GlobalEventService,
+		//private metaService: MetaService,
 		//private federatedInstanceService: FederatedInstanceService,
 		//private fetchInstanceMetadataService: FetchInstanceMetadataService,
 		//private userCacheService: UserCacheService,
@@ -112,6 +115,7 @@ export class ApPersonService implements OnModuleInit {
 		this.userEntityService = this.moduleRef.get('UserEntityService');
 		this.idService = this.moduleRef.get('IdService');
 		this.globalEventService = this.moduleRef.get('GlobalEventService');
+		this.metaService = this.moduleRef.get('MetaService');
 		this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService');
 		this.fetchInstanceMetadataService = this.moduleRef.get('FetchInstanceMetadataService');
 		this.userCacheService = this.moduleRef.get('UserCacheService');
@@ -327,10 +331,12 @@ export class ApPersonService implements OnModuleInit {
 		}
 
 		// Register host
-		this.federatedInstanceService.fetch(host).then(i => {
+		this.federatedInstanceService.fetch(host).then(async i => {
 			this.instancesRepository.increment({ id: i.id }, 'usersCount', 1);
-			this.instanceChart.newUser(i.host);
 			this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
+			if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
+				this.instanceChart.newUser(i.host);
+			}
 		});
 
 		this.usersChart.update(user!, true);
diff --git a/packages/backend/src/core/entities/AntennaEntityService.ts b/packages/backend/src/core/entities/AntennaEntityService.ts
index 89137c0ec0ad5672168e0f706bc77949ff8332a0..e02daefd6458f15367a5c70923efb18ac0a04698 100644
--- a/packages/backend/src/core/entities/AntennaEntityService.ts
+++ b/packages/backend/src/core/entities/AntennaEntityService.ts
@@ -37,6 +37,7 @@ export class AntennaEntityService {
 			notify: antenna.notify,
 			withReplies: antenna.withReplies,
 			withFile: antenna.withFile,
+			isActive: antenna.isActive,
 			hasUnreadNote,
 		};
 	}
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index 068ffad09d891f6dd6e4f79c4610e9a7d81c17c8..b693883e06fe138b73452d1d1755fd6dc9e841b5 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -8,7 +8,7 @@ import type { Packed } from '@/misc/json-schema.js';
 import type { Promiseable } from '@/misc/prelude/await-all.js';
 import { awaitAll } from '@/misc/prelude/await-all.js';
 import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js';
-import { Cache } from '@/misc/cache.js';
+import { KVCache } from '@/misc/cache.js';
 import type { Instance } from '@/models/entities/Instance.js';
 import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
 import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/entities/User.js';
@@ -52,7 +52,7 @@ export class UserEntityService implements OnModuleInit {
 	private customEmojiService: CustomEmojiService;
 	private antennaService: AntennaService;
 	private roleService: RoleService;
-	private userInstanceCache: Cache<Instance | null>;
+	private userInstanceCache: KVCache<Instance | null>;
 
 	constructor(
 		private moduleRef: ModuleRef,
@@ -121,7 +121,7 @@ export class UserEntityService implements OnModuleInit {
 		//private antennaService: AntennaService,
 		//private roleService: RoleService,
 	) {
-		this.userInstanceCache = new Cache<Instance | null>(1000 * 60 * 60 * 3);
+		this.userInstanceCache = new KVCache<Instance | null>(1000 * 60 * 60 * 3);
 	}
 
 	onModuleInit() {
diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts
index 43a71a2b572dc6fa7b2b5e5ffb10c1eb71b4aacd..b249cf448055d23619fe90e4d5cbbe39a9d54e92 100644
--- a/packages/backend/src/misc/cache.ts
+++ b/packages/backend/src/misc/cache.ts
@@ -2,11 +2,11 @@ import { bindThis } from '@/decorators.js';
 
 // TODO: メモリ節約のためあまり参照されないキーを定期的に削除できるようにする?
 
-export class Cache<T> {
+export class KVCache<T> {
 	public cache: Map<string | null, { date: number; value: T; }>;
 	private lifetime: number;
 
-	constructor(lifetime: Cache<never>['lifetime']) {
+	constructor(lifetime: KVCache<never>['lifetime']) {
 		this.cache = new Map();
 		this.lifetime = lifetime;
 	}
@@ -87,3 +87,88 @@ export class Cache<T> {
 		return value;
 	}
 }
+
+export class Cache<T> {
+	private cachedAt: number | null = null;
+	private value: T | undefined;
+	private lifetime: number;
+
+	constructor(lifetime: Cache<never>['lifetime']) {
+		this.lifetime = lifetime;
+	}
+
+	@bindThis
+	public set(value: T): void {
+		this.cachedAt = Date.now();
+		this.value = value;
+	}
+
+	@bindThis
+	public get(): T | undefined {
+		if (this.cachedAt == null) return undefined;
+		if ((Date.now() - this.cachedAt) > this.lifetime) {
+			this.value = undefined;
+			this.cachedAt = null;
+			return undefined;
+		}
+		return this.value;
+	}
+
+	@bindThis
+	public delete() {
+		this.value = undefined;
+		this.cachedAt = null;
+	}
+
+	/**
+	 * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します
+	 * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
+	 */
+	@bindThis
+	public async fetch(fetcher: () => Promise<T>, validator?: (cachedValue: T) => boolean): Promise<T> {
+		const cachedValue = this.get();
+		if (cachedValue !== undefined) {
+			if (validator) {
+				if (validator(cachedValue)) {
+					// Cache HIT
+					return cachedValue;
+				}
+			} else {
+				// Cache HIT
+				return cachedValue;
+			}
+		}
+
+		// Cache MISS
+		const value = await fetcher();
+		this.set(value);
+		return value;
+	}
+
+	/**
+	 * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します
+	 * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
+	 */
+	@bindThis
+	public async fetchMaybe(fetcher: () => Promise<T | undefined>, validator?: (cachedValue: T) => boolean): Promise<T | undefined> {
+		const cachedValue = this.get();
+		if (cachedValue !== undefined) {
+			if (validator) {
+				if (validator(cachedValue)) {
+					// Cache HIT
+					return cachedValue;
+				}
+			} else {
+				// Cache HIT
+				return cachedValue;
+			}
+		}
+
+		// Cache MISS
+		const value = await fetcher();
+		if (value !== undefined) {
+			this.set(value);
+		}
+		return value;
+	}
+}
diff --git a/packages/backend/src/misc/correct-filename.ts b/packages/backend/src/misc/correct-filename.ts
index 3357d8c1bd5c25664c2d4f3fd591106503c25fa8..23a0699f3971e4e468c379c67158f539d5f3c88d 100644
--- a/packages/backend/src/misc/correct-filename.ts
+++ b/packages/backend/src/misc/correct-filename.ts
@@ -1,15 +1,15 @@
 // 与えられた拡張子とファイル名が一致しているかどうかを確認し、
 // 一致していない場合は拡張子を付与して返す
 export function correctFilename(filename: string, ext: string | null) {
-    const dotExt = ext ? ext.startsWith('.') ? ext : `.${ext}` : '.unknown';
-    if (filename.endsWith(dotExt)) {
-        return filename;
-    }
-    if (ext === 'jpg' && filename.endsWith('.jpeg')) {
-        return filename;
-    }
-    if (ext === 'tif' && filename.endsWith('.tiff')) {
-        return filename;
-    }
-    return `${filename}${dotExt}`;
+	const dotExt = ext ? ext.startsWith('.') ? ext : `.${ext}` : '.unknown';
+	if (filename.endsWith(dotExt)) {
+		return filename;
+	}
+	if (ext === 'jpg' && filename.endsWith('.jpeg')) {
+		return filename;
+	}
+	if (ext === 'tif' && filename.endsWith('.tiff')) {
+		return filename;
+	}
+	return `${filename}${dotExt}`;
 }
diff --git a/packages/backend/src/models/entities/Antenna.ts b/packages/backend/src/models/entities/Antenna.ts
index 5b2164ef17c8587d6e29b6fa25243eadbabb2186..e63e7f2c72e3c7f3c57cbc4adaf5b87116365f79 100644
--- a/packages/backend/src/models/entities/Antenna.ts
+++ b/packages/backend/src/models/entities/Antenna.ts
@@ -13,6 +13,10 @@ export class Antenna {
 	})
 	public createdAt: Date;
 
+	@Index()
+	@Column('timestamp with time zone')
+	public lastUsedAt: Date;
+
 	@Index()
 	@Column({
 		...id(),
@@ -83,4 +87,10 @@ export class Antenna {
 
 	@Column('boolean')
 	public notify: boolean;
+
+	@Index()
+	@Column('boolean', {
+		default: true,
+	})
+	public isActive: boolean;
 }
diff --git a/packages/backend/src/models/entities/Meta.ts b/packages/backend/src/models/entities/Meta.ts
index 57338ecbd2771d857c22503d3a62466feafff88e..2e4f90b57f8d6e56adc1a6327c9733e1f910aef7 100644
--- a/packages/backend/src/models/entities/Meta.ts
+++ b/packages/backend/src/models/entities/Meta.ts
@@ -42,11 +42,6 @@ export class Meta {
 	})
 	public disableRegistration: boolean;
 
-	@Column('boolean', {
-		default: false,
-	})
-	public useStarForReactionFallback: boolean;
-
 	@Column('varchar', {
 		length: 1024, array: true, default: '{}',
 	})
@@ -396,6 +391,16 @@ export class Meta {
 	})
 	public enableActiveEmailValidation: boolean;
 
+	@Column('boolean', {
+		default: true,
+	})
+	public enableChartsForRemoteUser: boolean;
+
+	@Column('boolean', {
+		default: true,
+	})
+	public enableChartsForFederatedInstances: boolean;
+
 	@Column('jsonb', {
 		default: { },
 	})
diff --git a/packages/backend/src/models/entities/Role.ts b/packages/backend/src/models/entities/Role.ts
index 85ff2667405082724f905beb05041814b1f620c6..eca9bcf2706d5ab54e182968cfff1859b27596ea 100644
--- a/packages/backend/src/models/entities/Role.ts
+++ b/packages/backend/src/models/entities/Role.ts
@@ -54,6 +54,16 @@ type CondFormulaValueFollowingMoreThanOrEq = {
 	value: number;
 };
 
+type CondFormulaValueNotesLessThanOrEq = {
+	type: 'notesLessThanOrEq';
+	value: number;
+};
+
+type CondFormulaValueNotesMoreThanOrEq = {
+	type: 'notesMoreThanOrEq';
+	value: number;
+};
+
 export type RoleCondFormulaValue =
 	CondFormulaValueAnd |
 	CondFormulaValueOr |
@@ -65,7 +75,9 @@ export type RoleCondFormulaValue =
 	CondFormulaValueFollowersLessThanOrEq |
 	CondFormulaValueFollowersMoreThanOrEq |
 	CondFormulaValueFollowingLessThanOrEq |
-	CondFormulaValueFollowingMoreThanOrEq;
+	CondFormulaValueFollowingMoreThanOrEq |
+	CondFormulaValueNotesLessThanOrEq |
+	CondFormulaValueNotesMoreThanOrEq;
 
 @Entity()
 export class Role {
diff --git a/packages/backend/src/models/json-schema/antenna.ts b/packages/backend/src/models/json-schema/antenna.ts
index f0994e48f7e53556dd37276d87acb0b639594b64..4483510610a00a93b8cf880b80815ce5f630c416 100644
--- a/packages/backend/src/models/json-schema/antenna.ts
+++ b/packages/backend/src/models/json-schema/antenna.ts
@@ -75,6 +75,10 @@ export const packedAntennaSchema = {
 			type: 'boolean',
 			optional: false, nullable: false,
 		},
+		isActive: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
 		hasUnreadNote: {
 			type: 'boolean',
 			optional: false, nullable: false,
diff --git a/packages/backend/src/queue/processors/CleanProcessorService.ts b/packages/backend/src/queue/processors/CleanProcessorService.ts
index 7fd2cde9c0c21939b77dff2567c247edbca9bcb5..9534454fd75de93c63f2c5fa5024133de7aaa3cf 100644
--- a/packages/backend/src/queue/processors/CleanProcessorService.ts
+++ b/packages/backend/src/queue/processors/CleanProcessorService.ts
@@ -1,7 +1,7 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { In, LessThan } from 'typeorm';
 import { DI } from '@/di-symbols.js';
-import type { AntennaNotesRepository, MutedNotesRepository, NotificationsRepository, RoleAssignmentsRepository, UserIpsRepository } from '@/models/index.js';
+import type { AntennaNotesRepository, AntennasRepository, MutedNotesRepository, NotificationsRepository, RoleAssignmentsRepository, UserIpsRepository } from '@/models/index.js';
 import type { Config } from '@/config.js';
 import type Logger from '@/logger.js';
 import { bindThis } from '@/decorators.js';
@@ -26,6 +26,9 @@ export class CleanProcessorService {
 		@Inject(DI.mutedNotesRepository)
 		private mutedNotesRepository: MutedNotesRepository,
 
+		@Inject(DI.antennasRepository)
+		private antennasRepository: AntennasRepository,
+
 		@Inject(DI.antennaNotesRepository)
 		private antennaNotesRepository: AntennaNotesRepository,
 
@@ -55,8 +58,16 @@ export class CleanProcessorService {
 			reason: 'word',
 		});
 
-		this.antennaNotesRepository.delete({
+		this.mutedNotesRepository.delete({
 			id: LessThan(this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 90)))),
+			reason: 'word',
+		});
+
+		// 7日以上使われてないアンテナを停止
+		this.antennasRepository.update({
+			lastUsedAt: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 7))),
+		}, {
+			isActive: false,
 		});
 
 		const expiredRoleAssignments = await this.roleAssignmentsRepository.createQueryBuilder('assign')
diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts
index 43a92bb2672de8099709c1dd851789d0e6702a59..f637bf88188ffdcf18eaeed3b115fbcbc3e6585d 100644
--- a/packages/backend/src/queue/processors/DeliverProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts
@@ -7,7 +7,7 @@ import { MetaService } from '@/core/MetaService.js';
 import { ApRequestService } from '@/core/activitypub/ApRequestService.js';
 import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
 import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js';
-import { Cache } from '@/misc/cache.js';
+import { KVCache } from '@/misc/cache.js';
 import type { Instance } from '@/models/entities/Instance.js';
 import InstanceChart from '@/core/chart/charts/instance.js';
 import ApRequestChart from '@/core/chart/charts/ap-request.js';
@@ -22,7 +22,7 @@ import type { DeliverJobData } from '../types.js';
 @Injectable()
 export class DeliverProcessorService {
 	private logger: Logger;
-	private suspendedHostsCache: Cache<Instance[]>;
+	private suspendedHostsCache: KVCache<Instance[]>;
 	private latest: string | null;
 
 	constructor(
@@ -46,7 +46,7 @@ export class DeliverProcessorService {
 		private queueLoggerService: QueueLoggerService,
 	) {
 		this.logger = this.queueLoggerService.logger.createSubLogger('deliver');
-		this.suspendedHostsCache = new Cache<Instance[]>(1000 * 60 * 60);
+		this.suspendedHostsCache = new KVCache<Instance[]>(1000 * 60 * 60);
 	}
 
 	@bindThis
@@ -88,10 +88,12 @@ export class DeliverProcessorService {
 				}
 
 				this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
-
-				this.instanceChart.requestSent(i.host, true);
 				this.apRequestChart.deliverSucc();
 				this.federationChart.deliverd(i.host, true);
+
+				if (meta.enableChartsForFederatedInstances) {
+					this.instanceChart.requestSent(i.host, true);
+				}
 			});
 
 			return 'Success';
@@ -107,9 +109,12 @@ export class DeliverProcessorService {
 					});
 				}
 
-				this.instanceChart.requestSent(i.host, false);
 				this.apRequestChart.deliverFail();
 				this.federationChart.deliverd(i.host, false);
+
+				if (meta.enableChartsForFederatedInstances) {
+					this.instanceChart.requestSent(i.host, false);
+				}
 			});
 
 			if (res instanceof StatusError) {
diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts
index 41fe06b7c361d054a4b112716d860828341101f0..ed7f38d0137242e97504122089d5dad62c7c99da 100644
--- a/packages/backend/src/queue/processors/InboxProcessorService.ts
+++ b/packages/backend/src/queue/processors/InboxProcessorService.ts
@@ -184,9 +184,12 @@ export class InboxProcessorService {
 
 			this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
 
-			this.instanceChart.requestReceived(i.host);
 			this.apRequestChart.inbox();
 			this.federationChart.inbox(i.host);
+
+			if (meta.enableChartsForFederatedInstances) {
+				this.instanceChart.requestReceived(i.host);
+			}
 		});
 
 		// アクティビティを処理
diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts
index 364b46696de4154dbeebcfcf6826d8798cca3034..86019d4166c39744e31b410fc1e934ea5c7c5113 100644
--- a/packages/backend/src/server/NodeinfoServerService.ts
+++ b/packages/backend/src/server/NodeinfoServerService.ts
@@ -4,7 +4,7 @@ import type { NotesRepository, UsersRepository } from '@/models/index.js';
 import type { Config } from '@/config.js';
 import { MetaService } from '@/core/MetaService.js';
 import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
-import { Cache } from '@/misc/cache.js';
+import { KVCache } from '@/misc/cache.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { bindThis } from '@/decorators.js';
 import NotesChart from '@/core/chart/charts/notes.js';
@@ -118,7 +118,7 @@ export class NodeinfoServerService {
 			};
 		};
 
-		const cache = new Cache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10);
+		const cache = new KVCache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10);
 
 		fastify.get(nodeinfo2_1path, async (request, reply) => {
 			const base = await cache.fetch(null, () => nodeinfo2());
diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts
index 87438c348da90c653e52abb034d1b9c7b30a9302..a1895e3705661d672ea4eb41b755135b8441ce9d 100644
--- a/packages/backend/src/server/api/AuthenticateService.ts
+++ b/packages/backend/src/server/api/AuthenticateService.ts
@@ -3,7 +3,7 @@ import { DI } from '@/di-symbols.js';
 import type { AccessTokensRepository, AppsRepository, UsersRepository } from '@/models/index.js';
 import type { LocalUser } from '@/models/entities/User.js';
 import type { AccessToken } from '@/models/entities/AccessToken.js';
-import { Cache } from '@/misc/cache.js';
+import { KVCache } from '@/misc/cache.js';
 import type { App } from '@/models/entities/App.js';
 import { UserCacheService } from '@/core/UserCacheService.js';
 import isNativeToken from '@/misc/is-native-token.js';
@@ -18,7 +18,7 @@ export class AuthenticationError extends Error {
 
 @Injectable()
 export class AuthenticateService {
-	private appCache: Cache<App>;
+	private appCache: KVCache<App>;
 
 	constructor(
 		@Inject(DI.usersRepository)
@@ -32,7 +32,7 @@ export class AuthenticateService {
 
 		private userCacheService: UserCacheService,
 	) {
-		this.appCache = new Cache<App>(Infinity);
+		this.appCache = new KVCache<App>(Infinity);
 	}
 
 	@bindThis
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index 516e90dcb376464943a023ad78f4dcb18aad76cb..835e88419302e89f25ee358e7b7cb1b3f4b5f3d6 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -42,6 +42,7 @@ import * as ep___admin_promo_create from './endpoints/admin/promo/create.js';
 import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js';
 import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js';
 import * as ep___admin_queue_inboxDelayed from './endpoints/admin/queue/inbox-delayed.js';
+import * as ep___admin_queue_promote from './endpoints/admin/queue/promote.js';
 import * as ep___admin_queue_stats from './endpoints/admin/queue/stats.js';
 import * as ep___admin_relays_add from './endpoints/admin/relays/add.js';
 import * as ep___admin_relays_list from './endpoints/admin/relays/list.js';
@@ -370,6 +371,7 @@ const $admin_promo_create: Provider = { provide: 'ep:admin/promo/create', useCla
 const $admin_queue_clear: Provider = { provide: 'ep:admin/queue/clear', useClass: ep___admin_queue_clear.default };
 const $admin_queue_deliverDelayed: Provider = { provide: 'ep:admin/queue/deliver-delayed', useClass: ep___admin_queue_deliverDelayed.default };
 const $admin_queue_inboxDelayed: Provider = { provide: 'ep:admin/queue/inbox-delayed', useClass: ep___admin_queue_inboxDelayed.default };
+const $admin_queue_promote: Provider = { provide: 'ep:admin/queue/promote', useClass: ep___admin_queue_promote.default };
 const $admin_queue_stats: Provider = { provide: 'ep:admin/queue/stats', useClass: ep___admin_queue_stats.default };
 const $admin_relays_add: Provider = { provide: 'ep:admin/relays/add', useClass: ep___admin_relays_add.default };
 const $admin_relays_list: Provider = { provide: 'ep:admin/relays/list', useClass: ep___admin_relays_list.default };
@@ -702,6 +704,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$admin_queue_clear,
 		$admin_queue_deliverDelayed,
 		$admin_queue_inboxDelayed,
+		$admin_queue_promote,
 		$admin_queue_stats,
 		$admin_relays_add,
 		$admin_relays_list,
@@ -1028,6 +1031,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$admin_queue_clear,
 		$admin_queue_deliverDelayed,
 		$admin_queue_inboxDelayed,
+		$admin_queue_promote,
 		$admin_queue_stats,
 		$admin_relays_add,
 		$admin_relays_list,
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index 2930468a2215a30da4d165cc4532bcd004c7a591..f6fc79fc70b46c512ada61a6c3689f109093a2b9 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -42,6 +42,7 @@ import * as ep___admin_promo_create from './endpoints/admin/promo/create.js';
 import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js';
 import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js';
 import * as ep___admin_queue_inboxDelayed from './endpoints/admin/queue/inbox-delayed.js';
+import * as ep___admin_queue_promote from './endpoints/admin/queue/promote.js';
 import * as ep___admin_queue_stats from './endpoints/admin/queue/stats.js';
 import * as ep___admin_relays_add from './endpoints/admin/relays/add.js';
 import * as ep___admin_relays_list from './endpoints/admin/relays/list.js';
@@ -368,6 +369,7 @@ const eps = [
 	['admin/queue/clear', ep___admin_queue_clear],
 	['admin/queue/deliver-delayed', ep___admin_queue_deliverDelayed],
 	['admin/queue/inbox-delayed', ep___admin_queue_inboxDelayed],
+	['admin/queue/promote', ep___admin_queue_promote],
 	['admin/queue/stats', ep___admin_queue_stats],
 	['admin/relays/add', ep___admin_relays_add],
 	['admin/relays/list', ep___admin_relays_list],
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
index dad0e3ef860f1fbf4889d33ed18477e1499dca35..bc0475e05c8284e1540e5ff7ea3a991df04fea94 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
@@ -1,5 +1,5 @@
 import { Inject, Injectable } from '@nestjs/common';
-import { DataSource } from 'typeorm';
+import { DataSource, IsNull } from 'typeorm';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { EmojisRepository } from '@/models/index.js';
 import { DI } from '@/di-symbols.js';
@@ -19,6 +19,11 @@ export const meta = {
 			code: 'NO_SUCH_EMOJI',
 			id: '684dec9d-a8c2-4364-9aa8-456c49cb1dc8',
 		},
+		sameNameEmojiExists: {
+			message: 'Emoji that have same name already exists.',
+			code: 'SAME_NAME_EMOJI_EXISTS',
+			id: '7180fe9d-1ee3-bff9-647d-fe9896d2ffb8',
+		},
 	},
 } as const;
 
@@ -26,7 +31,7 @@ export const paramDef = {
 	type: 'object',
 	properties: {
 		id: { type: 'string', format: 'misskey:id' },
-		name: { type: 'string' },
+		name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
 		category: {
 			type: 'string',
 			nullable: true,
@@ -57,9 +62,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const emoji = await this.emojisRepository.findOneBy({ id: ps.id });
-
+			const sameNameEmoji = await this.emojisRepository.findOneBy({ name: ps.name, host: IsNull() });
 			if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji);
-
+			if (sameNameEmoji != null && sameNameEmoji.id !== ps.id) throw new ApiError(meta.errors.sameNameEmojiExists);
 			await this.emojisRepository.update(emoji.id, {
 				updatedAt: new Date(),
 				name: ps.name,
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index ce7e0d569dcd291dcd3ebc5a02928e7e03595dbb..fc318a621a7766d974f2a171911f6db0534a6864 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -239,6 +239,14 @@ export const meta = {
 				type: 'boolean',
 				optional: true, nullable: false,
 			},
+			enableChartsForRemoteUser: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			enableChartsForFederatedInstances: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
 			policies: {
 				type: 'object',
 				optional: false, nullable: false,
@@ -299,7 +307,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				enableServiceWorker: instance.enableServiceWorker,
 				translatorAvailable: instance.deeplAuthKey != null,
 				cacheRemoteFiles: instance.cacheRemoteFiles,
-				useStarForReactionFallback: instance.useStarForReactionFallback,
 				pinnedUsers: instance.pinnedUsers,
 				hiddenTags: instance.hiddenTags,
 				blockedHosts: instance.blockedHosts,
@@ -337,6 +344,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				deeplIsPro: instance.deeplIsPro,
 				enableIpLogging: instance.enableIpLogging,
 				enableActiveEmailValidation: instance.enableActiveEmailValidation,
+				enableChartsForRemoteUser: instance.enableChartsForRemoteUser,
+				enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances,
 				policies: { ...DEFAULT_POLICIES, ...instance.policies },
 			};
 		});
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/promote.ts b/packages/backend/src/server/api/endpoints/admin/queue/promote.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4e57e6613e8ccb29365b9e67d8b4ec83b1b28ae1
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/queue/promote.ts
@@ -0,0 +1,52 @@
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { QueueService } from '@/core/QueueService.js';
+
+export const meta = {
+	tags: ['admin'],
+
+	requireCredential: true,
+	requireModerator: true,
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		type: { type: 'string', enum: ['deliver', 'inbox'] },
+	},
+	required: ['type'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+	constructor(
+		private moderationLogService: ModerationLogService,
+		private queueService: QueueService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			let delayedQueues;
+
+			switch (ps.type) {
+				case 'deliver':
+					delayedQueues = await this.queueService.deliverQueue.getDelayed();
+					for (let queueIndex = 0; queueIndex < delayedQueues.length; queueIndex++) {
+						const queue = delayedQueues[queueIndex];
+						await queue.promote();
+					}
+					break;
+				
+				case 'inbox':
+					delayedQueues = await this.queueService.inboxQueue.getDelayed();
+					for (let queueIndex = 0; queueIndex < delayedQueues.length; queueIndex++) {
+						const queue = delayedQueues[queueIndex];
+						await queue.promote();
+					}
+					break;
+			}
+
+			this.moderationLogService.insertModerationLog(me, 'promoteQueue');
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index 2f23aca243fc8f966bec86ac1d025186b2a29552..11de29bf834c8fd6e6d376ac0a542d527e9a8ce6 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -17,7 +17,6 @@ export const paramDef = {
 	type: 'object',
 	properties: {
 		disableRegistration: { type: 'boolean', nullable: true },
-		useStarForReactionFallback: { type: 'boolean', nullable: true },
 		pinnedUsers: { type: 'array', nullable: true, items: {
 			type: 'string',
 		} },
@@ -93,6 +92,8 @@ export const paramDef = {
 		objectStorageS3ForcePathStyle: { type: 'boolean' },
 		enableIpLogging: { type: 'boolean' },
 		enableActiveEmailValidation: { type: 'boolean' },
+		enableChartsForRemoteUser: { type: 'boolean' },
+		enableChartsForFederatedInstances: { type: 'boolean' },
 	},
 	required: [],
 } as const;
@@ -114,10 +115,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				set.disableRegistration = ps.disableRegistration;
 			}
 
-			if (typeof ps.useStarForReactionFallback === 'boolean') {
-				set.useStarForReactionFallback = ps.useStarForReactionFallback;
-			}
-
 			if (Array.isArray(ps.pinnedUsers)) {
 				set.pinnedUsers = ps.pinnedUsers.filter(Boolean);
 			}
@@ -382,6 +379,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				set.enableActiveEmailValidation = ps.enableActiveEmailValidation;
 			}
 
+			if (ps.enableChartsForRemoteUser !== undefined) {
+				set.enableChartsForRemoteUser = ps.enableChartsForRemoteUser;
+			}
+
+			if (ps.enableChartsForFederatedInstances !== undefined) {
+				set.enableChartsForFederatedInstances = ps.enableChartsForFederatedInstances;
+			}
+
 			await this.metaService.update(set);
 			this.moderationLogService.insertModerationLog(me, 'updateMeta');
 		});
diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts
index b57906a688eeea07e62659014739f3a6c1f7f340..b7ce3363a93aeb0414280d6d4e0fead6f2ca24e4 100644
--- a/packages/backend/src/server/api/endpoints/antennas/create.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/create.ts
@@ -79,7 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private globalEventService: GlobalEventService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			if (ps.keywords.length === 0) {
+			if ((ps.keywords.length === 0) || ps.keywords[0].every(x => x === '')) {
 				throw new Error('invalid param');
 			}
 
@@ -103,9 +103,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				}
 			}
 
+			const now = new Date();
+
 			const antenna = await this.antennasRepository.insert({
 				id: this.idService.genId(),
-				createdAt: new Date(),
+				createdAt: now,
+				lastUsedAt: now,
 				userId: me.id,
 				name: ps.name,
 				src: ps.src,
diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts
index fbb5acf6172072e5ef5701f0866e86ce663778d2..039ba1115aeb7c5284d16cd9a66621019c8b9e9a 100644
--- a/packages/backend/src/server/api/endpoints/antennas/notes.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts
@@ -101,6 +101,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				this.noteReadService.read(me.id, notes);
 			}
 
+			this.antennasRepository.update(antenna.id, {
+				lastUsedAt: new Date(),
+			});
+
 			return await this.noteEntityService.packMany(notes, me);
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/drive/files.ts b/packages/backend/src/server/api/endpoints/drive/files.ts
index f6fad50fd9f8c56aec895ec379ff0e9d45d4d69f..4609307774e4b490f1c87464d577b4adf0593ac4 100644
--- a/packages/backend/src/server/api/endpoints/drive/files.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files.ts
@@ -31,6 +31,7 @@ export const paramDef = {
 		untilId: { type: 'string', format: 'misskey:id' },
 		folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
 		type: { type: 'string', nullable: true, pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1) },
+		sort: { type: 'string', nullable: true, enum: ['+createdAt', '-createdAt', '+name', '-name', '+size', '-size'] },
 	},
 	required: [],
 } as const;
@@ -63,6 +64,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				}
 			}
 
+			switch (ps.sort) {
+				case '+createdAt': query.orderBy('file.createdAt', 'DESC'); break;
+				case '-createdAt': query.orderBy('file.createdAt', 'ASC'); break;
+				case '+name': query.orderBy('file.name', 'DESC'); break;
+				case '-name': query.orderBy('file.name', 'ASC'); break;
+				case '+size': query.orderBy('file.size', 'DESC'); break;
+				case '-size': query.orderBy('file.size', 'ASC'); break;
+			}
+
 			const files = await query.take(ps.limit).getMany();
 
 			return await this.driveFileEntityService.packMany(files, { detail: false, self: true });
diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts
index 29f24b045a240aaad03a83c0020ca2b5da7b4c44..ba432c273bfb7dbc42d8df9d98764723ac41d66a 100644
--- a/packages/backend/src/server/api/endpoints/users/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/show.ts
@@ -48,6 +48,7 @@ export const meta = {
 			message: 'No such user.',
 			code: 'NO_SUCH_USER',
 			id: '4362f8dc-731f-4ad8-a694-be5a88922a24',
+			httpStatusCode: 404,
 		},
 	},
 } as const;
diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts
index 21cf414087c364cf846cffa08a5263929c1bfe6d..b3e193cd34f934734eae1cb80c507284adc72377 100644
--- a/packages/backend/src/server/web/UrlPreviewService.ts
+++ b/packages/backend/src/server/web/UrlPreviewService.ts
@@ -1,7 +1,6 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { summaly } from 'summaly';
 import { DI } from '@/di-symbols.js';
-import type { UsersRepository } from '@/models/index.js';
 import type { Config } from '@/config.js';
 import { MetaService } from '@/core/MetaService.js';
 import { HttpRequestService } from '@/core/HttpRequestService.js';
@@ -9,6 +8,7 @@ import type Logger from '@/logger.js';
 import { query } from '@/misc/prelude/url.js';
 import { LoggerService } from '@/core/LoggerService.js';
 import { bindThis } from '@/decorators.js';
+import { ApiError } from '@/server/api/error.js';
 import type { FastifyRequest, FastifyReply } from 'fastify';
 
 @Injectable()
@@ -40,9 +40,9 @@ export class UrlPreviewService {
 
 	@bindThis
 	public async handle(
-		request: FastifyRequest<{ Querystring: { url: string; lang: string; } }>,
+		request: FastifyRequest<{ Querystring: { url: string; lang?: string; } }>,
 		reply: FastifyReply,
-	) {
+	): Promise<object | undefined> {
 		const url = request.query.url;
 		if (typeof url !== 'string') {
 			reply.code(400);
@@ -78,7 +78,7 @@ export class UrlPreviewService {
 
 			this.logger.succ(`Got preview of ${url}: ${summary.title}`);
 
-			if (summary.url && !(summary.url.startsWith('http://') || summary.url.startsWith('https://'))) {
+			if (!(summary.url.startsWith('http://') || summary.url.startsWith('https://'))) {
 				throw new Error('unsupported schema included');
 			}
 
@@ -95,9 +95,15 @@ export class UrlPreviewService {
 			return summary;
 		} catch (err) {
 			this.logger.warn(`Failed to get preview of ${url}: ${err}`);
-			reply.code(200);
+			reply.code(422);
 			reply.header('Cache-Control', 'max-age=86400, immutable');
-			return {};
+			return {
+				error: new ApiError({
+					message: 'Failed to get preview',
+					code: 'URL_PREVIEW_FAILED',
+					id: '09d01cb5-53b9-4856-82e5-38a50c290a3b',
+				}),
+			};
 		}
 	}
 }
diff --git a/packages/backend/test/e2e/clips.ts b/packages/backend/test/e2e/clips.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f35aae9dc65da1eb4e5bec9c1686d656a5179f39
--- /dev/null
+++ b/packages/backend/test/e2e/clips.ts
@@ -0,0 +1,962 @@
+process.env.NODE_ENV = 'test';
+
+import * as assert from 'assert';
+import { JTDDataType } from 'ajv/dist/jtd';
+import { DEFAULT_POLICIES } from '@/core/RoleService.js';
+import type { Packed } from '@/misc/json-schema.js';
+import { paramDef as CreateParamDef } from '@/server/api/endpoints/clips/create.js';
+import { paramDef as UpdateParamDef } from '@/server/api/endpoints/clips/update.js';
+import { paramDef as DeleteParamDef } from '@/server/api/endpoints/clips/delete.js';
+import { paramDef as ShowParamDef } from '@/server/api/endpoints/clips/show.js';
+import { paramDef as FavoriteParamDef } from '@/server/api/endpoints/clips/favorite.js';
+import { paramDef as UnfavoriteParamDef } from '@/server/api/endpoints/clips/unfavorite.js';
+import { paramDef as AddNoteParamDef } from '@/server/api/endpoints/clips/add-note.js';
+import { paramDef as RemoveNoteParamDef } from '@/server/api/endpoints/clips/remove-note.js';
+import { paramDef as NotesParamDef } from '@/server/api/endpoints/clips/notes.js';
+import { 
+	signup, 
+	post, 
+	startServer, 
+	api,
+	successfulApiCall, 
+	failedApiCall,
+	ApiRequest,
+	hiddenNote,
+} from '../utils.js';
+import type { INestApplicationContext } from '@nestjs/common';
+
+describe('クリップ', () => {
+	type User = Packed<'User'>;
+	type Note = Packed<'Note'>;
+	type Clip = Packed<'Clip'>;
+
+	let app: INestApplicationContext;
+
+	let alice: User;
+	let bob: User;
+	let aliceNote: Note;
+	let aliceHomeNote: Note;
+	let aliceFollowersNote: Note;
+	let aliceSpecifiedNote: Note;
+	let bobNote: Note;
+	let bobHomeNote: Note;
+	let bobFollowersNote: Note;
+	let bobSpecifiedNote: Note;
+
+	const compareBy = <T extends { id: string }, >(selector: (s: T) => string = (s: T): string => s.id) => (a: T, b: T): number => {
+		return selector(a).localeCompare(selector(b));
+	};
+
+	type CreateParam = JTDDataType<typeof CreateParamDef>;
+	const defaultCreate = (): Partial<CreateParam> => ({
+		name: 'test',
+	});
+	const create = async (parameters: Partial<CreateParam> = {}, request: Partial<ApiRequest> = {}): Promise<Clip> => {
+		const clip = await successfulApiCall<Clip>({
+			endpoint: '/clips/create',
+			parameters: {
+				...defaultCreate(),
+				...parameters,
+			},
+			user: alice,
+			...request,
+		});
+
+		// 入力が結果として入っていること
+		assert.deepStrictEqual(clip, {
+			...clip,
+			...defaultCreate(),
+			...parameters,
+		});
+		return clip;
+	};
+
+	const createMany = async (parameters: Partial<CreateParam>, count = 10, user = alice): Promise<Clip[]> => {
+		return await Promise.all([...Array(count)].map((_, i) => create({
+			name: `test${i}`,
+			...parameters,
+		}, { user })));
+	};
+
+	type UpdateParam = JTDDataType<typeof UpdateParamDef>;
+	const update = async (parameters: Partial<UpdateParam>, request: Partial<ApiRequest> = {}): Promise<Clip> => {
+		const clip = await successfulApiCall<Clip>({
+			endpoint: '/clips/update',
+			parameters: { 
+				name: 'updated',
+				...parameters,
+			},
+			user: alice,
+			...request,
+		});
+		
+		// 入力が結果として入っていること。clipIdはidになるので消しておく
+		delete (parameters as { clipId?: string }).clipId;
+		assert.deepStrictEqual(clip, {
+			...clip,
+			...parameters,
+		});
+		return clip;
+	};
+	
+	type DeleteParam = JTDDataType<typeof DeleteParamDef>;
+	const deleteClip = async (parameters: DeleteParam, request: Partial<ApiRequest> = {}): Promise<void> => {
+		return await successfulApiCall<void>({
+			endpoint: '/clips/delete',
+			parameters,
+			user: alice,
+			...request,
+		}, {
+			status: 204,
+		});
+	};
+
+	type ShowParam = JTDDataType<typeof ShowParamDef>;
+	const show = async (parameters: ShowParam, request: Partial<ApiRequest> = {}): Promise<Clip> => {
+		return await successfulApiCall<Clip>({
+			endpoint: '/clips/show',
+			parameters,
+			user: alice,
+			...request,
+		});
+	};
+
+	const list = async (request: Partial<ApiRequest>): Promise<Clip[]> => {
+		return successfulApiCall<Clip[]>({
+			endpoint: '/clips/list',
+			parameters: {},
+			user: alice,
+			...request,
+		});
+	};
+	
+	const usersClips = async (request: Partial<ApiRequest>): Promise<Clip[]> => {
+		return await successfulApiCall<Clip[]>({
+			endpoint: '/users/clips',
+			parameters: {},
+			user: alice,
+			...request,
+		});
+	};
+
+	beforeAll(async () => {
+		app = await startServer();
+		alice = await signup({ username: 'alice' });
+		bob = await signup({ username: 'bob' });
+
+		// FIXME: misskey-jsのNoteはoutdatedなので直接変換できない
+		aliceNote = await post(alice, { text: 'test' }) as any; 
+		aliceHomeNote = await post(alice, { text: 'home only', visibility: 'home' }) as any; 
+		aliceFollowersNote = await post(alice, { text: 'followers only', visibility: 'followers' }) as any; 
+		aliceSpecifiedNote = await post(alice, { text: 'specified only', visibility: 'specified' }) as any; 
+		bobNote = await post(bob, { text: 'test' }) as any; 
+		bobHomeNote = await post(bob, { text: 'home only', visibility: 'home' }) as any; 
+		bobFollowersNote = await post(bob, { text: 'followers only', visibility: 'followers' }) as any; 
+		bobSpecifiedNote = await post(bob, { text: 'specified only', visibility: 'specified' }) as any; 
+	}, 1000 * 60 * 2);
+
+	afterAll(async () => {
+		await app.close();
+	});
+
+	afterEach(async () => {
+		// テスト間で影響し合わないように毎回全部消す。
+		for (const user of [alice, bob]) {
+			const list = await api('/clips/list', { limit: 11 }, user);
+			for (const clip of list.body) {
+				await api('/clips/delete', { clipId: clip.id }, user);
+			}
+		}
+	});
+
+	test('の作成ができる', async () => {
+		const res = await create();
+		// ISO 8601で日付が返ってくること
+		assert.strictEqual(res.createdAt, new Date(res.createdAt).toISOString());	
+		assert.strictEqual(res.lastClippedAt, null);
+		assert.strictEqual(res.name, 'test');
+		assert.strictEqual(res.description, null);
+		assert.strictEqual(res.isPublic, false);
+		assert.strictEqual(res.favoritedCount, 0);
+		assert.strictEqual(res.isFavorited, false);
+	});
+
+	test('の作成はポリシーで定められた数以上はできない。', async () => {
+		// ポリシー + 1まで作れるという所がミソ
+		const clipLimit = DEFAULT_POLICIES.clipLimit + 1;
+		for (let i = 0; i < clipLimit; i++) {
+			await create();
+		}
+
+		await failedApiCall({
+			endpoint: '/clips/create',
+			parameters: defaultCreate(),
+			user: alice,
+		}, {
+			status: 400,
+			code: 'TOO_MANY_CLIPS',
+			id: '920f7c2d-6208-4b76-8082-e632020f5883',
+		});
+	});
+
+	const createClipAllowedPattern = [
+		{ label: 'nameが最大長', parameters: { name: 'x'.repeat(100) } },
+		{ label: 'private', parameters: { isPublic: false } },
+		{ label: 'public', parameters: { isPublic: true } },
+		{ label: 'descriptionがnull', parameters: { description: null } },
+		{ label: 'descriptionが最大長', parameters: { description: 'a'.repeat(2048) } },
+	];
+	test.each(createClipAllowedPattern)('の作成は$labelでもできる', async ({ parameters }) => await create(parameters));
+
+	const createClipDenyPattern = [
+		{ label: 'nameがnull', parameters: { name: null } },
+		{ label: 'nameが最大長+1', parameters: { name: 'x'.repeat(101) } },
+		{ label: 'isPublicがboolじゃない', parameters: { isPublic: 'true' } },
+		{ label: 'descriptionがゼロ長', parameters: { description: '' } },
+		{ label: 'descriptionが最大長+1', parameters: { description: 'a'.repeat(2049) } },
+	];
+	test.each(createClipDenyPattern)('の作成は$labelならできない', async ({ parameters }) => failedApiCall({
+		endpoint: '/clips/create',
+		parameters: { 
+			...defaultCreate(),
+			...parameters,
+		},
+		user: alice,
+	}, {
+		status: 400,
+		code: 'INVALID_PARAM',
+		id: '3d81ceae-475f-4600-b2a8-2bc116157532',
+	}));
+
+	test('の更新ができる', async () => {
+		const res = await update({ 
+			clipId: (await create()).id,
+			name: 'updated',
+			description: 'new description',
+			isPublic: true,
+		});
+
+		// ISO 8601で日付が返ってくること
+		assert.strictEqual(res.createdAt, new Date(res.createdAt).toISOString());	
+		assert.strictEqual(res.lastClippedAt, null);
+		assert.strictEqual(res.name, 'updated');
+		assert.strictEqual(res.description, 'new description');
+		assert.strictEqual(res.isPublic, true);
+		assert.strictEqual(res.favoritedCount, 0);
+		assert.strictEqual(res.isFavorited, false);
+	});
+
+	test.each(createClipAllowedPattern)('の更新は$labelでもできる', async ({ parameters }) => await update({
+		clipId: (await create()).id,
+		name: 'updated',
+		...parameters,
+	}));
+	
+	test.each([
+		{ label: 'clipIdがnull', parameters: { clipId: null } },
+		{ label: '存在しないクリップ', parameters: { clipId: 'xxxxxx' }, assertion: {
+			code: 'NO_SUCH_CLIP',
+			id: 'b4d92d70-b216-46fa-9a3f-a8c811699257',
+		} },
+		{ label: '他人のクリップ', user: (): User => bob, assertion: {
+			code: 'NO_SUCH_CLIP',
+			id: 'b4d92d70-b216-46fa-9a3f-a8c811699257',
+		} },
+		...createClipDenyPattern as any,
+	])('の更新は$labelならできない', async ({ parameters, user, assertion }) => failedApiCall({
+		endpoint: '/clips/update',
+		parameters: { 
+			clipId: (await create({}, { user: (user ?? ((): User => alice))() })).id,
+			name: 'updated',
+			...parameters,
+		},
+		user: alice,
+	}, {
+		status: 400,
+		code: 'INVALID_PARAM',
+		id: '3d81ceae-475f-4600-b2a8-2bc116157532',
+		...assertion,
+	}));
+
+	test('の削除ができる', async () => {
+		await deleteClip({ 
+			clipId: (await create()).id,
+		});
+		assert.deepStrictEqual(await list({}), []);
+	});
+
+	test.each([
+		{ label: 'clipIdがnull', parameters: { clipId: null } },
+		{ label: '存在しないクリップ', parameters: { clipId: 'xxxxxx' }, assertion: {
+			code: 'NO_SUCH_CLIP',
+			id: '70ca08ba-6865-4630-b6fb-8494759aa754',
+		} },
+		{ label: '他人のクリップ', user: (): User => bob, assertion: {
+			code: 'NO_SUCH_CLIP',
+			id: '70ca08ba-6865-4630-b6fb-8494759aa754',
+		} },
+	])('の削除は$labelならできない', async ({ parameters, user, assertion }) => failedApiCall({
+		endpoint: '/clips/delete',
+		parameters: { 
+			clipId: (await create({}, { user: (user ?? ((): User => alice))() })).id,
+			...parameters,
+		},
+		user: alice,
+	}, {
+		status: 400,
+		code: 'INVALID_PARAM',
+		id: '3d81ceae-475f-4600-b2a8-2bc116157532',
+		...assertion,
+	}));
+
+	test('のID指定取得ができる', async () => {
+		const clip = await create();
+		const res = await show({ clipId: clip.id });
+		assert.deepStrictEqual(res, clip);
+	});
+
+	test('のID指定取得は他人のPrivateなクリップは取得できない', async () => {
+		const clip = await create({ isPublic: false }, { user: bob } );
+		failedApiCall({
+			endpoint: '/clips/show',
+			parameters: { clipId: clip.id },
+			user: alice,
+		}, {
+			status: 400,
+			code: 'NO_SUCH_CLIP',
+			id: 'c3c5fe33-d62c-44d2-9ea5-d997703f5c20',
+		});
+	});
+
+	test.each([
+		{ label: 'clipId未指定', parameters: { clipId: undefined } }, 
+		{ label: '存在しないクリップ', parameters: { clipId: 'xxxxxx' }, assetion: { 
+			code: 'NO_SUCH_CLIP',
+			id: 'c3c5fe33-d62c-44d2-9ea5-d997703f5c20',
+		} },
+	])('のID指定取得は$labelならできない', async ({ parameters, assetion }) => failedApiCall({
+		endpoint: '/clips/show',
+		parameters: { 
+			...parameters,
+		},
+		user: alice,
+	}, {
+		status: 400,
+		code: 'INVALID_PARAM',
+		id: '3d81ceae-475f-4600-b2a8-2bc116157532',
+		...assetion,
+	}));
+
+	test('の一覧(clips/list)が取得できる(空)', async () => {
+		const res = await list({});
+		assert.deepStrictEqual(res, []);
+	});
+
+	test('の一覧(clips/list)が取得できる(上限いっぱい)', async () => {
+		const clipLimit = DEFAULT_POLICIES.clipLimit + 1;
+		const clips = await createMany({}, clipLimit);
+		const res = await list({
+			parameters: { limit: 1 }, // FIXME: 無視されて11全部返ってくる
+		});
+
+		// 返ってくる配列には順序保障がないのでidでソートして厳密比較
+		assert.deepStrictEqual(
+			res.sort(compareBy(s => s.id)), 
+			clips.sort(compareBy(s => s.id)),
+		);
+	});
+
+	test('の一覧が取得できる(空)', async () => {
+		const res = await usersClips({
+			parameters: { 
+				userId: alice.id,
+			},
+		});
+		assert.deepStrictEqual(res, []);
+	});
+
+	test.each([
+		{ label: '' },
+		{ label: '他人アカウントから', user: (): User => bob },
+	])('の一覧が$label取得できる', async () => {
+		const clips = await createMany({ isPublic: true });
+		const res = await usersClips({
+			parameters: { 
+				userId: alice.id,
+			},
+		});
+
+		// 返ってくる配列には順序保障がないのでidでソートして厳密比較
+		assert.deepStrictEqual(
+			res.sort(compareBy<Clip>(s => s.id)), 
+			clips.sort(compareBy(s => s.id)));
+
+		// 認証状態で見たときだけisFavoritedが入っている
+		for (const clip of res) {
+			assert.strictEqual(clip.isFavorited, false);
+		}
+	});
+
+	test.each([
+		{ label: '未認証', user: (): undefined => undefined },
+		{ label: '存在しないユーザーのもの', parameters: { userId: 'xxxxxxx' } },
+	])('の一覧は$labelでも取得できる', async ({ parameters, user }) => {
+		const clips = await createMany({ isPublic: true });
+		const res = await usersClips({
+			parameters: {
+				userId: alice.id,
+				limit: clips.length,
+				...parameters,
+			},
+			user: (user ?? ((): User => alice))(),
+		});
+
+		// 未認証で見たときはisFavoritedは入らない
+		for (const clip of res) {
+			assert.strictEqual('isFavorited' in clip, false);
+		}
+	});
+
+	test('の一覧はPrivateなクリップを含まない(自分のものであっても。)', async () => {
+		await create({ isPublic: false });
+		const aliceClip = await create({ isPublic: true });
+		const res = await usersClips({
+			parameters: { 
+				userId: alice.id,
+				limit: 2,
+			},
+		});
+		assert.deepStrictEqual(res, [aliceClip]);
+	});
+
+	test('の一覧はID指定で範囲選択ができる', async () => {
+		const clips = await createMany({ isPublic: true }, 7);
+		clips.sort(compareBy(s => s.id));
+		const res = await usersClips({
+			parameters: { 
+				userId: alice.id,
+				sinceId: clips[1].id,
+				untilId: clips[5].id,
+				limit: 4,
+			},
+		});
+
+		// Promise.allで返ってくる配列には順序保障がないのでidでソートして厳密比較
+		assert.deepStrictEqual(
+			res.sort(compareBy<Clip>(s => s.id)), 
+			[clips[2], clips[3], clips[4]], // sinceIdとuntilId自体は結果に含まれない
+			clips[1].id + ' ... ' + clips[3].id + ' with ' + clips.map(s => s.id) + ' vs. ' + res.map(s => s.id));
+	});
+
+	test.each([
+		{ label: 'userId未指定', parameters: { userId: undefined } },
+		{ label: 'limitゼロ', parameters: { limit: 0 } },
+		{ label: 'limit最大+1', parameters: { limit: 101 } },
+	])('の一覧は$labelだと取得できない', async ({ parameters }) => failedApiCall({
+		endpoint: '/users/clips',
+		parameters: { 
+			userId: alice.id,
+			...parameters,
+		},
+		user: alice,
+	}, {
+		status: 400,
+		code: 'INVALID_PARAM',
+		id: '3d81ceae-475f-4600-b2a8-2bc116157532',
+	}));
+
+	test.each([
+		{ label: '作成', endpoint: '/clips/create' },
+		{ label: 'æ›´æ–°', endpoint: '/clips/update' },
+		{ label: '削除', endpoint: '/clips/delete' },
+		{ label: '取得', endpoint: '/clips/list' },
+		{ label: 'お気に入り設定', endpoint: '/clips/favorite' },
+		{ label: 'お気に入り解除', endpoint: '/clips/unfavorite' },
+		{ label: 'お気に入り取得', endpoint: '/clips/my-favorites' },
+		{ label: 'ノート追加', endpoint: '/clips/add-note' },
+		{ label: 'ノート削除', endpoint: '/clips/remove-note' },
+	])('の$labelは未認証ではできない', async ({ endpoint }) => await failedApiCall({
+		endpoint: endpoint,
+		parameters: {},
+		user: undefined,
+	}, {
+		status: 401,
+		code: 'CREDENTIAL_REQUIRED',
+		id: '1384574d-a912-4b81-8601-c7b1c4085df1',
+	}));
+
+	describe('のお気に入り', () => {
+		let aliceClip: Clip;
+
+		type FavoriteParam = JTDDataType<typeof FavoriteParamDef>;
+		const favorite = async (parameters: FavoriteParam, request: Partial<ApiRequest> = {}): Promise<void> => {
+			return successfulApiCall<void>({
+				endpoint: '/clips/favorite',
+				parameters,
+				user: alice,
+				...request,
+			}, {
+				status: 204,
+			});
+		};
+
+		type UnfavoriteParam = JTDDataType<typeof UnfavoriteParamDef>;
+		const unfavorite = async (parameters: UnfavoriteParam, request: Partial<ApiRequest> = {}): Promise<void> => {
+			return successfulApiCall<void>({
+				endpoint: '/clips/unfavorite',
+				parameters,
+				user: alice,
+				...request,
+			}, {
+				status: 204,
+			});
+		};
+
+		const myFavorites = async (request: Partial<ApiRequest> = {}): Promise<Clip[]> => {
+			return successfulApiCall<Clip[]>({
+				endpoint: '/clips/my-favorites',
+				parameters: {},
+				user: alice,
+				...request,
+			});
+		};
+	
+		beforeEach(async () => {
+			aliceClip = await create();
+		});
+
+		test('を設定できる。', async () => {
+			await favorite({ clipId: aliceClip.id });
+			const clip = await show({ clipId: aliceClip.id });
+			assert.strictEqual(clip.favoritedCount, 1);
+			assert.strictEqual(clip.isFavorited, true);
+		});
+
+		test('はPublicな他人のクリップに設定できる。', async () => {
+			const publicClip = await create({ isPublic: true });
+			await favorite({ clipId: publicClip.id }, { user: bob });
+			const clip = await show({ clipId: publicClip.id }, { user: bob });
+			assert.strictEqual(clip.favoritedCount, 1);
+			assert.strictEqual(clip.isFavorited, true);
+
+			// isFavoritedは見る人によって切り替わる。
+			const clip2 = await show({ clipId: publicClip.id });
+			assert.strictEqual(clip2.favoritedCount, 1);
+			assert.strictEqual(clip2.isFavorited, false);
+		});
+		
+		test('は1つのクリップに対して複数人が設定できる。', async () => {
+			const publicClip = await create({ isPublic: true });
+			await favorite({ clipId: publicClip.id }, { user: bob });
+			await favorite({ clipId: publicClip.id });
+			const clip = await show({ clipId: publicClip.id }, { user: bob });
+			assert.strictEqual(clip.favoritedCount, 2);
+			assert.strictEqual(clip.isFavorited, true);
+			
+			const clip2 = await show({ clipId: publicClip.id });
+			assert.strictEqual(clip2.favoritedCount, 2);
+			assert.strictEqual(clip2.isFavorited, true);
+		});
+
+		test('は11を超えて設定できる。', async () => {
+			const clips = [
+				aliceClip,
+				...await createMany({}, 10, alice),
+				...await createMany({ isPublic: true }, 10, bob),
+			];
+			for (const clip of clips) {
+				await favorite({ clipId: clip.id });
+			}
+
+			// pagenationはない。全部一気にとれる。
+			const favorited = await myFavorites();
+			assert.strictEqual(favorited.length, clips.length);
+			for (const clip of favorited) {
+				assert.strictEqual(clip.favoritedCount, 1);
+				assert.strictEqual(clip.isFavorited, true);
+			}
+		});
+
+		test('は同じクリップに対して二回設定できない。', async () => {
+			await favorite({ clipId: aliceClip.id });
+			await failedApiCall({
+				endpoint: '/clips/favorite',
+				parameters: { 
+					clipId: aliceClip.id,
+				},
+				user: alice,
+			}, {
+				status: 400,
+				code: 'ALREADY_FAVORITED',
+				id: '92658936-c625-4273-8326-2d790129256e',
+			});
+		});
+
+		test.each([
+			{ label: 'clipIdがnull', parameters: { clipId: null } },
+			{ label: '存在しないクリップ', parameters: { clipId: 'xxxxxx' }, assertion: {
+				code: 'NO_SUCH_CLIP',
+				id: '4c2aaeae-80d8-4250-9606-26cb1fdb77a5',
+			} },
+			{ label: '他人のクリップ', user: (): User => bob, assertion: {
+				code: 'NO_SUCH_CLIP',
+				id: '4c2aaeae-80d8-4250-9606-26cb1fdb77a5',
+			} },
+		])('の設定は$labelならできない', async ({ parameters, user, assertion }) => failedApiCall({
+			endpoint: '/clips/favorite',
+			parameters: { 
+				clipId: (await create({}, { user: (user ?? ((): User => alice))() })).id,
+				...parameters,
+			},
+			user: alice,
+		}, {
+			status: 400,
+			code: 'INVALID_PARAM',
+			id: '3d81ceae-475f-4600-b2a8-2bc116157532',
+			...assertion,
+		}));
+		
+		test('を設定解除できる。', async () => {
+			await favorite({ clipId: aliceClip.id });
+			await unfavorite({ clipId: aliceClip.id });
+			const clip = await show({ clipId: aliceClip.id });
+			assert.strictEqual(clip.favoritedCount, 0);
+			assert.strictEqual(clip.isFavorited, false);
+			assert.deepStrictEqual(await myFavorites(), []);
+		});
+
+		test.each([
+			{ label: 'clipIdがnull', parameters: { clipId: null } },
+			{ label: '存在しないクリップ', parameters: { clipId: 'xxxxxx' }, assertion: {
+				code: 'NO_SUCH_CLIP',
+				id: '2603966e-b865-426c-94a7-af4a01241dc1',
+			} },
+			{ label: '他人のクリップ', user: (): User => bob, assertion: {
+				code: 'NOT_FAVORITED',
+				id: '90c3a9e8-b321-4dae-bf57-2bf79bbcc187',
+			} },
+			{ label: 'お気に入りしていないクリップ', assertion: {
+				code: 'NOT_FAVORITED',
+				id: '90c3a9e8-b321-4dae-bf57-2bf79bbcc187',
+			} },
+		])('の設定解除は$labelならできない', async ({ parameters, user, assertion }) => failedApiCall({
+			endpoint: '/clips/unfavorite',
+			parameters: { 
+				clipId: (await create({}, { user: (user ?? ((): User => alice))() })).id,
+				...parameters,
+			},
+			user: alice,
+		}, {
+			status: 400,
+			code: 'INVALID_PARAM',
+			id: '3d81ceae-475f-4600-b2a8-2bc116157532',
+			...assertion,
+		}));
+		
+		test('を取得できる。', async () => {
+			await favorite({ clipId: aliceClip.id });
+			const favorited = await myFavorites();
+			assert.deepStrictEqual(favorited, [await show({ clipId: aliceClip.id })]);
+		});
+
+		test('を取得したとき他人のお気に入りは含まない。', async () => {
+			await favorite({ clipId: aliceClip.id });
+			const favorited = await myFavorites({ user: bob });
+			assert.deepStrictEqual(favorited, []);
+		});
+	});
+
+	describe('に紐づくノート', () => {
+		let aliceClip: Clip;
+
+		const sampleNotes = (): Note[] => [
+			aliceNote, aliceHomeNote, aliceFollowersNote, aliceSpecifiedNote,
+			bobNote, bobHomeNote, bobFollowersNote, bobSpecifiedNote,
+		];
+
+		type AddNoteParam = JTDDataType<typeof AddNoteParamDef>;
+		const addNote = async (parameters: AddNoteParam, request: Partial<ApiRequest> = {}): Promise<void> => {
+			return successfulApiCall<void>({
+				endpoint: '/clips/add-note',
+				parameters,
+				user: alice,
+				...request,
+			}, {
+				status: 204,
+			});
+		};
+
+		type RemoveNoteParam = JTDDataType<typeof RemoveNoteParamDef>;
+		const removeNote = async (parameters: RemoveNoteParam, request: Partial<ApiRequest> = {}): Promise<void> => {
+			return successfulApiCall<void>({
+				endpoint: '/clips/remove-note',
+				parameters,
+				user: alice,
+				...request,
+			}, {
+				status: 204,
+			});
+		};
+
+		type NotesParam = JTDDataType<typeof NotesParamDef>;
+		const notes = async (parameters: Partial<NotesParam>, request: Partial<ApiRequest> = {}): Promise<Note[]> => {
+			return successfulApiCall<Note[]>({
+				endpoint: '/clips/notes',
+				parameters,
+				user: alice,
+				...request,
+			});
+		};
+
+		beforeEach(async () => {
+			aliceClip = await create();
+		});
+
+		test('を追加できる。', async () => {
+			await addNote({ clipId: aliceClip.id, noteId: aliceNote.id });
+			const res = await show({ clipId: aliceClip.id });
+			assert.strictEqual(res.lastClippedAt, new Date(res.lastClippedAt ?? '').toISOString());
+			assert.deepStrictEqual(await notes({ clipId: aliceClip.id }), [aliceNote]);
+			
+			// 他人の非公開ノートも突っ込める
+			await addNote({ clipId: aliceClip.id, noteId: bobHomeNote.id });
+			await addNote({ clipId: aliceClip.id, noteId: bobFollowersNote.id });
+			await addNote({ clipId: aliceClip.id, noteId: bobSpecifiedNote.id });
+		});
+
+		test('として同じノートを二回紐づけることはできない', async () => {
+			await addNote({ clipId: aliceClip.id, noteId: aliceNote.id });
+			await failedApiCall({
+				endpoint: '/clips/add-note',
+				parameters: { 
+					clipId: aliceClip.id,
+					noteId: aliceNote.id,
+				},
+				user: alice,
+			}, {
+				status: 400,
+				code: 'ALREADY_CLIPPED',
+				id: '734806c4-542c-463a-9311-15c512803965',
+			});
+		});
+
+		// TODO: 17000msくらいかかる...
+		test('をポリシーで定められた上限いっぱい(200)を超えて追加はできない。', async () => {
+			const noteLimit = DEFAULT_POLICIES.noteEachClipsLimit + 1;
+			const noteList = await Promise.all([...Array(noteLimit)].map((_, i) => post(alice, {
+				text: `test ${i}`,
+			}) as unknown)) as Note[];
+			await Promise.all(noteList.map(s => addNote({ clipId: aliceClip.id, noteId: s.id })));
+			
+			await failedApiCall({
+				endpoint: '/clips/add-note',
+				parameters: { 
+					clipId: aliceClip.id,
+					noteId: aliceNote.id,
+				},
+				user: alice,
+			}, {
+				status: 400,
+				code: 'TOO_MANY_CLIP_NOTES',
+				id: 'f0dba960-ff73-4615-8df4-d6ac5d9dc118',
+			});
+		});
+
+		test('は他人のクリップへ追加できない。', async () => await failedApiCall({
+			endpoint: '/clips/add-note',
+			parameters: { 
+				clipId: aliceClip.id,
+				noteId: aliceNote.id,
+			},
+			user: bob,
+		}, {
+			status: 400,
+			code: 'NO_SUCH_CLIP',
+			id: 'd6e76cc0-a1b5-4c7c-a287-73fa9c716dcf',
+		}));
+
+		test.each([
+			{ label: 'clipId未指定', parameters: { clipId: undefined } }, 
+			{ label: 'noteId未指定', parameters: { noteId: undefined } }, 
+			{ label: '存在しないクリップ', parameters: { clipId: 'xxxxxx' }, assetion: { 
+				code: 'NO_SUCH_CLIP',
+				id: 'd6e76cc0-a1b5-4c7c-a287-73fa9c716dcf',
+			} },
+			{ label: '存在しないノート', parameters: { noteId: 'xxxxxx' }, assetion: {
+				code: 'NO_SUCH_NOTE',
+				id: 'fc8c0b49-c7a3-4664-a0a6-b418d386bb8b',
+			} },
+			{ label: '他人のクリップ', user: (): object => bob, assetion: {
+				code: 'NO_SUCH_CLIP',
+				id: 'd6e76cc0-a1b5-4c7c-a287-73fa9c716dcf',
+			} },
+		])('の追加は$labelだとできない', async ({ parameters, user, assetion }) => failedApiCall({
+			endpoint: '/clips/add-note',
+			parameters: { 
+				clipId: aliceClip.id,
+				noteId: aliceNote.id,
+				...parameters,
+			},
+			user: (user ?? ((): User => alice))(),
+		}, {
+			status: 400,
+			code: 'INVALID_PARAM',
+			id: '3d81ceae-475f-4600-b2a8-2bc116157532',
+			...assetion,
+		}));
+
+		test('を削除できる。', async () => {
+			await addNote({ clipId: aliceClip.id, noteId: aliceNote.id });
+			await removeNote({ clipId: aliceClip.id, noteId: aliceNote.id });
+			assert.deepStrictEqual(await notes({ clipId: aliceClip.id }), []);
+		});
+		
+		test.each([
+			{ label: 'clipId未指定', parameters: { clipId: undefined } }, 
+			{ label: 'noteId未指定', parameters: { noteId: undefined } }, 
+			{ label: '存在しないクリップ', parameters: { clipId: 'xxxxxx' }, assetion: { 
+				code: 'NO_SUCH_CLIP',
+				id: 'b80525c6-97f7-49d7-a42d-ebccd49cfd52', // add-noteと異なる
+			} },
+			{ label: '存在しないノート', parameters: { noteId: 'xxxxxx' }, assetion: {
+				code: 'NO_SUCH_NOTE',
+				id: 'aff017de-190e-434b-893e-33a9ff5049d8', // add-noteと異なる
+			} },
+			{ label: '他人のクリップ', user: (): object => bob, assetion: {
+				code: 'NO_SUCH_CLIP',
+				id: 'b80525c6-97f7-49d7-a42d-ebccd49cfd52', // add-noteと異なる
+			} },
+		])('の削除は$labelだとできない', async ({ parameters, user, assetion }) => failedApiCall({
+			endpoint: '/clips/remove-note',
+			parameters: { 
+				clipId: aliceClip.id,
+				noteId: aliceNote.id,
+				...parameters,
+			},
+			user: (user ?? ((): User => alice))(),
+		}, {
+			status: 400,
+			code: 'INVALID_PARAM',
+			id: '3d81ceae-475f-4600-b2a8-2bc116157532',
+			...assetion,
+		}));
+
+		test('を取得できる。', async () => {
+			const noteList = sampleNotes();
+			for (const note of noteList) {
+				await addNote({ clipId: aliceClip.id, noteId: note.id });
+			}
+
+			const res = await notes({ clipId: aliceClip.id });
+			
+			// 自分のノートは非公開でも入れられるし、見える
+			// 他人の非公開ノートは入れられるけど、除外される
+			const expects = [
+				aliceNote, aliceHomeNote, aliceFollowersNote, aliceSpecifiedNote,
+				bobNote, bobHomeNote, 
+			];
+			assert.deepStrictEqual(
+				res.sort(compareBy(s => s.id)),
+				expects.sort(compareBy(s => s.id)));
+		});
+
+		test('を始端IDとlimitで取得できる。', async () => {
+			const noteList = sampleNotes();
+			noteList.sort(compareBy(s => s.id));
+			for (const note of noteList) {
+				await addNote({ clipId: aliceClip.id, noteId: note.id });
+			}
+
+			const res = await notes({ 
+				clipId: aliceClip.id,
+				sinceId: noteList[2].id,
+				limit: 3,
+			});
+
+			// Promise.allで返ってくる配列はID順で並んでないのでソートして厳密比較
+			const expects = [noteList[3], noteList[4], noteList[5]];
+			assert.deepStrictEqual(
+				res.sort(compareBy(s => s.id)),
+				expects.sort(compareBy(s => s.id)));
+		});
+
+		test('をID範囲指定で取得できる。', async () => {
+			const noteList = sampleNotes();
+			noteList.sort(compareBy(s => s.id));
+			for (const note of noteList) {
+				await addNote({ clipId: aliceClip.id, noteId: note.id });
+			}
+
+			const res = await notes({
+				clipId: aliceClip.id,
+				sinceId: noteList[1].id,
+				untilId: noteList[4].id,
+			});
+			
+			// Promise.allで返ってくる配列はID順で並んでないのでソートして厳密比較
+			const expects = [noteList[2], noteList[3]];
+			assert.deepStrictEqual(
+				res.sort(compareBy(s => s.id)),
+				expects.sort(compareBy(s => s.id)));
+		});
+
+		test.todo('Remoteのノートもクリップできる。どうテストしよう?');
+
+		test('は他人のPublicなクリップからも取得できる。', async () => {
+			const bobClip = await create({ isPublic: true }, { user: bob } );
+			await addNote({ clipId: bobClip.id, noteId: aliceNote.id }, { user: bob });
+			const res = await notes({ clipId: bobClip.id });
+			assert.deepStrictEqual(res, [aliceNote]);
+		});
+
+		test('はPublicなクリップなら認証なしでも取得できる。(非公開ノートはhideされて返ってくる)', async () => {
+			const publicClip = await create({ isPublic: true });
+			await addNote({ clipId: publicClip.id, noteId: aliceNote.id });
+			await addNote({ clipId: publicClip.id, noteId: aliceHomeNote.id });
+			await addNote({ clipId: publicClip.id, noteId: aliceFollowersNote.id });
+			await addNote({ clipId: publicClip.id, noteId: aliceSpecifiedNote.id });
+
+			const res = await notes({ clipId: publicClip.id }, { user: undefined });
+			const expects = [
+				aliceNote, aliceHomeNote, 
+				// 認証なしだと非公開ノートは結果には含むけどhideされる。
+				hiddenNote(aliceFollowersNote), hiddenNote(aliceSpecifiedNote),
+			];
+			assert.deepStrictEqual(
+				res.sort(compareBy(s => s.id)),
+				expects.sort(compareBy(s => s.id)));
+		});
+		
+		test.todo('ブロック、ミュートされたユーザーからの設定&取得etc.');
+
+		test.each([
+			{ label: 'clipId未指定', parameters: { clipId: undefined } },
+			{ label: 'limitゼロ', parameters: { limit: 0 } },
+			{ label: 'limit最大+1', parameters: { limit: 101 } },
+			{ label: '存在しないクリップ', parameters: { clipId: 'xxxxxx' }, assertion: {
+				code: 'NO_SUCH_CLIP',
+				id: '1d7645e6-2b6d-4635-b0fe-fe22b0e72e00',
+			} },
+			{ label: '他人のPrivateなクリップから', user: (): object => bob, assertion: {
+				code: 'NO_SUCH_CLIP',
+				id: '1d7645e6-2b6d-4635-b0fe-fe22b0e72e00',
+			} },
+			{ label: '未認証でPrivateなクリップから', user: (): undefined => undefined, assertion: {
+				code: 'NO_SUCH_CLIP',
+				id: '1d7645e6-2b6d-4635-b0fe-fe22b0e72e00',
+			} },
+		])('は$labelだと取得できない', async ({ parameters, user, assertion }) => failedApiCall({
+			endpoint: '/clips/notes',
+			parameters: { 
+				clipId: aliceClip.id,
+				...parameters,
+			},
+			user: (user ?? ((): User => alice))(),
+		}, {
+			status: 400,
+			code: 'INVALID_PARAM',
+			id: '3d81ceae-475f-4600-b2a8-2bc116157532',
+			...assertion,
+		}));
+	});
+});
diff --git a/packages/backend/test/e2e/endpoints.ts b/packages/backend/test/e2e/endpoints.ts
index cbe7b894f4b279d4beeecb6b866fb1d32c239fe7..afb72c84d40e4bdcc89851c8cb9ec3c2f9ade48e 100644
--- a/packages/backend/test/e2e/endpoints.ts
+++ b/packages/backend/test/e2e/endpoints.ts
@@ -162,14 +162,14 @@ describe('Endpoints', () => {
 			const res = await api('/users/show', {
 				userId: '000000000000000000000000',
 			});
-			assert.strictEqual(res.status, 400);
+			assert.strictEqual(res.status, 404);
 		});
 
 		test('間違ったIDで怒られる', async () => {
 			const res = await api('/users/show', {
 				userId: 'kyoppie',
 			});
-			assert.strictEqual(res.status, 400);
+			assert.strictEqual(res.status, 404);
 		});
 	});
 
@@ -841,4 +841,12 @@ describe('Endpoints', () => {
 			assert.strictEqual(res.body[0].id, carolPost.id);
 		});
 	});
+
+	describe('URL preview', () => {
+		test('Error from summaly becomes HTTP 422', async () => {
+			const res = await simpleGet('/url?url=https://e:xample.com');
+			assert.strictEqual(res.status, 422);
+			assert.strictEqual(res.body.error.code, 'URL_PREVIEW_FAILED');
+		});
+	});
 });
diff --git a/packages/backend/test/unit/DriveService.ts b/packages/backend/test/unit/DriveService.ts
index 0549800a684c106b1adbce3e388d258aa4d1cba7..4065665579556365f475e6cbb86ceae062c60f95 100644
--- a/packages/backend/test/unit/DriveService.ts
+++ b/packages/backend/test/unit/DriveService.ts
@@ -1,55 +1,56 @@
 process.env.NODE_ENV = 'test';
 
-import { jest } from '@jest/globals';
 import { Test } from '@nestjs/testing';
+import { DeleteObjectCommandOutput, DeleteObjectCommand, NoSuchKey, InvalidObjectState, S3Client } from '@aws-sdk/client-s3';
+import { mockClient } from 'aws-sdk-client-mock';
 import { GlobalModule } from '@/GlobalModule.js';
 import { DriveService } from '@/core/DriveService.js';
 import { CoreModule } from '@/core/CoreModule.js';
-import { S3Service } from '@/core/S3Service';
-import type { Meta } from '@/models';
-import type { DeleteObjectOutput } from 'aws-sdk/clients/s3';
-import type { AWSError } from 'aws-sdk/lib/error';
-import type { PromiseResult, Request } from 'aws-sdk/lib/request';
 import type { TestingModule } from '@nestjs/testing';
 
 describe('DriveService', () => {
 	let app: TestingModule;
 	let driveService: DriveService;
+	const s3Mock = mockClient(S3Client);
 
-	beforeEach(async () => {
+	beforeAll(async () => {
 		app = await Test.createTestingModule({
 			imports: [GlobalModule, CoreModule],
-			providers: [DriveService, S3Service],
+			providers: [DriveService],
 		}).compile();
 		app.enableShutdownHooks();
 		driveService = app.get<DriveService>(DriveService);
+	});
 
-		const s3Service = app.get<S3Service>(S3Service);
-		const s3 = s3Service.getS3({} as Meta);
-
-		// new S3() surprisingly does not return an instance of class S3.
-		// Let's use getPrototypeOf here to get a real prototype, since spying on S3.prototype doesn't work.
-		// TODO: Use `aws-sdk-client-mock` package when upgrading to AWS SDK v3.
-		jest.spyOn(Object.getPrototypeOf(s3), 'deleteObject').mockImplementation(() => {
-			// Roughly mock AWS request object
-			return {
-				async promise(): Promise<PromiseResult<DeleteObjectOutput, AWSError>> {
-					const err = new Error('mock') as AWSError;
-					err.code = 'NoSuchKey';
-					throw err;
-				},
-			} as Request<DeleteObjectOutput, AWSError>;
-		});
+	beforeEach(async () => {
+		s3Mock.reset();
+	});
+
+	afterAll(async () => {
+		await app.close();
 	});
 
 	describe('Object storage', () => {
+		test('delete a file', async () => {
+			s3Mock.on(DeleteObjectCommand)
+				.resolves({} as DeleteObjectCommandOutput);
+			
+			await driveService.deleteObjectStorageFile('peace of the world');
+		});
+
+		test('delete a file then unexpected error', async () => {
+			s3Mock.on(DeleteObjectCommand)
+				.rejects(new InvalidObjectState({ $metadata: {}, message: '' }));
+
+			await expect(driveService.deleteObjectStorageFile('unexpected')).rejects.toThrowError(Error);
+		});
+
 		test('delete a file with no valid key', async () => {
-			try {
-				await driveService.deleteObjectStorageFile('lol no way');
-			} catch (err: any) {
-				console.log(err.cause);
-				throw err;
-			}
+			// Some S3 implementations returns 404 Not Found on deleting with a non-existent key
+			s3Mock.on(DeleteObjectCommand)
+				.rejects(new NoSuchKey({ $metadata: {}, message: 'allowed error.' }));
+
+			await driveService.deleteObjectStorageFile('lol no way');
 		});
 	});
 });
diff --git a/packages/backend/test/unit/ReactionService.ts b/packages/backend/test/unit/ReactionService.ts
index 6a20a1e08e15361a9f50c9c3ad21e3e70dce7107..38db081ac089ca359bc82199d2daba590895eaef 100644
--- a/packages/backend/test/unit/ReactionService.ts
+++ b/packages/backend/test/unit/ReactionService.ts
@@ -74,19 +74,19 @@ describe('ReactionService', () => {
 		});
 
 		test('fallback - undefined', async () => {
-			assert.strictEqual(await reactionService.toDbReaction(undefined), '👍');
+			assert.strictEqual(await reactionService.toDbReaction(undefined), '❤');
 		});
 
 		test('fallback - null', async () => {
-			assert.strictEqual(await reactionService.toDbReaction(null), '👍');
+			assert.strictEqual(await reactionService.toDbReaction(null), '❤');
 		});
 
 		test('fallback - empty', async () => {
-			assert.strictEqual(await reactionService.toDbReaction(''), '👍');
+			assert.strictEqual(await reactionService.toDbReaction(''), '❤');
 		});
 
 		test('fallback - unknown', async () => {
-			assert.strictEqual(await reactionService.toDbReaction('unknown'), '👍');
+			assert.strictEqual(await reactionService.toDbReaction('unknown'), '❤');
 		});
 	});
 });
diff --git a/packages/backend/test/unit/S3Service.ts b/packages/backend/test/unit/S3Service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1dfa22afd2901a3f729ef28073cd379de7e5d52d
--- /dev/null
+++ b/packages/backend/test/unit/S3Service.ts
@@ -0,0 +1,77 @@
+process.env.NODE_ENV = 'test';
+
+import { Test } from '@nestjs/testing';
+import { UploadPartCommand, CompleteMultipartUploadCommand, CreateMultipartUploadCommand, S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
+import { mockClient } from 'aws-sdk-client-mock';
+import { GlobalModule } from '@/GlobalModule.js';
+import { CoreModule } from '@/core/CoreModule.js';
+import { S3Service } from '@/core/S3Service';
+import { Meta } from '@/models';
+import type { TestingModule } from '@nestjs/testing';
+
+describe('S3Service', () => {
+	let app: TestingModule;
+	let s3Service: S3Service;
+	const s3Mock = mockClient(S3Client);
+
+	beforeAll(async () => {
+		app = await Test.createTestingModule({
+			imports: [GlobalModule, CoreModule],
+			providers: [S3Service],
+		}).compile();
+		app.enableShutdownHooks();
+		s3Service = app.get<S3Service>(S3Service);
+	});
+
+	beforeEach(async () => {
+		s3Mock.reset();
+	});
+
+	afterAll(async () => {
+		await app.close();
+	});
+
+	describe('upload', () => {
+		test('upload a file', async () => {
+			s3Mock.on(PutObjectCommand).resolves({});
+
+			await s3Service.upload({ objectStorageRegion: 'us-east-1' } as Meta, {
+				Bucket: 'fake',
+				Key: 'fake',
+				Body: 'x',
+			});
+		});
+
+		test('upload a large file', async () => {
+			s3Mock.on(CreateMultipartUploadCommand).resolves({ UploadId: '1' });
+			s3Mock.on(UploadPartCommand).resolves({ ETag: '1' });
+			s3Mock.on(CompleteMultipartUploadCommand).resolves({ Bucket: 'fake', Key: 'fake' });
+
+			await s3Service.upload({} as Meta, {
+				Bucket: 'fake',
+				Key: 'fake',
+				Body: 'x'.repeat(8 * 1024 * 1024 + 1), // デフォルトpartSizeにしている 8 * 1024 * 1024 を越えるサイズ
+			});
+		});
+
+		test('upload a file error', async () => {
+			s3Mock.on(PutObjectCommand).rejects({ name: 'Fake Error' });
+
+			await expect(s3Service.upload({ objectStorageRegion: 'us-east-1' } as Meta, {
+				Bucket: 'fake',
+				Key: 'fake',
+				Body: 'x',
+			})).rejects.toThrowError(Error);
+		});
+
+		test('upload a large file error', async () => {
+			s3Mock.on(UploadPartCommand).rejects();
+
+			await expect(s3Service.upload({} as Meta, {
+				Bucket: 'fake',
+				Key: 'fake',
+				Body: 'x'.repeat(8 * 1024 * 1024 + 1), // デフォルトpartSizeにしている 8 * 1024 * 1024 を越えるサイズ
+			})).rejects.toThrowError(Error);
+		});
+	});
+});
diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts
index 4d52c2f06284e4c2031bc78c3dde20c5d96990c3..4f501a8726cd84dde4b1835543af10f1e3beb328 100644
--- a/packages/backend/test/utils.ts
+++ b/packages/backend/test/utils.ts
@@ -1,5 +1,7 @@
+import * as assert from 'node:assert';
 import { readFile } from 'node:fs/promises';
 import { isAbsolute, basename } from 'node:path';
+import { inspect } from 'node:util';
 import WebSocket from 'ws';
 import fetch, { Blob, File, RequestInit } from 'node-fetch';
 import { DataSource } from 'typeorm';
@@ -22,6 +24,36 @@ export const api = async (endpoint: string, params: any, me?: any) => {
 	return await request(`api/${normalized}`, params, me);
 };
 
+export type ApiRequest = {
+	endpoint: string,
+	parameters: object,
+	user: object | undefined,
+};
+
+export const successfulApiCall = async <T, >(request: ApiRequest, assertion: {
+	status: number,
+} = { status: 200 }): Promise<T> => {
+	const { endpoint, parameters, user } = request;
+	const { status } = assertion;
+	const res = await api(endpoint, parameters, user);
+	assert.strictEqual(res.status, status, inspect(res.body));
+	return res.body;
+};
+
+export const failedApiCall = async <T, >(request: ApiRequest, assertion: {
+	status: number,
+	code: string,
+	id: string
+}): Promise<T> => {
+	const { endpoint, parameters, user } = request;
+	const { status, code, id } = assertion;
+	const res = await api(endpoint, parameters, user);
+	assert.strictEqual(res.status, status, inspect(res.body));
+	assert.strictEqual(res.body.error.code, code, inspect(res.body));
+	assert.strictEqual(res.body.error.id, id, inspect(res.body));
+	return res.body;
+};
+
 const request = async (path: string, params: any, me?: any): Promise<{ body: any, status: number }> => {
 	const auth = me ? {
 		i: me.token,
@@ -69,6 +101,21 @@ export const post = async (user: any, params?: misskey.Endpoints['notes/create']
 	return res.body ? res.body.createdNote : null;
 };
 
+// 非公開ノートをAPI越しに見たときのノート NoteEntityService.ts
+export const hiddenNote = (note: any): any => {
+	const temp = {
+		...note,
+		fileIds: [],
+		files: [],
+		text: null,
+		cw: null,
+		isHidden: true,
+	};
+	delete temp.visibleUserIds;
+	delete temp.poll;
+	return temp;
+};
+
 export const react = async (user: any, note: any, reaction: string): Promise<any> => {
 	await api('notes/reactions/create', {
 		noteId: note.id,
diff --git a/packages/frontend/src/cache.ts b/packages/frontend/src/cache.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c95da64bbaebd23be9cd8f700ae7adca8be2e69d
--- /dev/null
+++ b/packages/frontend/src/cache.ts
@@ -0,0 +1,6 @@
+import * as misskey from 'misskey-js';
+import { Cache } from '@/scripts/cache';
+
+export const clipsCache = new Cache<misskey.entities.Clip[]>(Infinity);
+export const rolesCache = new Cache(Infinity);
+export const userListsCache = new Cache<misskey.entities.UserList[]>(Infinity);
diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue
index 8c17c0530adee25cafb4608ae9452721a2cb5fb9..ab408b500867d06fa9e175a9f138045ef84a9a85 100644
--- a/packages/frontend/src/components/MkDrive.file.vue
+++ b/packages/frontend/src/components/MkDrive.file.vue
@@ -32,14 +32,14 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, defineAsyncComponent, ref } from 'vue';
+import { computed, ref } from 'vue';
 import * as Misskey from 'misskey-js';
-import copyToClipboard from '@/scripts/copy-to-clipboard';
 import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
 import bytes from '@/filters/bytes';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { $i } from '@/account';
+import { getDriveFileMenu } from '@/scripts/get-drive-file-menu';
 
 const props = withDefaults(defineProps<{
 	file: Misskey.entities.DriveFile;
@@ -60,48 +60,16 @@ const isDragging = ref(false);
 
 const title = computed(() => `${props.file.name}\n${props.file.type} ${bytes(props.file.size)}`);
 
-function getMenu() {
-	return [{
-		text: i18n.ts.rename,
-		icon: 'ti ti-forms',
-		action: rename,
-	}, {
-		text: props.file.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive,
-		icon: props.file.isSensitive ? 'ti ti-eye' : 'ti ti-eye-off',
-		action: toggleSensitive,
-	}, {
-		text: i18n.ts.describeFile,
-		icon: 'ti ti-text-caption',
-		action: describe,
-	}, null, {
-		text: i18n.ts.copyUrl,
-		icon: 'ti ti-link',
-		action: copyUrl,
-	}, {
-		type: 'a',
-		href: props.file.url,
-		target: '_blank',
-		text: i18n.ts.download,
-		icon: 'ti ti-download',
-		download: props.file.name,
-	}, null, {
-		text: i18n.ts.delete,
-		icon: 'ti ti-trash',
-		danger: true,
-		action: deleteFile,
-	}];
-}
-
 function onClick(ev: MouseEvent) {
 	if (props.selectMode) {
 		emit('chosen', props.file);
 	} else {
-		os.popupMenu(getMenu(), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined);
+		os.popupMenu(getDriveFileMenu(props.file), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined);
 	}
 }
 
 function onContextmenu(ev: MouseEvent) {
-	os.contextMenu(getMenu(), ev);
+	os.contextMenu(getDriveFileMenu(props.file), ev);
 }
 
 function onDragstart(ev: DragEvent) {
@@ -118,62 +86,6 @@ function onDragend() {
 	isDragging.value = false;
 	emit('dragend');
 }
-
-function rename() {
-	os.inputText({
-		title: i18n.ts.renameFile,
-		placeholder: i18n.ts.inputNewFileName,
-		default: props.file.name,
-	}).then(({ canceled, result: name }) => {
-		if (canceled) return;
-		os.api('drive/files/update', {
-			fileId: props.file.id,
-			name: name,
-		});
-	});
-}
-
-function describe() {
-	os.popup(defineAsyncComponent(() => import('@/components/MkFileCaptionEditWindow.vue')), {
-		default: props.file.comment != null ? props.file.comment : '',
-		file: props.file,
-	}, {
-		done: caption => {
-			os.api('drive/files/update', {
-				fileId: props.file.id,
-				comment: caption.length === 0 ? null : caption,
-			});
-		},
-	}, 'closed');
-}
-
-function toggleSensitive() {
-	os.api('drive/files/update', {
-		fileId: props.file.id,
-		isSensitive: !props.file.isSensitive,
-	});
-}
-
-function copyUrl() {
-	copyToClipboard(props.file.url);
-	os.success();
-}
-/*
-function addApp() {
-	alert('not implemented yet');
-}
-*/
-async function deleteFile() {
-	const { canceled } = await os.confirm({
-		type: 'warning',
-		text: i18n.t('driveFileDeleteConfirm', { name: props.file.name }),
-	});
-
-	if (canceled) return;
-	os.api('drive/files/delete', {
-		fileId: props.file.id,
-	});
-}
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index af81051a54ec68fb49a1c6e9ba6c055e01a3c141..72c6e55df10a934eb6370b23aa0700217d5b74f9 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -109,6 +109,9 @@
 				<button v-if="appearNote.myReaction != null" ref="reactButton" :class="$style.footerButton" class="_button" @click="undoReact(appearNote)">
 					<i class="ti ti-minus"></i>
 				</button>
+				<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown="clip()">
+					<i class="ti ti-paperclip"></i>
+				</button>
 				<button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown="menu()">
 					<i class="ti ti-dots"></i>
 				</button>
@@ -151,7 +154,7 @@ import { reactionPicker } from '@/scripts/reaction-picker';
 import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm';
 import { $i } from '@/account';
 import { i18n } from '@/i18n';
-import { getNoteMenu } from '@/scripts/get-note-menu';
+import { getNoteClipMenu, getNoteMenu } from '@/scripts/get-note-menu';
 import { useNoteCapture } from '@/scripts/use-note-capture';
 import { deepClone } from '@/scripts/clone';
 import { useTooltip } from '@/scripts/use-tooltip';
@@ -192,6 +195,7 @@ const menuButton = shallowRef<HTMLElement>();
 const renoteButton = shallowRef<HTMLElement>();
 const renoteTime = shallowRef<HTMLElement>();
 const reactButton = shallowRef<HTMLElement>();
+const clipButton = shallowRef<HTMLElement>();
 let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note);
 const isMyRenote = $i && ($i.id === note.userId);
 const showContent = ref(false);
@@ -392,6 +396,10 @@ function menu(viaKeyboard = false): void {
 	}).then(focus);
 }
 
+async function clip() {
+	os.popupMenu(await getNoteClipMenu({ note: note, isDeleted, currentClipPage }), clipButton.value).then(focus);
+}
+
 function showRenoteMenu(viaKeyboard = false): void {
 	if (!isMyRenote) return;
 	os.popupMenu([{
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index ea72e1b517cb418c11f1f5e555abded765167614..715fd3a9a8e3773190384057da67b03c5e57248b 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -114,6 +114,9 @@
 				<button v-if="appearNote.myReaction != null" ref="reactButton" class="button _button reacted" @click="undoReact(appearNote)">
 					<i class="ti ti-minus"></i>
 				</button>
+				<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" class="button _button" @mousedown="clip()">
+					<i class="ti ti-paperclip"></i>
+				</button>
 				<button ref="menuButton" class="button _button" @mousedown="menu()">
 					<i class="ti ti-dots"></i>
 				</button>
@@ -156,7 +159,7 @@ import { reactionPicker } from '@/scripts/reaction-picker';
 import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm';
 import { $i } from '@/account';
 import { i18n } from '@/i18n';
-import { getNoteMenu } from '@/scripts/get-note-menu';
+import { getNoteClipMenu, getNoteMenu } from '@/scripts/get-note-menu';
 import { useNoteCapture } from '@/scripts/use-note-capture';
 import { deepClone } from '@/scripts/clone';
 import { useTooltip } from '@/scripts/use-tooltip';
@@ -196,6 +199,7 @@ const menuButton = shallowRef<HTMLElement>();
 const renoteButton = shallowRef<HTMLElement>();
 const renoteTime = shallowRef<HTMLElement>();
 const reactButton = shallowRef<HTMLElement>();
+const clipButton = shallowRef<HTMLElement>();
 let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note);
 const isMyRenote = $i && ($i.id === note.userId);
 const showContent = ref(false);
@@ -384,6 +388,10 @@ function menu(viaKeyboard = false): void {
 	}).then(focus);
 }
 
+async function clip() {
+	os.popupMenu(await getNoteClipMenu({ note: note, isDeleted }), clipButton.value).then(focus);
+}
+
 function showRenoteMenu(viaKeyboard = false): void {
 	if (!isMyRenote) return;
 	os.popupMenu([{
diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue
index 7fb830d537c8ec3731277280d326820a52cdaffc..814ab53d27197b6064a44238853406c3653e2bef 100644
--- a/packages/frontend/src/components/global/MkAvatar.vue
+++ b/packages/frontend/src/components/global/MkAvatar.vue
@@ -1,25 +1,26 @@
 <template>
-<span v-if="!link" v-user-preview="preview ? user.id : undefined" class="_noSelect" :class="[$style.root, { [$style.cat]: user.isCat, [$style.square]: squareAvatars }]" :style="{ color }" :title="acct(user)" @click="onClick">
+<component :is="link ? MkA : 'span'" v-user-preview="preview ? user.id : undefined" v-bind="bound" class="_noSelect" :class="[$style.root, { [$style.cat]: user.isCat, [$style.square]: squareAvatars }]" :style="{ color }" :title="acct(user)" @click="onClick">
 	<img :class="$style.inner" :src="url" decoding="async"/>
 	<MkUserOnlineIndicator v-if="indicator" :class="$style.indicator" :user="user"/>
-	<template v-if="user.isCat">
-		<div :class="$style.earLeft"/>
-		<div :class="$style.earRight"/>
-	</template>
-</span>
-<MkA v-else v-user-preview="preview ? user.id : undefined" class="_noSelect" :class="[$style.root, { [$style.cat]: user.isCat, [$style.square]: squareAvatars }]" :style="{ color }" :title="acct(user)" :to="userPage(user)" :target="target">
-	<img :class="$style.inner" :src="url" decoding="async"/>
-	<MkUserOnlineIndicator v-if="indicator" :class="$style.indicator" :user="user"/>
-	<template v-if="user.isCat">
-		<div :class="$style.earLeft"/>
-		<div :class="$style.earRight"/>
-	</template>
-</MkA>
+	<div v-if="user.isCat" :class="[$style.ears, { [$style.mask]: useBlurEffect }]">
+		<div :class="$style.earLeft">
+			<div v-if="useBlurEffect" :class="$style.layer">
+				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
+			</div>
+		</div>
+		<div :class="$style.earRight">
+			<div v-if="useBlurEffect" :class="$style.layer">
+				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
+			</div>
+		</div>
+	</div>
+</component>
 </template>
 
 <script lang="ts" setup>
 import { watch } from 'vue';
 import * as misskey from 'misskey-js';
+import MkA from './MkA.vue';
 import { getStaticImageUrl } from '@/scripts/media-proxy';
 import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash';
 import { acct, userPage } from '@/filters/user';
@@ -27,6 +28,7 @@ import MkUserOnlineIndicator from '@/components/MkUserOnlineIndicator.vue';
 import { defaultStore } from '@/store';
 
 const squareAvatars = $ref(defaultStore.state.squareAvatars);
+const useBlurEffect = $ref(defaultStore.state.useBlurEffect);
 
 const props = withDefaults(defineProps<{
 	user: misskey.entities.User;
@@ -45,15 +47,20 @@ const emit = defineEmits<{
 	(ev: 'click', v: MouseEvent): void;
 }>();
 
+const bound = $computed(() => props.link
+	? { to: userPage(props.user), target: props.target }
+	: {});
+
 const url = $computed(() => defaultStore.state.disableShowingAnimatedImages
 	? getStaticImageUrl(props.user.avatarUrl)
 	: props.user.avatarUrl);
 
-function onClick(ev: MouseEvent) {
+function onClick(ev: MouseEvent): void {
+	if (props.link) return;
 	emit('click', ev);
 }
 
-let color = $ref();
+let color = $ref<string | undefined>();
 
 watch(() => props.user.avatarBlurhash, () => {
 	color = extractAvgColorFromBlurhash(props.user.avatarBlurhash);
@@ -120,42 +127,113 @@ watch(() => props.user.avatarBlurhash, () => {
 }
 
 .cat {
-	> .earLeft,
-	> .earRight {
+	> .ears {
 		contain: strict;
-		display: inline-block;
-		height: 50%;
-		width: 50%;
-		background: currentColor;
+		position: absolute;
+		top: -50%;
+		left: -50%;
+		width: 100%;
+		height: 100%;
+		padding: 50%;
+
+		&.mask {
+			-webkit-mask:
+				url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><filter id="a"><feGaussianBlur in="SourceGraphic" stdDeviation="1"/></filter><circle cx="16" cy="16" r="15" filter="url(%23a)"/></svg>') center / 50% 50%,
+				linear-gradient(#fff, #fff);
+			-webkit-mask-composite: destination-out, source-over;
+			mask:
+				url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><filter id="a"><feGaussianBlur in="SourceGraphic" stdDeviation="1"/></filter><circle cx="16" cy="16" r="15" filter="url(%23a)"/></svg>') exclude center / 50% 50%,
+				linear-gradient(#fff, #fff); // polyfill of `image(#fff)`
+		}
 
-		&::before {
+		> .earLeft,
+		> .earRight {
 			contain: strict;
-			content: '';
-			display: block;
-			width: 60%;
-			height: 60%;
-			margin: 20%;
-			background: #df548f;
+			display: inline-block;
+			height: 50%;
+			width: 50%;
+			background: currentColor;
+
+			&::after {
+				contain: strict;
+				content: '';
+				display: block;
+				width: 60%;
+				height: 60%;
+				margin: 20%;
+				background: #df548f;
+			}
+
+			> .layer {
+				contain: strict;
+				position: absolute;
+				top: 0;
+				width: 280%;
+				height: 280%;
+
+				> .plot {
+					contain: strict;
+					width: 100%;
+					height: 100%;
+					clip-path: path('M0 0H1V1H0z');
+					transform: scale(32767);
+					transform-origin: 0 0;
+				}
+			}
 		}
-	}
-
-	> .earLeft {
-		border-radius: 0 75% 75%;
-		transform: rotate(37.5deg) skew(30deg);
-	}
 
-	> .earRight {
-		border-radius: 75% 0 75% 75%;
-		transform: rotate(-37.5deg) skew(-30deg);
-	}
-
-	&:hover {
 		> .earLeft {
-			animation: earwiggleleft 1s infinite;
+			transform: rotate(37.5deg) skew(30deg);
+
+			&, &::after {
+				border-radius: 0 75% 75%;
+			}
+
+			> .layer {
+				left: 0;
+				transform:
+					skew(-30deg)
+					rotate(-37.5deg)
+					translate(-2.82842712475%, /* -2 * sqrt(2) */
+										-38.5857864376%); /* 40 - 2 * sqrt(2) */
+
+				> .plot {
+					background-position: 20% 10%; /* ~= 37.5deg */
+				}
+			}
 		}
 
 		> .earRight {
-			animation: earwiggleright 1s infinite;
+			transform: rotate(-37.5deg) skew(-30deg);
+
+			&, &::after {
+				border-radius: 75% 0 75% 75%;
+			}
+
+			> .layer {
+				right: 0;
+				transform:
+					skew(30deg)
+					rotate(37.5deg)
+					translate(2.82842712475%, /* 2 * sqrt(2) */
+										-38.5857864376%); /* 40 - 2 * sqrt(2) */
+
+				> .plot {
+					background-position: 80% 10%; /* ~= 37.5deg */
+				}
+			}
+		}
+	}
+
+	&:hover {
+		> .ears {
+			> .earLeft {
+				animation: earwiggleleft 1s infinite;
+			}
+
+			> .earRight {
+				animation: earwiggleright 1s infinite;
+			}
 		}
 	}
 }
diff --git a/packages/frontend/src/pages/admin/RolesEditorFormula.vue b/packages/frontend/src/pages/admin/RolesEditorFormula.vue
index 07729b8cf94badb7e05f715803978a0650f41390..343d2c4c5cf7e0b6d8089d8506011ad844d6b9b2 100644
--- a/packages/frontend/src/pages/admin/RolesEditorFormula.vue
+++ b/packages/frontend/src/pages/admin/RolesEditorFormula.vue
@@ -10,6 +10,8 @@
 			<option value="followersMoreThanOrEq">{{ i18n.ts._role._condition.followersMoreThanOrEq }}</option>
 			<option value="followingLessThanOrEq">{{ i18n.ts._role._condition.followingLessThanOrEq }}</option>
 			<option value="followingMoreThanOrEq">{{ i18n.ts._role._condition.followingMoreThanOrEq }}</option>
+			<option value="notesLessThanOrEq">{{ i18n.ts._role._condition.notesLessThanOrEq }}</option>
+			<option value="notesMoreThanOrEq">{{ i18n.ts._role._condition.notesMoreThanOrEq }}</option>
 			<option value="and">{{ i18n.ts._role._condition.and }}</option>
 			<option value="or">{{ i18n.ts._role._condition.or }}</option>
 			<option value="not">{{ i18n.ts._role._condition.not }}</option>
@@ -42,7 +44,7 @@
 		<template #suffix>sec</template>
 	</MkInput>
 
-	<MkInput v-else-if="['followersLessThanOrEq', 'followersMoreThanOrEq', 'followingLessThanOrEq', 'followingMoreThanOrEq'].includes(type)" v-model="v.value" type="number">
+	<MkInput v-else-if="['followersLessThanOrEq', 'followersMoreThanOrEq', 'followingLessThanOrEq', 'followingMoreThanOrEq', 'notesLessThanOrEq', 'notesMoreThanOrEq'].includes(type)" v-model="v.value" type="number">
 	</MkInput>
 </div>
 </template>
@@ -91,6 +93,8 @@ const type = computed({
 		if (t === 'followersMoreThanOrEq') v.value.value = 10;
 		if (t === 'followingLessThanOrEq') v.value.value = 10;
 		if (t === 'followingMoreThanOrEq') v.value.value = 10;
+		if (t === 'notesLessThanOrEq') v.value.value = 10;
+		if (t === 'notesMoreThanOrEq') v.value.value = 10;
 		v.value.type = t;
 	},
 });
diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue
index 550de24bb2cf288ef857ad2e133c34009da77c64..8aae39cba1a6cf0cf057bf6fea89108fbe19e0dd 100644
--- a/packages/frontend/src/pages/admin/index.vue
+++ b/packages/frontend/src/pages/admin/index.vue
@@ -12,7 +12,7 @@
 				<MkInfo v-if="noBotProtection" warn class="info">{{ i18n.ts.noBotProtectionWarning }} <MkA to="/admin/security" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
 				<MkInfo v-if="noEmailServer" warn class="info">{{ i18n.ts.noEmailServerWarning }} <MkA to="/admin/email-settings" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
 
-				<MkSuperMenu :def="menuDef" :grid="currentPage?.route.name == null"></MkSuperMenu>
+				<MkSuperMenu :def="menuDef" :grid="narrow"></MkSuperMenu>
 			</div>
 		</MkSpacer>
 	</div>
@@ -220,6 +220,12 @@ onUnmounted(() => {
 	ro.disconnect();
 });
 
+watch(router.currentRef, (to) => {
+	if (to.route.path === "/admin" && to.child?.route.name == null && !narrow) {
+		router.replace('/admin/overview');
+	}
+});
+
 provideMetadataReceiver((info) => {
 	if (info == null) {
 		childInfo = null;
diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue
index 7c2f04a9abb61e58fddbee694f0f1f16a18c8c1a..ebe1a8ade098cc6847b54249545fee1f3ca1c5b3 100644
--- a/packages/frontend/src/pages/admin/moderation.vue
+++ b/packages/frontend/src/pages/admin/moderation.vue
@@ -46,7 +46,7 @@ let sensitiveWords: string = $ref('');
 
 async function init() {
 	const meta = await os.api('admin/meta');
-	sensitiveWords = meta.pinnedUsers.join('\n');
+	sensitiveWords = meta.sensitiveWords.join('\n');
 }
 
 function save() {
diff --git a/packages/frontend/src/pages/admin/queue.vue b/packages/frontend/src/pages/admin/queue.vue
index 80e97fed93834e702261a6569af845629f749479..509d329eb16ced178d5ef5e31e6293355f7c3ed3 100644
--- a/packages/frontend/src/pages/admin/queue.vue
+++ b/packages/frontend/src/pages/admin/queue.vue
@@ -4,6 +4,8 @@
 	<MkSpacer :content-max="800">
 		<XQueue v-if="tab === 'deliver'" domain="deliver"/>
 		<XQueue v-else-if="tab === 'inbox'" domain="inbox"/>
+		<br>
+		<MkButton @click="promoteAllQueues"><i class="ti ti-reload"></i> {{ i18n.ts.retryAllQueuesNow }}</MkButton>
 	</MkSpacer>
 </MkStickyContainer>
 </template>
@@ -15,6 +17,7 @@ import * as os from '@/os';
 import * as config from '@/config';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
+import MkButton from '@/components/MkButton.vue';
 
 let tab = $ref('deliver');
 
@@ -30,6 +33,18 @@ function clear() {
 	});
 }
 
+function promoteAllQueues() {
+	os.confirm({
+		type: 'warning',
+		title: i18n.ts.retryAllQueuesConfirmTitle,
+		text: i18n.ts.retryAllQueuesConfirmText,
+	}).then(({ canceled }) => {
+		if (canceled) return;
+
+		os.apiWithDialog('admin/queue/promote', { type: tab });
+	});
+}
+
 const headerActions = $computed(() => [{
 	asFullButton: true,
 	icon: 'ti ti-external-link',
diff --git a/packages/frontend/src/pages/admin/roles.edit.vue b/packages/frontend/src/pages/admin/roles.edit.vue
index e6896237f8f9b3723a3674a205edd94f2ad5a972..b1aa03f1f74f23a366a8b03d297f114d4c46cfc1 100644
--- a/packages/frontend/src/pages/admin/roles.edit.vue
+++ b/packages/frontend/src/pages/admin/roles.edit.vue
@@ -26,6 +26,7 @@ import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import { useRouter } from '@/router';
 import MkButton from '@/components/MkButton.vue';
+import { rolesCache } from '@/cache';
 
 const router = useRouter();
 
@@ -61,6 +62,7 @@ if (props.id) {
 }
 
 async function save() {
+	rolesCache.delete();
 	if (role) {
 		os.apiWithDialog('admin/roles/update', {
 			roleId: role.id,
diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue
index 12f341c01d7064b4857b8549066ee60859edcccd..65e64930d5261792f1f3b03d7db98c1d50c88e72 100644
--- a/packages/frontend/src/pages/admin/settings.vue
+++ b/packages/frontend/src/pages/admin/settings.vue
@@ -43,6 +43,14 @@
 							<MkSwitch v-model="emailRequiredForSignup">
 								<template #label>{{ i18n.ts.emailRequiredForSignup }}</template>
 							</MkSwitch>
+
+							<MkSwitch v-model="enableChartsForRemoteUser">
+								<template #label>{{ i18n.ts.enableChartsForRemoteUser }}</template>
+							</MkSwitch>
+
+							<MkSwitch v-model="enableChartsForFederatedInstances">
+								<template #label>{{ i18n.ts.enableChartsForFederatedInstances }}</template>
+							</MkSwitch>
 						</div>
 					</FormSection>
 
@@ -175,6 +183,8 @@ let cacheRemoteFiles: boolean = $ref(false);
 let enableRegistration: boolean = $ref(false);
 let emailRequiredForSignup: boolean = $ref(false);
 let enableServiceWorker: boolean = $ref(false);
+let enableChartsForRemoteUser: boolean = $ref(false);
+let enableChartsForFederatedInstances: boolean = $ref(false);
 let swPublicKey: any = $ref(null);
 let swPrivateKey: any = $ref(null);
 let deeplAuthKey: string = $ref('');
@@ -198,6 +208,8 @@ async function init() {
 	enableRegistration = !meta.disableRegistration;
 	emailRequiredForSignup = meta.emailRequiredForSignup;
 	enableServiceWorker = meta.enableServiceWorker;
+	enableChartsForRemoteUser = meta.enableChartsForRemoteUser;
+	enableChartsForFederatedInstances = meta.enableChartsForFederatedInstances;
 	swPublicKey = meta.swPublickey;
 	swPrivateKey = meta.swPrivateKey;
 	deeplAuthKey = meta.deeplAuthKey;
@@ -222,6 +234,8 @@ function save() {
 		disableRegistration: !enableRegistration,
 		emailRequiredForSignup,
 		enableServiceWorker,
+		enableChartsForRemoteUser,
+		enableChartsForFederatedInstances,
 		swPublicKey,
 		swPrivateKey,
 		deeplAuthKey,
diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue
index 7515a9122a53802e4f03f6114f9b8830551f5eb2..2b64de088ab717cd8e4f68a2a8affda095bf8d27 100644
--- a/packages/frontend/src/pages/clip.vue
+++ b/packages/frontend/src/pages/clip.vue
@@ -30,6 +30,7 @@ import * as os from '@/os';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import { url } from '@/config';
 import MkButton from '@/components/MkButton.vue';
+import { clipsCache } from '@/cache';
 
 const props = defineProps<{
 	clipId: string,
@@ -108,6 +109,8 @@ const headerActions = $computed(() => clip && isOwned ? [{
 			clipId: clip.id,
 			...result,
 		});
+
+		clipsCache.delete();
 	},
 }, ...(clip.isPublic ? [{
 	icon: 'ti ti-share',
@@ -133,6 +136,8 @@ const headerActions = $computed(() => clip && isOwned ? [{
 		await os.apiWithDialog('clips/delete', {
 			clipId: clip.id,
 		});
+
+		clipsCache.delete();
 	},
 }] : null);
 
diff --git a/packages/frontend/src/pages/my-clips/index.vue b/packages/frontend/src/pages/my-clips/index.vue
index 4c23985f3baff9ede11d306e8454cf3e66fd7e84..aad914d6bbff5c13e0c8000b9b2ea10d1c6e2110 100644
--- a/packages/frontend/src/pages/my-clips/index.vue
+++ b/packages/frontend/src/pages/my-clips/index.vue
@@ -28,6 +28,7 @@ import MkClipPreview from '@/components/MkClipPreview.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
+import { clipsCache } from '@/cache';
 
 const pagination = {
 	endpoint: 'clips/list' as const,
@@ -65,6 +66,8 @@ async function create() {
 
 	os.apiWithDialog('clips/create', result);
 
+	clipsCache.delete();
+
 	pagingComponent.reload();
 }
 
diff --git a/packages/frontend/src/pages/my-lists/index.vue b/packages/frontend/src/pages/my-lists/index.vue
index 8a96b54881bd4831006e2dd13627f5f042934263..11a2aca8c504e9fc8f256191de8e904778aea38e 100644
--- a/packages/frontend/src/pages/my-lists/index.vue
+++ b/packages/frontend/src/pages/my-lists/index.vue
@@ -24,6 +24,7 @@ import MkAvatars from '@/components/MkAvatars.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
+import { userListsCache } from '@/cache';
 
 const pagingComponent = $shallowRef<InstanceType<typeof MkPagination>>();
 
@@ -38,6 +39,7 @@ async function create() {
 	});
 	if (canceled) return;
 	await os.apiWithDialog('users/lists/create', { name: name });
+	userListsCache.delete();
 	pagingComponent.reload();
 }
 
diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue
index 205434971dccbd6a4f3438d2c3c885c0395f2741..768a48746c95c3dd965f7d9472a5a6c347da5e24 100644
--- a/packages/frontend/src/pages/my-lists/list.vue
+++ b/packages/frontend/src/pages/my-lists/list.vue
@@ -37,6 +37,7 @@ import { definePageMetadata } from '@/scripts/page-metadata';
 import { i18n } from '@/i18n';
 import { userPage } from '@/filters/user';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
+import { userListsCache } from '@/cache';
 
 const props = defineProps<{
 	listId: string;
@@ -97,6 +98,8 @@ async function renameList() {
 		name: name,
 	});
 
+	userListsCache.delete();
+
 	list.name = name;
 }
 
@@ -107,10 +110,10 @@ async function deleteList() {
 	});
 	if (canceled) return;
 
-	await os.api('users/lists/delete', {
+	await os.apiWithDialog('users/lists/delete', {
 		listId: list.id,
 	});
-	os.success();
+	userListsCache.delete();
 	mainRouter.push('/my/lists');
 }
 
diff --git a/packages/frontend/src/pages/settings/drive-cleaner.vue b/packages/frontend/src/pages/settings/drive-cleaner.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8178343bbb425dd282b463442a1bbd12608e8152
--- /dev/null
+++ b/packages/frontend/src/pages/settings/drive-cleaner.vue
@@ -0,0 +1,156 @@
+<template>
+<div class="_gaps">
+	<MkSelect v-model="sortModeSelect">
+		<template #label>{{ i18n.ts.sort }}</template>
+		<option v-for="x in sortOptions" :key="x.value" :value="x.value">{{ x.displayName }}</option>
+	</MkSelect>
+	<div v-if="!fetching">
+		<MkPagination v-slot="{items}" :pagination="pagination">
+			<div class="_gaps">
+				<div
+					v-for="file in items" :key="file.id"
+					class="_button"
+					@click="$event => onClick($event, file)"
+					@contextmenu.stop="$event => onContextMenu($event, file)"
+				>
+					<div :class="$style.file">
+						<div v-if="file.isSensitive" class="sensitive-label">{{ i18n.ts.sensitive }}</div>
+						<MkDriveFileThumbnail :class="$style.fileThumbnail" :file="file" fit="contain"/>
+						<div :class="$style.fileBody">
+							<div style="margin-bottom: 4px;">
+								{{ file.name }}
+							</div>
+							<div>
+								<span style="margin-right: 1em;">{{ file.type }}</span>
+								<span>{{ bytes(file.size) }}</span>
+							</div>
+							<div>
+								<span>{{ i18n.ts.registeredDate }}: <MkTime :time="file.createdAt" mode="detail"/></span>
+							</div>
+							<div v-if="sortModeSelect === 'sizeDesc'">
+								<div :class="$style.meter"><div :class="$style.meterValue" :style="genUsageBar(file.size)"></div></div>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+		</MkPagination>
+	</div>
+	<div v-else>
+		<MkLoading/>
+	</div>
+</div>
+</template>
+
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import tinycolor from 'tinycolor2';
+import * as os from '@/os';
+import MkPagination from '@/components/MkPagination.vue';
+import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
+import { i18n } from '@/i18n';
+import bytes from '@/filters/bytes';
+import { dateString } from '@/filters/date';
+import { definePageMetadata } from '@/scripts/page-metadata';
+import MkSelect from '@/components/MkSelect.vue';
+import { getDriveFileMenu } from '@/scripts/get-drive-file-menu';
+
+let sortMode = ref('+size');
+const pagination = {
+	endpoint: 'drive/files' as const,
+	limit: 10,
+	params: computed(() => ({ sort: sortMode.value })),
+};
+
+const sortOptions = [
+	{ value: 'sizeDesc', displayName: i18n.ts._drivecleaner.orderBySizeDesc },
+	{ value: 'createdAtAsc', displayName: i18n.ts._drivecleaner.orderByCreatedAtAsc },
+];
+
+const capacity = ref<number>(0);
+const usage = ref<number>(0);
+const fetching = ref(true);
+const sortModeSelect = ref('sizeDesc');
+
+fetchDriveInfo();
+
+watch(sortModeSelect, () => {
+	switch (sortModeSelect.value) {
+		case 'sizeDesc':
+			sortMode.value = '+size';
+			fetchDriveInfo();
+			break;
+		
+		case 'createdAtAsc':
+			sortMode.value = '-createdAt';
+			fetchDriveInfo();
+			break;
+	}
+});
+
+function fetchDriveInfo(): void {
+	fetching.value = true;
+	os.api('drive').then(info => {
+		capacity.value = info.capacity;
+		usage.value = info.usage;
+		fetching.value = false;
+	});
+}
+
+function genUsageBar(fsize: number): object {
+	return {
+		width: `${fsize / usage.value * 100}%`,
+		background: tinycolor({ h: 180 - (fsize / usage.value * 180), s: 0.7, l: 0.5 }),
+	};
+}
+
+function onClick(ev: MouseEvent, file) {
+	os.popupMenu(getDriveFileMenu(file), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined);
+}
+
+function onContextMenu(ev: MouseEvent, file): void {
+	os.contextMenu(getDriveFileMenu(file), ev);
+}
+
+definePageMetadata({
+	title: i18n.ts.drivecleaner,
+	icon: 'ti ti-trash',
+});
+</script>
+
+<style lang="scss" module>
+.file {
+	display: flex;
+	width: 100%;
+	box-sizing: border-box;
+	text-align: left;
+	align-items: center;
+
+	&:hover {
+		color: var(--accent);
+	}
+}
+
+.fileThumbnail {
+	width: 100px;
+	height: 100px;
+}
+
+.fileBody {
+	margin-left: 0.3em;
+	padding: 8px;
+	flex: 1;
+}
+
+.meter {
+	margin-top: 8px;
+	height: 12px;
+	background: rgba(0, 0, 0, 0.1);
+	overflow: clip;
+	border-radius: 999px;
+}
+
+.meterValue {
+	height: 100%;
+}
+</style>
diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue
index a23bdfe69ec316449fbd5966c4f62e5d20afd74e..d3fb422e01d41bc5c8039b7f46eac4f9a818a8cd 100644
--- a/packages/frontend/src/pages/settings/drive.vue
+++ b/packages/frontend/src/pages/settings/drive.vue
@@ -32,6 +32,9 @@
 				<template #suffix>{{ uploadFolder ? uploadFolder.name : '-' }}</template>
 				<template #suffixIcon><i class="ti ti-folder"></i></template>
 			</FormLink>
+			<FormLink to="/settings/drive/cleaner">
+				{{ i18n.ts.drivecleaner }}
+			</FormLink>
 			<MkSwitch v-model="keepOriginalUploading">
 				<template #label>{{ i18n.ts.keepOriginalUploading }}</template>
 				<template #caption>{{ i18n.ts.keepOriginalUploadingDescription }}</template>
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index 2e2c456c07603044d44ee15f707958f5e61dbed5..dd62a325303b3b6f6b8c1dc266a615520d403742 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -47,6 +47,7 @@
 		<div class="_gaps_m">
 			<div class="_gaps_s">
 				<MkSwitch v-model="showNoteActionsOnlyHover">{{ i18n.ts.showNoteActionsOnlyHover }}</MkSwitch>
+				<MkSwitch v-model="showClipButtonInNoteFooter">{{ i18n.ts.showClipButtonInNoteFooter }}</MkSwitch>
 				<MkSwitch v-model="collapseRenotes">{{ i18n.ts.collapseRenotes }}</MkSwitch>
 				<MkSwitch v-model="advancedMfm">{{ i18n.ts.enableAdvancedMfm }}</MkSwitch>
 				<MkSwitch v-if="advancedMfm" v-model="animatedMfm">{{ i18n.ts.enableAnimatedMfm }}</MkSwitch>
@@ -143,6 +144,7 @@ async function reloadAsk() {
 const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind'));
 const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior'));
 const showNoteActionsOnlyHover = computed(defaultStore.makeGetterSetter('showNoteActionsOnlyHover'));
+const showClipButtonInNoteFooter = computed(defaultStore.makeGetterSetter('showClipButtonInNoteFooter'));
 const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes'));
 const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v));
 const useBlurEffectForModal = computed(defaultStore.makeGetterSetter('useBlurEffectForModal'));
diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue
index f1a450e18e644fa3cecd141acd3aa87e85c21b08..ae36466eec4a39e0d72747df75de8b505bfab456 100644
--- a/packages/frontend/src/pages/settings/index.vue
+++ b/packages/frontend/src/pages/settings/index.vue
@@ -7,7 +7,7 @@
 				<div v-if="!narrow || currentPage?.route.name == null" class="nav">
 					<div class="baaadecd">
 						<MkInfo v-if="emailNotConfigured" warn class="info">{{ i18n.ts.emailNotConfiguredWarning }} <MkA to="/settings/email" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
-						<MkSuperMenu :def="menuDef" :grid="currentPage?.route.name == null"></MkSuperMenu>
+						<MkSuperMenu :def="menuDef" :grid="narrow"></MkSuperMenu>
 					</div>
 				</div>
 				<div v-if="!(narrow && currentPage?.route.name == null)" class="main">
@@ -230,6 +230,12 @@ onUnmounted(() => {
 	ro.disconnect();
 });
 
+watch(router.currentRef, (to) => {
+	if (to.route.name === "settings" && to.child?.route.name == null && !narrow) {
+		router.replace('/settings/profile');
+	}
+});
+
 const emailNotConfigured = computed(() => instance.enableEmail && ($i.email == null || !$i.emailVerified));
 
 provideMetadataReceiver((info) => {
diff --git a/packages/frontend/src/pages/settings/navbar.vue b/packages/frontend/src/pages/settings/navbar.vue
index ead551e7c4fd1a1eb4981de20f1f0973ec629ee2..b3b33b8026d1667a90f7f0f0a32dc428751b2114 100644
--- a/packages/frontend/src/pages/settings/navbar.vue
+++ b/packages/frontend/src/pages/settings/navbar.vue
@@ -1,9 +1,34 @@
 <template>
 <div class="_gaps_m">
-	<MkTextarea v-model="items" tall manual-save>
+	<FormSlot>
 		<template #label>{{ i18n.ts.navbar }}</template>
-		<template #caption><button class="_textButton" @click="addItem">{{ i18n.ts.addItem }}</button></template>
-	</MkTextarea>
+		<MkContainer :show-header="false">
+			<Sortable 
+				v-model="items"
+				item-key="id"
+				:animation="150"
+				:handle="'.' + $style.itemHandle"
+				@start="e => e.item.classList.add('active')"
+				@end="e => e.item.classList.remove('active')"
+			>
+				<template #item="{element,index}">
+					<div
+						v-if="element.type === '-' || navbarItemDef[element.type]"
+						:class="$style.item"
+					>
+						<button class="_button" :class="$style.itemHandle"><i class="ti ti-menu"></i></button>
+						<i class="ti-fw" :class="[$style.itemIcon, navbarItemDef[element.type]?.icon]"></i><span :class="$style.itemText">{{ navbarItemDef[element.type]?.title ?? i18n.ts.divider }}</span>
+						<button class="_button" :class="$style.itemRemove" @click="removeItem(index)"><i class="ti ti-x"></i></button>
+					</div>
+				</template>
+			</Sortable>
+		</MkContainer>
+	</FormSlot>
+	<div class="_buttons">
+		<MkButton @click="addItem"><i class="ti ti-plus"></i> {{ i18n.ts.addItem }}</MkButton>
+		<MkButton danger @click="reset"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton>
+		<MkButton primary class="save" @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
+	</div>
 
 	<MkRadios v-model="menuDisplay">
 		<template #label>{{ i18n.ts.display }}</template>
@@ -12,26 +37,30 @@
 		<option value="top">{{ i18n.ts._menuDisplay.top }}</option>
 		<!-- <MkRadio v-model="menuDisplay" value="hide" disabled>{{ i18n.ts._menuDisplay.hide }}</MkRadio>--> <!-- TODO: サイドバーを完全に隠せるようにすると、別途ハンバーガーボタンのようなものをUIに表示する必要があり面倒 -->
 	</MkRadios>
-
-	<MkButton danger @click="reset()"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton>
 </div>
 </template>
 
 <script lang="ts" setup>
-import { computed, ref, watch } from 'vue';
-import MkTextarea from '@/components/MkTextarea.vue';
+import { computed, defineAsyncComponent, ref, watch } from 'vue';
 import MkRadios from '@/components/MkRadios.vue';
 import MkButton from '@/components/MkButton.vue';
+import FormSlot from '@/components/form/slot.vue';
+import MkContainer from '@/components/MkContainer.vue';
 import * as os from '@/os';
 import { navbarItemDef } from '@/navbar';
 import { defaultStore } from '@/store';
 import { unisonReload } from '@/scripts/unison-reload';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
+import { deepClone } from '@/scripts/clone';
+
+const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
 
-const items = ref(defaultStore.state.menu.join('\n'));
+const items = ref(defaultStore.state.menu.map(x => ({
+	id: Math.random().toString(),
+	type: x,
+})));
 
-const split = computed(() => items.value.trim().split('\n').filter(x => x.trim() !== ''));
 const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
 
 async function reloadAsk() {
@@ -55,23 +84,28 @@ async function addItem() {
 		}],
 	});
 	if (canceled) return;
-	items.value = [...split.value, item].join('\n');
+	items.value = [...items.value, {
+		id: Math.random().toString(),
+		type: item,
+	}];
+}
+
+function removeItem(index: number) {
+	items.value.splice(index, 1);
 }
 
 async function save() {
-	defaultStore.set('menu', split.value);
+	defaultStore.set('menu', items.value.map(x => x.type));
 	await reloadAsk();
 }
 
 function reset() {
-	defaultStore.reset('menu');
-	items.value = defaultStore.state.menu.join('\n');
+	items.value = defaultStore.def.menu.default.map(x => ({
+		id: Math.random().toString(),
+		type: x,
+	}));
 }
 
-watch(items, async () => {
-	await save();
-});
-
 watch(menuDisplay, async () => {
 	await reloadAsk();
 });
@@ -85,3 +119,44 @@ definePageMetadata({
 	icon: 'ti ti-list',
 });
 </script>
+
+<style lang="scss" module>
+.item {
+	position: relative;
+	display: block;
+	line-height: 2.85rem;
+	text-overflow: ellipsis;
+	overflow: hidden;
+	white-space: nowrap;
+	color: var(--navFg);
+}
+
+.itemIcon {
+	position: relative;
+	width: 32px;
+	margin-right: 8px;
+}
+
+.itemText {
+	position: relative;
+	font-size: 0.9em;
+}
+
+.itemRemove {
+	position: absolute;
+	z-index: 10000;
+	width: 32px;
+	height: 32px;
+	color: #ff2a2a;
+	right: 8px;
+	opacity: 0.8;
+}
+
+.itemHandle {
+	cursor: move;
+	width: 32px;
+	height: 32px;
+	margin: 0 8px;
+	opacity: 0.5;
+}
+</style>
diff --git a/packages/frontend/src/pages/settings/webhook.edit.vue b/packages/frontend/src/pages/settings/webhook.edit.vue
index a01e3f8cee4b74a1f26c0f94e3833509b5608398..3c782973aec6e185d373d336eb4a2301afd322fa 100644
--- a/packages/frontend/src/pages/settings/webhook.edit.vue
+++ b/packages/frontend/src/pages/settings/webhook.edit.vue
@@ -1,7 +1,7 @@
 <template>
 <div class="_gaps_m">
 	<MkInput v-model="name">
-		<template #label>Name</template>
+		<template #label>{{ i18n.ts._webhookSettings.name }}</template>
 	</MkInput>
 
 	<MkInput v-model="url" type="url">
@@ -10,24 +10,24 @@
 
 	<MkInput v-model="secret">
 		<template #prefix><i class="ti ti-lock"></i></template>
-		<template #label>Secret</template>
+		<template #label>{{ i18n.ts._webhookSettings.secret }}</template>
 	</MkInput>
 
 	<FormSection>
-		<template #label>Events</template>
+		<template #label>{{ i18n.ts._webhookSettings.events }}</template>
 
 		<div class="_gaps_s">
-			<MkSwitch v-model="event_follow">Follow</MkSwitch>
-			<MkSwitch v-model="event_followed">Followed</MkSwitch>
-			<MkSwitch v-model="event_note">Note</MkSwitch>
-			<MkSwitch v-model="event_reply">Reply</MkSwitch>
-			<MkSwitch v-model="event_renote">Renote</MkSwitch>
-			<MkSwitch v-model="event_reaction">Reaction</MkSwitch>
-			<MkSwitch v-model="event_mention">Mention</MkSwitch>
+			<MkSwitch v-model="event_follow">{{ i18n.ts._webhookSettings._events.follow }}</MkSwitch>
+			<MkSwitch v-model="event_followed">{{ i18n.ts._webhookSettings._events.followed }}</MkSwitch>
+			<MkSwitch v-model="event_note">{{ i18n.ts._webhookSettings._events.note }}</MkSwitch>
+			<MkSwitch v-model="event_reply">{{ i18n.ts._webhookSettings._events.reply }}</MkSwitch>
+			<MkSwitch v-model="event_renote">{{ i18n.ts._webhookSettings._events.renote }}</MkSwitch>
+			<MkSwitch v-model="event_reaction">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch>
+			<MkSwitch v-model="event_mention">{{ i18n.ts._webhookSettings._events.mention }}</MkSwitch>
 		</div>
 	</FormSection>
 
-	<MkSwitch v-model="active">Active</MkSwitch>
+	<MkSwitch v-model="active">{{ i18n.ts._webhookSettings.active }}</MkSwitch>
 
 	<div class="_buttons">
 		<MkButton primary inline @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
diff --git a/packages/frontend/src/pages/settings/webhook.new.vue b/packages/frontend/src/pages/settings/webhook.new.vue
index 45ab5722c3c1c9e950bd9a753860cc8ea89d617c..6eb8a654f55da4d6387c585cf5fae0c0d6e8447f 100644
--- a/packages/frontend/src/pages/settings/webhook.new.vue
+++ b/packages/frontend/src/pages/settings/webhook.new.vue
@@ -1,7 +1,7 @@
 <template>
 <div class="_gaps_m">
 	<MkInput v-model="name">
-		<template #label>Name</template>
+		<template #label>{{ i18n.ts._webhookSettings.name }}</template>
 	</MkInput>
 
 	<MkInput v-model="url" type="url">
@@ -10,20 +10,20 @@
 
 	<MkInput v-model="secret">
 		<template #prefix><i class="ti ti-lock"></i></template>
-		<template #label>Secret</template>
+		<template #label>{{ i18n.ts._webhookSettings.secret }}</template>
 	</MkInput>
 
 	<FormSection>
-		<template #label>Events</template>
+		<template #label>{{ i18n.ts._webhookSettings.events }}</template>
 
 		<div class="_gaps_s">
-			<MkSwitch v-model="event_follow">Follow</MkSwitch>
-			<MkSwitch v-model="event_followed">Followed</MkSwitch>
-			<MkSwitch v-model="event_note">Note</MkSwitch>
-			<MkSwitch v-model="event_reply">Reply</MkSwitch>
-			<MkSwitch v-model="event_renote">Renote</MkSwitch>
-			<MkSwitch v-model="event_reaction">Reaction</MkSwitch>
-			<MkSwitch v-model="event_mention">Mention</MkSwitch>
+			<MkSwitch v-model="event_follow">{{ i18n.ts._webhookSettings._events.follow }}</MkSwitch>
+			<MkSwitch v-model="event_followed">{{ i18n.ts._webhookSettings._events.followed }}</MkSwitch>
+			<MkSwitch v-model="event_note">{{ i18n.ts._webhookSettings._events.note }}</MkSwitch>
+			<MkSwitch v-model="event_reply">{{ i18n.ts._webhookSettings._events.reply }}</MkSwitch>
+			<MkSwitch v-model="event_renote">{{ i18n.ts._webhookSettings._events.renote }}</MkSwitch>
+			<MkSwitch v-model="event_reaction">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch>
+			<MkSwitch v-model="event_mention">{{ i18n.ts._webhookSettings._events.mention }}</MkSwitch>
 		</div>
 	</FormSection>
 
diff --git a/packages/frontend/src/pages/settings/webhook.vue b/packages/frontend/src/pages/settings/webhook.vue
index e10f65b0afbe09ad8f9b3013ad057659d6ae246f..bc729ab871528c5901a08831460b71aaa08e6ec6 100644
--- a/packages/frontend/src/pages/settings/webhook.vue
+++ b/packages/frontend/src/pages/settings/webhook.vue
@@ -1,7 +1,7 @@
 <template>
 <div class="_gaps_m">
 	<FormLink :to="`/settings/webhook/new`">
-		Create webhook
+		{{ i18n.ts._webhookSettings.createWebhook }}
 	</FormLink>
 
 	<FormSection>
@@ -31,6 +31,7 @@ import MkPagination from '@/components/MkPagination.vue';
 import FormSection from '@/components/form/section.vue';
 import FormLink from '@/components/form/link.vue';
 import { definePageMetadata } from '@/scripts/page-metadata';
+import { i18n } from '@/i18n';
 
 const pagination = {
 	endpoint: 'i/webhooks/list' as const,
diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts
index 590c5765fd508095764b25339d9437c4fcc9b844..c8077edd288ca0cb7ee9c03fde5e1624107e7309 100644
--- a/packages/frontend/src/router.ts
+++ b/packages/frontend/src/router.ts
@@ -65,6 +65,10 @@ export const routes = [{
 		path: '/drive',
 		name: 'drive',
 		component: page(() => import('./pages/settings/drive.vue')),
+	}, {
+		path: '/drive/cleaner',
+		name: 'drive',
+		component: page(() => import('./pages/settings/drive-cleaner.vue')),
 	}, {
 		path: '/notifications',
 		name: 'notifications',
diff --git a/packages/frontend/src/scripts/aiscript/ui.ts b/packages/frontend/src/scripts/aiscript/ui.ts
index 6b8041d78e38ba71277df0686a8080f326dfe79e..2ca1b164ae14316fd51f57d1b157e8a17d6874ac 100644
--- a/packages/frontend/src/scripts/aiscript/ui.ts
+++ b/packages/frontend/src/scripts/aiscript/ui.ts
@@ -471,7 +471,7 @@ export function registerAsUiLib(components: Ref<AsUiComponent>[], done: (root: R
 		components.push(component);
 		const instance = values.OBJ(new Map([
 			['id', values.STR(_id)],
-			['update', values.FN_NATIVE(async ([def], opts) => {
+			['update', values.FN_NATIVE(([def], opts) => {
 				utils.assertObject(def);
 				const updates = getOptions(def, call);
 				for (const update of def.value.keys()) {
@@ -491,13 +491,13 @@ export function registerAsUiLib(components: Ref<AsUiComponent>[], done: (root: R
 	return {
 		'Ui:root': rootInstance,
 
-		'Ui:patch': values.FN_NATIVE(async ([id, val], opts) => {
+		'Ui:patch': values.FN_NATIVE(([id, val], opts) => {
 			utils.assertString(id);
 			utils.assertArray(val);
 			patch(id.value, val.value, opts.call);
 		}),
 
-		'Ui:get': values.FN_NATIVE(async ([id], opts) => {
+		'Ui:get': values.FN_NATIVE(([id], opts) => {
 			utils.assertString(id);
 			const instance = instances[id.value];
 			if (instance) {
@@ -508,7 +508,7 @@ export function registerAsUiLib(components: Ref<AsUiComponent>[], done: (root: R
 		}),
 
 		// Ui:root.update({ children: [...] }) の糖衣構文
-		'Ui:render': values.FN_NATIVE(async ([children], opts) => {
+		'Ui:render': values.FN_NATIVE(([children], opts) => {
 			utils.assertArray(children);
 		
 			rootComponent.value.children = children.value.map(v => {
@@ -517,51 +517,51 @@ export function registerAsUiLib(components: Ref<AsUiComponent>[], done: (root: R
 			});
 		}),
 
-		'Ui:C:container': values.FN_NATIVE(async ([def, id], opts) => {
+		'Ui:C:container': values.FN_NATIVE(([def, id], opts) => {
 			return createComponentInstance('container', def, id, getContainerOptions, opts.call);
 		}),
 
-		'Ui:C:text': values.FN_NATIVE(async ([def, id], opts) => {
+		'Ui:C:text': values.FN_NATIVE(([def, id], opts) => {
 			return createComponentInstance('text', def, id, getTextOptions, opts.call);
 		}),
 
-		'Ui:C:mfm': values.FN_NATIVE(async ([def, id], opts) => {
+		'Ui:C:mfm': values.FN_NATIVE(([def, id], opts) => {
 			return createComponentInstance('mfm', def, id, getMfmOptions, opts.call);
 		}),
 
-		'Ui:C:textarea': values.FN_NATIVE(async ([def, id], opts) => {
+		'Ui:C:textarea': values.FN_NATIVE(([def, id], opts) => {
 			return createComponentInstance('textarea', def, id, getTextareaOptions, opts.call);
 		}),
 
-		'Ui:C:textInput': values.FN_NATIVE(async ([def, id], opts) => {
+		'Ui:C:textInput': values.FN_NATIVE(([def, id], opts) => {
 			return createComponentInstance('textInput', def, id, getTextInputOptions, opts.call);
 		}),
 
-		'Ui:C:numberInput': values.FN_NATIVE(async ([def, id], opts) => {
+		'Ui:C:numberInput': values.FN_NATIVE(([def, id], opts) => {
 			return createComponentInstance('numberInput', def, id, getNumberInputOptions, opts.call);
 		}),
 
-		'Ui:C:button': values.FN_NATIVE(async ([def, id], opts) => {
+		'Ui:C:button': values.FN_NATIVE(([def, id], opts) => {
 			return createComponentInstance('button', def, id, getButtonOptions, opts.call);
 		}),
 
-		'Ui:C:buttons': values.FN_NATIVE(async ([def, id], opts) => {
+		'Ui:C:buttons': values.FN_NATIVE(([def, id], opts) => {
 			return createComponentInstance('buttons', def, id, getButtonsOptions, opts.call);
 		}),
 
-		'Ui:C:switch': values.FN_NATIVE(async ([def, id], opts) => {
+		'Ui:C:switch': values.FN_NATIVE(([def, id], opts) => {
 			return createComponentInstance('switch', def, id, getSwitchOptions, opts.call);
 		}),
 
-		'Ui:C:select': values.FN_NATIVE(async ([def, id], opts) => {
+		'Ui:C:select': values.FN_NATIVE(([def, id], opts) => {
 			return createComponentInstance('select', def, id, getSelectOptions, opts.call);
 		}),
 
-		'Ui:C:folder': values.FN_NATIVE(async ([def, id], opts) => {
+		'Ui:C:folder': values.FN_NATIVE(([def, id], opts) => {
 			return createComponentInstance('folder', def, id, getFolderOptions, opts.call);
 		}),
 
-		'Ui:C:postFormButton': values.FN_NATIVE(async ([def, id], opts) => {
+		'Ui:C:postFormButton': values.FN_NATIVE(([def, id], opts) => {
 			return createComponentInstance('postFormButton', def, id, getPostFormButtonOptions, opts.call);
 		}),
 	};
diff --git a/packages/frontend/src/scripts/cache.ts b/packages/frontend/src/scripts/cache.ts
new file mode 100644
index 0000000000000000000000000000000000000000..858e5f03bfe5a439c0e709b5ee51c09001770b7b
--- /dev/null
+++ b/packages/frontend/src/scripts/cache.ts
@@ -0,0 +1,80 @@
+
+export class Cache<T> {
+	private cachedAt: number | null = null;
+	private value: T | undefined;
+	private lifetime: number;
+
+	constructor(lifetime: Cache<never>['lifetime']) {
+		this.lifetime = lifetime;
+	}
+
+	public set(value: T): void {
+		this.cachedAt = Date.now();
+		this.value = value;
+	}
+
+	public get(): T | undefined {
+		if (this.cachedAt == null) return undefined;
+		if ((Date.now() - this.cachedAt) > this.lifetime) {
+			this.value = undefined;
+			this.cachedAt = null;
+			return undefined;
+		}
+		return this.value;
+	}
+
+	public delete() {
+		this.value = undefined;
+		this.cachedAt = null;
+	}
+
+	/**
+	 * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します
+	 * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
+	 */
+	public async fetch(fetcher: () => Promise<T>, validator?: (cachedValue: T) => boolean): Promise<T> {
+		const cachedValue = this.get();
+		if (cachedValue !== undefined) {
+			if (validator) {
+				if (validator(cachedValue)) {
+					// Cache HIT
+					return cachedValue;
+				}
+			} else {
+				// Cache HIT
+				return cachedValue;
+			}
+		}
+
+		// Cache MISS
+		const value = await fetcher();
+		this.set(value);
+		return value;
+	}
+
+	/**
+	 * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します
+	 * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
+	 */
+	public async fetchMaybe(fetcher: () => Promise<T | undefined>, validator?: (cachedValue: T) => boolean): Promise<T | undefined> {
+		const cachedValue = this.get();
+		if (cachedValue !== undefined) {
+			if (validator) {
+				if (validator(cachedValue)) {
+					// Cache HIT
+					return cachedValue;
+				}
+			} else {
+				// Cache HIT
+				return cachedValue;
+			}
+		}
+
+		// Cache MISS
+		const value = await fetcher();
+		if (value !== undefined) {
+			this.set(value);
+		}
+		return value;
+	}
+}
diff --git a/packages/frontend/src/scripts/get-drive-file-menu.ts b/packages/frontend/src/scripts/get-drive-file-menu.ts
new file mode 100644
index 0000000000000000000000000000000000000000..52e610e437cb74f6c3ac18ccf6ac1de17021575a
--- /dev/null
+++ b/packages/frontend/src/scripts/get-drive-file-menu.ts
@@ -0,0 +1,93 @@
+import * as Misskey from 'misskey-js';
+import { defineAsyncComponent } from 'vue';
+import { i18n } from '@/i18n';
+import copyToClipboard from '@/scripts/copy-to-clipboard';
+import * as os from '@/os';
+
+function rename(file: Misskey.entities.DriveFile) {
+	os.inputText({
+		title: i18n.ts.renameFile,
+		placeholder: i18n.ts.inputNewFileName,
+		default: file.name,
+	}).then(({ canceled, result: name }) => {
+		if (canceled) return;
+		os.api('drive/files/update', {
+			fileId: file.id,
+			name: name,
+		});
+	});
+}
+
+function describe(file: Misskey.entities.DriveFile) {
+	os.popup(defineAsyncComponent(() => import('@/components/MkFileCaptionEditWindow.vue')), {
+		default: file.comment != null ? file.comment : '',
+		file: file,
+	}, {
+		done: caption => {
+			os.api('drive/files/update', {
+				fileId: file.id,
+				comment: caption.length === 0 ? null : caption,
+			});
+		},
+	}, 'closed');
+}
+
+function toggleSensitive(file: Misskey.entities.DriveFile) {
+	os.api('drive/files/update', {
+		fileId: file.id,
+		isSensitive: !file.isSensitive,
+	});
+}
+
+function copyUrl(file: Misskey.entities.DriveFile) {
+	copyToClipboard(file.url);
+	os.success();
+}
+/*
+function addApp() {
+	alert('not implemented yet');
+}
+*/
+async function deleteFile(file: Misskey.entities.DriveFile) {
+	const { canceled } = await os.confirm({
+		type: 'warning',
+		text: i18n.t('driveFileDeleteConfirm', { name: file.name }),
+	});
+
+	if (canceled) return;
+	os.api('drive/files/delete', {
+		fileId: file.id,
+	});
+}
+
+export function getDriveFileMenu(file: Misskey.entities.DriveFile) {
+	return [{
+		text: i18n.ts.rename,
+		icon: 'ti ti-forms',
+		action: () => rename(file),
+	}, {
+		text: file.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive,
+		icon: file.isSensitive ? 'ti ti-eye' : 'ti ti-eye-off',
+		action: () => toggleSensitive(file),
+	}, {
+		text: i18n.ts.describeFile,
+		icon: 'ti ti-text-caption',
+		action: () => describe(file),
+	}, null, {
+		text: i18n.ts.copyUrl,
+		icon: 'ti ti-link',
+		action: () => copyUrl(file),
+	}, {
+		type: 'a',
+		href: file.url,
+		target: '_blank',
+		text: i18n.ts.download,
+		icon: 'ti ti-download',
+		download: file.name,
+	}, null, {
+		text: i18n.ts.delete,
+		icon: 'ti ti-trash',
+		danger: true,
+		action: () => deleteFile(file),
+	}];
+}
diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts
index 9c0ff3d1b2ac42f45968e12c739bcf124828f45e..00f2523bf9ef94e225aeb19d9636bc30533868de 100644
--- a/packages/frontend/src/scripts/get-note-menu.ts
+++ b/packages/frontend/src/scripts/get-note-menu.ts
@@ -10,6 +10,81 @@ import { url } from '@/config';
 import { noteActions } from '@/store';
 import { miLocalStorage } from '@/local-storage';
 import { getUserMenu } from '@/scripts/get-user-menu';
+import { clipsCache } from '@/cache';
+
+export async function getNoteClipMenu(props: {
+	note: misskey.entities.Note;
+	isDeleted: Ref<boolean>;
+	currentClipPage?: Ref<misskey.entities.Clip>;
+}) {
+	const isRenote = (
+		props.note.renote != null &&
+		props.note.text == null &&
+		props.note.fileIds.length === 0 &&
+		props.note.poll == null
+	);
+
+	const appearNote = isRenote ? props.note.renote as misskey.entities.Note : props.note;
+
+	const clips = await clipsCache.fetch(() => os.api('clips/list'));
+	return [...clips.map(clip => ({
+		text: clip.name,
+		action: () => {
+			claimAchievement('noteClipped1');
+			os.promiseDialog(
+				os.api('clips/add-note', { clipId: clip.id, noteId: appearNote.id }),
+				null,
+				async (err) => {
+					if (err.id === '734806c4-542c-463a-9311-15c512803965') {
+						const confirm = await os.confirm({
+							type: 'warning',
+							text: i18n.t('confirmToUnclipAlreadyClippedNote', { name: clip.name }),
+						});
+						if (!confirm.canceled) {
+							os.apiWithDialog('clips/remove-note', { clipId: clip.id, noteId: appearNote.id });
+							if (props.currentClipPage?.value.id === clip.id) props.isDeleted.value = true;
+						}
+					} else {
+						os.alert({
+							type: 'error',
+							text: err.message + '\n' + err.id,
+						});
+					}
+				},
+			);
+		},
+	})), null, {
+		icon: 'ti ti-plus',
+		text: i18n.ts.createNew,
+		action: async () => {
+			const { canceled, result } = await os.form(i18n.ts.createNewClip, {
+				name: {
+					type: 'string',
+					label: i18n.ts.name,
+				},
+				description: {
+					type: 'string',
+					required: false,
+					multiline: true,
+					label: i18n.ts.description,
+				},
+				isPublic: {
+					type: 'boolean',
+					label: i18n.ts.public,
+					default: false,
+				},
+			});
+			if (canceled) return;
+
+			const clip = await os.apiWithDialog('clips/create', result);
+
+			clipsCache.delete();
+
+			claimAchievement('noteClipped1');
+			os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id });
+		},
+	}];
+}
 
 export function getNoteMenu(props: {
 	note: misskey.entities.Note;
@@ -208,64 +283,7 @@ export function getNoteMenu(props: {
 				type: 'parent',
 				icon: 'ti ti-paperclip',
 				text: i18n.ts.clip,
-				children: async () => {
-					const clips = await os.api('clips/list');
-					return [{
-						icon: 'ti ti-plus',
-						text: i18n.ts.createNew,
-						action: async () => {
-							const { canceled, result } = await os.form(i18n.ts.createNewClip, {
-								name: {
-									type: 'string',
-									label: i18n.ts.name,
-								},
-								description: {
-									type: 'string',
-									required: false,
-									multiline: true,
-									label: i18n.ts.description,
-								},
-								isPublic: {
-									type: 'boolean',
-									label: i18n.ts.public,
-									default: false,
-								},
-							});
-							if (canceled) return;
-
-							const clip = await os.apiWithDialog('clips/create', result);
-
-							claimAchievement('noteClipped1');
-							os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id });
-						},
-					}, null, ...clips.map(clip => ({
-						text: clip.name,
-						action: () => {
-							claimAchievement('noteClipped1');
-							os.promiseDialog(
-								os.api('clips/add-note', { clipId: clip.id, noteId: appearNote.id }),
-								null,
-								async (err) => {
-									if (err.id === '734806c4-542c-463a-9311-15c512803965') {
-										const confirm = await os.confirm({
-											type: 'warning',
-											text: i18n.t('confirmToUnclipAlreadyClippedNote', { name: clip.name }),
-										});
-										if (!confirm.canceled) {
-											os.apiWithDialog('clips/remove-note', { clipId: clip.id, noteId: appearNote.id });
-											if (props.currentClipPage?.value.id === clip.id) props.isDeleted.value = true;
-										}
-									} else {
-										os.alert({
-											type: 'error',
-											text: err.message + '\n' + err.id,
-										});
-									}
-								},
-							);
-						},
-					}))];
-				},
+				children: () => getNoteClipMenu(props),
 			},
 			statePromise.then(state => state.isMutedThread ? {
 				icon: 'ti ti-message-off',
diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts
index d7eb331183a69bebe672dcdbeb3c0cf85a094f29..fe941c77b27fa1fb9735420894a892f1545b4f46 100644
--- a/packages/frontend/src/scripts/get-user-menu.ts
+++ b/packages/frontend/src/scripts/get-user-menu.ts
@@ -8,6 +8,7 @@ import { userActions } from '@/store';
 import { $i, iAmModerator } from '@/account';
 import { mainRouter } from '@/router';
 import { Router } from '@/nirax';
+import { rolesCache, userListsCache } from '@/cache';
 
 export function getUserMenu(user: misskey.entities.UserDetailed, router: Router = mainRouter) {
 	const meId = $i ? $i.id : null;
@@ -126,7 +127,7 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router
 		icon: 'ti ti-list',
 		text: i18n.ts.addToList,
 		children: async () => {
-			const lists = await os.api('users/lists/list');
+			const lists = await userListsCache.fetch(() => os.api('users/lists/list'));
 
 			return lists.map(list => ({
 				text: list.name,
@@ -147,7 +148,7 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router
 				icon: 'ti ti-badges',
 				text: i18n.ts.roles,
 				children: async () => {
-					const roles = await os.api('admin/roles/list');
+					const roles = await rolesCache.fetch(() => os.api('admin/roles/list'));
 
 					return roles.filter(r => r.target === 'manual').map(r => ({
 						text: r.name,
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 3d87234f417ae41811f55f488b0b022a0be595f8..c3cf48afc4cbf9d5b51cd79dba87ee633025e0c8 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -290,6 +290,10 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'device',
 		default: false,
 	},
+	showClipButtonInNoteFooter: {
+		where: 'device',
+		default: false,
+	},
 	aiChanMode: {
 		where: 'device',
 		default: false,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d2397b154ec671f65169c7e5038c49ee8ea0429f..4cc42f17882085b2b1ecd3ae4e5c491066aa2098 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -48,6 +48,9 @@ importers:
 
   packages/backend:
     specifiers:
+      '@aws-sdk/client-s3': ^3.294.0
+      '@aws-sdk/lib-storage': ^3.294.0
+      '@aws-sdk/node-http-handler': ^3.292.0
       '@bull-board/api': 5.0.0
       '@bull-board/fastify': 5.0.0
       '@bull-board/ui': 5.0.0
@@ -127,7 +130,7 @@ importers:
       ajv: 8.12.0
       archiver: 5.3.1
       autwh: 0.1.0
-      aws-sdk: 2.1318.0
+      aws-sdk-client-mock: ^2.1.1
       bcryptjs: 2.4.3
       blurhash: 2.0.5
       bull: 4.10.4
@@ -219,6 +222,9 @@ importers:
       ws: 8.12.1
       xev: 3.0.2
     dependencies:
+      '@aws-sdk/client-s3': 3.294.0
+      '@aws-sdk/lib-storage': 3.294.0_@aws-sdk+client-s3@3.294.0
+      '@aws-sdk/node-http-handler': 3.292.0
       '@bull-board/api': 5.0.0
       '@bull-board/fastify': 5.0.0
       '@bull-board/ui': 5.0.0
@@ -241,7 +247,6 @@ importers:
       ajv: 8.12.0
       archiver: 5.3.1
       autwh: 0.1.0
-      aws-sdk: 2.1318.0
       bcryptjs: 2.4.3
       blurhash: 2.0.5
       bull: 4.10.4
@@ -385,6 +390,7 @@ importers:
       '@types/ws': 8.5.4
       '@typescript-eslint/eslint-plugin': 5.54.1_mlk7dnz565t663n4razh6a6v6i
       '@typescript-eslint/parser': 5.54.1_ycpbpc6yetojsgtrx3mwntkhsu
+      aws-sdk-client-mock: 2.1.1
       cross-env: 7.0.3
       eslint: 8.35.0
       eslint-plugin-import: 2.27.5_uyiasnnzcqrxqkfvjklwnmwcha
@@ -645,6 +651,987 @@ packages:
       '@jridgewell/trace-mapping': 0.3.17
     dev: true
 
+  /@aws-crypto/crc32/3.0.0:
+    resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==}
+    dependencies:
+      '@aws-crypto/util': 3.0.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 1.14.1
+    dev: false
+
+  /@aws-crypto/crc32c/3.0.0:
+    resolution: {integrity: sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==}
+    dependencies:
+      '@aws-crypto/util': 3.0.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 1.14.1
+    dev: false
+
+  /@aws-crypto/ie11-detection/3.0.0:
+    resolution: {integrity: sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==}
+    dependencies:
+      tslib: 1.14.1
+    dev: false
+
+  /@aws-crypto/sha1-browser/3.0.0:
+    resolution: {integrity: sha512-NJth5c997GLHs6nOYTzFKTbYdMNA6/1XlKVgnZoaZcQ7z7UJlOgj2JdbHE8tiYLS3fzXNCguct77SPGat2raSw==}
+    dependencies:
+      '@aws-crypto/ie11-detection': 3.0.0
+      '@aws-crypto/supports-web-crypto': 3.0.0
+      '@aws-crypto/util': 3.0.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/util-locate-window': 3.208.0
+      '@aws-sdk/util-utf8-browser': 3.259.0
+      tslib: 1.14.1
+    dev: false
+
+  /@aws-crypto/sha256-browser/3.0.0:
+    resolution: {integrity: sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==}
+    dependencies:
+      '@aws-crypto/ie11-detection': 3.0.0
+      '@aws-crypto/sha256-js': 3.0.0
+      '@aws-crypto/supports-web-crypto': 3.0.0
+      '@aws-crypto/util': 3.0.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/util-locate-window': 3.208.0
+      '@aws-sdk/util-utf8-browser': 3.259.0
+      tslib: 1.14.1
+    dev: false
+
+  /@aws-crypto/sha256-js/3.0.0:
+    resolution: {integrity: sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==}
+    dependencies:
+      '@aws-crypto/util': 3.0.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 1.14.1
+    dev: false
+
+  /@aws-crypto/supports-web-crypto/3.0.0:
+    resolution: {integrity: sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==}
+    dependencies:
+      tslib: 1.14.1
+    dev: false
+
+  /@aws-crypto/util/3.0.0:
+    resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==}
+    dependencies:
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/util-utf8-browser': 3.259.0
+      tslib: 1.14.1
+    dev: false
+
+  /@aws-sdk/abort-controller/3.292.0:
+    resolution: {integrity: sha512-lf+OPptL01kvryIJy7+dvFux5KbJ6OTwLPPEekVKZ2AfEvwcVtOZWFUhyw3PJCBTVncjKB1Kjl3V/eTS3YuPXQ==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/chunked-blob-reader-native/3.292.0:
+    resolution: {integrity: sha512-A34sBrnggm9mXPZeeEie4jDv9zHRMS0LSm85VkfrBLuYYsfsw9DxmW59wJkuo6DIm/RK04oH5+lRMt34koBgrw==}
+    dependencies:
+      '@aws-sdk/util-base64': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/chunked-blob-reader/3.292.0:
+    resolution: {integrity: sha512-ccFPnzBjLbDCmFjTXwhsfD58vtEiAjbor3A9tvnou+3Dj6RrMEGPaTu5tcw3mwWb2zh1K3HFJg6Bmb0no49TRw==}
+    dependencies:
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/client-s3/3.294.0:
+    resolution: {integrity: sha512-J0rTBpZlmeNWgpYaGM7w55Hdmh8LWfYFmb09Fr0Oee/VGFgi28p3vCCnP+ploo1TlFRdsPlGZJ7zod+m/iPeBg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-crypto/sha1-browser': 3.0.0
+      '@aws-crypto/sha256-browser': 3.0.0
+      '@aws-crypto/sha256-js': 3.0.0
+      '@aws-sdk/client-sts': 3.294.0
+      '@aws-sdk/config-resolver': 3.292.0
+      '@aws-sdk/credential-provider-node': 3.294.0
+      '@aws-sdk/eventstream-serde-browser': 3.292.0
+      '@aws-sdk/eventstream-serde-config-resolver': 3.292.0
+      '@aws-sdk/eventstream-serde-node': 3.292.0
+      '@aws-sdk/fetch-http-handler': 3.292.0
+      '@aws-sdk/hash-blob-browser': 3.292.0
+      '@aws-sdk/hash-node': 3.292.0
+      '@aws-sdk/hash-stream-node': 3.292.0
+      '@aws-sdk/invalid-dependency': 3.292.0
+      '@aws-sdk/md5-js': 3.292.0
+      '@aws-sdk/middleware-bucket-endpoint': 3.292.0
+      '@aws-sdk/middleware-content-length': 3.292.0
+      '@aws-sdk/middleware-endpoint': 3.292.0
+      '@aws-sdk/middleware-expect-continue': 3.292.0
+      '@aws-sdk/middleware-flexible-checksums': 3.292.0
+      '@aws-sdk/middleware-host-header': 3.292.0
+      '@aws-sdk/middleware-location-constraint': 3.292.0
+      '@aws-sdk/middleware-logger': 3.292.0
+      '@aws-sdk/middleware-recursion-detection': 3.292.0
+      '@aws-sdk/middleware-retry': 3.293.0
+      '@aws-sdk/middleware-sdk-s3': 3.292.0
+      '@aws-sdk/middleware-serde': 3.292.0
+      '@aws-sdk/middleware-signing': 3.292.0
+      '@aws-sdk/middleware-ssec': 3.292.0
+      '@aws-sdk/middleware-stack': 3.292.0
+      '@aws-sdk/middleware-user-agent': 3.293.0
+      '@aws-sdk/node-config-provider': 3.292.0
+      '@aws-sdk/node-http-handler': 3.292.0
+      '@aws-sdk/protocol-http': 3.292.0
+      '@aws-sdk/signature-v4-multi-region': 3.292.0
+      '@aws-sdk/smithy-client': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/url-parser': 3.292.0
+      '@aws-sdk/util-base64': 3.292.0
+      '@aws-sdk/util-body-length-browser': 3.292.0
+      '@aws-sdk/util-body-length-node': 3.292.0
+      '@aws-sdk/util-defaults-mode-browser': 3.292.0
+      '@aws-sdk/util-defaults-mode-node': 3.292.0
+      '@aws-sdk/util-endpoints': 3.293.0
+      '@aws-sdk/util-retry': 3.292.0
+      '@aws-sdk/util-stream-browser': 3.292.0
+      '@aws-sdk/util-stream-node': 3.292.0
+      '@aws-sdk/util-user-agent-browser': 3.292.0
+      '@aws-sdk/util-user-agent-node': 3.292.0
+      '@aws-sdk/util-utf8': 3.292.0
+      '@aws-sdk/util-waiter': 3.292.0
+      '@aws-sdk/xml-builder': 3.292.0
+      fast-xml-parser: 4.1.2
+      tslib: 2.5.0
+    transitivePeerDependencies:
+      - '@aws-sdk/signature-v4-crt'
+      - aws-crt
+    dev: false
+
+  /@aws-sdk/client-sso-oidc/3.294.0:
+    resolution: {integrity: sha512-/ZfDud76MdSPJ/TxjV2xLE30XbBQDZwKQ32axwoK1eziPvrAIUBYVgpBwj+m0quhoiQhBKkg3aFl6j39AF2thw==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-crypto/sha256-browser': 3.0.0
+      '@aws-crypto/sha256-js': 3.0.0
+      '@aws-sdk/config-resolver': 3.292.0
+      '@aws-sdk/fetch-http-handler': 3.292.0
+      '@aws-sdk/hash-node': 3.292.0
+      '@aws-sdk/invalid-dependency': 3.292.0
+      '@aws-sdk/middleware-content-length': 3.292.0
+      '@aws-sdk/middleware-endpoint': 3.292.0
+      '@aws-sdk/middleware-host-header': 3.292.0
+      '@aws-sdk/middleware-logger': 3.292.0
+      '@aws-sdk/middleware-recursion-detection': 3.292.0
+      '@aws-sdk/middleware-retry': 3.293.0
+      '@aws-sdk/middleware-serde': 3.292.0
+      '@aws-sdk/middleware-stack': 3.292.0
+      '@aws-sdk/middleware-user-agent': 3.293.0
+      '@aws-sdk/node-config-provider': 3.292.0
+      '@aws-sdk/node-http-handler': 3.292.0
+      '@aws-sdk/protocol-http': 3.292.0
+      '@aws-sdk/smithy-client': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/url-parser': 3.292.0
+      '@aws-sdk/util-base64': 3.292.0
+      '@aws-sdk/util-body-length-browser': 3.292.0
+      '@aws-sdk/util-body-length-node': 3.292.0
+      '@aws-sdk/util-defaults-mode-browser': 3.292.0
+      '@aws-sdk/util-defaults-mode-node': 3.292.0
+      '@aws-sdk/util-endpoints': 3.293.0
+      '@aws-sdk/util-retry': 3.292.0
+      '@aws-sdk/util-user-agent-browser': 3.292.0
+      '@aws-sdk/util-user-agent-node': 3.292.0
+      '@aws-sdk/util-utf8': 3.292.0
+      tslib: 2.5.0
+    transitivePeerDependencies:
+      - aws-crt
+    dev: false
+
+  /@aws-sdk/client-sso/3.294.0:
+    resolution: {integrity: sha512-+FuxQTi5WvnaXM5JbNLkBIzQ3An4gA0ox61N1u+3xled+nywKb1yQ7WmRpyMG5bLbkmnj3aqoo5/uskFc4c4EA==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-crypto/sha256-browser': 3.0.0
+      '@aws-crypto/sha256-js': 3.0.0
+      '@aws-sdk/config-resolver': 3.292.0
+      '@aws-sdk/fetch-http-handler': 3.292.0
+      '@aws-sdk/hash-node': 3.292.0
+      '@aws-sdk/invalid-dependency': 3.292.0
+      '@aws-sdk/middleware-content-length': 3.292.0
+      '@aws-sdk/middleware-endpoint': 3.292.0
+      '@aws-sdk/middleware-host-header': 3.292.0
+      '@aws-sdk/middleware-logger': 3.292.0
+      '@aws-sdk/middleware-recursion-detection': 3.292.0
+      '@aws-sdk/middleware-retry': 3.293.0
+      '@aws-sdk/middleware-serde': 3.292.0
+      '@aws-sdk/middleware-stack': 3.292.0
+      '@aws-sdk/middleware-user-agent': 3.293.0
+      '@aws-sdk/node-config-provider': 3.292.0
+      '@aws-sdk/node-http-handler': 3.292.0
+      '@aws-sdk/protocol-http': 3.292.0
+      '@aws-sdk/smithy-client': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/url-parser': 3.292.0
+      '@aws-sdk/util-base64': 3.292.0
+      '@aws-sdk/util-body-length-browser': 3.292.0
+      '@aws-sdk/util-body-length-node': 3.292.0
+      '@aws-sdk/util-defaults-mode-browser': 3.292.0
+      '@aws-sdk/util-defaults-mode-node': 3.292.0
+      '@aws-sdk/util-endpoints': 3.293.0
+      '@aws-sdk/util-retry': 3.292.0
+      '@aws-sdk/util-user-agent-browser': 3.292.0
+      '@aws-sdk/util-user-agent-node': 3.292.0
+      '@aws-sdk/util-utf8': 3.292.0
+      tslib: 2.5.0
+    transitivePeerDependencies:
+      - aws-crt
+    dev: false
+
+  /@aws-sdk/client-sts/3.294.0:
+    resolution: {integrity: sha512-AefqwhFjTDzelZuSYhriJbiI+GQwf2yKiKAnCt0gRj6rswewStM63Gtlhfb01sFPp+ZiqPcyQ47LqUaHp1mz/g==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-crypto/sha256-browser': 3.0.0
+      '@aws-crypto/sha256-js': 3.0.0
+      '@aws-sdk/config-resolver': 3.292.0
+      '@aws-sdk/credential-provider-node': 3.294.0
+      '@aws-sdk/fetch-http-handler': 3.292.0
+      '@aws-sdk/hash-node': 3.292.0
+      '@aws-sdk/invalid-dependency': 3.292.0
+      '@aws-sdk/middleware-content-length': 3.292.0
+      '@aws-sdk/middleware-endpoint': 3.292.0
+      '@aws-sdk/middleware-host-header': 3.292.0
+      '@aws-sdk/middleware-logger': 3.292.0
+      '@aws-sdk/middleware-recursion-detection': 3.292.0
+      '@aws-sdk/middleware-retry': 3.293.0
+      '@aws-sdk/middleware-sdk-sts': 3.292.0
+      '@aws-sdk/middleware-serde': 3.292.0
+      '@aws-sdk/middleware-signing': 3.292.0
+      '@aws-sdk/middleware-stack': 3.292.0
+      '@aws-sdk/middleware-user-agent': 3.293.0
+      '@aws-sdk/node-config-provider': 3.292.0
+      '@aws-sdk/node-http-handler': 3.292.0
+      '@aws-sdk/protocol-http': 3.292.0
+      '@aws-sdk/smithy-client': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/url-parser': 3.292.0
+      '@aws-sdk/util-base64': 3.292.0
+      '@aws-sdk/util-body-length-browser': 3.292.0
+      '@aws-sdk/util-body-length-node': 3.292.0
+      '@aws-sdk/util-defaults-mode-browser': 3.292.0
+      '@aws-sdk/util-defaults-mode-node': 3.292.0
+      '@aws-sdk/util-endpoints': 3.293.0
+      '@aws-sdk/util-retry': 3.292.0
+      '@aws-sdk/util-user-agent-browser': 3.292.0
+      '@aws-sdk/util-user-agent-node': 3.292.0
+      '@aws-sdk/util-utf8': 3.292.0
+      fast-xml-parser: 4.1.2
+      tslib: 2.5.0
+    transitivePeerDependencies:
+      - aws-crt
+    dev: false
+
+  /@aws-sdk/config-resolver/3.292.0:
+    resolution: {integrity: sha512-cB3twnNR7vYvlt2jvw8VlA1+iv/tVzl+/S39MKqw2tepU+AbJAM0EHwb/dkf1OKSmlrnANXhshx80MHF9zL4mA==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/signature-v4': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/util-config-provider': 3.292.0
+      '@aws-sdk/util-middleware': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/credential-provider-env/3.292.0:
+    resolution: {integrity: sha512-YbafSG0ZEKE2969CJWVtUhh3hfOeLPecFVoXOtegCyAJgY5Ghtu4TsVhL4DgiGAgOC30ojAmUVQEXzd7xJF5xA==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/property-provider': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/credential-provider-imds/3.292.0:
+    resolution: {integrity: sha512-W/peOgDSRYulgzFpUhvgi1pCm6piBz6xrVN17N4QOy+3NHBXRVMVzYk6ct2qpLPgJUSEZkcpP+Gds+bBm8ed1A==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/node-config-provider': 3.292.0
+      '@aws-sdk/property-provider': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/url-parser': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/credential-provider-ini/3.294.0:
+    resolution: {integrity: sha512-pdTPbaAb5bWA+DnuKoL2TpXeNDp6Ejpv/OYt+bw2gdzl9w5r/ZCtUTTbW+Vvejr4WL5s3c1bY96kwdqCn7iLqA==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/credential-provider-env': 3.292.0
+      '@aws-sdk/credential-provider-imds': 3.292.0
+      '@aws-sdk/credential-provider-process': 3.292.0
+      '@aws-sdk/credential-provider-sso': 3.294.0
+      '@aws-sdk/credential-provider-web-identity': 3.292.0
+      '@aws-sdk/property-provider': 3.292.0
+      '@aws-sdk/shared-ini-file-loader': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    transitivePeerDependencies:
+      - aws-crt
+    dev: false
+
+  /@aws-sdk/credential-provider-node/3.294.0:
+    resolution: {integrity: sha512-zUL1Qhb4BsQIZCs/TPpG4oIYH/9YsGiS+Se1tasSGjTOLfBy7jhOZ0QIdpEeyAx/EP8blOBredM9xWfEXgiHVA==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/credential-provider-env': 3.292.0
+      '@aws-sdk/credential-provider-imds': 3.292.0
+      '@aws-sdk/credential-provider-ini': 3.294.0
+      '@aws-sdk/credential-provider-process': 3.292.0
+      '@aws-sdk/credential-provider-sso': 3.294.0
+      '@aws-sdk/credential-provider-web-identity': 3.292.0
+      '@aws-sdk/property-provider': 3.292.0
+      '@aws-sdk/shared-ini-file-loader': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    transitivePeerDependencies:
+      - aws-crt
+    dev: false
+
+  /@aws-sdk/credential-provider-process/3.292.0:
+    resolution: {integrity: sha512-CFVXuMuUvg/a4tknzRikEDwZBnKlHs1LZCpTXIGjBdUTdosoi4WNzDLzGp93ZRTtcgFz+4wirz2f7P3lC0NrQw==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/property-provider': 3.292.0
+      '@aws-sdk/shared-ini-file-loader': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/credential-provider-sso/3.294.0:
+    resolution: {integrity: sha512-UxrcAA/0l7j9+3tolYcG5M61D/IE1Bjd/9H87H1i2A2BrwUUBhW1Dp/vvROEDrrywlMDG3CDF3T/7ADtTak+sg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/client-sso': 3.294.0
+      '@aws-sdk/property-provider': 3.292.0
+      '@aws-sdk/shared-ini-file-loader': 3.292.0
+      '@aws-sdk/token-providers': 3.294.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    transitivePeerDependencies:
+      - aws-crt
+    dev: false
+
+  /@aws-sdk/credential-provider-web-identity/3.292.0:
+    resolution: {integrity: sha512-4DbtIEM9gGVfqYlMdYXg3XY+vBhemjB1zXIequottW8loLYM8Vuz4/uGxxKNze6evVVzowsA0wKrYclE1aj/Rg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/property-provider': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/eventstream-codec/3.292.0:
+    resolution: {integrity: sha512-P0np4vhCKf/JH6I39Id8DxZR+UZzG+Br+vOrTinerMfOhzTa2229XmL8pwlMpOoxnJLMPmEDtD1KQqLslBEXtw==}
+    dependencies:
+      '@aws-crypto/crc32': 3.0.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/util-hex-encoding': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/eventstream-serde-browser/3.292.0:
+    resolution: {integrity: sha512-VzRbJqqE444GOuoNTxTJ1dC1IhNhA6jfHjgsI8iDRHraaEukGqsPx1vkc+byxrDEjgxKN5IqOwZ4yJWMIAozBA==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/eventstream-serde-universal': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/eventstream-serde-config-resolver/3.292.0:
+    resolution: {integrity: sha512-Ndx+qJyWmBCW9FSm68AGLoO4AZ0AaL/wjpJEgFF2sZBWjYe9O9PB9IGR/yuqCBTElf3YtSiFMsloikQaz2ft6g==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/eventstream-serde-node/3.292.0:
+    resolution: {integrity: sha512-NFCEiNCetNye7jQfRd5y/7J9dLg9+uL57698wYeXeadlwJ8Cd/Nhsz+t7RIbP05VqshU+anXARMB1avl9oAijQ==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/eventstream-serde-universal': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/eventstream-serde-universal/3.292.0:
+    resolution: {integrity: sha512-1gqZNx+S1EUpl3Tq6uIesiDx8gnkpXqPsFfCZT7lSWWXBpnHmnUZAh3jbiO9UlQbYuB9SfT0EBKb1iOY9z4j1Q==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/eventstream-codec': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/fetch-http-handler/3.292.0:
+    resolution: {integrity: sha512-zh3bhUJbL8RSa39ZKDcy+AghtUkIP8LwcNlwRIoxMQh3Row4D1s4fCq0KZCx98NJBEXoiTLyTQlZxxI//BOb1Q==}
+    dependencies:
+      '@aws-sdk/protocol-http': 3.292.0
+      '@aws-sdk/querystring-builder': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/util-base64': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/hash-blob-browser/3.292.0:
+    resolution: {integrity: sha512-4+Fm4IOkxGqgx8dU0EbExCq6xx30y369ZSXz89h9YDQYdJ2Muw7iNCHAg/4VM+gfp0vo9J8zPOTsSju8LNS5Jg==}
+    dependencies:
+      '@aws-sdk/chunked-blob-reader': 3.292.0
+      '@aws-sdk/chunked-blob-reader-native': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/hash-node/3.292.0:
+    resolution: {integrity: sha512-1yLxmIsvE+eK36JXEgEIouTITdykQLVhsA5Oai//Lar6Ddgu1sFpLDbdkMtKbrh4I0jLN9RacNCkeVQjZPTCCQ==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/util-buffer-from': 3.292.0
+      '@aws-sdk/util-utf8': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/hash-stream-node/3.292.0:
+    resolution: {integrity: sha512-p2nj9A5lZKQU45Q4Od3iZDvpziEpojAyuyAI0HPzpIuJIfzFQ0/7pMBKde1li6wq93rpyFLwNufV6FEZnKCYRg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/util-utf8': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/invalid-dependency/3.292.0:
+    resolution: {integrity: sha512-39OUV78CD3TmEbjhpt+V+Fk4wAGWhixqHxDSN8+4WL0uB4Fl7k5m3Z9hNY78AttHQSl2twR7WtLztnXPAFsriw==}
+    dependencies:
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/is-array-buffer/3.292.0:
+    resolution: {integrity: sha512-kW/G5T/fzI0sJH5foZG6XJiNCevXqKLxV50qIT4B1pMuw7regd4ALIy0HwSqj1nnn9mSbRWBfmby0jWCJsMcwg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/lib-storage/3.294.0_@aws-sdk+client-s3@3.294.0:
+    resolution: {integrity: sha512-5H/1EgGDIt8Ls/YOepfkyyBwkyQ9d668/gmnWGWRvytar+cVMHu/D5G88831luPrlzyZ+jR+Te7Nc2oqYqamTw==}
+    engines: {node: '>=14.0.0'}
+    peerDependencies:
+      '@aws-sdk/abort-controller': ^3.0.0
+      '@aws-sdk/client-s3': ^3.0.0
+    dependencies:
+      '@aws-sdk/client-s3': 3.294.0
+      '@aws-sdk/middleware-endpoint': 3.292.0
+      '@aws-sdk/smithy-client': 3.292.0
+      buffer: 5.6.0
+      events: 3.3.0
+      stream-browserify: 3.0.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/md5-js/3.292.0:
+    resolution: {integrity: sha512-ngfsKLgQenXW3EbsDf47PVNys1SecTbsq6k88h7+Aa8BU49+9ZOIz4VDpWuPiNyYpeV7jJdl1dfD+ujOYvvgNw==}
+    dependencies:
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/util-utf8': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/middleware-bucket-endpoint/3.292.0:
+    resolution: {integrity: sha512-XRy9RSUIRcbxYfH504ywhQllgfdf3wVhk2k0mMPYnUbeEhAFe1/eUog2v/bi07/q5TQ4Hppi+W3nHCVualQEow==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/protocol-http': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/util-arn-parser': 3.292.0
+      '@aws-sdk/util-config-provider': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/middleware-content-length/3.292.0:
+    resolution: {integrity: sha512-2gMWzQus5mj14menolpPDbYBeaOYcj7KNFZOjTjjI3iQ0KqyetG6XasirNrcJ/8QX1BRmpTol8Xjp2Ue3Gbzwg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/protocol-http': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/middleware-endpoint/3.292.0:
+    resolution: {integrity: sha512-cPMkiSxpZGG6tYlW4OS+ucS6r43f9ddX9kcUoemJCY10MOuogdPjulCAjE0HTs2PLKSOrrG4CTP4Q4wWDrH4Bw==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/middleware-serde': 3.292.0
+      '@aws-sdk/protocol-http': 3.292.0
+      '@aws-sdk/signature-v4': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/url-parser': 3.292.0
+      '@aws-sdk/util-config-provider': 3.292.0
+      '@aws-sdk/util-middleware': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/middleware-expect-continue/3.292.0:
+    resolution: {integrity: sha512-bZ2bsBud3E6BebZWGxVcWxBSg09bP0KyX8PT0jI66JM0yTbZSJhoGhlKAqfNG46R9h4K5tCYB2uYgV/3oU/ZpQ==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/protocol-http': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/middleware-flexible-checksums/3.292.0:
+    resolution: {integrity: sha512-AxU/Gb+TRdl/0jHmbreYh3QnB0jR25zgjPZ4/JbGBJ2SQI9jm3LCNK9XOrPUmZp/vu9wsvyxtmKQidpQ5+FX5w==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-crypto/crc32': 3.0.0
+      '@aws-crypto/crc32c': 3.0.0
+      '@aws-sdk/is-array-buffer': 3.292.0
+      '@aws-sdk/protocol-http': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/util-utf8': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/middleware-host-header/3.292.0:
+    resolution: {integrity: sha512-mHuCWe3Yg2S5YZ7mB7sKU6C97XspfqrimWjMW9pfV2usAvLA3R0HrB03jpR5vpZ3P4q7HB6wK3S6CjYMGGRNag==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/protocol-http': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/middleware-location-constraint/3.292.0:
+    resolution: {integrity: sha512-WTbMyoCckdkmq7Yok0gI4226gTmxP/zM1fbFiC+liZXBJ+H5EvIFmu30tWbX+4m41LL/XQVm65olXJFwhoExGQ==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/middleware-logger/3.292.0:
+    resolution: {integrity: sha512-yZNY1XYmG3NG+uonET7jzKXNiwu61xm/ZZ6i/l51SusuaYN+qQtTAhOFsieQqTehF9kP4FzbsWgPDwD8ZZX9lw==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/middleware-recursion-detection/3.292.0:
+    resolution: {integrity: sha512-kA3VZpPko0Zqd7CYPTKAxhjEv0HJqFu2054L04dde1JLr43ro+2MTdX7vsHzeAFUVRphqatFFofCumvXmU6Mig==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/protocol-http': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/middleware-retry/3.293.0:
+    resolution: {integrity: sha512-7tiaz2GzRecNHaZ6YnF+Nrtk3au8qF6oiipf11R7MJiqJ0fkMLnz/iRrlakDziS9qF/a9v+3yxb4W4NHK3f4Tw==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/protocol-http': 3.292.0
+      '@aws-sdk/service-error-classification': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/util-middleware': 3.292.0
+      '@aws-sdk/util-retry': 3.292.0
+      tslib: 2.5.0
+      uuid: 8.3.2
+    dev: false
+
+  /@aws-sdk/middleware-sdk-s3/3.292.0:
+    resolution: {integrity: sha512-kEUmh3ZM34H+2bEQfpZhVotJCNYpSbq9Q4YxlWVbnjiO/VS+S9BFEM3Fcj5+EzEgI02tNNi6/qTXj3iS8tT6hA==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/protocol-http': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/util-arn-parser': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/middleware-sdk-sts/3.292.0:
+    resolution: {integrity: sha512-GN5ZHEqXZqDi+HkVbaXRX9HaW/vA5rikYpWKYsmxTUZ7fB7ijvEO3co3lleJv2C+iGYRtUIHC4wYNB5xgoTCxg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/middleware-signing': 3.292.0
+      '@aws-sdk/property-provider': 3.292.0
+      '@aws-sdk/protocol-http': 3.292.0
+      '@aws-sdk/signature-v4': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/middleware-serde/3.292.0:
+    resolution: {integrity: sha512-6hN9mTQwSvV8EcGvtXbS/MpK7WMCokUku5Wu7X24UwCNMVkoRHLIkYcxHcvBTwttuOU0d8hph1/lIX4dkLwkQw==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/middleware-signing/3.292.0:
+    resolution: {integrity: sha512-GVfoSjDjEQ4TaO6x9MffyP3uRV+2KcS5FtexLCYOM9pJcnE9tqq9FJOrZ1xl1g+YjUVKxo4x8lu3tpEtIb17qg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/property-provider': 3.292.0
+      '@aws-sdk/protocol-http': 3.292.0
+      '@aws-sdk/signature-v4': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/util-middleware': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/middleware-ssec/3.292.0:
+    resolution: {integrity: sha512-VfwrTEs9nYU6sCnt/cffhnJ2djGkMyMbBEysMZm2HEbFMloGKBd0Wtvk9y+SWPa6+DDRe2CqqX8jMzrO4JT4Eg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/middleware-stack/3.292.0:
+    resolution: {integrity: sha512-WdQpRkuMysrEwrkByCM1qCn2PPpFGGQ2iXqaFha5RzCdZDlxJni9cVNb6HzWUcgjLEYVTXCmOR9Wxm3CNW44Qg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/middleware-user-agent/3.293.0:
+    resolution: {integrity: sha512-gZ7/e6XwpKk9mvgA78q4Ffc796jTn02TUKx2qMDnkLVbeJXBNN2jnvYEKq8v70+o7fd/ALRudg8gBDmkkhM/Hw==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/protocol-http': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/util-endpoints': 3.293.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/node-config-provider/3.292.0:
+    resolution: {integrity: sha512-S3NnC9dQ5GIbJYSDIldZb4zdpCOEua1tM7bjYL3VS5uqCEM93kIi/o/UkIUveMp/eqTS2LJa5HjNIz5Te6je0A==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/property-provider': 3.292.0
+      '@aws-sdk/shared-ini-file-loader': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/node-http-handler/3.292.0:
+    resolution: {integrity: sha512-L/E3UDSwXLXjt1XWWh0RBD55F+aZI1AEdPwdES9i1PjnZLyuxuDhEDptVibNN56+I9/4Q3SbmuVRVlOD0uzBag==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/abort-controller': 3.292.0
+      '@aws-sdk/protocol-http': 3.292.0
+      '@aws-sdk/querystring-builder': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/property-provider/3.292.0:
+    resolution: {integrity: sha512-dHArSvsiqhno/g55N815gXmAMrmN8DP7OeFNqJ4wJG42xsF2PFN3DAsjIuHuXMwu+7A3R1LHqIpvv0hA9KeoJQ==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/protocol-http/3.292.0:
+    resolution: {integrity: sha512-NLi4fq3k41aXIh1I97yX0JTy+3p6aW1NdwFwdMa674z86QNfb4SfRQRZBQe9wEnAZ/eWHVnlKIuII+U1URk/Kg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/querystring-builder/3.292.0:
+    resolution: {integrity: sha512-XElIFJaReIm24eEvBtV2dOtZvcm3gXsGu/ftG8MLJKbKXFKpAP1q+K6En0Bs7/T88voKghKdKpKT+eZUWgTqlg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/util-uri-escape': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/querystring-parser/3.292.0:
+    resolution: {integrity: sha512-iTYpYo7a8X9RxiPbjjewIpm6XQPx2EOcF1dWCPRII9EFlmZ4bwnX+PDI36fIo9oVs8TIKXmwNGODU9nsg7CSAw==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/service-error-classification/3.292.0:
+    resolution: {integrity: sha512-X1k3sixCeC45XSNHBe+kRBQBwPDyTFtFITb8O5Qw4dS9XWGhrUJT4CX0qE5aj8qP3F9U5nRizs9c2mBVVP0Caw==}
+    engines: {node: '>=14.0.0'}
+    dev: false
+
+  /@aws-sdk/shared-ini-file-loader/3.292.0:
+    resolution: {integrity: sha512-Av2TTYg1Jig2kbkD56ybiqZJB6vVrYjv1W5UQwY/q3nA/T2mcrgQ20ByCOt5Bv9VvY7FSgC+znj+L4a7RLGmBg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/signature-v4-multi-region/3.292.0:
+    resolution: {integrity: sha512-MjWEIjbAr7n9vsFeLpoRzNSYFgWOROf1mLj6Db8TfRowaortUBO7PbleLV4n3SPujSnxhaVBzlmnCY2AjatH9g==}
+    engines: {node: '>=14.0.0'}
+    peerDependencies:
+      '@aws-sdk/signature-v4-crt': ^3.118.0
+    peerDependenciesMeta:
+      '@aws-sdk/signature-v4-crt':
+        optional: true
+    dependencies:
+      '@aws-sdk/protocol-http': 3.292.0
+      '@aws-sdk/signature-v4': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/util-arn-parser': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/signature-v4/3.292.0:
+    resolution: {integrity: sha512-+rw47VY5mvBecn13tDQTl1ipGWg5tE63faWgmZe68HoBL87ZiDzsd7bUKOvjfW21iMgWlwAppkaNNQayYRb2zg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/is-array-buffer': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/util-hex-encoding': 3.292.0
+      '@aws-sdk/util-middleware': 3.292.0
+      '@aws-sdk/util-uri-escape': 3.292.0
+      '@aws-sdk/util-utf8': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/smithy-client/3.292.0:
+    resolution: {integrity: sha512-S8PKzjPkZ6SXYZuZiU787dMsvQ0d/LFEhw2OI4Oe2An9Fc2IwJ2FYukyHoQJOV2tV0DiuMebPo7eMyQyjKElvA==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/middleware-stack': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/token-providers/3.294.0:
+    resolution: {integrity: sha512-6nwO04LtC5f4AsUvGZXyjaswuEK4Rr2VsuANpMKrPCgunRfI58a8YXLniudOSXN6e7CFJ6M3uo/h5YXqtnzGug==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/client-sso-oidc': 3.294.0
+      '@aws-sdk/property-provider': 3.292.0
+      '@aws-sdk/shared-ini-file-loader': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    transitivePeerDependencies:
+      - aws-crt
+    dev: false
+
+  /@aws-sdk/types/3.292.0:
+    resolution: {integrity: sha512-1teYAY2M73UXZxMAxqZxVS2qwXjQh0OWtt7qyLfha0TtIk/fZ1hRwFgxbDCHUFcdNBSOSbKH/ESor90KROXLCQ==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/url-parser/3.292.0:
+    resolution: {integrity: sha512-NZeAuZCk1x6TIiWuRfbOU6wHPBhf0ly2qOHzWut4BCH+b4RrDmFF8EmXcH1auEfGhE7yRyR6XqIN0t3S+hYACA==}
+    dependencies:
+      '@aws-sdk/querystring-parser': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-arn-parser/3.292.0:
+    resolution: {integrity: sha512-xfE4U94TfjMC2WNNDte/kDByf16GrQKaS0BKsm+Fk/PaeHUofEp8suOEz/EVdEoa3Ayy2Uc5QdhrGnlqf8MxeA==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-base64/3.292.0:
+    resolution: {integrity: sha512-zjNCwNdy617yFvEjZorepNWXB2sQCVfsShCwFy/kIQ5iW5tT2jQKaqc0K77diU9atkooxw9p1W9m9sOgrkOFNw==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/util-buffer-from': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-body-length-browser/3.292.0:
+    resolution: {integrity: sha512-Wd/BM+JsMiKvKs/bN3z6TredVEHh2pKudGfg3CSjTRpqFpOG903KDfyHBD42yg5PuCHoHoewJvTPKwgn7/vhaw==}
+    dependencies:
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-body-length-node/3.292.0:
+    resolution: {integrity: sha512-BBgipZ2P6RhogWE/qj0oqpdlyd3iSBYmb+aD/TBXwB2lA/X8A99GxweBd/kp06AmcJRoMS9WIXgbWkiiBlRlSA==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-buffer-from/3.292.0:
+    resolution: {integrity: sha512-RxNZjLoXNxHconH9TYsk5RaEBjSgTtozHeyIdacaHPj5vlQKi4hgL2hIfKeeNiAfQEVjaUFF29lv81xpNMzVMQ==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/is-array-buffer': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-config-provider/3.292.0:
+    resolution: {integrity: sha512-t3noYll6bPRSxeeNNEkC5czVjAiTPcsq00OwfJ2xyUqmquhLEfLwoJKmrT1uP7DjIEXdUtfoIQ2jWiIVm/oO5A==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-defaults-mode-browser/3.292.0:
+    resolution: {integrity: sha512-7+zVUlMGfa8/KT++9humHo6IDxTnxMCmWUj5jVNlkpk6h7Ecmppf7aXotviyVIA43lhtz0p2AErs0N0ekEUK+w==}
+    engines: {node: '>= 10.0.0'}
+    dependencies:
+      '@aws-sdk/property-provider': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      bowser: 2.11.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-defaults-mode-node/3.292.0:
+    resolution: {integrity: sha512-SSIw85eF4BVs0fOJRyshT+R3b/UmBPhiVKCUZm2rq6+lIGkDPiSwQU3d/80AhXtiL5SFT/IzAKKgQd8qMa7q3A==}
+    engines: {node: '>= 10.0.0'}
+    dependencies:
+      '@aws-sdk/config-resolver': 3.292.0
+      '@aws-sdk/credential-provider-imds': 3.292.0
+      '@aws-sdk/node-config-provider': 3.292.0
+      '@aws-sdk/property-provider': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-endpoints/3.293.0:
+    resolution: {integrity: sha512-R/99aNV49Refpv5guiUjEUrZYlvnfaNBniB+/ZtMO3ixxUopapssCrUivuJrmhccmrYaTCZw7dRzIWjU1jJhKg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-hex-encoding/3.292.0:
+    resolution: {integrity: sha512-qBd5KFIUywQ3qSSbj814S2srk0vfv8A6QMI+Obs1y2LHZFdQN5zViptI4UhXhKOHe+NnrHWxSuLC/LMH6q3SmA==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-locate-window/3.208.0:
+    resolution: {integrity: sha512-iua1A2+P7JJEDHVgvXrRJSvsnzG7stYSGQnBVphIUlemwl6nN5D+QrgbjECtrbxRz8asYFHSzhdhECqN+tFiBg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-middleware/3.292.0:
+    resolution: {integrity: sha512-KjhS7flfoBKDxbiBZjLjMvEizXgjfQb7GQEItgzGoI9rfGCmZtvqCcqQQoIlxb8bIzGRggAUHtBGWnlLbpb+GQ==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-retry/3.292.0:
+    resolution: {integrity: sha512-JEHyF7MpVeRF5uR4LDYgpOKcFpOPiAj8TqN46SVOQQcL1K+V7cSr7O7N7J6MwJaN9XOzAcBadeIupMm7/BFbgw==}
+    engines: {node: '>= 14.0.0'}
+    dependencies:
+      '@aws-sdk/service-error-classification': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-stream-browser/3.292.0:
+    resolution: {integrity: sha512-yzwpjq18oefyp/Sv+Z0VWh7ziRPp+qM0pDUrTfuAnXg+mrlxaPDXJOhp5LoY8AVHcDPOEdIbzz0b00G48FabIg==}
+    dependencies:
+      '@aws-sdk/fetch-http-handler': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/util-base64': 3.292.0
+      '@aws-sdk/util-hex-encoding': 3.292.0
+      '@aws-sdk/util-utf8': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-stream-node/3.292.0:
+    resolution: {integrity: sha512-p3DHXvWo4Zdka75HwewUnWjpFp/gOT4SYYEOAsv3BwuZGxfmnojK9OVCkUBJ7s6LeHMKTgGqQPwAnVFu7iIZNg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/node-http-handler': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      '@aws-sdk/util-buffer-from': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-uri-escape/3.292.0:
+    resolution: {integrity: sha512-hOQtUMQ4VcQ9iwKz50AoCp1XBD5gJ9nly/gJZccAM7zSA5mOO8RRKkbdonqquVHxrO0CnYgiFeCh3V35GFecUw==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-user-agent-browser/3.292.0:
+    resolution: {integrity: sha512-dld+lpC3QdmTQHdBWJ0WFDkXDSrJgfz03q6mQ8+7H+BC12ZhT0I0g9iuvUjolqy7QR00OxOy47Y9FVhq8EC0Gg==}
+    dependencies:
+      '@aws-sdk/types': 3.292.0
+      bowser: 2.11.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-user-agent-node/3.292.0:
+    resolution: {integrity: sha512-f+NfIMal5E61MDc5WGhUEoicr7b1eNNhA+GgVdSB/Hg5fYhEZvFK9RZizH5rrtsLjjgcr9nPYSR7/nDKCJLumw==}
+    engines: {node: '>=14.0.0'}
+    peerDependencies:
+      aws-crt: '>=1.0.0'
+    peerDependenciesMeta:
+      aws-crt:
+        optional: true
+    dependencies:
+      '@aws-sdk/node-config-provider': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-utf8-browser/3.259.0:
+    resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==}
+    dependencies:
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-utf8/3.292.0:
+    resolution: {integrity: sha512-FPkj+Z59/DQWvoVu2wFaRncc3KVwe/pgK3MfVb0Lx+Ibey5KUx+sNpJmYcVYHUAe/Nv/JeIpOtYuC96IXOnI6w==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/util-buffer-from': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/util-waiter/3.292.0:
+    resolution: {integrity: sha512-+7j+mcWUY4GwU8nTK4MvLWpOzS34SJZL85qLxQ04pysoCSHkInyS51D1ejBVNlJdbUSFvIcU0WHU0y6MDDeJzg==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@aws-sdk/abort-controller': 3.292.0
+      '@aws-sdk/types': 3.292.0
+      tslib: 2.5.0
+    dev: false
+
+  /@aws-sdk/xml-builder/3.292.0:
+    resolution: {integrity: sha512-0zgnhdwUy30q/1NPXi5ekdzHQqCs3ZJaUeGbvYMO54osi4K5hygAyTsyWtv6oaJggRqZrB0LAZ9xN6hG+sA8/g==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      tslib: 2.5.0
+    dev: false
+
   /@babel/code-frame/7.18.6:
     resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==}
     engines: {node: '>=6.9.0'}
@@ -2317,6 +3304,12 @@ packages:
     resolution: {integrity: sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==}
     engines: {node: '>=14.16'}
 
+  /@sinonjs/commons/1.8.6:
+    resolution: {integrity: sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==}
+    dependencies:
+      type-detect: 4.0.8
+    dev: true
+
   /@sinonjs/commons/2.0.0:
     resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==}
     dependencies:
@@ -2327,6 +3320,24 @@ packages:
     dependencies:
       '@sinonjs/commons': 2.0.0
 
+  /@sinonjs/fake-timers/9.1.2:
+    resolution: {integrity: sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==}
+    dependencies:
+      '@sinonjs/commons': 1.8.6
+    dev: true
+
+  /@sinonjs/samsam/7.0.1:
+    resolution: {integrity: sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==}
+    dependencies:
+      '@sinonjs/commons': 2.0.0
+      lodash.get: 4.4.2
+      type-detect: 4.0.8
+    dev: true
+
+  /@sinonjs/text-encoding/0.7.2:
+    resolution: {integrity: sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==}
+    dev: true
+
   /@sqltools/formatter/1.2.5:
     resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==}
     dev: false
@@ -3065,6 +4076,12 @@ packages:
       '@types/node': 18.15.0
     dev: true
 
+  /@types/sinon/10.0.13:
+    resolution: {integrity: sha512-UVjDqJblVNQYvVNUsj0PuYYw0ELRmgt1Nt5Vk0pT5f16ROGfcKJY8o1HVuMOJOpD727RrGB9EGvoaTQE5tgxZQ==}
+    dependencies:
+      '@types/sinonjs__fake-timers': 8.1.2
+    dev: true
+
   /@types/sinonjs__fake-timers/8.1.1:
     resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==}
     dev: true
@@ -4205,6 +5222,7 @@ packages:
   /available-typed-arrays/1.0.5:
     resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==}
     engines: {node: '>= 0.4'}
+    dev: true
 
   /avvio/8.2.0:
     resolution: {integrity: sha512-bbCQdg7bpEv6kGH41RO/3B2/GMMmJSo2iBK+X8AWN9mujtfUipMDfIjsgHCfpnKqoGEQrrmCDKSa5OQ19+fDmg==}
@@ -4216,21 +5234,13 @@ packages:
       - supports-color
     dev: false
 
-  /aws-sdk/2.1318.0:
-    resolution: {integrity: sha512-xRCKqx4XWXUIpjDCVHmdOSINEVCIC5+yhmgUGR9A6VfxfPs59HbxKyd5LB+CmXhVbwVUM4SRWG5O+agQj+w7Eg==}
-    engines: {node: '>= 10.0.0'}
+  /aws-sdk-client-mock/2.1.1:
+    resolution: {integrity: sha512-UuxXmICU4nmXTRm2BzLZdXmnyI+5NEBb5McRDkObasXVxXChvLm0Ci/PGENh4sCD+Es64SJiz70mtY48JROk0A==}
     dependencies:
-      buffer: 4.9.2
-      events: 1.1.1
-      ieee754: 1.1.13
-      jmespath: 0.16.0
-      querystring: 0.2.0
-      sax: 1.2.1
-      url: 0.10.3
-      util: 0.12.5
-      uuid: 8.0.0
-      xml2js: 0.4.19
-    dev: false
+      '@types/sinon': 10.0.13
+      sinon: 14.0.2
+      tslib: 2.5.0
+    dev: true
 
   /aws-sign2/0.7.0:
     resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==}
@@ -4241,7 +5251,7 @@ packages:
   /axios/0.24.0:
     resolution: {integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==}
     dependencies:
-      follow-redirects: 1.15.2_debug@4.3.4
+      follow-redirects: 1.15.2
     transitivePeerDependencies:
       - debug
     dev: false
@@ -4458,6 +5468,10 @@ packages:
   /boolbase/1.0.0:
     resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
 
+  /bowser/2.11.0:
+    resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==}
+    dev: false
+
   /brace-expansion/1.1.11:
     resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
     dependencies:
@@ -4561,12 +5575,11 @@ packages:
     engines: {node: '>=4'}
     dev: false
 
-  /buffer/4.9.2:
-    resolution: {integrity: sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==}
+  /buffer/5.6.0:
+    resolution: {integrity: sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==}
     dependencies:
       base64-js: 1.5.1
       ieee754: 1.2.1
-      isarray: 1.0.0
     dev: false
 
   /buffer/5.7.1:
@@ -6897,11 +7910,6 @@ packages:
     resolution: {integrity: sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg==}
     dev: false
 
-  /events/1.1.1:
-    resolution: {integrity: sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==}
-    engines: {node: '>=0.4.x'}
-    dev: false
-
   /events/3.3.0:
     resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
     engines: {node: '>=0.8.x'}
@@ -7163,6 +8171,13 @@ packages:
       strnum: 1.0.5
     dev: false
 
+  /fast-xml-parser/4.1.2:
+    resolution: {integrity: sha512-CDYeykkle1LiA/uqQyNwYpFbyF6Axec6YapmpUP+/RHWIoR1zKjocdvNaTsxCxZzQ6v9MLXaSYm9Qq0thv0DHg==}
+    hasBin: true
+    dependencies:
+      strnum: 1.0.5
+    dev: false
+
   /fastify-plugin/4.5.0:
     resolution: {integrity: sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg==}
     dev: false
@@ -7398,6 +8413,16 @@ packages:
       readable-stream: 2.3.7
     dev: false
 
+  /follow-redirects/1.15.2:
+    resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
+    engines: {node: '>=4.0'}
+    peerDependencies:
+      debug: '*'
+    peerDependenciesMeta:
+      debug:
+        optional: true
+    dev: false
+
   /follow-redirects/1.15.2_debug@4.3.4:
     resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
     engines: {node: '>=4.0'}
@@ -7408,11 +8433,13 @@ packages:
         optional: true
     dependencies:
       debug: 4.3.4
+    dev: true
 
   /for-each/0.3.3:
     resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
     dependencies:
       is-callable: 1.2.7
+    dev: true
 
   /for-in/1.0.2:
     resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==}
@@ -7861,6 +8888,7 @@ packages:
     resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
     dependencies:
       get-intrinsic: 1.2.0
+    dev: true
 
   /got/11.8.5:
     resolution: {integrity: sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==}
@@ -8294,10 +9322,6 @@ packages:
       safari-14-idb-fix: 3.0.0
     dev: false
 
-  /ieee754/1.1.13:
-    resolution: {integrity: sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==}
-    dev: false
-
   /ieee754/1.2.1:
     resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
 
@@ -8510,6 +9534,7 @@ packages:
     dependencies:
       call-bind: 1.0.2
       has-tostringtag: 1.0.0
+    dev: true
 
   /is-array-buffer/3.0.2:
     resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==}
@@ -8553,6 +9578,7 @@ packages:
   /is-callable/1.2.7:
     resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
     engines: {node: '>= 0.4'}
+    dev: true
 
   /is-ci/3.0.1:
     resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==}
@@ -8653,13 +9679,6 @@ packages:
     engines: {node: '>=6'}
     dev: true
 
-  /is-generator-function/1.0.10:
-    resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==}
-    engines: {node: '>= 0.4'}
-    dependencies:
-      has-tostringtag: 1.0.0
-    dev: false
-
   /is-glob/3.1.0:
     resolution: {integrity: sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==}
     engines: {node: '>=0.10.0'}
@@ -8832,6 +9851,7 @@ packages:
       for-each: 0.3.3
       gopd: 1.0.1
       has-tostringtag: 1.0.0
+    dev: true
 
   /is-typedarray/1.0.0:
     resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==}
@@ -8881,7 +9901,6 @@ packages:
 
   /isarray/0.0.1:
     resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==}
-    dev: false
 
   /isarray/1.0.0:
     resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
@@ -9511,11 +10530,6 @@ packages:
     resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==}
     dev: true
 
-  /jmespath/0.16.0:
-    resolution: {integrity: sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==}
-    engines: {node: '>= 0.6.0'}
-    dev: false
-
   /joi/17.7.0:
     resolution: {integrity: sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==}
     dependencies:
@@ -9751,6 +10765,10 @@ packages:
     resolution: {integrity: sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==}
     dev: false
 
+  /just-extend/4.2.1:
+    resolution: {integrity: sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==}
+    dev: true
+
   /jwa/2.0.0:
     resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==}
     dependencies:
@@ -10587,6 +11605,16 @@ packages:
     resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
     dev: false
 
+  /nise/5.1.4:
+    resolution: {integrity: sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==}
+    dependencies:
+      '@sinonjs/commons': 2.0.0
+      '@sinonjs/fake-timers': 10.0.2
+      '@sinonjs/text-encoding': 0.7.2
+      just-extend: 4.2.1
+      path-to-regexp: 1.8.0
+    dev: true
+
   /node-abi/3.31.0:
     resolution: {integrity: sha512-eSKV6s+APenqVh8ubJyiu/YhZgxQpGP66ntzUb3lY1xB9ukSRaGnx0AIxI+IM+1+IVYC1oWobgG5L3Lt9ARykQ==}
     engines: {node: '>=10'}
@@ -11219,6 +12247,12 @@ packages:
       path-root-regex: 0.1.2
     dev: false
 
+  /path-to-regexp/1.8.0:
+    resolution: {integrity: sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==}
+    dependencies:
+      isarray: 0.0.1
+    dev: true
+
   /path-to-regexp/3.2.0:
     resolution: {integrity: sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==}
     dev: false
@@ -11985,10 +13019,6 @@ packages:
       pump: 2.0.1
     dev: false
 
-  /punycode/1.3.2:
-    resolution: {integrity: sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==}
-    dev: false
-
   /punycode/2.3.0:
     resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
     engines: {node: '>=6'}
@@ -12041,11 +13071,6 @@ packages:
       strict-uri-encode: 1.1.0
     dev: false
 
-  /querystring/0.2.0:
-    resolution: {integrity: sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==}
-    engines: {node: '>=0.4.x'}
-    dev: false
-
   /querystring/0.2.1:
     resolution: {integrity: sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==}
     engines: {node: '>=0.4.x'}
@@ -12649,10 +13674,6 @@ packages:
       immutable: 4.2.2
       source-map-js: 1.0.2
 
-  /sax/1.2.1:
-    resolution: {integrity: sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==}
-    dev: false
-
   /sax/1.2.4:
     resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
     dev: false
@@ -12817,6 +13838,17 @@ packages:
       is-arrayish: 0.3.2
     dev: false
 
+  /sinon/14.0.2:
+    resolution: {integrity: sha512-PDpV0ZI3ZCS3pEqx0vpNp6kzPhHrLx72wA0G+ZLaaJjLIYeE0n8INlgaohKuGy7hP0as5tbUd23QWu5U233t+w==}
+    dependencies:
+      '@sinonjs/commons': 2.0.0
+      '@sinonjs/fake-timers': 9.1.2
+      '@sinonjs/samsam': 7.0.1
+      diff: 5.1.0
+      nise: 5.1.4
+      supports-color: 7.2.0
+    dev: true
+
   /sisteransi/1.0.5:
     resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
     dev: true
@@ -13113,6 +14145,13 @@ packages:
       internal-slot: 1.0.5
     dev: true
 
+  /stream-browserify/3.0.0:
+    resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==}
+    dependencies:
+      inherits: 2.0.4
+      readable-stream: 3.6.0
+    dev: false
+
   /stream-combiner/0.0.4:
     resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==}
     dependencies:
@@ -13803,14 +14842,12 @@ packages:
 
   /tslib/1.14.1:
     resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
-    dev: true
 
   /tslib/2.4.1:
     resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==}
 
   /tslib/2.5.0:
     resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
-    dev: false
 
   /tsutils/3.21.0_typescript@4.5.4:
     resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
@@ -14175,13 +15212,6 @@ packages:
     resolution: {integrity: sha512-mYFmBHCapZjtcNHW0MDq9967t+z4Dmg5CJ0KqysK3+ZbyoNOWQHksGCTWwDhxGXllkWlOc10Xfko6v4a3ucM6A==}
     dev: false
 
-  /url/0.10.3:
-    resolution: {integrity: sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==}
-    dependencies:
-      punycode: 1.3.2
-      querystring: 0.2.0
-    dev: false
-
   /urlsafe-base64/1.0.0:
     resolution: {integrity: sha512-RtuPeMy7c1UrHwproMZN9gN6kiZ0SvJwRaEzwZY0j9MypEkFqyBaKv176jvlPtg58Zh36bOkS0NFABXMHvvGCA==}
     dev: false
@@ -14202,26 +15232,12 @@ packages:
   /util-deprecate/1.0.2:
     resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
 
-  /util/0.12.5:
-    resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==}
-    dependencies:
-      inherits: 2.0.4
-      is-arguments: 1.1.1
-      is-generator-function: 1.0.10
-      is-typed-array: 1.1.10
-      which-typed-array: 1.1.9
-    dev: false
-
   /uuid/3.4.0:
     resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==}
     deprecated: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
     hasBin: true
     dev: false
 
-  /uuid/8.0.0:
-    resolution: {integrity: sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==}
-    dev: false
-
   /uuid/8.3.2:
     resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
     hasBin: true
@@ -14678,6 +15694,7 @@ packages:
       gopd: 1.0.1
       has-tostringtag: 1.0.0
       is-typed-array: 1.1.10
+    dev: true
 
   /which/1.3.1:
     resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
@@ -14784,13 +15801,6 @@ packages:
     resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
     engines: {node: '>=12'}
 
-  /xml2js/0.4.19:
-    resolution: {integrity: sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==}
-    dependencies:
-      sax: 1.2.4
-      xmlbuilder: 9.0.7
-    dev: false
-
   /xml2js/0.4.23:
     resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==}
     engines: {node: '>=4.0.0'}
@@ -14804,11 +15814,6 @@ packages:
     engines: {node: '>=4.0'}
     dev: false
 
-  /xmlbuilder/9.0.7:
-    resolution: {integrity: sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==}
-    engines: {node: '>=4.0'}
-    dev: false
-
   /xmlchars/2.2.0:
     resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
     dev: false