diff --git a/.github/ISSUE_TEMPLATE/01_bug-report.yml b/.github/ISSUE_TEMPLATE/01_bug-report.yml
index ac2b39cc12c6..b33c528bd2e2 100644
--- a/.github/ISSUE_TEMPLATE/01_bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/01_bug-report.yml
@@ -1,41 +1,41 @@
-name: 🐛 Bug Report
-description: Create a report to help us improve
-labels: ["⚠bug?"]
+name: ⚠ バグ報告
+description: 修正/改善のための報告
+labels: ["⚠Bug"]
 
 body:
   - type: markdown
     attributes:
       value: |
-        Thanks for reporting!
-        First, in order to avoid duplicate Issues, please search to see if the problem you found has already been reported.
-        Also, If you are NOT owner/admin of server, PLEASE DONT REPORT SERVER SPECIFIC ISSUES TO HERE! (e.g. feature XXX is not working in misskey.example) Please try with another misskey servers, and if your issue is only reproducible with specific server, contact your server's owner/admin first.
+        バグ報告を怜蚎しおいただきありがずうございたす!
+        重耇を避けるために、同様の問題が既に報告されおいないか[にりらみすきヌ郚のIssue](https://github.com/niri-la/misskey.niri.la/issues)を確認しおみおください。
+        たた、にりらみすきヌ郚で再珟したバグのみを報告しおください。
 
   - type: textarea
     attributes:
-      label: 💡 Summary
-      description: Tell us what the bug is
+      label: 💡 抂芁
+      description: どんなバグですか?
     validations:
       required: true
 
   - type: textarea
     attributes:
-      label: 🥰 Expected Behavior
-      description: Tell us what should happen
+      label: 🥰 想定される挙動
+      description: 本来どうあるべきですか?
     validations:
       required: true
 
   - type: textarea
     attributes:
-      label: 🀬 Actual Behavior
+      label: 🀬 実際の挙動
       description: |
-        Tell us what happens instead of the expected behavior.
-        Please include errors from the developer console and/or server log files if you have access to them.
+        実際には䜕が起きおいたすか?
+        可胜であれば、デベロッパヌツヌルでの゚ラヌメッセヌゞなども茉せおください。
     validations:
       required: true
 
   - type: textarea
     attributes:
-      label: 📝 Steps to Reproduce
+      label: 📝 再珟手順
       placeholder: |
         1.
         2.
@@ -45,53 +45,17 @@ body:
 
   - type: textarea
     attributes:
-      label: 💻 Frontend Environment
+      label: 💻 お䜿いの環境
       description: |
-        Tell us where on the platform it happens
-        DO NOT WRITE "latest". Please provide the specific version.
+        どんな環境で問題が起きたしたか?
+        "最新"のような曞き方はせず、具䜓的なバヌゞョンを教えおください。
 
-        Examples:
-          * Model and OS of the device(s): MacBook Pro (14inch, 2021), macOS Ventura 13.4
-          * Browser: Chrome 113.0.5672.126
-          * Server URL: misskey.io
-          * Misskey: 13.x.x
+        䟋:
+          * 機皮ずOS: MacBook Pro (14inch, 2021), macOS Ventura 13.4
+          * ブラりザ: Chrome 113.0.5672.126
       value: |
-        * Model and OS of the device(s):
-        * Browser:
-        * Server URL:
-        * Misskey:
+        * 機皮ずOS:
+        * ブラりザ:
       render: markdown
     validations:
       required: false
-
-  - type: textarea
-    attributes:
-      label: 🛰 Backend Environment (for server admin)
-      description: |
-        Tell us where on the platform it happens
-        DO NOT WRITE "latest". Please provide the specific version.
-        If you are using a managed service, put that after the version.
-
-        Examples:
-          * Installation Method or Hosting Service: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment
-          * Misskey: 13.x.x
-          * Node: 20.x.x
-          * PostgreSQL: 15.x.x
-          * Redis: 7.x.x
-          * OS and Architecture: Ubuntu 22.04.2 LTS aarch64
-      value: |
-        * Installation Method or Hosting Service:
-        * Misskey:
-        * Node:
-        * PostgreSQL:
-        * Redis:
-        * OS and Architecture:
-      render: markdown
-    validations:
-      required: false
-
-  - type: checkboxes
-    attributes:
-      label: Do you want to address this bug yourself?
-      options:
-        - label: Yes, I will patch the bug myself and send a pull request
diff --git a/.github/ISSUE_TEMPLATE/02_feature-request.yml b/.github/ISSUE_TEMPLATE/02_feature-request.yml
index 8d7b0b2539e9..f6ba3d36ea43 100644
--- a/.github/ISSUE_TEMPLATE/02_feature-request.yml
+++ b/.github/ISSUE_TEMPLATE/02_feature-request.yml
@@ -1,22 +1,17 @@
-name: ✹ Feature Request
-description: Suggest an idea for this project
+name: ✹ 新機胜の芁望
+description: 新しい機胜の提案
 labels: ["✹Feature"]
 
 body:
   - type: textarea
     attributes:
-      label: Summary
-      description: Tell us what the suggestion is
+      label: 抂芁
+      description: どんな提案ですか?
     validations:
       required: true
   - type: textarea
     attributes:
-      label: Purpose
-      description: Describe the specific problem or need you think this feature will solve, and who it will help.
+      label: 目的
+      description: その機胜の远加によっお、どんな問題やニヌズが解決されそうですか? たた、それはどんな人に圹立ちそうですか?
     validations:
       required: true
-  - type: checkboxes
-    attributes:
-      label: Do you want to implement this feature yourself?
-      options:
-        - label: Yes, I will implement this by myself and send a pull request
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
deleted file mode 100644
index e8b65dc3b9fc..000000000000
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-contact_links:
-  - name: 💬 Misskey official Discord
-    url: https://discord.gg/Wp8gVStHW3
-    about: Chat freely about Misskey
diff --git a/.github/workflows/check-misskey-js-autogen.yml b/.github/workflows/check-misskey-js-autogen.yml
index 8fad129115ae..1e03fdc28656 100644
--- a/.github/workflows/check-misskey-js-autogen.yml
+++ b/.github/workflows/check-misskey-js-autogen.yml
@@ -22,7 +22,7 @@ jobs:
         uses: actions/checkout@v4.1.1
         with:
           submodules: true
-          ref: ${{ github.event.pull_request.head.sha }}
+          ref: refs/pull/${{ github.event.number }}/merge
 
       - name: setup pnpm
         uses: pnpm/action-setup@v3
diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml
new file mode 100644
index 000000000000..b54795f8ce93
--- /dev/null
+++ b/.github/workflows/docker-image.yml
@@ -0,0 +1,50 @@
+name: Build and Push Image (Docker Buildx; GitHub Actions)
+
+on:
+  push:
+    tags:
+      - 'v*'
+
+env:
+  DOCKER_REGISTRY_NAME: ghcr.io
+  DOCKER_IMAGE_NAME: ${{ github.repository }}
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout Repository
+        uses: actions/checkout@v3
+
+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v1
+
+      - name: Setup Docker Buildx
+        uses: docker/setup-buildx-action@v1
+
+      - name: Login to Docker hub
+        uses: docker/login-action@v1
+        with:
+          registry: ${{ env.DOCKER_REGISTRY_NAME }}
+          username: ${{ github.repository_owner }}
+          password: ${{ github.token }} # github.token 
+
+      - name: Extract metadata (tags, labels) for Docker
+        id: meta
+        uses: docker/metadata-action@v3
+        with:
+          images: ${{ env.DOCKER_REGISTRY_NAME }}/${{ env.DOCKER_IMAGE_NAME }}
+
+      - name: Build & Push
+        uses: docker/build-push-action@v2
+        env:
+          DOCKER_BUILDKIT: 1
+        with:
+          context: .
+          push: true
+          platforms: linux/amd64,linux/arm64
+          tags: ${{ steps.meta.outputs.tags }}
+          labels: ${{ steps.meta.outputs.labels }}
+          cache-from: ${{ env.DOCKER_REGISTRY_NAME }}/${{ env.DOCKER_IMAGE_NAME }}:latest
+          build-args: BUILDKIT_INLINE_CACHE=1
diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml
index 1e020b73680b..f28c6ef1d878 100644
--- a/.github/workflows/test-frontend.yml
+++ b/.github/workflows/test-frontend.yml
@@ -60,6 +60,7 @@ jobs:
 
   e2e:
     runs-on: ubuntu-latest
+    if: false
 
     strategy:
       fail-fast: false
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bafee277d259..20f301d0981c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,17 +5,26 @@
 -
 
 ### Client
-- 
+-
 
 ### Server
 -
 
 -->
 
-## 2024.3.1
+## 202x.x.x-kinel.x (unreleased)
 
-### General
--
+### Client
+- Enhance: 画像アップロヌド時に瞮小する堎合の倧きさを2048x2048以䞋から2560x2560以䞋に倉曎したした
+  - 既存のファむルは曎新されず、新芏アップロヌド分にのみ適甚されたす
+- Fix: パブリック投皿をホヌム投皿に倉曎するモデレヌション操䜜がUI䞊で行えなくなっおいた問題を修正
+- Fix: `ナヌザペヌゞでセンシティブチャンネルの投皿を閉じる`蚭定が無効にならない問題を修正
+
+## 2024.3.1-kinel.1
+
+2024.3.1ず党く同䞀です
+
+## 2024.3.1
 
 ### Client
 - Fix: 絵文字関係の䞍具合を修正 (#13485)
@@ -23,8 +32,9 @@
   - Unicode絵文字が履歎に残っおいる or ピン留めされおいるずリアクションデッキが衚瀺できなくなる
 - Fix: カスタム絵文字の画像読み蟌みに倱敗した際はテキストではなくダミヌ画像を衚瀺 #13487
 
-### Server
--
+## 2024.3.0-kinel.1
+
+2024.3.0ず党く同䞀です
 
 ## 2024.3.0
 
@@ -64,7 +74,31 @@
   - `category`および`licence`が指定なしの時勝手にnullに䞊曞きされる挙動を修正
 - Fix: 通知の受信蚭定で「盞互フォロヌ」が正しく動䜜しない問題を修正
 
-## 2024.2.0
+## 2024.2.0-kinel.3
+
+### Client
+- Fix: センシティブチャンネルの投皿がセンシティブなファむルを含む投皿扱いされる問題を修正
+
+### Server
+- Fix: 犁止ワヌド蚭定によっおリモヌトの投皿がブロックされるず、InboxキュヌにDelayed Jobずしお残っおしたう問題を修正
+- Fix: ロヌカルナヌザヌがただ誰もフォロヌしおいないリモヌトナヌザヌによる、通知を匕き起こす可胜性のある投皿を拒吊するず、InboxキュヌにDelayed Jobずしお残っおしたう問題を修正
+
+## 2024.2.0-kinel.2
+
+### Server
+- ロヌカルナヌザヌがただ誰もフォロヌしおいないリモヌトナヌザヌによる、通知を匕き起こす可胜性のある投皿を拒吊できるように
+
+## 2024.2.0-kinel.1
+
+### Client
+- センシティブチャンネルのNoteの畳たれ方が゜フトミュヌトず同様になりたした。
+- このフォヌクぞのリンクがmisskey暙準のものに倉曎になりたした。
+- NSFWガむドラむンぞのリンクをメニュヌに远加したした。
+
+### Server
+- リモヌトナヌザの削陀が蚘録されないようになりたした。
+
+## 2024.2.0 (merged to 2024.2.0-kinel.1)
 
 ### Note
 - 倖郚サむトからプラグむンをむンストヌルする堎合のパスが`/install-extentions`から`/install-extensions`に倉わりたす。以前のパスからは自動でリダむレクトされるようになっおいたすが、新しいパスに倉曎するこずをお勧めしたす。
@@ -152,7 +186,21 @@
 - Fix: コントロヌルパネル->モデレヌション->「誰でも新芏登録できるようにする」の初期倀をONからOFFに倉曎 #13122
 - Fix: リモヌトナヌザヌが埩掻しおもキャッシュにより該圓ナヌザヌのActivityが受け入れられないのを修正 #13273
 
-## 2023.12.2
+### Service Worker
+- Enhance: オフラむン衚瀺のデザむンを改善・倚蚀語察応
+
+## 2023.12.2-kinel.2
+
+### Server
+- スパムが行動埌即時にアカりントを削陀するような堎合ぞの察策ずしお、アカりント削陀時にアカりントデヌタが保持されるようになりたした。
+  - 個人情報の保護に関する法埋に基づく正圓な芁求があった堎合には削陀されたす。
+  - たた、より良い察応方法が远加された際にこのデヌタは削陀されたす。
+
+## 2023.12.2-kinel.1
+
+2023.12.2ず党く同䞀です
+
+## 2023.12.2 (merged to 2023.12.2-kinel.1)
 
 ### General
 - v2023.12.1でDockerを利甚しおサヌバヌを起動できない問題を修正
@@ -160,7 +208,11 @@
 ### Client
 - Enhance: 怜玢画面においおEnterキヌ抌䞋で怜玢できるように
 
-## 2023.12.1
+## 2023.12.1-kinel.1
+
+2023.12.1ず党く同䞀です
+
+## 2023.12.1 (merged to 2023.12.1-kinel.1)
 
 ### Note
 - アクセストヌクンの暩限が再敎理されたため、䞀郚のAPIが叀いAPIトヌクンでは動䜜しなくなりたした。\
@@ -185,7 +237,16 @@
 - Fix: サヌドパヌティアプリケヌションがWebsocket APIに無条件にアクセスできる問題を修正
 - Fix: サヌドパヌティアプリケヌションがナヌザヌの蚱可なしに非公開の情報を芋るこずができる問題を修正
 
-## 2023.12.0
+## 2023.12.0-kinel.1
+
+### General
+- Fix: VRChatのワヌルドURLのサムネむルがfaviconになる問題を修正
+- `mixi:content-rating`が指定されおいるりェブサむト(DLSite等)のサムネむルが衚瀺されなくなりたした
+
+### Client
+- Fix: パブリック投皿をホヌム投皿に倉曎するモデレヌション操䜜におけるモデログの内容ず衚瀺が䞍適切だった問題を修正
+
+## 2023.12.0 (merged to 2023.12.0-kinel.1)
 
 ### Note
 - 䟝存関係の曎新に䌎い、Node.js 20.10.0が最小芁件になりたした
@@ -290,7 +351,69 @@
 - Fix: ハッシュタグのトレンド陀倖蚭定が即時に効果を持぀ように修正
 - Fix: HTTP Digestヘッダのアルゎリズム郚分に倧文字の"SHA-256"しか䜿えない
 
-## 2023.11.1
+## 2023.11.1-kinel.4
+
+### Client
+- Fix: 音がならない問題
+
+## 2023.11.1-kinel.3
+
+### General
+- publicノヌトをhomeノヌトにするモデレヌションを远加
+- featured甚スコアを絶察に曎新するように
+- 蚭定したタグをトレンドに衚瀺させないようにする項目を管理画面で蚭定できるように
+- Fix: 党䜓ハむラむトでナヌザヌミュヌトが正垞に機胜しない問題
+
+### Client
+- Enhance: チャンネル、クリップ、ペヌゞ、Play、ギャラリヌにURLのコピヌボタンを蚭眮 #11305
+- Enhance: サりンド蚭定に「サりンドを出力しない」ず「Misskeyがアクティブな時のみサりンドを出力する」を远加
+- Fix: 共有機胜をサポヌトしおいないブラりザの堎合は共有ボタンを非衚瀺にする #11305
+- Fix: 通知のグルヌピング蚭定を倉曎しおもリロヌドされるたで衚瀺が倉わらない問題を修正 #12470
+
+### Server
+- Fix: includeSensitiveChannelがfunout timelineの範囲では機胜しない問題
+
+## 2023.11.1-kinel.2
+
+### General
+- こんにりらみすきヌ郚がハむラむトから陀倖されるようになりたした
+- モデレヌタヌがナヌザヌのアむコンもしくはバナヌ画像を未蚭定状態にできる機胜を远加
+- Fix: ハむラむトでナヌザミュヌトが機胜しない問題
+
+### Client
+- リアクションの暪幅を150pxに制限するかどうかナヌザヌが遞べるように
+- Fix: ハヌドミュヌトにノヌト匕っかかるず゚ラヌが発生しTLが曎新されないこずがある問題
+- Fix: 絵文字入力時のサゞェストがおかしい
+- Fix: ナヌザヌペヌゞでハむラむトノヌトがない堎合は盎近のノヌトを衚瀺するように修正
+
+### Server
+
+## 2023.11.1-kinel.1
+
+### Note
+- 2023.9.3-kinel.4以前で利甚されおいた゜フト/ハヌドミュヌトの蚭定は、そのたた2023.11.1-kinel.1向けに移行されたす。
+  - 本来、2023.10.0の倉曎により以䞋の事象が発生する可胜性がありたした。
+    - ゜フトミュヌトの蚭定がリセットされる
+    - ハヌドミュヌトの蚭定は゜フトミュヌトのものになり、ハヌドミュヌト機胜が䞀時廃止
+  - 本曎新では、ハヌドミュヌト機胜を再実装し、2023.9.3-kinel.4以前からの曎新時に蚭定が正垞に匕き継げるようになっおいたす。
+
+### General
+- Feat: TL䞊からノヌトが芋えなくなるワヌドミュヌトであるハヌドミュヌトを远加
+- Fix: MFM `$[unixtime ]` に䞍正な倀を入力した際に発生する各皮゚ラヌを修正
+
+### Client
+- Enhance: 絵文字のオヌトコンプリヌト機胜匷化 #12364
+- Enhance: ノヌトプレビュヌに「内容を隠す」が反映されるように
+- fix: 「蚭定のバックアップ」で䞀郚の項目がバックアップに含たれおいなかった問題を修正
+- Fix: 䞀床に倧量の通知が入った際に通知音が音割れする問題を修正
+- Fix: りィゞェットのゞョブキュヌにお音声の発音方法倉曎に远埓できおいなかったのを修正 #12367
+
+### Server
+- Fix: 䜕もノヌトしおいないナヌザヌのフィヌドにアクセスするず゚ラヌになる問題を修正
+- Fix: 時間経過により無効化されたアンテナを再有効化したずき、サヌバ再起動たでその状況が反映されないのを修正 #12303
+- Enhance: MFM `$[ruby ]` が他゜フトりェアず連合されるように
+
+## 2023.11.1 (merged to 2023.11.1-kinel.1)
 
 ### Note
 - 悪意のある第䞉者がリモヌトナヌザヌになりすたした任意のアクティビティを受け取れおしたう問題を修正したした。詳しくは[GitHub security advisory](https://github.com/misskey-dev/misskey/security/advisories/GHSA-3f39-6537-3cgc)をご芧ください。
@@ -326,7 +449,7 @@
 - Fix: ActivityPubに関するセキュリティの向䞊
 - Fix: 非公開の投皿に察しお返信できないように
 
-## 2023.11.0
+## 2023.11.0 (merged to 2023.11.1-kinel.1)
 
 ### Note
 - iOS 16.4未満を䜿甚しおいる堎合はiOS 16.4以䞊にアップデヌトをお願いしたす
@@ -402,7 +525,7 @@
 - Fix: サヌバヌサむドからのテスト通知を正しく行えるように修正
 - Fix: GTLの「リノヌトを衚瀺」オプションが機胜しないのを修正 #12233
 
-## 2023.10.2
+## 2023.10.2 (merged to 2023.11.1-kinel.1)
 
 ### General
 - Feat: アンテナでロヌカルの投皿のみ収集できるようになりたした
@@ -416,6 +539,7 @@
 ### Client
 - Enhance: TLの返信衚瀺オプションを蚘憶するように
 - Enhance: 投皿されおから時間が経過しおいるノヌトであるこずを芖芚的に分かりやすく
+- Feat: 絵文字ピッカヌのカテゎリに「/」を入れるこずでフォルダ分け衚瀺できるように
 
 ### Server
 - Enhance: タむムラむン取埗時のパフォヌマンスを向䞊
@@ -426,7 +550,7 @@
 - Change: ナヌザヌのisCatがtrueでも、サヌバヌではnyaizeが行われなくなりたした
   - isCatな堎合、クラむアントでnyaize凊理を行うこずを掚奚したす
 
-## 2023.10.1
+## 2023.10.1 (merged to 2023.11.1-kinel.1)
 ### General
 - Enhance: ロヌカルタむムラむン、゜ヌシャルタむムラむンで返信を含むかどうか蚭定可胜に
 
@@ -437,7 +561,7 @@
 - Fix: フォロヌしおいるナヌザヌからの自分の投皿ぞの返信がタむムラむンに含たれない問題を修正
 - Fix: users/notesでセンシティブチャンネルの投皿が含たれる堎合がある問題を修正
 
-## 2023.10.0
+## 2023.10.0 (merged to 2023.11.1-kinel.1)
 ### NOTE
 - 2023.9.2で導入されたノヌト線集機胜はクオリティの高い実装が困難であるこずが刀明したため撀回されたした
 - アップデヌトを行うず、タむムラむンが䞀時的にリセットされたす
@@ -486,7 +610,30 @@
 - Fix: 「ファむル付きのみ」のTLでファむル無しの新着ノヌトが流れる問題を修正
 - Fix: プロセスが終了しない、あるいは非垞に時間がかかる問題を修正
 
-## 2023.9.3
+## 2023.9.3-kinel.4
+
+### Client
+- Fix: Renoteの省略衚瀺でセンシティブチャンネルの自動CWが効かない問題
+
+### Server
+- 2023.10.x向けのTLを内郚的に構築するようになりたした
+  - これにより、2023.9.3-kinel.4曎新埌のnoteは2023.10.x以降に曎新した埌でも芋るこずが出来たす
+  - 2023.10.xには、曎新以前のnoteをTLで芋るこずが出来ないずいう仕様がありたした
+  - 2023.9.xを利甚しおいる内は特に圱響ありたせん
+
+## 2023.9.3-kinel.3 (unreleased)
+
+### General
+- ノヌト線集機胜を削陀
+  - roleにより犁止されおたためナヌザには圱響がありたせん。
+- 様々なカラムでリプラむが衚瀺されないのを衚瀺されるように戻したした
+
+## 2023.9.3-kinel.2
+
+### General
+- noteがデフォルトで線集できないようになりたした。
+
+## 2023.9.3-kinel.1
 ### General
 - Enhance: ノヌトの翻蚳機胜の利甚可吊をロヌルで蚭定可胜に
 
@@ -495,11 +642,15 @@
 - Enhance: モデレヌションログ機胜の匷化
 - Enhance: ロヌカリれヌションの曎新
 
+---
+
+- Fix: "右クリックでリアクションピッカヌを開くようにする"が2぀ある
+
 ### Server
 - Fix: Redisに叀いバヌゞョンのキャッシュが残っおいる堎合、キャッシュが消えるたでの間通知が届かなくなる問題を修正
 - Fix: 埌方互換性の修正
 
-## 2023.9.2
+## 2023.9.2 (merged to 2023.9.3)
 
 ### General
 - Feat: ノヌトの線集をできるように
@@ -521,11 +672,16 @@
 - Enhance: MasterプロセスのPIDを曞き出せるように
 - Enhance: admin/ad/createにおレスポンス200、蚭定した広告情報を返すように
 
-## 2023.9.1
+## 2023.9.1-kinel.1
 
 ### General
 - Enhance: モデレヌションログ機胜の匷化
 
+---
+
+- 4K/8K画像が2K以䞋で衚瀺されおしたう問題を修正したした
+  - 既存のファむルは曎新されず、新芏アップロヌド分にのみ適甚されたす
+
 ### Client
 - Fix: ノヌトのメニュヌにある「詳现」ボタンの衚瀺がログむン/ログアりト状態で統䞀されおいない問題を修正
 
@@ -533,7 +689,7 @@
 - Fix: お知らせのペヌゞネヌションが機胜しない
 - Fix: 「ナヌザヌの新芏投皿」の通知蚭定を切り替えるずサヌバヌ内郚゚ラヌが出る
 
-## 2023.9.0
+## 2023.9.0 (merged to 2023.9.1)
 
 ### Note
 - meilisearchを䜿甚する堎合、v1.2以䞊が必芁です
@@ -629,21 +785,59 @@
 - Fix: 䞀郚のサヌバヌ内郚゚ラヌがスタックトレヌスを返さないように修正
 - Fix: 䞀郚のリモヌトナヌザヌをフォロヌするこずができない問題を修正
 
-## 13.14.2
+## 13.14.2-kinel.4
+
+### General
+- お知らせ機胜の匷化
+    - ナヌザヌ個別のお知らせを䜜成可胜に
+    - お知らせのバナヌ衚瀺やダむアログ衚瀺が可胜に
+    - お知らせのアむコンを蚭定可胜に
+- ハむラむトからテキストのみのおやすみnoteを陀倖するようになりたした
 
 ### Client
-- リストTLで、ナヌザヌが远加・削陀されおもTLを初期化しないように
-- URL取埗倉数を関数に倉曎 CURRENT_URL -> Mk:url()
-- Fix: モバむル衚瀺のずきペヌゞ䞋郚がナビゲヌションバヌに隠れる問題を修正
-- Fix: 䞀郚モヌダルダむアログでスクロヌルできない問題を修正
-- Fix: Selecting all emojis in Custom emoji is impossible
-- Fix: PhotoSwipeによるメモリリヌクの修正
+- メニュヌのスむッチの動䜜を改善
+- Enhance: ナヌザヌメニュヌでスむッチでナヌザヌリストに远加・削陀できるように
+- 画像の圧瞮方法を遞択可胜にしたした
+  - サむズ倉曎を行うかを遞択可胜にしたした
+  - 匷制的に非可逆圧瞮できるようになりたした
+- センシティブチャンネルのNoteがたたたれる方法がCWになりたした
+- About Misskeyにこのforkに付いおの情報を远加したした
+- Fix: 未読のお知らせの「わかった」をクリック・タップしおもその堎で「わかった」が消えない問題を修正
+- Fix: Misskeyプラグむンをむンストヌルする際のAiScriptバヌゞョンのチェックが0.14.0以降に察応しおいない問題を修正
 
 ### Server
-- Fix: APIのオフセットが壊れおいたせいで「もっず芋る」でもっず芋れない問題を修正
-- Fix: 倖郚サヌバヌの投皿がタむムラむンに衚瀺されないこずがある問題を修正
+- ファむルアップロヌド時等にファむル名の拡匵子を修正する関数(correctFilename)の挙動を改善
+- fix: muteがapiからのuser list timeline取埗で機胜しない問題を修正
+
+## 13.14.2-kinel.3
+
+### General
+- 曎新埌初回に衚瀺される`曎新情報を芋る`ボタンのリンク先を[にりらみすきヌ郚のCHANGELOG.md](https://github.com/niri-la/misskey.niri.la/blob/develop/CHANGELOG.md)に倉曎したした
 
-## 13.14.1
+### Client
+- 長いnoteなどがたたたれない問題を修正したした
+- センシティブチャンネルのNoteがナヌザペヌゞからたたたれるのを無効化できるようにしたした
+
+### Server
+
+## 13.14.2-kinel.2
+
+### General
+- ハむラむトからのおはnoteの陀倖でCWを芋忘れおいたのを修正
+- チャンネルをセンシティブ指定できるようになりたした
+- ハむラむトから陀倖するチャンネルをsensitiveチャンネルのみにしたした
+- センシティブチャンネルのNoteがクロヌラヌによるむンデックスを拒吊するようになりたした
+
+### Client
+- センシティブチャンネルのNoteのReNoteはデフォルトでHome TLに流れるようになりたした
+- センシティブチャンネルのNoteがナヌザペヌゞから非衚瀺たたはたたたれるようになりたした
+- Enhance: Renote自䜓を通報できるように
+
+### Server
+- 通報のメヌル通知を無効化するオプションを远加
+- 通知の保存䞊限を倉曎可胜に
+
+## 13.14.2-kinel.1
 
 ### General
 - 招埅機胜を改善したした
@@ -654,22 +848,20 @@
 - identicon生成を無効にしおパフォヌマンスを向䞊させるこずができるようになりたした
 - サヌバヌのマシン情報の公開を無効にしおパフォヌマンスを向䞊させるこずができるようになりたした
 
+---
+
+- 管理者専甚の他人を芋るwebhookが増えたした
+- ハむラむトからおはnoteずチャンネル投皿を陀倖するようになりたした
+
 ### Client
-- deck UIのカラムのメニュヌからアンテナずリストの線集画面を開けるように
 - ドラむブファむルのメニュヌで画像をクロップできるように
-- 画像を動画ず同様に簡単に隠せるように
 - Enhance: ノヌトの埋め蟌みが耇数画像ず動画を衚瀺されるように
 - オリゞナル画像を保持せずにアップロヌドする堎合webpでアップロヌドされるように(Safari以倖)
-- 芋たこずのあるRenoteを省略しお衚瀺をオンのずきに自分のnoteのrenoteを省略するように
 - フォルダヌやファむルに察しおも開発者モヌド䜿甚時、IDをコピヌできるように
-- 匕甚察象を「もっず芋る」で展開した堎合、「閉じる」で畳めるように
 - プロフィヌルURLをコピヌできるボタンを远加 #11190
 - `CURRENT_URL`で珟圚衚瀺䞭のURLを取埗できるように(AiScript)
 - ナヌザヌのContextMenuに「アンテナに远加」ボタンを远加
-- フォロヌやお気に入り登録をしおいないチャンネルを開く時は抂芁ペヌゞを開くように
-- 画面ビュヌワをタップした堎合、マりスクリックず同様に画像ビュヌワを閉じるように
 - オフラむン時の画面にリロヌドボタンを远加
-- Renote時に公開範囲のデフォルト蚭定が適甚されるように
 - Deckで非ルヌトペヌゞにアクセスした際に簡易UIで衚瀺しない蚭定を远加
 - ロヌル蚭定画面でロヌルIDを確認できるように
 - コンテキストメニュヌ衚瀺時のパフォヌマンスを改善
@@ -677,16 +869,26 @@
 - 本文にMFMが含たれおいる堎合に自動でたたたれる機胜が、返信先や匕甚RNにも適甚されるように
   - position は察象倖になりたした
 - AiScriptを0.15.0に曎新
+- リストTLで、ナヌザヌが远加・削陀されおもTLを初期化しないように
+- URL取埗倉数を関数に倉曎 CURRENT_URL -> Mk:url()
 - Fix: サヌバヌメトリクスが90床傟いおいる
 - Fix: 非ログむン時にクレデンシャルが必芁なペヌゞに行くず゚ラヌが出る問題を修正
 - Fix: sparkle内にリンクを入れるずクリック䞍胜になる問題の修正
 - Fix: ZenUIでポップアップの衚瀺䜍眮がおかしい問題を修正
 - Fix: ペヌゞ遷移でスクロヌル䜍眮が保持されない問題を修正
 - Fix: フォルダヌのペヌゞネヌションが機胜しない #11180
-- Fix: 長い文章を投皿する際、プレビュヌが画面からはみ出る問題を修正
 - Fix: システムフォント蚭定が正しく反映されない問題を修正
 - Fix: アンケヌト終了時のプッシュ通知が正しく衚瀺されない問題を修正
 - Fix: MasterVolumeが0の時だけでなく各通知音の音量蚭定が0のずきも、HTMLAudioElement.playが実行されないように倉曎
+- Fix: モバむル衚瀺のずきペヌゞ䞋郚がナビゲヌションバヌに隠れる問題を修正
+- Fix: 䞀郚モヌダルダむアログでスクロヌルできない問題を修正
+- Fix: Selecting all emojis in Custom emoji is impossible
+- Fix: PhotoSwipeによるメモリリヌクの修正
+
+---
+
+- プレビュヌの衚瀺状態を蚘憶するように
+- 絵文字ピッカヌの怜玢の衚瀺件数を100件に増加
 
 ### Server
 - JSON.parse の回数を削枛するこずで、ストリヌミングのパフォヌマンスを向䞊したした
@@ -706,6 +908,28 @@
 - Fix: むンスタンスのアむコンがbase64の堎合の挙動を修正
 - Fix: ロヌカルの `Person` を指す `acct` URI を解析するずきのバグを修正したした
 - Fix: 無効化されたアンテナが再床有効化されないこずがある問題を修正
+- Fix: APIのオフセットが壊れおいたせいで「もっず芋る」でもっず芋れない問題を修正
+- Fix: 倖郚サヌバヌの投皿がタむムラむンに衚瀺されないこずがある問題を修正
+
+---
+
+- 通報をDiscordのWebhookに送信できるように
+
+## 13.13.2-kinel-0.0.1
+
+### General
+
+### Client
+- deck UIのカラムのメニュヌからアンテナずリストの線集画面を開けるように
+- 画像を動画ず同様に簡単に隠せるように
+- 芋たこずのあるRenoteを省略しお衚瀺をオンのずきに自分のnoteのrenoteを省略するように
+- フォロヌやお気に入り登録をしおいないチャンネルを開く時は抂芁ペヌゞを開くように
+- 匕甚察象を「もっず芋る」で展開した堎合、「閉じる」で畳めるように
+- Renote時に公開範囲のデフォルト蚭定が適甚されるように
+- 画面ビュヌワをタップした堎合、マりスクリックず同様に画像ビュヌワを閉じるように
+- Fix: 長い文章を投皿する際、プレビュヌが画面からはみ出る問題を修正
+
+### Server
 
 ## 13.13.2
 
diff --git a/COPYING b/COPYING
index 6a5f3ca1d598..44aedb83aa77 100644
--- a/COPYING
+++ b/COPYING
@@ -1,5 +1,5 @@
 Unless otherwise stated this repository is
-Copyright © 2014-2024 syuilo and contributors
+Copyright © 2014-2024 syuilo and contributors, anatawa12, Sayamame-beans, ni-rila for changes in this fork.
 
 And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE.
 
diff --git a/locales/en-US.yml b/locales/en-US.yml
index d00f23632cc1..6b83e3f32b88 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -118,6 +118,7 @@ pinned: "Pin to profile"
 you: "You"
 clickToShow: "Click to show"
 sensitive: "Sensitive"
+sensitiveChannelAutoCW: "Sensitive Channel Auto CW"
 add: "Add"
 reaction: "Reactions"
 reactions: "Reactions"
diff --git a/locales/index.d.ts b/locales/index.d.ts
index a51d35437a66..a41642e36e79 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -212,6 +212,10 @@ export interface Locale extends ILocale {
      * このノヌトを削陀しおもう䞀床線集したすかこのノヌトぞのリアクション、リノヌト、返信も党お削陀されたす。
      */
     "deleteAndEditConfirm": string;
+    /**
+     * ホヌム投皿にする
+     */
+    "makeNoteHome": string;
     /**
      * リストに远加
      */
@@ -488,6 +492,10 @@ export interface Locale extends ILocale {
      * センシティブ
      */
     "sensitive": string;
+    /**
+     * センシティブチャンネル自動CW
+     */
+    "sensitiveChannelAutoCW": string;
     /**
      * 远加
      */
@@ -932,6 +940,10 @@ export interface Locale extends ILocale {
      * このノヌトを削陀したすか
      */
     "noteDeleteConfirm": string;
+    /**
+     * 本圓にホヌム投皿にしたすか
+     */
+    "makeNoteHomeConfirm": string;
     /**
      * これ以䞊ピン留めできたせん
      */
@@ -3200,6 +3212,10 @@ export interface Locale extends ILocale {
      * 右クリックでリアクションピッカヌを開く
      */
     "useReactionPickerForContextMenu": string;
+    /**
+     * ナヌザペヌゞでセンシティブチャンネルの投皿を閉じる
+     */
+    "collapseSensitiveChannel": string;
     /**
      * {users}が入力䞭
      */
@@ -4892,6 +4908,10 @@ export interface Locale extends ILocale {
      * {name}のセンシティブなファむルを含む投皿
      */
     "userSaysSomethingSensitive": ParameterizedString<"name">;
+    /**
+     * {name}のセンシティブチャンネルでの投皿
+     */
+    "userSaysSomethingInSensitiveChannel": ParameterizedString<"name">;
     /**
      * スワむプしおタブを切り替える
      */
@@ -6939,6 +6959,14 @@ export interface Locale extends ILocale {
          * プロゞェクトメンバヌ
          */
         "projectMembers": string;
+        /**
+         * このサヌバヌで䜿甚しおいるforkの䞻芁な開発者
+         */
+        "forkContributors": string;
+        /**
+         * このサヌバヌで䜿甚しおいるforkの党おのコントリビュヌタヌ
+         */
+        "allForkContributors": string;
     };
     "_displayOfSensitiveMedia": {
         /**
@@ -9198,8 +9226,42 @@ export interface Locale extends ILocale {
              * メンションされたずき
              */
             "mention": string;
+            /**
+             * 以䞋のナヌザがnoteしたずき
+             */
+            "usersLabel": string;
+            /**
+             * このサヌバヌのナヌザの@に挟たれた郚分を改行で区切っお指定したす
+             */
+            "usersCaption": string;
         };
     };
+    "_imageCompressionMode": {
+        /**
+         * 画像の圧瞮圢匏
+         */
+        "title": string;
+        /**
+         * オリゞナル画像を保持しない堎合に、Web公開甚画像の圧瞮圢匏を遞択できたす。瞮小する堎合は2560x2560より小さくなるように瞮小されたす。非可逆圧瞮を指定しない堎合は、元画像に応じお非可逆圧瞮か可逆圧瞮かが自動的に遞択されたす。
+         */
+        "description": string;
+        /**
+         * 瞮小しお再圧瞮する
+         */
+        "resizeCompress": string;
+        /**
+         * 瞮小せず再圧瞮する
+         */
+        "noResizeCompress": string;
+        /**
+         * 瞮小しお非可逆圧瞮する
+         */
+        "resizeCompressLossy": string;
+        /**
+         * 瞮小せず非可逆圧瞮する
+         */
+        "noResizeCompressLossy": string;
+    };
     "_moderationLogTypes": {
         /**
          * ロヌルを䜜成
@@ -9345,6 +9407,10 @@ export interface Locale extends ILocale {
          * ナヌザヌのバナヌを解陀
          */
         "unsetUserBanner": string;
+        /**
+         * ノヌトをホヌム投皿に倉曎
+         */
+        "makeNoteHome": string;
     };
     "_fileViewer": {
         /**
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 61f2db9f831e..e7e17da47c3d 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -49,6 +49,7 @@ copyLinkRenote: "リノヌトのリンクをコピヌ"
 delete: "削陀"
 deleteAndEdit: "削陀しお線集"
 deleteAndEditConfirm: "このノヌトを削陀しおもう䞀床線集したすかこのノヌトぞのリアクション、リノヌト、返信も党お削陀されたす。"
+makeNoteHome: "ホヌム投皿にする"
 addToList: "リストに远加"
 addToAntenna: "アンテナに远加"
 sendMessage: "メッセヌゞを送信"
@@ -118,6 +119,7 @@ pinned: "ピン留め"
 you: "あなた"
 clickToShow: "クリックしお衚瀺"
 sensitive: "センシティブ"
+sensitiveChannelAutoCW: "センシティブチャンネル自動CW"
 add: "远加"
 reaction: "リアクション"
 reactions: "リアクション"
@@ -229,6 +231,7 @@ blockedUsers: "ブロックしたナヌザヌ"
 noUsers: "ナヌザヌはいたせん"
 editProfile: "プロフィヌルを線集"
 noteDeleteConfirm: "このノヌトを削陀したすか"
+makeNoteHomeConfirm: "本圓にホヌム投皿にしたすか"
 pinLimitExceeded: "これ以䞊ピン留めできたせん"
 intro: "Misskeyのむンストヌルが完了したした管理者アカりントを䜜成したしょう。"
 done: "完了"
@@ -796,6 +799,7 @@ emailNotification: "メヌル通知"
 publish: "公開"
 inChannelSearch: "チャンネル内怜玢"
 useReactionPickerForContextMenu: "右クリックでリアクションピッカヌを開く"
+collapseSensitiveChannel: "ナヌザペヌゞでセンシティブチャンネルの投皿を閉じる"
 typingUsers: "{users}が入力䞭"
 jumpToSpecifiedDate: "特定の日付にゞャンプ"
 showingPastTimeline: "過去のタむムラむンを衚瀺しおいたす"
@@ -1219,6 +1223,7 @@ backToTitle: "タむトルぞ"
 hemisphere: "お䜏たいの地域"
 withSensitive: "センシティブなファむルを含むノヌトを衚瀺"
 userSaysSomethingSensitive: "{name}のセンシティブなファむルを含む投皿"
+userSaysSomethingInSensitiveChannel: "{name}のセンシティブチャンネルでの投皿"
 enableHorizontalSwipe: "スワむプしおタブを切り替える"
 loading: "読み蟌み䞭"
 surrender: "やめる"
@@ -1810,6 +1815,8 @@ _aboutMisskey:
   morePatrons: "他にも倚くの方が支揎しおくれおいたす。ありがずうございたす🥰"
   patrons: "支揎者"
   projectMembers: "プロゞェクトメンバヌ"
+  forkContributors: "このサヌバヌで䜿甚しおいるforkの䞻芁な開発者"
+  allForkContributors: "このサヌバヌで䜿甚しおいるforkの党おのコントリビュヌタヌ"
 
 _displayOfSensitiveMedia:
   respect: "センシティブ蚭定されたメディアを隠す"
@@ -2440,6 +2447,16 @@ _webhookSettings:
     renote: "Renoteされたずき"
     reaction: "リアクションがあったずき"
     mention: "メンションされたずき"
+    usersLabel: "以䞋のナヌザがnoteしたずき"
+    usersCaption: "このサヌバヌのナヌザの@に挟たれた郚分を改行で区切っお指定したす"
+
+_imageCompressionMode:
+  title: "画像の圧瞮圢匏"
+  description: "オリゞナル画像を保持しない堎合に、Web公開甚画像の圧瞮圢匏を遞択できたす。瞮小する堎合は2560x2560より小さくなるように瞮小されたす。非可逆圧瞮を指定しない堎合は、元画像に応じお非可逆圧瞮か可逆圧瞮かが自動的に遞択されたす。"
+  resizeCompress: "瞮小しお再圧瞮する"
+  noResizeCompress: "瞮小せず再圧瞮する"
+  resizeCompressLossy: "瞮小しお非可逆圧瞮する"
+  noResizeCompressLossy: "瞮小せず非可逆圧瞮する"
 
 _moderationLogTypes:
   createRole: "ロヌルを䜜成"
@@ -2478,6 +2495,7 @@ _moderationLogTypes:
   deleteAvatarDecoration: "アむコンデコレヌションを削陀"
   unsetUserAvatar: "ナヌザヌのアむコンを解陀"
   unsetUserBanner: "ナヌザヌのバナヌを解陀"
+  makeNoteHome: "ノヌトをホヌム投皿に倉曎"
 
 _fileViewer:
   title: "ファむルの詳现"
diff --git a/package.json b/package.json
index 41b865456dbb..01032829c550 100644
--- a/package.json
+++ b/package.json
@@ -1,10 +1,10 @@
 {
 	"name": "misskey",
-	"version": "2024.3.1",
+	"version": "2024.3.1-kinel.1",
 	"codename": "nasubi",
 	"repository": {
 		"type": "git",
-		"url": "https://github.com/misskey-dev/misskey.git"
+		"url": "https://github.com/niri-la/misskey.niri.la"
 	},
 	"packageManager": "pnpm@8.15.4",
 	"workspaces": [
diff --git a/packages/backend/migration/1700906353915-HardMuteAndSoftMuteFromRegistory.js b/packages/backend/migration/1700906353915-HardMuteAndSoftMuteFromRegistory.js
new file mode 100644
index 000000000000..27e2d3b93b72
--- /dev/null
+++ b/packages/backend/migration/1700906353915-HardMuteAndSoftMuteFromRegistory.js
@@ -0,0 +1,66 @@
+export class HardMuteAndSoftMuteFromRegistory1700906353915 {
+    async up(queryRunner) {
+			// for servers start using this fork of misskey after 2023.11.1-kinel.1,
+			// This migration should not be run so opt in this migration by setting
+			// `MIGRATE_HARD_MUTE_AND_SOFT_MUTE_FROM_REGISTORY` to `true`
+			if (process.env.MIGRATE_HARD_MUTE_AND_SOFT_MUTE_FROM_REGISTORY !== 'true') return;
+
+			// until 2023.9.3-kinel.4, `mutedWords` means hard muted words
+			// since 2023.11.1-kinel.1, `mutedWords` means soft muted words and `hardMutedWords` means hard muted words
+			// so migrate hard muted words to `hardMutedWords`
+			await queryRunner.query(`UPDATE "user_profile" SET "hardMutedWords" = "mutedWords";`);
+
+			// then, migrate soft muted words from registry
+			let entries = await queryRunner.query(
+				`SELECT "userId", "value" FROM "registry_item"
+               WHERE "registry_item"."domain" IS NULL
+               AND "registry_item"."key" = $1
+               AND "registry_item"."scope" = $2;`,
+				['mutedWords', ['client', 'base']]);
+
+			for (let entry of entries) {
+				await queryRunner.query(`UPDATE "user_profile" SET "mutedWords" = $1 WHERE "user_profile"."userId" = $2;`,
+					[JSON.stringify(entry.value), entry.userId]);
+			}
+    }
+
+    async down(queryRunner) {
+			// for servers start using this fork of misskey after 2023.11.1-kinel.1,
+			// This migration should not be run so opt in this migration by setting
+			// `MIGRATE_HARD_MUTE_AND_SOFT_MUTE_FROM_REGISTORY` to `true`
+			if (process.env.MIGRATE_HARD_MUTE_AND_SOFT_MUTE_FROM_REGISTORY !== 'true') return;
+
+			const entries = await queryRunner.query(`SELECT "userId", "mutedWords" FROM "user_profile";`);
+			for (let entry of entries) {
+				let existingEntry = await queryRunner.query(
+					`SELECT "id", "userId", "value" FROM "registry_item"
+               WHERE "registry_item"."domain" IS NULL
+                 AND "registry_item"."key" = $1
+								 AND "registry_item"."scope" = $2
+								 AND "registry_item"."userId" = $3;`,
+					['mutedWords', ['client', 'base'], entry.userId]);
+
+				if (existingEntry.length > 0) {
+					await queryRunner.connection.createQueryBuilder()
+						.update('registry_item')
+						.set({ value: entry.mutedWords })
+						.where('id = :id', { id: existingEntry[0].id })
+						.execute();
+				} else {
+					await queryRunner.connection.createQueryBuilder()
+						.insert()
+						.into('registry_item')
+						.values({
+							key: 'mutedWords',
+							scope: ['client', 'base'],
+							userId: entry.userId,
+							domain: null,
+							value: entry.mutedWords
+						})
+						.execute();
+				}
+			}
+
+			await queryRunner.query(`UPDATE "user_profile" SET "mutedWords" = "hardMutedWords";`);
+    }
+}
diff --git a/packages/backend/migration/1708005334196-NirilaDeleteUserLogRepository.js b/packages/backend/migration/1708005334196-NirilaDeleteUserLogRepository.js
new file mode 100644
index 000000000000..eae6fba01c1c
--- /dev/null
+++ b/packages/backend/migration/1708005334196-NirilaDeleteUserLogRepository.js
@@ -0,0 +1,24 @@
+/*
+ * SPDX-FileCopyrightText: anatawa12
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class NirilaDeleteUserLogRepository1708005334196 {
+	constructor() {
+		this.name = 'NirilaDeleteUserLogRepository1708005334196'
+	}
+
+	async up(queryRunner) {
+		await queryRunner.query(`CREATE TABLE "nirila_delete_user_log" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "username" character varying(128) NOT NULL, "email" character varying(128), "info" jsonb NOT NULL, CONSTRAINT "PK_34be175a3f407ea9f0dd42da572" PRIMARY KEY ("id")); COMMENT ON COLUMN "nirila_delete_user_log"."username" IS 'The username of the deleted User.'; COMMENT ON COLUMN "nirila_delete_user_log"."email" IS 'The email adddress of the deleted User.'`);
+		await queryRunner.query(`CREATE INDEX "IDX_343c0efea0f002a2bf34960bd1" ON "nirila_delete_user_log" ("userId") `);
+		await queryRunner.query(`CREATE INDEX "IDX_940ffb8ea315bef184208780f8" ON "nirila_delete_user_log" ("username") `);
+		await queryRunner.query(`CREATE INDEX "IDX_da5751593e765f39d09303f768" ON "nirila_delete_user_log" ("email") `);
+	}
+
+	async down(queryRunner) {
+		await queryRunner.query(`DROP INDEX "public"."IDX_da5751593e765f39d09303f768"`);
+		await queryRunner.query(`DROP INDEX "public"."IDX_940ffb8ea315bef184208780f8"`);
+		await queryRunner.query(`DROP INDEX "public"."IDX_343c0efea0f002a2bf34960bd1"`);
+		await queryRunner.query(`DROP TABLE "nirila_delete_user_log"`);
+	}
+}
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index 0ca1fa55c1f8..cb92bd877920 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -93,6 +93,15 @@ type Source = {
 	perUserNotificationsMaxCount?: number;
 	deactivateAntennaThreshold?: number;
 	pidFile: string;
+
+	nirila?: {
+		abuseDiscordHook?: string;
+		disableAbuseRepository?: boolean;
+		maxWebImageSize?: number;
+		withRepliesInHomeTL?: boolean;
+		withRepliesInUserList?: boolean;
+		blockMentionsFromUnfamiliarRemoteUsers?: boolean;
+	}
 };
 
 export type Config = {
@@ -170,6 +179,15 @@ export type Config = {
 	perUserNotificationsMaxCount: number;
 	deactivateAntennaThreshold: number;
 	pidFile: string;
+
+	nirila: {
+		abuseDiscordHook?: string;
+		disableAbuseRepository?: boolean;
+		maxWebImageSize?: number;
+		withRepliesInHomeTL?: boolean,
+		withRepliesInUserList: boolean,
+		blockMentionsFromUnfamiliarRemoteUsers: boolean;
+	}
 };
 
 const _filename = fileURLToPath(import.meta.url);
@@ -211,6 +229,11 @@ export function loadConfig(): Config {
 	const redis = convertRedisOptions(config.redis, host);
 
 	return {
+		// to avoid merge conflict in the future, this is at top
+		nirila: Object.assign({
+			withRepliesInUserList: true,
+			blockMentionsFromUnfamiliarRemoteUsers: false,
+		}, config.nirila ?? {}),
 		version,
 		publishTarballInsteadOfProvideRepositoryUrl: !!config.publishTarballInsteadOfProvideRepositoryUrl,
 		url: url.origin,
diff --git a/packages/backend/src/core/AbuseDiscordHookService.ts b/packages/backend/src/core/AbuseDiscordHookService.ts
new file mode 100644
index 000000000000..ca9e53432e6c
--- /dev/null
+++ b/packages/backend/src/core/AbuseDiscordHookService.ts
@@ -0,0 +1,40 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { DI } from '@/di-symbols.js';
+import type { MiUser } from '@/models/_.js';
+import { bindThis } from '@/decorators.js';
+import type { Config } from '@/config.js';
+import { HttpRequestService } from '@/core/HttpRequestService.js';
+
+@Injectable()
+export class AbuseDiscordHookService {
+	constructor(
+		@Inject(DI.config)
+		private config: Config,
+
+		private httpRequestService: HttpRequestService,
+	) {
+	}
+
+	@bindThis
+	public send(me: MiUser, user: MiUser, comment: string): void {
+		const webhookUrl = this.config.nirila?.abuseDiscordHook;
+		if (webhookUrl) {
+			setImmediate(async () => {
+				const content = 'New abuse report created!\n'
+					+ `author: \`@${me.username}${me.host ? `@${me.host}` : ''}\`\n`
+					+ `target user: \`@${user.username}${user.host ? `@${user.host}` : ''}\`\n`
+					+ 'Comment:\n'
+					+ comment;
+
+				await this.httpRequestService.send(webhookUrl, {
+					method: 'POST',
+					headers: {
+						'User-Agent': 'Niri-la-Misskey-Hooks',
+						'Content-Type': 'application/json',
+					},
+					body: JSON.stringify({ content }),
+				});
+			});
+		}
+	}
+}
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index 506bea8c0cb9..e9ca80dd5ddd 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -140,6 +140,7 @@ import { VmimiRelayTimelineService } from './VmimiRelayTimelineService.js';
 import { QueueModule } from './QueueModule.js';
 import { QueueService } from './QueueService.js';
 import { LoggerService } from './LoggerService.js';
+import { AbuseDiscordHookService } from './AbuseDiscordHookService.js';
 import type { Provider } from '@nestjs/common';
 
 //#region 文字列ベヌスでのinjection甹(埪環参照察応のため)
@@ -419,6 +420,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		ApPersonService,
 		ApQuestionService,
 		QueueService,
+		AbuseDiscordHookService,
 
 		//#region 文字列ベヌスでのinjection甹(埪環参照察応のため)
 		$VmimiRelayTimelineService,
@@ -693,6 +695,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		ApPersonService,
 		ApQuestionService,
 		QueueService,
+		AbuseDiscordHookService,
 
 		//#region 文字列ベヌスでのinjection甹(埪環参照察応のため)
 		$VmimiRelayTimelineService,
diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts
index 1bc1df1dda4c..c55e098bc400 100644
--- a/packages/backend/src/core/DriveService.ts
+++ b/packages/backend/src/core/DriveService.ts
@@ -301,19 +301,26 @@ export class DriveService {
 		let img: sharp.Sharp | null = null;
 		let satisfyWebpublic: boolean;
 		let isAnimated: boolean;
+		let compressedWidth: number;
+		let compressedHeight: number;
 
 		try {
 			img = await sharpBmp(path, type);
 			const metadata = await img.metadata();
 			isAnimated = !!(metadata.pages && metadata.pages > 1);
 
+			const maxSize = this.config.nirila?.maxWebImageSize ?? 8192;
+			// nirila Extension: We want to keep original size as possible
+			// noinspection PointlessBooleanExpressionJS
 			satisfyWebpublic = !!(
 				type !== 'image/svg+xml' && // security reason
 				type !== 'image/avif' && // not supported by Mastodon and MS Edge
 			!(metadata.exif ?? metadata.iptc ?? metadata.xmp ?? metadata.tifftagPhotoshop) &&
-			metadata.width && metadata.width <= 2048 &&
-			metadata.height && metadata.height <= 2048
+			metadata.width && metadata.width <= maxSize &&
+			metadata.height && metadata.height <= maxSize
 			);
+			compressedWidth = metadata.width && metadata.width <= maxSize ? metadata.width : maxSize;
+			compressedHeight = metadata.height && metadata.height <= maxSize ? metadata.height : maxSize;
 		} catch (err) {
 			this.registerLogger.warn(`sharp failed: ${err}`);
 			return {
@@ -330,9 +337,9 @@ export class DriveService {
 
 			try {
 				if (['image/jpeg', 'image/webp', 'image/avif'].includes(type)) {
-					webpublic = await this.imageProcessingService.convertSharpToWebp(img, 2048, 2048);
+					webpublic = await this.imageProcessingService.convertSharpToWebp(img, compressedWidth, compressedHeight);
 				} else if (['image/png', 'image/bmp', 'image/svg+xml'].includes(type)) {
-					webpublic = await this.imageProcessingService.convertSharpToPng(img, 2048, 2048);
+					webpublic = await this.imageProcessingService.convertSharpToPng(img, compressedWidth, compressedHeight);
 				} else {
 					this.registerLogger.debug('web image not created (not an required image)');
 				}
diff --git a/packages/backend/src/core/FanoutTimelineService.ts b/packages/backend/src/core/FanoutTimelineService.ts
index f6dabfadcd6d..461ba9e21e67 100644
--- a/packages/backend/src/core/FanoutTimelineService.ts
+++ b/packages/backend/src/core/FanoutTimelineService.ts
@@ -48,6 +48,11 @@ export class FanoutTimelineService {
 	) {
 	}
 
+	@bindThis
+	public remove(tl: FanoutTimelineName, id: string, pipeline: Redis.ChainableCommander) {
+		pipeline.lrem('list:' + tl, 0, id);
+	}
+
 	@bindThis
 	public push(tl: FanoutTimelineName, id: string, maxlen: number, pipeline: Redis.ChainableCommander) {
 		// リモヌトから遅れお届いた(もしくは埌から远加された)投皿日時が叀い投皿が远加されるずペヌゞネヌション時に問題を匕き起こすため、
diff --git a/packages/backend/src/core/FeaturedService.ts b/packages/backend/src/core/FeaturedService.ts
index b3335e38da47..1af2e15db45b 100644
--- a/packages/backend/src/core/FeaturedService.ts
+++ b/packages/backend/src/core/FeaturedService.ts
@@ -77,6 +77,50 @@ export class FeaturedService {
 		return Array.from(ranking.keys());
 	}
 
+	// TODO: find better place?
+	@bindThis
+	public shouldBeIncludedInGlobalOrUserFeatured(note: MiNote): boolean {
+		if (note.visibility !== 'public') return false; // non-public note
+		if (note.userHost != null) return false; // remote
+		if (note.replyId != null) return false; // reply
+		// Channels are checked outside
+
+		// In nirila misskey, it was very common to notes with `:ohayo_nirila_misskey:` or `:oyasumi_nirila_misskey:`
+		// Will get many reaction`:ohayo_nirila_misskey:` or `:oyasumi_nirila_misskey:` so exclude them
+		// if they don't have any images.
+		if (note.fileIds.length === 0) {
+			for (const exclusion of ["おはよう", "おやすみ", ":ohayo_nirila_misskey:", ":oyasumi_nirila_misskey:", ":kon_nirila_misskey:"]) {
+				if (note.text?.includes(exclusion)) return false;
+				if (note.cw?.includes(exclusion)) return false;
+			}
+		}
+
+		return true;
+	}
+
+	@bindThis
+	private removeNoteFromRankingOf(name: string, windowRange: number, element: string, redisPipeline: Redis.ChainableCommander) {
+		// removing from current & previous window is enough
+		const currentWindow = this.getCurrentWindow(windowRange);
+		const previousWindow = currentWindow - 1;
+
+		redisPipeline.zrem(`${name}:${currentWindow}`, element);
+		redisPipeline.zrem(`${name}:${previousWindow}`, element);
+	}
+
+	@bindThis
+	public async removeNote(note: MiNote): Promise<void> {
+		const redisPipeline = this.redisClient.pipeline();
+		this.removeNoteFromRankingOf('featuredGlobalNotesRanking', GLOBAL_NOTES_RANKING_WINDOW, note.id, redisPipeline);
+		this.removeNoteFromRankingOf(`featuredPerUserNotesRanking:${note.userId}`, PER_USER_NOTES_RANKING_WINDOW, note.id, redisPipeline);
+
+		if (note.channelId) {
+			this.removeNoteFromRankingOf(`featuredInChannelNotesRanking:${note.channelId}`, GLOBAL_NOTES_RANKING_WINDOW, note.id, redisPipeline);
+		}
+
+		await redisPipeline.exec();
+	}
+
 	@bindThis
 	private async removeFromRanking(name: string, windowRange: number, element: string): Promise<void> {
 		const currentWindow = this.getCurrentWindow(windowRange);
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 81ae2908d3dc..c01535abea57 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -61,6 +61,8 @@ import { isReply } from '@/misc/is-reply.js';
 import { trackPromise } from '@/misc/promise-tracker.js';
 import { isNotNull } from '@/misc/is-not-null.js';
 import { IdentifiableError } from '@/misc/identifiable-error.js';
+import { LoggerService } from '@/core/LoggerService.js';
+import type Logger from '@/logger.js';
 
 type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
 
@@ -151,6 +153,7 @@ type Option = {
 
 @Injectable()
 export class NoteCreateService implements OnApplicationShutdown {
+	private logger: Logger;
 	#shutdownController = new AbortController();
 
 	constructor(
@@ -219,7 +222,10 @@ export class NoteCreateService implements OnApplicationShutdown {
 		private instanceChart: InstanceChart,
 		private utilityService: UtilityService,
 		private userBlockingService: UserBlockingService,
-	) { }
+		private loggerService: LoggerService,
+	) {
+		this.logger = this.loggerService.getLogger('note:create');
+	}
 
 	@bindThis
 	public async create(user: {
@@ -365,6 +371,18 @@ export class NoteCreateService implements OnApplicationShutdown {
 			mentionedUsers = data.apMentions ?? await this.extractMentionedUsers(user, combinedTokens);
 		}
 
+		const willCauseNotification = mentionedUsers.some(u => u.host === null)
+			|| (data.visibility === 'specified' && data.visibleUsers?.some(u => u.host === null))
+			|| data.reply?.userHost === null || (this.isQuote(data) && data.renote?.userHost === null) || false;
+
+		if (this.config.nirila.blockMentionsFromUnfamiliarRemoteUsers && user.host !== null && willCauseNotification) {
+			const userEntity = await this.usersRepository.findOneBy({ id: user.id });
+			if ((userEntity?.followersCount ?? 0) === 0) {
+				this.logger.error('Request rejected because user has no local followers', { user: user.id, note: data });
+				throw new IdentifiableError('e11b3a16-f543-4885-8eb1-66cad131dbfd', 'Notes including mentions, replies, or renotes from remote users are not allowed until user has at least one local follower.');
+			}
+		}
+
 		tags = tags.filter(tag => Array.from(tag).length <= 128).splice(0, 32);
 
 		if (data.reply && (user.id !== data.reply.userId) && !mentionedUsers.some(u => u.id === data.reply!.userId)) {
@@ -603,7 +621,8 @@ export class NoteCreateService implements OnApplicationShutdown {
 			this.roleService.addNoteToRoleTimeline(noteObj);
 
 			this.webhookService.getActiveWebhooks().then(webhooks => {
-				webhooks = webhooks.filter(x => x.userId === user.id && x.on.includes('note'));
+				const userNoteEvent = `note@${user.username}` as const;
+				webhooks = webhooks.filter(x => (x.userId === user.id && x.on.includes('note')) || x.on.includes(userNoteEvent));
 				for (const webhook of webhooks) {
 					this.queueService.webhookDeliver(webhook, 'note', {
 						note: noteObj,
@@ -739,14 +758,14 @@ export class NoteCreateService implements OnApplicationShutdown {
 			.where('id = :id', { id: renote.id })
 			.execute();
 
-		// 30%の確率、3日以内に投皿されたノヌトの堎合ハむラむト甚ランキング曎新
-		if (Math.random() < 0.3 && (Date.now() - this.idService.parse(renote.id).date.getTime()) < 1000 * 60 * 60 * 24 * 3) {
+		// ~~30%の確率、~~3日以内に投皿されたノヌトの堎合ハむラむト甚ランキング曎新
+		if ((Date.now() - this.idService.parse(renote.id).date.getTime()) < 1000 * 60 * 60 * 24 * 3) {
 			if (renote.channelId != null) {
 				if (renote.replyId == null) {
 					this.featuredService.updateInChannelNotesRanking(renote.channelId, renote.id, 5);
 				}
 			} else {
-				if (renote.visibility === 'public' && renote.userHost == null && renote.replyId == null) {
+				if (this.featuredService.shouldBeIncludedInGlobalOrUserFeatured(renote)) {
 					this.featuredService.updateGlobalNotesRanking(renote.id, 5);
 					this.featuredService.updatePerUserNotesRanking(renote.userId, renote.id, 5);
 				}
diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts
index c258a22927d4..d27b14d9bdb8 100644
--- a/packages/backend/src/core/QueueService.ts
+++ b/packages/backend/src/core/QueueService.ts
@@ -7,7 +7,7 @@ import { randomUUID } from 'node:crypto';
 import { Inject, Injectable } from '@nestjs/common';
 import type { IActivity } from '@/core/activitypub/type.js';
 import type { MiDriveFile } from '@/models/DriveFile.js';
-import type { MiWebhook, webhookEventTypes } from '@/models/Webhook.js';
+import type { MiWebhook, WebhookEventType } from '@/models/Webhook.js';
 import type { Config } from '@/config.js';
 import { DI } from '@/di-symbols.js';
 import { bindThis } from '@/decorators.js';
@@ -432,7 +432,7 @@ export class QueueService {
 	}
 
 	@bindThis
-	public webhookDeliver(webhook: MiWebhook, type: typeof webhookEventTypes[number], content: unknown) {
+	public webhookDeliver(webhook: MiWebhook, type: WebhookEventType, content: unknown) {
 		const data = {
 			type,
 			content,
diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts
index cb0b079df0dd..412cf8697fa0 100644
--- a/packages/backend/src/core/ReactionService.ts
+++ b/packages/backend/src/core/ReactionService.ts
@@ -196,9 +196,9 @@ export class ReactionService {
 			.where('id = :id', { id: note.id })
 			.execute();
 
-		// 30%の確率、セルフではない、3日以内に投皿されたノヌトの堎合ハむラむト甚ランキング曎新
+		// ~~30%の確率~~、セルフではない、3日以内に投皿されたノヌトの堎合ハむラむト甚ランキング曎新
 		if (
-			Math.random() < 0.3 &&
+			//Math.random() < 0.3 &&
 			note.userId !== user.id &&
 			(Date.now() - this.idService.parse(note.id).date.getTime()) < 1000 * 60 * 60 * 24 * 3
 		) {
@@ -207,7 +207,7 @@ export class ReactionService {
 					this.featuredService.updateInChannelNotesRanking(note.channelId, note.id, 1);
 				}
 			} else {
-				if (note.visibility === 'public' && note.userHost == null && note.replyId == null) {
+				if (this.featuredService.shouldBeIncludedInGlobalOrUserFeatured(note)) {
 					this.featuredService.updateGlobalNotesRanking(note.id, 1);
 					this.featuredService.updatePerUserNotesRanking(note.userId, note.id, 1);
 				}
diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts
index 1621c41bcc54..c29c9e9ed505 100644
--- a/packages/backend/src/core/activitypub/ApInboxService.ts
+++ b/packages/backend/src/core/activitypub/ApInboxService.ts
@@ -27,6 +27,7 @@ import { QueueService } from '@/core/QueueService.js';
 import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/_.js';
 import { bindThis } from '@/decorators.js';
 import type { MiRemoteUser } from '@/models/User.js';
+import { AbuseDiscordHookService } from '@/core/AbuseDiscordHookService.js';
 import { isNotNull } from '@/misc/is-not-null.js';
 import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
 import { ApNoteService } from './models/ApNoteService.js';
@@ -85,6 +86,7 @@ export class ApInboxService {
 		private apQuestionService: ApQuestionService,
 		private queueService: QueueService,
 		private globalEventService: GlobalEventService,
+		private abuseDiscordHookService: AbuseDiscordHookService,
 	) {
 		this.logger = this.apLoggerService.logger;
 	}
@@ -526,15 +528,19 @@ export class ApInboxService {
 		});
 		if (users.length < 1) return 'skip';
 
+		const comment = `${activity.content}\n${JSON.stringify(uris, null, 2)}`;
+
 		await this.abuseUserReportsRepository.insert({
 			id: this.idService.gen(),
 			targetUserId: users[0].id,
 			targetUserHost: users[0].host,
 			reporterId: actor.id,
 			reporterHost: actor.host,
-			comment: `${activity.content}\n${JSON.stringify(uris, null, 2)}`,
+			comment,
 		});
 
+		this.abuseDiscordHookService.send(actor, users[0], comment);
+
 		return 'ok';
 	}
 
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index 14761357a5a8..6d5f225b6f0b 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -303,11 +303,13 @@ export class UserEntityService implements OnModuleInit {
 			schema?: S,
 			includeSecrets?: boolean,
 			userProfile?: MiUserProfile,
+			asModerator?: boolean,
 		},
 	): Promise<Packed<S>> {
 		const opts = Object.assign({
 			schema: 'UserLite',
 			includeSecrets: false,
+			asModerator: undefined as boolean | undefined,
 		}, options);
 
 		const user = typeof src === 'object' ? src : await this.usersRepository.findOneByOrFail({ id: src });
@@ -315,7 +317,7 @@ export class UserEntityService implements OnModuleInit {
 		const isDetailed = opts.schema !== 'UserLite';
 		const meId = me ? me.id : null;
 		const isMe = meId === user.id;
-		const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false;
+		const iAmModerator = opts.asModerator ?? (me ? await this.roleService.isModerator(me as MiUser) : false);
 
 		const relation = meId && !isMe && isDetailed ? await this.getRelation(meId, user.id) : null;
 		const pins = isDetailed ? await this.userNotePiningsRepository.createQueryBuilder('pin')
diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts
index 919f4794a325..bf941aba0038 100644
--- a/packages/backend/src/di-symbols.ts
+++ b/packages/backend/src/di-symbols.ts
@@ -13,6 +13,7 @@ export const DI = {
 	redisForTimelines: Symbol('redisForTimelines'),
 
 	//#region Repositories
+	nirilaDeleteUserLogRepository: Symbol('nirilaDeleteUserLogRepository'),
 	usersRepository: Symbol('usersRepository'),
 	notesRepository: Symbol('notesRepository'),
 	announcementsRepository: Symbol('announcementsRepository'),
diff --git a/packages/backend/src/models/NirilaDeleteUserLog.ts b/packages/backend/src/models/NirilaDeleteUserLog.ts
new file mode 100644
index 000000000000..ab838dcb0e2b
--- /dev/null
+++ b/packages/backend/src/models/NirilaDeleteUserLog.ts
@@ -0,0 +1,38 @@
+/*
+ * SPDX-FileCopyrightText: anatawa12
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { PrimaryColumn, Entity, Index, Column } from 'typeorm';
+import { MiUserProfile } from '@/models/UserProfile.js';
+import { id } from './util/id.js';
+import { MiUser } from './User.js';
+
+@Entity('nirila_delete_user_log')
+export class NirilaDeleteUserLog {
+	// delete user log id
+	@PrimaryColumn(id())
+	public id: string;
+
+	@Index()
+	@Column(id())
+	public userId: MiUser['id'];
+
+	@Index()
+	@Column('varchar', {
+		length: 128,
+		comment: 'The username of the deleted User.',
+	})
+	public username: MiUser['username'];
+
+	@Index()
+	@Column('varchar', {
+		length: 128, nullable: true,
+		comment: 'The email adddress of the deleted User.',
+	})
+	public email: MiUserProfile['email'];
+
+	// user profile info
+	@Column('jsonb')
+	public info: Record<string, any>;
+}
diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts
index bd447570ddf0..fa95331ba950 100644
--- a/packages/backend/src/models/RepositoryModule.ts
+++ b/packages/backend/src/models/RepositoryModule.ts
@@ -5,10 +5,16 @@
 
 import { Module } from '@nestjs/common';
 import { DI } from '@/di-symbols.js';
-import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiAvatarDecoration, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook, MiBubbleGameRecord, MiReversiGame } from './_.js';
+import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiAvatarDecoration, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook, MiBubbleGameRecord, MiReversiGame, NirilaDeleteUserLog } from './_.js';
 import type { DataSource } from 'typeorm';
 import type { Provider } from '@nestjs/common';
 
+const $nirilaDeleteUserLogRepository: Provider = {
+	provide: DI.nirilaDeleteUserLogRepository,
+	useFactory: (db: DataSource) => db.getRepository(NirilaDeleteUserLog),
+	inject: [DI.db],
+};
+
 const $usersRepository: Provider = {
 	provide: DI.usersRepository,
 	useFactory: (db: DataSource) => db.getRepository(MiUser),
@@ -415,6 +421,7 @@ const $reversiGamesRepository: Provider = {
 	imports: [
 	],
 	providers: [
+		$nirilaDeleteUserLogRepository,
 		$usersRepository,
 		$notesRepository,
 		$announcementsRepository,
@@ -484,6 +491,7 @@ const $reversiGamesRepository: Provider = {
 		$reversiGamesRepository,
 	],
 	exports: [
+		$nirilaDeleteUserLogRepository,
 		$usersRepository,
 		$notesRepository,
 		$announcementsRepository,
diff --git a/packages/backend/src/models/Webhook.ts b/packages/backend/src/models/Webhook.ts
index db24c03b3dba..be42342e3fa6 100644
--- a/packages/backend/src/models/Webhook.ts
+++ b/packages/backend/src/models/Webhook.ts
@@ -8,6 +8,7 @@ import { id } from './util/id.js';
 import { MiUser } from './User.js';
 
 export const webhookEventTypes = ['mention', 'unfollow', 'follow', 'followed', 'note', 'reply', 'renote', 'reaction'] as const;
+export type WebhookEventType = (typeof webhookEventTypes)[number] | `note@${string}`;
 
 @Entity('webhook')
 export class MiWebhook {
@@ -37,7 +38,7 @@ export class MiWebhook {
 	@Column('varchar', {
 		length: 128, array: true, default: '{}',
 	})
-	public on: (typeof webhookEventTypes)[number][];
+	public on: WebhookEventType[];
 
 	@Column('varchar', {
 		length: 1024,
diff --git a/packages/backend/src/models/_.ts b/packages/backend/src/models/_.ts
index 43d42d80dd32..500850d135dd 100644
--- a/packages/backend/src/models/_.ts
+++ b/packages/backend/src/models/_.ts
@@ -3,6 +3,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
+import { NirilaDeleteUserLog } from '@/models/NirilaDeleteUserLog.js';
 import { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
 import { MiAccessToken } from '@/models/AccessToken.js';
 import { MiAd } from '@/models/Ad.js';
@@ -74,6 +75,8 @@ import { MiReversiGame } from '@/models/ReversiGame.js';
 import type { Repository } from 'typeorm';
 
 export {
+	NirilaDeleteUserLog,
+
 	MiAbuseUserReport,
 	MiAccessToken,
 	MiAd,
@@ -143,6 +146,7 @@ export {
 	MiReversiGame,
 };
 
+export type NirilaDeleteUserLogRepository = Repository<NirilaDeleteUserLog>;
 export type AbuseUserReportsRepository = Repository<MiAbuseUserReport>;
 export type AccessTokensRepository = Repository<MiAccessToken>;
 export type AdsRepository = Repository<MiAd>;
diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts
index 2d14537bbb07..985707c58fca 100644
--- a/packages/backend/src/postgres.ts
+++ b/packages/backend/src/postgres.ts
@@ -11,6 +11,7 @@ import { DataSource, Logger } from 'typeorm';
 import * as highlight from 'cli-highlight';
 import { entities as charts } from '@/core/chart/entities.js';
 
+import { NirilaDeleteUserLog } from '@/models/NirilaDeleteUserLog.js';
 import { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
 import { MiAccessToken } from '@/models/AccessToken.js';
 import { MiAd } from '@/models/Ad.js';
@@ -127,6 +128,7 @@ class MyCustomLogger implements Logger {
 }
 
 export const entities = [
+	NirilaDeleteUserLog,
 	MiAnnouncement,
 	MiAnnouncementRead,
 	MiMeta,
diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts
index 14a53e0c428d..974fce2e21e1 100644
--- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts
@@ -6,7 +6,7 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { MoreThan } from 'typeorm';
 import { DI } from '@/di-symbols.js';
-import type { DriveFilesRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import type { DriveFilesRepository, MiUser, NotesRepository, UserProfilesRepository, UsersRepository, NirilaDeleteUserLogRepository, SigninsRepository } from '@/models/_.js';
 import type Logger from '@/logger.js';
 import { DriveService } from '@/core/DriveService.js';
 import type { MiDriveFile } from '@/models/DriveFile.js';
@@ -14,6 +14,10 @@ import type { MiNote } from '@/models/Note.js';
 import { EmailService } from '@/core/EmailService.js';
 import { bindThis } from '@/decorators.js';
 import { SearchService } from '@/core/SearchService.js';
+import { RoleService } from '@/core/RoleService.js';
+import { RoleEntityService } from '@/core/entities/RoleEntityService.js';
+import { IdService } from '@/core/IdService.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
 import type * as Bull from 'bullmq';
 import type { DbUserDeleteJobData } from '../types.js';
@@ -35,6 +39,17 @@ export class DeleteAccountProcessorService {
 		@Inject(DI.driveFilesRepository)
 		private driveFilesRepository: DriveFilesRepository,
 
+		@Inject(DI.nirilaDeleteUserLogRepository)
+		private nirilaDeleteUserLogRepository: NirilaDeleteUserLogRepository,
+
+		@Inject(DI.signinsRepository)
+		private signinsRepository: SigninsRepository,
+
+		private roleService: RoleService,
+		private roleEntityService: RoleEntityService,
+		private idService: IdService,
+		private userEntityService: UserEntityService,
+
 		private driveService: DriveService,
 		private emailService: EmailService,
 		private queueLoggerService: QueueLoggerService,
@@ -43,6 +58,77 @@ export class DeleteAccountProcessorService {
 		this.logger = this.queueLoggerService.logger.createSubLogger('delete-account');
 	}
 
+	@bindThis
+	async logDelete(user: MiUser) {
+		if (user.host != null) {
+			this.logger.info(`Skipping logging account deletion of ${user.id} ...`);
+			return;
+		}
+
+		const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
+
+		// data from src/server/api/endpoints/users/show.ts
+		const detailedUser = await this.userEntityService.pack<'UserDetailed'>(user, null, {
+			schema: 'UserDetailed',
+			asModerator: true,
+		});
+
+		// data from src/server/api/endpoints/admin/show-user.ts
+
+		const isModerator = await this.roleService.isModerator(user);
+		const isSilenced = !(await this.roleService.getUserPolicies(user.id)).canPublicNote;
+
+		const signins = await this.signinsRepository.findBy({ userId: user.id });
+
+		const roleAssigns = await this.roleService.getUserAssigns(user.id);
+		const roles = await this.roleService.getUserRoles(user.id);
+
+		const adminInfo = {
+			email: profile.email,
+			emailVerified: profile.emailVerified,
+			autoAcceptFollowed: profile.autoAcceptFollowed,
+			noCrawle: profile.noCrawle,
+			preventAiLearning: profile.preventAiLearning,
+			alwaysMarkNsfw: profile.alwaysMarkNsfw,
+			autoSensitive: profile.autoSensitive,
+			carefulBot: profile.carefulBot,
+			injectFeaturedNote: profile.injectFeaturedNote,
+			receiveAnnouncementEmail: profile.receiveAnnouncementEmail,
+			mutedWords: profile.mutedWords,
+			mutedInstances: profile.mutedInstances,
+			notificationRecieveConfig: profile.notificationRecieveConfig,
+			isModerator: isModerator,
+			isSilenced: isSilenced,
+			isSuspended: user.isSuspended,
+			isHibernated: user.isHibernated,
+			lastActiveDate: user.lastActiveDate,
+			moderationNote: profile.moderationNote ?? '',
+			signins,
+			policies: await this.roleService.getUserPolicies(user.id),
+			roles: await this.roleEntityService.packMany(roles, { id: user.id }), // note: me is unused param
+			roleAssigns: roleAssigns.map(a => ({
+				createdAt: this.idService.parse(a.id).date.toISOString(),
+				expiresAt: a.expiresAt ? a.expiresAt.toISOString() : null,
+				roleId: a.roleId,
+			})),
+		};
+
+		const info: Record<string, any> = {
+			user: detailedUser,
+			adminInfo,
+		};
+
+		await this.nirilaDeleteUserLogRepository.insert({
+			id: this.idService.gen(),
+			userId: user.id,
+			username: user.username,
+			email: profile.email,
+			info,
+		});
+
+		this.logger.info(`Finished logging account deletion of ${user.id} ...`);
+	}
+
 	@bindThis
 	public async process(job: Bull.Job<DbUserDeleteJobData>): Promise<string | void> {
 		this.logger.info(`Deleting account of ${job.data.user.id} ...`);
@@ -52,6 +138,12 @@ export class DeleteAccountProcessorService {
 			return;
 		}
 
+		try {
+			this.logDelete(user);
+		} catch (e) {
+			this.logger.error(`Failed to log delete: ${e}`);
+		}
+
 		{ // Delete notes
 			let cursor: MiNote['id'] | null = null;
 
diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts
index 3addead0587b..adbd343b3eb2 100644
--- a/packages/backend/src/queue/processors/InboxProcessorService.ts
+++ b/packages/backend/src/queue/processors/InboxProcessorService.ts
@@ -185,6 +185,7 @@ export class InboxProcessorService {
 			await this.apInboxService.performActivity(authUser.user, activity);
 		} catch (e) {
 			if (e instanceof IdentifiableError) {
+				if (e.id === 'e11b3a16-f543-4885-8eb1-66cad131dbfd') return 'blocked mentions from unfamiliar user';
 				if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') {
 					return 'blocked notes with prohibited words';
 				}
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index 827c9e66b7ce..b70bdc19bac9 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -6,6 +6,7 @@
 import { Module } from '@nestjs/common';
 
 import { CoreModule } from '@/core/CoreModule.js';
+import * as ep___admin_nirilaDeleteUserLogAccess from './endpoints/admin/nirila-delete-user-log-access.js';
 import * as ep___admin_meta from './endpoints/admin/meta.js';
 import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
 import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js';
@@ -62,6 +63,7 @@ import * as ep___admin_relays_add from './endpoints/admin/relays/add.js';
 import * as ep___admin_relays_list from './endpoints/admin/relays/list.js';
 import * as ep___admin_relays_remove from './endpoints/admin/relays/remove.js';
 import * as ep___admin_resetPassword from './endpoints/admin/reset-password.js';
+import * as ep___admin_notePublicToHome from './endpoints/admin/note-public-to-home.js';
 import * as ep___admin_resolveAbuseUserReport from './endpoints/admin/resolve-abuse-user-report.js';
 import * as ep___admin_sendEmail from './endpoints/admin/send-email.js';
 import * as ep___admin_serverInfo from './endpoints/admin/server-info.js';
@@ -379,6 +381,7 @@ import { GetterService } from './GetterService.js';
 import { ApiLoggerService } from './ApiLoggerService.js';
 import type { Provider } from '@nestjs/common';
 
+const $admin_nirilaDeleteUserLogAccess: Provider = { provide: 'ep:admin/nirila-delete-user-log-access', useClass: ep___admin_nirilaDeleteUserLogAccess.default };
 const $admin_meta: Provider = { provide: 'ep:admin/meta', useClass: ep___admin_meta.default };
 const $admin_abuseUserReports: Provider = { provide: 'ep:admin/abuse-user-reports', useClass: ep___admin_abuseUserReports.default };
 const $admin_accounts_create: Provider = { provide: 'ep:admin/accounts/create', useClass: ep___admin_accounts_create.default };
@@ -435,6 +438,7 @@ const $admin_relays_add: Provider = { provide: 'ep:admin/relays/add', useClass:
 const $admin_relays_list: Provider = { provide: 'ep:admin/relays/list', useClass: ep___admin_relays_list.default };
 const $admin_relays_remove: Provider = { provide: 'ep:admin/relays/remove', useClass: ep___admin_relays_remove.default };
 const $admin_resetPassword: Provider = { provide: 'ep:admin/reset-password', useClass: ep___admin_resetPassword.default };
+const $admin_notePublicToHome: Provider = { provide: 'ep:admin/note-public-to-home', useClass: ep___admin_notePublicToHome.default };
 const $admin_resolveAbuseUserReport: Provider = { provide: 'ep:admin/resolve-abuse-user-report', useClass: ep___admin_resolveAbuseUserReport.default };
 const $admin_sendEmail: Provider = { provide: 'ep:admin/send-email', useClass: ep___admin_sendEmail.default };
 const $admin_serverInfo: Provider = { provide: 'ep:admin/server-info', useClass: ep___admin_serverInfo.default };
@@ -756,6 +760,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
 	providers: [
 		GetterService,
 		ApiLoggerService,
+		$admin_nirilaDeleteUserLogAccess,
 		$admin_meta,
 		$admin_abuseUserReports,
 		$admin_accounts_create,
@@ -812,6 +817,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
 		$admin_relays_list,
 		$admin_relays_remove,
 		$admin_resetPassword,
+		$admin_notePublicToHome,
 		$admin_resolveAbuseUserReport,
 		$admin_sendEmail,
 		$admin_serverInfo,
@@ -1127,6 +1133,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
 		$reversi_verify,
 	],
 	exports: [
+		$admin_nirilaDeleteUserLogAccess,
 		$admin_meta,
 		$admin_abuseUserReports,
 		$admin_accounts_create,
@@ -1183,6 +1190,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
 		$admin_relays_list,
 		$admin_relays_remove,
 		$admin_resetPassword,
+		$admin_notePublicToHome,
 		$admin_resolveAbuseUserReport,
 		$admin_sendEmail,
 		$admin_serverInfo,
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index 28e7ef58c25f..d09aa5f5445b 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -6,6 +6,7 @@
 import { permissions } from 'misskey-js';
 import type { KeyOf, Schema } from '@/misc/json-schema.js';
 
+import * as ep___admin_nirilaDeleteUserLogAccess from './endpoints/admin/nirila-delete-user-log-access.js';
 import * as ep___admin_meta from './endpoints/admin/meta.js';
 import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
 import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js';
@@ -62,6 +63,7 @@ import * as ep___admin_relays_add from './endpoints/admin/relays/add.js';
 import * as ep___admin_relays_list from './endpoints/admin/relays/list.js';
 import * as ep___admin_relays_remove from './endpoints/admin/relays/remove.js';
 import * as ep___admin_resetPassword from './endpoints/admin/reset-password.js';
+import * as ep___admin_notePublicToHome from './endpoints/admin/note-public-to-home.js';
 import * as ep___admin_resolveAbuseUserReport from './endpoints/admin/resolve-abuse-user-report.js';
 import * as ep___admin_sendEmail from './endpoints/admin/send-email.js';
 import * as ep___admin_serverInfo from './endpoints/admin/server-info.js';
@@ -377,6 +379,7 @@ import * as ep___reversi_surrender from './endpoints/reversi/surrender.js';
 import * as ep___reversi_verify from './endpoints/reversi/verify.js';
 
 const eps = [
+	['admin/nirila-delete-user-log-access', ep___admin_nirilaDeleteUserLogAccess],
 	['admin/meta', ep___admin_meta],
 	['admin/abuse-user-reports', ep___admin_abuseUserReports],
 	['admin/accounts/create', ep___admin_accounts_create],
@@ -433,6 +436,7 @@ const eps = [
 	['admin/relays/list', ep___admin_relays_list],
 	['admin/relays/remove', ep___admin_relays_remove],
 	['admin/reset-password', ep___admin_resetPassword],
+	['admin/note-public-to-home', ep___admin_notePublicToHome],
 	['admin/resolve-abuse-user-report', ep___admin_resolveAbuseUserReport],
 	['admin/send-email', ep___admin_sendEmail],
 	['admin/server-info', ep___admin_serverInfo],
diff --git a/packages/backend/src/server/api/endpoints/admin/nirila-delete-user-log-access.ts b/packages/backend/src/server/api/endpoints/admin/nirila-delete-user-log-access.ts
new file mode 100644
index 000000000000..03e331749197
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/nirila-delete-user-log-access.ts
@@ -0,0 +1,92 @@
+/*
+ * SPDX-FileCopyrightText: anatawa12
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import type { NirilaDeleteUserLogRepository } from '@/models/_.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
+import { ApiError } from '@/server/api/error.js';
+import { QueryService } from '@/core/QueryService.js';
+import type { IEndpointMeta } from '@/server/api/endpoints.js';
+
+export const meta = {
+	tags: ['admin'],
+
+	requireCredential: true,
+	requireModerator: true,
+	kind: 'read:admin:nirila-delete-user-log-access',
+
+	errors: {
+		notFound: {
+			message: 'DeleteLog satisfies the request not found',
+			code: 'NOT_FOUND',
+			id: 'd29927d1-5b8c-4200-aa05-5537722ee7ab',
+		},
+	},
+
+	res: {
+		oneOf: [
+			{ type: "object" },
+			{
+				type: 'array',
+				items: { type: 'object' },
+			},
+		]
+	},
+} as const satisfies IEndpointMeta;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+
+		// access single by id
+		email: { type: 'string' },
+		username: { type: 'string' },
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: [],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		@Inject(DI.nirilaDeleteUserLogRepository)
+		private nirilaDeleteUserLogRepository: NirilaDeleteUserLogRepository,
+
+		private queryService: QueryService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			// single query
+			if (ps.email) {
+				const found = await this.nirilaDeleteUserLogRepository.findOneBy({ email: ps.email });
+				if (!found) throw new ApiError(meta.errors.notFound);
+				return found.info;
+			}
+
+			if (ps.username) {
+				const found = await this.nirilaDeleteUserLogRepository.findOneBy({ username: ps.username });
+				if (!found) throw new ApiError(meta.errors.notFound);
+				return found.info;
+			}
+
+			if (ps.userId) {
+				const found = await this.nirilaDeleteUserLogRepository.findOneBy({ userId: ps.userId });
+				if (!found) throw new ApiError(meta.errors.notFound);
+				return found.info;
+			}
+
+			// list query
+
+			const logs = await this.queryService.makePaginationQuery(this.nirilaDeleteUserLogRepository.createQueryBuilder('deleteLog'), ps.sinceId, ps.untilId)
+				.limit(ps.limit)
+				.getMany();
+
+			return logs.map(x => Object.assign(x.info, { id: x.id }));
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/note-public-to-home.ts b/packages/backend/src/server/api/endpoints/admin/note-public-to-home.ts
new file mode 100644
index 000000000000..4a426ec70cf2
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/note-public-to-home.ts
@@ -0,0 +1,134 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { DataSource } from 'typeorm';
+import * as Redis from 'ioredis';
+import type { NotesRepository, UsersRepository } from '@/models/_.js';
+import { MiNote, MiPoll } from '@/models/_.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { ApiError } from '@/server/api/error.js';
+import type { IEndpointMeta } from '@/server/api/endpoints.js';
+import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
+import { FeaturedService } from '@/core/FeaturedService.js';
+
+export const meta = {
+	tags: ['admin'],
+
+	requireCredential: true,
+	requireModerator: true,
+	kind: 'write:notes',
+
+	errors: {
+		noteNotFound: {
+			message: 'Note not found.',
+			code: 'NOTE_NOT_FOUND',
+			id: 'b107f543-27fb-4bac-9549-9bbb64d95e85',
+		},
+		noteNotPublic: {
+			message: 'Note is not public',
+			code: 'NOTE_NOT_PUBLIC',
+			id: '561e3371-6ef1-457b-8fdc-736a6e914782',
+		},
+	},
+} as const satisfies IEndpointMeta;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['noteId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		@Inject(DI.usersRepository)
+		private usersRepository: UsersRepository,
+
+		@Inject(DI.notesRepository)
+		private notesRepository: NotesRepository,
+
+		@Inject(DI.redisForTimelines)
+		private redisForTimelines: Redis.Redis,
+
+		@Inject(DI.db)
+		private db: DataSource,
+
+		private moderationLogService: ModerationLogService,
+		private fanoutTimelineService: FanoutTimelineService,
+		private featuredService: FeaturedService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const note = await this.notesRepository.findOneBy({ id: ps.noteId });
+
+			if (note == null) {
+				throw new ApiError(meta.errors.noteNotFound);
+			}
+
+			if (note.visibility !== 'public') {
+				throw new ApiError(meta.errors.noteNotPublic);
+			}
+
+			// Note: by design, visibility of replies and quoted renotes are not changed
+			// replies and quoted renotes have their own text, so it's another moderation entity
+
+			const user = await this.usersRepository.findOneByOrFail({ id: note.userId });
+			await moderationLogService.log(me, 'makeNoteHome', {
+				noteId: note.id,
+				noteUserId: note.userId,
+				noteUserUsername: user.username,
+				noteUserHost: user.host,
+				note: note,
+			});
+
+			// update basic note info
+			await this.db.transaction(async transactionalEntityManager => {
+				// change visibility of the note
+				await transactionalEntityManager.update(MiNote, { id: note.id }, { visibility: 'home' });
+				await transactionalEntityManager.update(MiPoll, { noteId: note.id }, { noteVisibility: 'home' });
+
+				// change visibility of pure renotes
+				await transactionalEntityManager.createQueryBuilder()
+					.from(MiNote, 'note')
+					.update()
+					.where('renoteId = :renoteId', { renoteId: note.id })
+					.andWhere('text IS NULL')
+					.andWhere('fileIds = \'{}\'')
+					.andWhere('hasPoll = false')
+					.andWhere('visibility = \'public\'')
+					.set({ visibility: 'home' })
+					.execute();
+			});
+
+			// collect renotes after changing visibility of original note
+			const renotes = await this.notesRepository.createQueryBuilder('note')
+				.where('note.renoteId = :renoteId', { renoteId: note.id })
+				.andWhere('note.text IS NULL')
+				.andWhere('note.fileIds = \'{}\'')
+				.andWhere('note.hasPoll = false')
+				.andWhere('note.visibility = \'home\'')
+				.getMany();
+
+			// remove from funout local timeline
+			const redisPipeline = this.redisForTimelines.pipeline();
+			this.fanoutTimelineService.remove('localTimeline', note.id, redisPipeline);
+			if (note.fileIds.length > 0) {
+				this.fanoutTimelineService.remove('localTimelineWithFiles', note.id, redisPipeline);
+			}
+			for (const renote of renotes) {
+				this.fanoutTimelineService.remove('localTimeline', renote.id, redisPipeline);
+			}
+			await redisPipeline.exec();
+
+			// remove from highlights
+			// since renotes are not included in featured, we don't need to remove them
+			await featuredService.removeNote(note);
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts
index 535a3ea30850..4df48d6f3b22 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts
@@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { IdService } from '@/core/IdService.js';
 import type { WebhooksRepository } from '@/models/_.js';
-import { webhookEventTypes } from '@/models/Webhook.js';
+import { webhookEventTypes, WebhookEventType } from '@/models/Webhook.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { DI } from '@/di-symbols.js';
 import { RoleService } from '@/core/RoleService.js';
@@ -26,6 +26,11 @@ export const meta = {
 			code: 'TOO_MANY_WEBHOOKS',
 			id: '87a9bb19-111e-4e37-81d3-a3e7426453b0',
 		},
+		adminWebhookDenied: {
+			message: 'You cannot create webhook for other users.',
+			code: 'ADMIN_WEBHOOK_DENIED',
+			id: '0d3321b1-6f66-41aa-9fbe-233c60ce19b0',
+		},
 	},
 
 	res: {
@@ -43,8 +48,10 @@ export const meta = {
 			on: {
 				type: 'array',
 				items: {
-					type: 'string',
-					enum: webhookEventTypes,
+					oneOf: [
+						{ type: 'string', enum: webhookEventTypes },
+						{ type: 'string', pattern: '^note@[a-zA-Z0-9]{1,20}$' },
+					],
 				},
 			},
 			url: { type: 'string' },
@@ -63,7 +70,10 @@ export const paramDef = {
 		url: { type: 'string', minLength: 1, maxLength: 1024 },
 		secret: { type: 'string', maxLength: 1024, default: '' },
 		on: { type: 'array', items: {
-			type: 'string', enum: webhookEventTypes,
+			oneOf: [
+				{ type: 'string', enum: webhookEventTypes },
+				{ type: 'string', pattern: '^note@[a-zA-Z0-9]{1,20}$' },
+			],
 		} },
 	},
 	required: ['name', 'url', 'on'],
@@ -89,13 +99,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw new ApiError(meta.errors.tooManyWebhooks);
 			}
 
+			if (ps.on.some(x => !(webhookEventTypes as readonly string[]).includes(x))) {
+				if (!await this.roleService.isAdministrator(me)) {
+					throw new ApiError(meta.errors.adminWebhookDenied);
+				}
+			}
+
 			const webhook = await this.webhooksRepository.insert({
 				id: this.idService.gen(),
 				userId: me.id,
 				name: ps.name,
 				url: ps.url,
 				secret: ps.secret,
-				on: ps.on,
+				on: ps.on as WebhookEventType[],
 			}).then(x => this.webhooksRepository.findOneByOrFail(x.identifiers[0]));
 
 			this.globalEventService.publishInternalEvent('webhookCreated', webhook);
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts
index fe07afb2d089..c395fc59e707 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts
@@ -33,8 +33,10 @@ export const meta = {
 				on: {
 					type: 'array',
 					items: {
-						type: 'string',
-						enum: webhookEventTypes,
+						oneOf: [
+							{ type: 'string', enum: webhookEventTypes },
+							{ type: 'string', pattern: '^note@[a-zA-Z0-9]{1,20}$' },
+						],
 					},
 				},
 				url: { type: 'string' },
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts
index 5ddb79caf283..012e3ef94638 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts
@@ -40,8 +40,10 @@ export const meta = {
 			on: {
 				type: 'array',
 				items: {
-					type: 'string',
-					enum: webhookEventTypes,
+					oneOf: [
+						{ type: 'string', enum: webhookEventTypes },
+						{ type: 'string', pattern: '^note@[a-zA-Z0-9]{1,20}$' },
+					],
 				},
 			},
 			url: { type: 'string' },
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts
index 6e380d76f89e..bf5faedd2262 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts
@@ -6,9 +6,10 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { WebhooksRepository } from '@/models/_.js';
-import { webhookEventTypes } from '@/models/Webhook.js';
+import { webhookEventTypes, WebhookEventType } from '@/models/Webhook.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { DI } from '@/di-symbols.js';
+import { RoleService } from '@/core/RoleService.js';
 import { ApiError } from '../../../error.js';
 
 export const meta = {
@@ -24,6 +25,11 @@ export const meta = {
 			code: 'NO_SUCH_WEBHOOK',
 			id: 'fb0fea69-da18-45b1-828d-bd4fd1612518',
 		},
+		adminWebhookDenied: {
+			message: 'You cannot create webhook for other users.',
+			code: 'UPDATE_ADMIN_WEBHOOK_DENIED',
+			id: 'eb43c0c4-24a3-487d-b139-f3e4e58f87a4',
+		},
 	},
 
 } as const;
@@ -36,7 +42,10 @@ export const paramDef = {
 		url: { type: 'string', minLength: 1, maxLength: 1024 },
 		secret: { type: 'string', maxLength: 1024, default: '' },
 		on: { type: 'array', items: {
-			type: 'string', enum: webhookEventTypes,
+			oneOf: [
+				{ type: 'string', enum: webhookEventTypes },
+				{ type: 'string', pattern: '^note@[a-zA-Z0-9]{1,20}$' },
+			],
 		} },
 		active: { type: 'boolean' },
 	},
@@ -52,6 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private webhooksRepository: WebhooksRepository,
 
 		private globalEventService: GlobalEventService,
+		private roleService: RoleService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const webhook = await this.webhooksRepository.findOneBy({
@@ -63,11 +73,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw new ApiError(meta.errors.noSuchWebhook);
 			}
 
+			if (ps.on.some(x => !(webhookEventTypes as readonly string[]).includes(x))) {
+				if (!await this.roleService.isAdministrator(me)) {
+					throw new ApiError(meta.errors.adminWebhookDenied);
+				}
+			}
+
 			await this.webhooksRepository.update(webhook.id, {
 				name: ps.name,
 				url: ps.url,
 				secret: ps.secret,
-				on: ps.on,
+				on: ps.on as WebhookEventType[],
 				active: ps.active,
 			});
 
diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts
index cc76c12f1d89..66338244c027 100644
--- a/packages/backend/src/server/api/endpoints/users/notes.ts
+++ b/packages/backend/src/server/api/endpoints/users/notes.ts
@@ -60,6 +60,7 @@ export const paramDef = {
 		untilDate: { type: 'integer' },
 		allowPartial: { type: 'boolean', default: false }, // true is recommended but for compatibility false by default
 		withFiles: { type: 'boolean', default: false },
+		includeSensitiveChannel: { type: 'boolean' },
 	},
 	required: ['userId'],
 } as const;
@@ -81,6 +82,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
 			const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
 			const isSelf = me && (me.id === ps.userId);
+			const includeSensitiveChannel = ps.includeSensitiveChannel ?? isSelf ?? false;
 
 			const serverSettings = await this.metaService.fetch();
 
@@ -98,6 +100,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				const timeline = await this.getFromDb({
 					untilId,
 					sinceId,
+					includeSensitiveChannel,
 					limit: ps.limit,
 					userId: ps.userId,
 					withChannelNotes: ps.withChannelNotes,
@@ -128,7 +131,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				excludeNoFiles: ps.withChannelNotes && ps.withFiles, // userTimelineWithChannel may include notes without files
 				excludePureRenotes: !ps.withRenotes,
 				noteFilter: note => {
-					if (note.channel?.isSensitive && !isSelf) return false;
+					if (note.channel?.isSensitive && !includeSensitiveChannel) return false;
 					if (note.visibility === 'specified' && (!me || (me.id !== note.userId && !note.visibleUserIds.some(v => v === me.id)))) return false;
 					if (note.visibility === 'followers' && !isFollowing && !isSelf) return false;
 
@@ -138,6 +141,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					untilId,
 					sinceId,
 					limit,
+					includeSensitiveChannel,
 					userId: ps.userId,
 					withChannelNotes: ps.withChannelNotes,
 					withFiles: ps.withFiles,
@@ -155,6 +159,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		limit: number,
 		userId: string,
 		withChannelNotes: boolean,
+		includeSensitiveChannel: boolean,
 		withFiles: boolean,
 		withRenotes: boolean,
 	}, me: MiLocalUser | null) {
@@ -170,7 +175,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			.leftJoinAndSelect('renote.user', 'renoteUser');
 
 		if (ps.withChannelNotes) {
-			if (!isSelf) query.andWhere(new Brackets(qb => {
+			if (!ps.includeSensitiveChannel) query.andWhere(new Brackets(qb => {
 				qb.orWhere('note.channelId IS NULL');
 				qb.orWhere('channel.isSensitive = false');
 			}));
diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts
index 1750dd6206f1..9c588def21e1 100644
--- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts
+++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts
@@ -14,6 +14,8 @@ import { EmailService } from '@/core/EmailService.js';
 import { DI } from '@/di-symbols.js';
 import { GetterService } from '@/server/api/GetterService.js';
 import { RoleService } from '@/core/RoleService.js';
+import { AbuseDiscordHookService } from '@/core/AbuseDiscordHookService.js';
+import type { Config } from '@/config.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -59,6 +61,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 	constructor(
 		@Inject(DI.abuseUserReportsRepository)
 		private abuseUserReportsRepository: AbuseUserReportsRepository,
+		@Inject(DI.config)
+		private config: Config,
 
 		private idService: IdService,
 		private metaService: MetaService,
@@ -66,6 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private getterService: GetterService,
 		private roleService: RoleService,
 		private globalEventService: GlobalEventService,
+		private abuseDiscordHookService: AbuseDiscordHookService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			// Lookup user
@@ -105,12 +110,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}
 
 				const meta = await this.metaService.fetch();
-				if (meta.email) {
+				if (meta.email && !config.nirila?.disableAbuseRepository) {
 					this.emailService.sendEmail(meta.email, 'New abuse report',
 						sanitizeHtml(ps.comment),
 						sanitizeHtml(ps.comment));
 				}
 			});
+
+			this.abuseDiscordHookService.send(me, user, ps.comment);
 		});
 	}
 }
diff --git a/packages/backend/src/server/web/views/note.pug b/packages/backend/src/server/web/views/note.pug
index 9bc652b6a108..f708508e4087 100644
--- a/packages/backend/src/server/web/views/note.pug
+++ b/packages/backend/src/server/web/views/note.pug
@@ -28,7 +28,7 @@ block og
 			// FIXME: add embed player for Twitter
 	if images.length
 		meta(property='twitter:card' content='summary_large_image')
-		each image in images	
+		each image in images
 			meta(property='og:image'     content= image.url)
 	else
 		meta(property='twitter:card' content='summary')
@@ -36,7 +36,8 @@ block og
 
 
 block meta
-	if user.host || isRenote || profile.noCrawle
+	// TODO: make user configurable if sensitive channel note should be indexed or not
+	if user.host || isRenote || profile.noCrawle || (note.channel && note.channel.isSensitive)
 		meta(name='robots' content='noindex')
 	if profile.preventAiLearning
 		meta(name='robots' content='noimageai')
diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts
index 929070d0d290..2fc94392d6e1 100644
--- a/packages/backend/src/types.ts
+++ b/packages/backend/src/types.ts
@@ -90,6 +90,7 @@ export const moderationLogTypes = [
 	'deleteAvatarDecoration',
 	'unsetUserAvatar',
 	'unsetUserBanner',
+	'makeNoteHome',
 ] as const;
 
 export type ModerationLogPayloads = {
@@ -282,6 +283,13 @@ export type ModerationLogPayloads = {
 		userHost: string | null;
 		fileId: string;
 	};
+	makeNoteHome: {
+		noteId: string;
+		noteUserId: string;
+		noteUserUsername: string;
+		noteUserHost: string | null;
+		note: any;
+	};
 };
 
 export type Serialized<T> = {
diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts
index 0e71d707dd13..e0c9c3af7782 100644
--- a/packages/backend/test/e2e/timelines.ts
+++ b/packages/backend/test/e2e/timelines.ts
@@ -1198,6 +1198,32 @@ describe('Timelines', () => {
 			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
 		});
 
+		test.concurrent('[withChannelNotes: true, includeSensitiveChannel: true] 他人が取埗した堎合もセンシティブチャンネル投皿が含たれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const channel = await api('/channels/create', { name: 'channel', isSensitive: true }, bob).then(x => x.body);
+			const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/users/notes', { userId: bob.id, withChannelNotes: true, includeSensitiveChannel: true }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+		});
+
+		test.concurrent('[withChannelNotes: true, includeSensitiveChannel: false] 自分が取埗した堎合もセンシティブチャンネル投皿が含たれない', async () => {
+			const [bob] = await Promise.all([signup()]);
+
+			const channel = await api('/channels/create', { name: 'channel', isSensitive: true }, bob).then(x => x.body);
+			const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/users/notes', { userId: bob.id, withChannelNotes: true, includeSensitiveChannel: false }, bob);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+		});
+
 		test.concurrent('ミュヌトしおいるナヌザヌに関連する投皿が含たれない', async () => {
 			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
 
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 03a283cab33b..009c1ce2ad53 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -141,6 +141,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</MkA>
 		</template>
 	</I18n>
+	<I18n v-if="muted === 'sensitiveChannel'" :src="i18n.ts.userSaysSomethingInSensitiveChannel" tag="small">
+		<template #name>
+			<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
+				<MkUserName :user="appearNote.user"/>
+			</MkA>
+		</template>
+	</I18n>
 	<I18n v-else :src="i18n.ts.userSaysSomething" tag="small">
 		<template #name>
 			<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
@@ -211,6 +218,7 @@ const emit = defineEmits<{
 }>();
 
 const inTimeline = inject<boolean>('inTimeline', false);
+const collapseSensitiveChannel = inject<boolean>('collapseSensitiveChannel', false);
 const inChannel = inject('inChannel', null);
 const currentClip = inject<Ref<Misskey.entities.Clip> | null>('currentClip', null);
 
@@ -272,9 +280,9 @@ const renoteCollapsed = ref(
 
 /* Overload FunctionにLintが察応しおいないのでコメントアりト
 function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: true): boolean;
-function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): boolean | 'sensitiveMute';
+function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): boolean | 'sensitiveMute' | 'sensitiveChannel';
 */
-function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): boolean | 'sensitiveMute' {
+function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): boolean | 'sensitiveMute' | 'sensitiveChannel' {
 	if (mutedWords == null) return false;
 
 	if (checkWordMute(noteToCheck, $i, mutedWords)) return true;
@@ -283,6 +291,7 @@ function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string
 
 	if (checkOnly) return false;
 
+	if (collapseSensitiveChannel && noteToCheck.channel?.isSensitive) return 'sensitiveChannel';
 	if (inTimeline && !defaultStore.state.tl.filter.withSensitive && noteToCheck.files?.some((v) => v.isSensitive)) return 'sensitiveMute';
 	return false;
 }
diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue
index a1c1ea234118..c7871ba96e82 100644
--- a/packages/frontend/src/components/MkTimeline.vue
+++ b/packages/frontend/src/components/MkTimeline.vue
@@ -40,7 +40,7 @@ const props = withDefaults(defineProps<{
 	onlyFiles?: boolean;
 }>(), {
 	withRenotes: true,
-	withReplies: false,
+	withReplies: true,
 	onlyFiles: false,
 });
 
diff --git a/packages/frontend/src/components/MkUpdated.vue b/packages/frontend/src/components/MkUpdated.vue
index 188cc37f4140..0f787431b725 100644
--- a/packages/frontend/src/components/MkUpdated.vue
+++ b/packages/frontend/src/components/MkUpdated.vue
@@ -27,7 +27,7 @@ const modal = shallowRef<InstanceType<typeof MkModal>>();
 
 function whatIsNew() {
 	modal.value?.close();
-	window.open(`https://misskey-hub.net/docs/releases/#_${version.replace(/\./g, '')}`, '_blank');
+	window.open(`https://github.com/niri-la/misskey.niri.la/blob/develop/CHANGELOG.md#${version.replace(/\./g, '')}`, '_blank');
 }
 
 onMounted(() => {
diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue
index be80baa774f3..63f3be498437 100644
--- a/packages/frontend/src/components/MkVisitorDashboard.vue
+++ b/packages/frontend/src/components/MkVisitorDashboard.vue
@@ -120,7 +120,13 @@ function showMenu(ev) {
 		action: () => {
 			window.open(instance.privacyPolicyUrl!, '_blank', 'noopener');
 		},
-	} : undefined, (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl) ? undefined : { type: 'divider' }, {
+	} : undefined, {
+		text: 'NSFWガむドラむン',
+		icon: 'ti ti-notebook',
+		action: () => {
+			window.open('https://kinel.notion.site/NSFW-39a3f0c8708e4e2594594a1c88099fe3', '_blank', 'noopener');
+		},
+	}, { type: 'divider' }, {
 		text: i18n.ts.help,
 		icon: 'ti ti-help-circle',
 		action: () => {
diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue
index 1a49dbf1d512..07c38bbf7094 100644
--- a/packages/frontend/src/pages/about-misskey.vue
+++ b/packages/frontend/src/pages/about-misskey.vue
@@ -112,6 +112,24 @@ SPDX-License-Identifier: AGPL-3.0-only
 						</div>
 					</div>
 				</FormSection>
+				<FormSection>
+					<template #label>{{ i18n.ts._aboutMisskey.forkContributors }}</template>
+					<div :class="$style.contributors">
+						<a href="https://github.com/anatawa12" target="_blank" :class="$style.contributor">
+							<img src="https://avatars.githubusercontent.com/u/22656849?v=4" :class="$style.contributorAvatar">
+							<span :class="$style.contributorUsername">@anatawa12</span>
+						</a>
+						<a href="https://github.com/niwaniwa" target="_blank" :class="$style.contributor">
+							<img src="https://avatars.githubusercontent.com/u/10182706?v=4" :class="$style.contributorAvatar">
+							<span :class="$style.contributorUsername">@niwaniwa</span>
+						</a>
+						<a href="https://github.com/Sayamame-beans" target="_blank" :class="$style.contributor">
+							<img src="https://avatars.githubusercontent.com/u/61457993?v=4" :class="$style.contributorAvatar">
+							<span :class="$style.contributorUsername">@Sayamame-beans</span>
+						</a>
+					</div>
+					<template #caption><MkLink url="https://github.com/niri-la/misskey.niri.la/graphs/contributors">{{ i18n.ts._aboutMisskey.allForkContributors }}</MkLink></template>
+				</FormSection>
 				<FormSection>
 					<template #label><Mfm text="$[jelly ❀]"/> {{ i18n.ts._aboutMisskey.patrons }}</template>
 					<div :class="$style.patronsWithIcon">
diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue
index e33c88272187..1e5fc589a685 100644
--- a/packages/frontend/src/pages/admin/modlog.ModLog.vue
+++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<b
 			:class="{
 				[$style.logGreen]: ['createRole', 'addCustomEmoji', 'createGlobalAnnouncement', 'createUserAnnouncement', 'createAd', 'createInvitation', 'createAvatarDecoration'].includes(log.type),
-				[$style.logYellow]: ['markSensitiveDriveFile', 'resetPassword'].includes(log.type),
+				[$style.logYellow]: ['markSensitiveDriveFile', 'resetPassword', 'makeNoteHome'].includes(log.type),
 				[$style.logRed]: ['suspend', 'deleteRole', 'suspendRemoteInstance', 'deleteGlobalAnnouncement', 'deleteUserAnnouncement', 'deleteCustomEmoji', 'deleteNote', 'deleteDriveFile', 'deleteAd', 'deleteAvatarDecoration'].includes(log.type)
 			}"
 		>{{ i18n.ts._moderationLogTypes[log.type] }}</b>
@@ -40,6 +40,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<span v-else-if="log.type === 'createAvatarDecoration'">: {{ log.info.avatarDecoration.name }}</span>
 		<span v-else-if="log.type === 'updateAvatarDecoration'">: {{ log.info.before.name }}</span>
 		<span v-else-if="log.type === 'deleteAvatarDecoration'">: {{ log.info.avatarDecoration.name }}</span>
+		<span v-else-if="log.type === 'makeNoteHome'">: @{{ log.info.noteUserUsername }}{{ log.info.noteUserHost ? '@' + log.info.noteUserHost : '' }}</span>
 	</template>
 	<template #icon>
 		<MkAvatar :user="log.user" :class="$style.avatar"/>
diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue
index 1919f808640f..b9d65aae9f57 100644
--- a/packages/frontend/src/pages/settings/drive.vue
+++ b/packages/frontend/src/pages/settings/drive.vue
@@ -44,6 +44,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<template #label>{{ i18n.ts.keepOriginalUploading }}</template>
 				<template #caption>{{ i18n.ts.keepOriginalUploadingDescription }}</template>
 			</MkSwitch>
+			<MkSelect v-model="imageCompressionMode">
+				<template #label>{{ i18n.ts._imageCompressionMode.title }}</template>
+				<option value="resizeCompress">{{ i18n.ts._imageCompressionMode.resizeCompress }}</option>
+				<option value="noResizeCompress">{{ i18n.ts._imageCompressionMode.noResizeCompress }}</option>
+				<option value="resizeCompressLossy">{{ i18n.ts._imageCompressionMode.resizeCompressLossy }}</option>
+				<option value="noResizeCompressLossy">{{ i18n.ts._imageCompressionMode.noResizeCompressLossy }}</option>
+				<template #caption>{{ i18n.ts._imageCompressionMode.description }}</template>
+			</MkSelect>
 			<MkSwitch v-model="alwaysMarkNsfw" @update:modelValue="saveProfile()">
 				<template #label>{{ i18n.ts.alwaysMarkSensitive }}</template>
 			</MkSwitch>
@@ -73,6 +81,7 @@ import MkChart from '@/components/MkChart.vue';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { signinRequired } from '@/account.js';
+import MkSelect from '@/components/MkSelect.vue';
 
 const $i = signinRequired();
 
@@ -96,6 +105,7 @@ const meterStyle = computed(() => {
 });
 
 const keepOriginalUploading = computed(defaultStore.makeGetterSetter('keepOriginalUploading'));
+const imageCompressionMode = computed(defaultStore.makeGetterSetter('imageCompressionMode'));
 
 misskeyApi('drive').then(info => {
 	capacity.value = info.capacity;
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index d13b6884bdd9..166695c9cbc1 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -58,6 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkSwitch v-if="advancedMfm" v-model="enableQuickAddMfmFunction">{{ i18n.ts.enableQuickAddMfmFunction }}</MkSwitch>
 				<MkSwitch v-model="showGapBetweenNotesInTimeline">{{ i18n.ts.showGapBetweenNotesInTimeline }}</MkSwitch>
 				<MkSwitch v-model="loadRawImages">{{ i18n.ts.loadRawImages }}</MkSwitch>
+        <MkSwitch v-model="collapseSensitiveChannel">{{ i18n.ts.collapseSensitiveChannel }}</MkSwitch>
 				<MkRadios v-model="reactionsDisplaySize">
 					<template #label>{{ i18n.ts.reactionsDisplaySize }}</template>
 					<option value="small">{{ i18n.ts.small }}</option>
@@ -296,6 +297,7 @@ const numberOfPageCache = computed(defaultStore.makeGetterSetter('numberOfPageCa
 const instanceTicker = computed(defaultStore.makeGetterSetter('instanceTicker'));
 const enableInfiniteScroll = computed(defaultStore.makeGetterSetter('enableInfiniteScroll'));
 const useReactionPickerForContextMenu = computed(defaultStore.makeGetterSetter('useReactionPickerForContextMenu'));
+const collapseSensitiveChannel = computed(defaultStore.makeGetterSetter('collapseSensitiveChannel'));
 const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars'));
 const showAvatarDecorations = computed(defaultStore.makeGetterSetter('showAvatarDecorations'));
 const mediaListWithOneImageAppearance = computed(defaultStore.makeGetterSetter('mediaListWithOneImageAppearance'));
diff --git a/packages/frontend/src/pages/settings/webhook.edit.vue b/packages/frontend/src/pages/settings/webhook.edit.vue
index e9fb1e471e16..463b4febb156 100644
--- a/packages/frontend/src/pages/settings/webhook.edit.vue
+++ b/packages/frontend/src/pages/settings/webhook.edit.vue
@@ -29,6 +29,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<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>
+
+			<MkTextarea v-if="$i?.isAdmin" v-model="users">
+				<template #label>{{ i18n.ts._webhookSettings._events.usersLabel }}</template>
+				<template #caption>{{ i18n.ts._webhookSettings._events.usersCaption }}</template>
+			</MkTextarea>
 		</div>
 	</FormSection>
 
@@ -52,6 +57,8 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { useRouter } from '@/router/supplier.js';
+import { $i } from '@/account.js';
+import MkTextarea from '@/components/MkTextarea.vue';
 
 const router = useRouter();
 
@@ -75,9 +82,10 @@ const event_reply = ref(webhook.on.includes('reply'));
 const event_renote = ref(webhook.on.includes('renote'));
 const event_reaction = ref(webhook.on.includes('reaction'));
 const event_mention = ref(webhook.on.includes('mention'));
+const users = ref((webhook.on as string[]).filter(x => x.startsWith('note@')).map(x => x.substring('note@'.length)).join('\n'));
 
 async function save(): Promise<void> {
-	const events = [];
+	const events: string[] = [];
 	if (event_follow.value) events.push('follow');
 	if (event_followed.value) events.push('followed');
 	if (event_note.value) events.push('note');
@@ -85,6 +93,7 @@ async function save(): Promise<void> {
 	if (event_renote.value) events.push('renote');
 	if (event_reaction.value) events.push('reaction');
 	if (event_mention.value) events.push('mention');
+	if (users.value !== '') events.push(...users.value.split('\n').filter(x => x).map(x => `note@${x}`));
 
 	os.apiWithDialog('i/webhooks/update', {
 		name: name.value,
@@ -119,3 +128,11 @@ definePageMetadata(() => ({
 	icon: 'ti ti-webhook',
 }));
 </script>
+
+<style lang="scss" module>
+
+.userItem {
+  display: flex;
+}
+
+</style>
diff --git a/packages/frontend/src/pages/settings/webhook.new.vue b/packages/frontend/src/pages/settings/webhook.new.vue
index 5bf85e48f426..88c7aa120659 100644
--- a/packages/frontend/src/pages/settings/webhook.new.vue
+++ b/packages/frontend/src/pages/settings/webhook.new.vue
@@ -29,6 +29,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<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>
+
+			<MkTextarea v-if="$i?.isAdmin" v-model="users">
+				<template #label>{{ i18n.ts._webhookSettings._events.usersLabel }}</template>
+				<template #caption>{{ i18n.ts._webhookSettings._events.usersCaption }}</template>
+			</MkTextarea>
 		</div>
 	</FormSection>
 
@@ -47,6 +52,8 @@ import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { $i } from '@/account.js';
+import MkTextarea from '@/components/MkTextarea.vue';
 
 const name = ref('');
 const url = ref('');
@@ -59,9 +66,10 @@ const event_reply = ref(true);
 const event_renote = ref(true);
 const event_reaction = ref(true);
 const event_mention = ref(true);
+const users = ref('');
 
 async function create(): Promise<void> {
-	const events = [];
+	const events: string[] = [];
 	if (event_follow.value) events.push('follow');
 	if (event_followed.value) events.push('followed');
 	if (event_note.value) events.push('note');
@@ -69,6 +77,7 @@ async function create(): Promise<void> {
 	if (event_renote.value) events.push('renote');
 	if (event_reaction.value) events.push('reaction');
 	if (event_mention.value) events.push('mention');
+	if (users.value !== '') events.push(...users.value.split('\n').filter(x => x).map(x => `note@${x}`));
 
 	os.apiWithDialog('i/webhooks/create', {
 		name: name.value,
diff --git a/packages/frontend/src/pages/user/index.timeline.vue b/packages/frontend/src/pages/user/index.timeline.vue
index 8dbf90f344ca..53f57dfe5ffe 100644
--- a/packages/frontend/src/pages/user/index.timeline.vue
+++ b/packages/frontend/src/pages/user/index.timeline.vue
@@ -18,17 +18,21 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref, computed } from 'vue';
+import { ref, computed, provide } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkNotes from '@/components/MkNotes.vue';
 import MkTab from '@/components/MkTab.vue';
 import { i18n } from '@/i18n.js';
+import { $i } from '@/account.js';
+import { defaultStore } from '@/store.js';
 
 const props = defineProps<{
 	user: Misskey.entities.UserDetailed;
 }>();
 
 const tab = ref<string | null>('all');
+const include = ref<string | null>('all');
+provide<boolean>('collapseSensitiveChannel', defaultStore.state.collapseSensitiveChannel);
 
 const pagination = computed(() => tab.value === 'featured' ? {
 	endpoint: 'users/featured-notes' as const,
@@ -45,6 +49,7 @@ const pagination = computed(() => tab.value === 'featured' ? {
 		withReplies: tab.value === 'all',
 		withChannelNotes: tab.value === 'all',
 		withFiles: tab.value === 'files',
+		includeSensitiveChannel: $i != null,
 	},
 });
 </script>
diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts
index b273bd36f398..4d955726a875 100644
--- a/packages/frontend/src/scripts/get-note-menu.ts
+++ b/packages/frontend/src/scripts/get-note-menu.ts
@@ -143,6 +143,17 @@ export function getNoteMenu(props: {
 
 	const cleanups = [] as (() => void)[];
 
+	function makeHome(): void {
+		os.confirm({
+			type: 'warning',
+			text: i18n.ts.makeNoteHomeConfirm,
+		}).then(({ canceled }) => {
+			if (canceled) return;
+
+			misskeyApi('admin/note-public-to-home', { noteId: appearNote.id });
+		});
+	}
+
 	function del(): void {
 		os.confirm({
 			type: 'warning',
@@ -405,7 +416,13 @@ export function getNoteMenu(props: {
 					text: i18n.ts.delete,
 					danger: true,
 					action: del,
-				}]
+				},
+				$i.isModerator || $i.isAdmin ? {
+					icon: 'ti ti-home',
+					text: i18n.ts.makeNoteHome,
+					danger: true,
+					action: makeHome,
+				} : undefined]
 			: []
 			)]
 			.filter(x => x !== undefined);
diff --git a/packages/frontend/src/scripts/get-note-summary.ts b/packages/frontend/src/scripts/get-note-summary.ts
index 6fd9947ac1e7..af6c1b4e376b 100644
--- a/packages/frontend/src/scripts/get-note-summary.ts
+++ b/packages/frontend/src/scripts/get-note-summary.ts
@@ -10,7 +10,7 @@ import { i18n } from '@/i18n.js';
  * 投皿を衚す文字列を取埗したす。
  * @param {*} note (packされた)投皿
  */
-export const getNoteSummary = (note?: Misskey.entities.Note | null): string => {
+export const getNoteSummary = (note?: Misskey.entities.Note | null, sensitiveChannelCW?: boolean): string => {
 	if (note == null) {
 		return '';
 	}
@@ -23,6 +23,10 @@ export const getNoteSummary = (note?: Misskey.entities.Note | null): string => {
 		return `(${i18n.ts.invisibleNote})`;
 	}
 
+	if (sensitiveChannelCW) {
+		return i18n.ts.sensitiveChannelAutoCW;
+	}
+
 	let summary = '';
 
 	// 本文
diff --git a/packages/frontend/src/scripts/upload/compress-config.ts b/packages/frontend/src/scripts/upload/compress-config.ts
index 3046b7f518b9..bbd9c2b75630 100644
--- a/packages/frontend/src/scripts/upload/compress-config.ts
+++ b/packages/frontend/src/scripts/upload/compress-config.ts
@@ -1,36 +1,110 @@
 /*
- * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-FileCopyrightText: syuilo and misskey-project, anatawa12
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import isAnimated from 'is-file-animated';
 import { isWebpSupported } from './isWebpSupported.js';
 import type { BrowserImageResizerConfigWithConvertedOutput } from '@misskey-dev/browser-image-resizer';
+import { defaultStore } from '@/store';
 
 const compressTypeMap = {
-	'image/jpeg': { quality: 0.90, mimeType: 'image/webp' },
-	'image/png': { quality: 1, mimeType: 'image/webp' },
-	'image/webp': { quality: 0.90, mimeType: 'image/webp' },
-	'image/svg+xml': { quality: 1, mimeType: 'image/webp' },
+	'lossy': { quality: 0.90, mimeType: 'image/webp' },
+	'lossless': { quality: 1, mimeType: 'image/webp' },
 } as const;
 
 const compressTypeMapFallback = {
-	'image/jpeg': { quality: 0.85, mimeType: 'image/jpeg' },
-	'image/png': { quality: 1, mimeType: 'image/png' },
-	'image/webp': { quality: 0.85, mimeType: 'image/jpeg' },
-	'image/svg+xml': { quality: 1, mimeType: 'image/png' },
+	'lossy': { quality: 0.85, mimeType: 'image/jpeg' },
+	'lossless': { quality: 1, mimeType: 'image/png' },
 } as const;
 
+const inputCompressKindMap = {
+	'image/jpeg': 'lossy',
+	'image/png': 'lossless',
+	'image/webp': 'lossy',
+	'image/svg+xml': 'lossless',
+} as const;
+
+const resizeSizeConfig = { maxWidth: 2560, maxHeight: 2560 } as const;
+const noResizeSizeConfig = { maxWidth: Number.MAX_SAFE_INTEGER, maxHeight: Number.MAX_SAFE_INTEGER } as const;
+
+async function isLosslessWebp(file: Blob): Promise<boolean> {
+	// file header
+	//   'RIFF': u32 @ 0x00
+	//   file size: u32 @ 0x04
+	//   'WEBP': u32 @ 0x08
+	// for simple lossless
+	//   'VP8L': u32 @ 0x0C
+	// so read 16 bytes and check those three magic numbers
+	const buffer = new Uint8Array(await file.slice(0, 16).arrayBuffer());
+
+	const header = 'RIFF\x00\x00\x00\x00WEBPVP8L';
+	for (let i = 0; i < header.length; i++) {
+		const code = header.charCodeAt(i);
+		if (code === 0) continue;
+		if (buffer[i] !== code) return false;
+	}
+	return true;
+}
+
+async function inputImageKind(file: File): Promise<'lossy' | 'lossless' | undefined> {
+	let compressKind: 'lossy' | 'lossless' | undefined = inputCompressKindMap[file.type];
+	if (!compressKind) return undefined; // unknown image format
+	if (await isAnimated(file)) return undefined; // animated image format
+	// WEBPs can be lossless
+	if (await isLosslessWebp(file)) compressKind = 'lossless';
+	return compressKind;
+}
+
 export async function getCompressionConfig(file: File): Promise<BrowserImageResizerConfigWithConvertedOutput | undefined> {
-	const imgConfig = (isWebpSupported() ? compressTypeMap : compressTypeMapFallback)[file.type];
-	if (!imgConfig || await isAnimated(file)) {
-		return;
+	const inputCompressKind = await inputImageKind(file);
+	if (!inputCompressKind) return undefined;
+
+	let compressKind: 'lossy' | 'lossless';
+	let resize: boolean;
+
+	switch (defaultStore.state.imageCompressionMode) {
+		case 'resizeCompress':
+		case null:
+		default:
+			resize = true;
+			compressKind = inputCompressKind;
+			break;
+		case 'noResizeCompress':
+			resize = false;
+			compressKind = inputCompressKind;
+			break;
+		case 'resizeCompressLossy':
+			resize = true;
+			compressKind = 'lossy';
+			break;
+		case 'noResizeCompressLossy':
+			resize = false;
+			compressKind = 'lossy';
+			break;
+	}
+
+	const webpSupported = isWebpSupported();
+
+	const imgFormatConfig = (webpSupported ? compressTypeMap : compressTypeMapFallback)[compressKind];
+	const sizeConfig = resize ? resizeSizeConfig : noResizeSizeConfig;
+
+	if (!resize) {
+		// we don't resize images so we may omit recompression
+		if (imgFormatConfig.mimeType === file.type && inputCompressKind === compressKind) {
+			// we don't have to recompress already compressed to preferred image format.
+			return undefined;
+		}
+
+		if (!webpSupported && file.type === 'image/webp' && compressKind === 'lossless') {
+			// lossless webp -> png recompression likely to increase image size so don't recompress
+			return undefined;
+		}
 	}
 
 	return {
-		maxWidth: 2048,
-		maxHeight: 2048,
 		debug: true,
-		...imgConfig,
+		...imgFormatConfig,
+		...sizeConfig,
 	};
 }
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 516fc296a18a..1bcf2b969941 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -113,6 +113,10 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'account',
 		default: false,
 	},
+	imageCompressionMode: {
+		where: 'account',
+		default: 'resizeCompress' as 'resizeCompress' | 'noResizeCompress' | 'resizeCompressLossy' | 'noResizeCompressLossy' | null,
+	},
 	memo: {
 		where: 'account',
 		default: null,
@@ -211,6 +215,10 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'device',
 		default: 'respect' as 'respect' | 'force' | 'ignore',
 	},
+	collapseSensitiveChannel: {
+		where: 'device',
+		default: true,
+	},
 	highlightSensitiveMedia: {
 		where: 'device',
 		default: false,
diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts
index 9b510a629241..d7507b04c481 100644
--- a/packages/frontend/src/ui/_common_/common.ts
+++ b/packages/frontend/src/ui/_common_/common.ts
@@ -97,7 +97,13 @@ export function openInstanceMenu(ev: MouseEvent) {
 		action: () => {
 			window.open(instance.privacyPolicyUrl, '_blank', 'noopener');
 		},
-	} : undefined, (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl) ? undefined : { type: 'divider' }, {
+	} : undefined, {
+		text: 'NSFWガむドラむン',
+		icon: 'ti ti-notebook',
+		action: () => {
+			window.open('https://kinel.notion.site/NSFW-39a3f0c8708e4e2594594a1c88099fe3', '_blank', 'noopener');
+		},
+	}, { type: 'divider' }, {
 		text: i18n.ts.help,
 		icon: 'ti ti-help-circle',
 		action: () => {
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index 8b1e8ddfc94f..30e215603294 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -208,6 +208,15 @@ type AdminInviteListResponse = operations['admin/invite/list']['responses']['200
 // @public (undocumented)
 type AdminMetaResponse = operations['admin/meta']['responses']['200']['content']['application/json'];
 
+// @public (undocumented)
+type AdminNirilaDeleteUserLogAccessRequest = operations['admin/nirila-delete-user-log-access']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type AdminNirilaDeleteUserLogAccessResponse = operations['admin/nirila-delete-user-log-access']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type AdminNotePublicToHomeRequest = operations['admin/note-public-to-home']['requestBody']['content']['application/json'];
+
 // @public (undocumented)
 type AdminPromoCreateRequest = operations['admin/promo/create']['requestBody']['content']['application/json'];
 
@@ -1135,6 +1144,8 @@ declare namespace entities {
         SigninResponse,
         EmptyRequest,
         EmptyResponse,
+        AdminNirilaDeleteUserLogAccessRequest,
+        AdminNirilaDeleteUserLogAccessResponse,
         AdminMetaResponse,
         AdminAbuseUserReportsRequest,
         AdminAbuseUserReportsResponse,
@@ -1207,6 +1218,7 @@ declare namespace entities {
         AdminRelaysRemoveRequest,
         AdminResetPasswordRequest,
         AdminResetPasswordResponse,
+        AdminNotePublicToHomeRequest,
         AdminResolveAbuseUserReportRequest,
         AdminSendEmailRequest,
         AdminServerInfoResponse,
@@ -2386,10 +2398,13 @@ type ModerationLog = {
 } | {
     type: 'unsetUserBanner';
     info: ModerationLogPayloads['unsetUserBanner'];
+} | {
+    type: 'makeNoteHome';
+    info: ModerationLogPayloads['makeNoteHome'];
 });
 
 // @public (undocumented)
-export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "updateRemoteInstanceNote", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner"];
+export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "updateRemoteInstanceNote", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner", "makeNoteHome"];
 
 // @public (undocumented)
 type MuteCreateRequest = operations['mute/create']['requestBody']['content']['application/json'];
@@ -2644,7 +2659,7 @@ type PagesUpdateRequest = operations['pages/update']['requestBody']['content']['
 function parse(acct: string): Acct;
 
 // @public (undocumented)
-export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "read:admin:show-users", "write:admin:suspend-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"];
+export const permissions: readonly ["read:admin:nirila-delete-user-log-access", "read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "read:admin:show-users", "write:admin:suspend-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"];
 
 // @public (undocumented)
 type PingResponse = operations['ping']['responses']['200']['content']['application/json'];
diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json
index 772f001c0734..255b37645b69 100644
--- a/packages/misskey-js/package.json
+++ b/packages/misskey-js/package.json
@@ -1,7 +1,7 @@
 {
 	"type": "module",
 	"name": "misskey-js",
-	"version": "2024.3.1",
+	"version": "2024.3.1-kinel.1",
 	"description": "Misskey SDK for JavaScript",
 	"types": "./built/dts/index.d.ts",
 	"exports": {
diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
index 7a58b3eb0401..fb6e1c9231f7 100644
--- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts
+++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
@@ -3,6 +3,17 @@ import type { Endpoints } from './endpoint.js';
 
 declare module '../api.js' {
   export interface APIClient {
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *read:admin:nirila-delete-user-log-access*
+     */
+    request<E extends 'admin/nirila-delete-user-log-access', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
     /**
      * No description provided.
      * 
@@ -620,6 +631,17 @@ declare module '../api.js' {
       credential?: string | null,
     ): Promise<SwitchCaseResponseType<E, P>>;
 
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *write:notes*
+     */
+    request<E extends 'admin/note-public-to-home', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
     /**
      * No description provided.
      * 
diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts
index 675511cf47f3..35511287cbbd 100644
--- a/packages/misskey-js/src/autogen/endpoint.ts
+++ b/packages/misskey-js/src/autogen/endpoint.ts
@@ -1,6 +1,8 @@
 import type {
 	EmptyRequest,
 	EmptyResponse,
+	AdminNirilaDeleteUserLogAccessRequest,
+	AdminNirilaDeleteUserLogAccessResponse,
 	AdminMetaResponse,
 	AdminAbuseUserReportsRequest,
 	AdminAbuseUserReportsResponse,
@@ -73,6 +75,7 @@ import type {
 	AdminRelaysRemoveRequest,
 	AdminResetPasswordRequest,
 	AdminResetPasswordResponse,
+	AdminNotePublicToHomeRequest,
 	AdminResolveAbuseUserReportRequest,
 	AdminSendEmailRequest,
 	AdminServerInfoResponse,
@@ -556,6 +559,7 @@ import type {
 } from './entities.js';
 
 export type Endpoints = {
+	'admin/nirila-delete-user-log-access': { req: AdminNirilaDeleteUserLogAccessRequest; res: AdminNirilaDeleteUserLogAccessResponse };
 	'admin/meta': { req: EmptyRequest; res: AdminMetaResponse };
 	'admin/abuse-user-reports': { req: AdminAbuseUserReportsRequest; res: AdminAbuseUserReportsResponse };
 	'admin/accounts/create': { req: AdminAccountsCreateRequest; res: AdminAccountsCreateResponse };
@@ -612,6 +616,7 @@ export type Endpoints = {
 	'admin/relays/list': { req: EmptyRequest; res: AdminRelaysListResponse };
 	'admin/relays/remove': { req: AdminRelaysRemoveRequest; res: EmptyResponse };
 	'admin/reset-password': { req: AdminResetPasswordRequest; res: AdminResetPasswordResponse };
+	'admin/note-public-to-home': { req: AdminNotePublicToHomeRequest; res: EmptyResponse };
 	'admin/resolve-abuse-user-report': { req: AdminResolveAbuseUserReportRequest; res: EmptyResponse };
 	'admin/send-email': { req: AdminSendEmailRequest; res: EmptyResponse };
 	'admin/server-info': { req: EmptyRequest; res: AdminServerInfoResponse };
diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts
index 57ad9ce4bc02..7c7af9fd474a 100644
--- a/packages/misskey-js/src/autogen/entities.ts
+++ b/packages/misskey-js/src/autogen/entities.ts
@@ -3,6 +3,8 @@ import { operations } from './types.js';
 export type EmptyRequest = Record<string, unknown> | undefined;
 export type EmptyResponse = Record<string, unknown> | undefined;
 
+export type AdminNirilaDeleteUserLogAccessRequest = operations['admin/nirila-delete-user-log-access']['requestBody']['content']['application/json'];
+export type AdminNirilaDeleteUserLogAccessResponse = operations['admin/nirila-delete-user-log-access']['responses']['200']['content']['application/json'];
 export type AdminMetaResponse = operations['admin/meta']['responses']['200']['content']['application/json'];
 export type AdminAbuseUserReportsRequest = operations['admin/abuse-user-reports']['requestBody']['content']['application/json'];
 export type AdminAbuseUserReportsResponse = operations['admin/abuse-user-reports']['responses']['200']['content']['application/json'];
@@ -75,6 +77,7 @@ export type AdminRelaysListResponse = operations['admin/relays/list']['responses
 export type AdminRelaysRemoveRequest = operations['admin/relays/remove']['requestBody']['content']['application/json'];
 export type AdminResetPasswordRequest = operations['admin/reset-password']['requestBody']['content']['application/json'];
 export type AdminResetPasswordResponse = operations['admin/reset-password']['responses']['200']['content']['application/json'];
+export type AdminNotePublicToHomeRequest = operations['admin/note-public-to-home']['requestBody']['content']['application/json'];
 export type AdminResolveAbuseUserReportRequest = operations['admin/resolve-abuse-user-report']['requestBody']['content']['application/json'];
 export type AdminSendEmailRequest = operations['admin/send-email']['requestBody']['content']['application/json'];
 export type AdminServerInfoResponse = operations['admin/server-info']['responses']['200']['content']['application/json'];
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index d8c12453eb19..d8479dd44262 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -12,6 +12,15 @@ type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> &
 type OneOf<T extends any[]> = T extends [infer Only] ? Only : T extends [infer A, infer B, ...infer Rest] ? OneOf<[XOR<A, B>, ...Rest]> : never;
 
 export type paths = {
+  '/admin/nirila-delete-user-log-access': {
+    /**
+     * admin/nirila-delete-user-log-access
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *read:admin:nirila-delete-user-log-access*
+     */
+    post: operations['admin/nirila-delete-user-log-access'];
+  };
   '/admin/meta': {
     /**
      * admin/meta
@@ -517,6 +526,15 @@ export type paths = {
      */
     post: operations['admin/reset-password'];
   };
+  '/admin/note-public-to-home': {
+    /**
+     * admin/note-public-to-home
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *write:notes*
+     */
+    post: operations['admin/note-public-to-home'];
+  };
   '/admin/resolve-abuse-user-report': {
     /**
      * admin/resolve-abuse-user-report
@@ -4859,6 +4877,68 @@ export type external = Record<string, never>;
 
 export type operations = {
 
+  /**
+   * admin/nirila-delete-user-log-access
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *read:admin:nirila-delete-user-log-access*
+   */
+  'admin/nirila-delete-user-log-access': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** @default 10 */
+          limit?: number;
+          /** Format: misskey:id */
+          sinceId?: string;
+          /** Format: misskey:id */
+          untilId?: string;
+          email?: string;
+          username?: string;
+          /** Format: misskey:id */
+          userId?: string;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': Record<string, never> | Record<string, never>[];
+        };
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
   /**
    * admin/meta
    * @description No description provided.
@@ -8173,6 +8253,58 @@ export type operations = {
       };
     };
   };
+  /**
+   * admin/note-public-to-home
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *write:notes*
+   */
+  'admin/note-public-to-home': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          noteId: string;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
   /**
    * admin/resolve-abuse-user-report
    * @description No description provided.
@@ -19056,7 +19188,7 @@ export type operations = {
           url: string;
           /** @default */
           secret?: string;
-          on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[];
+          on: (('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction') | string)[];
         };
       };
     };
@@ -19070,7 +19202,7 @@ export type operations = {
             /** Format: misskey:id */
             userId: string;
             name: string;
-            on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[];
+            on: (('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction') | string)[];
             url: string;
             secret: string;
             active: boolean;
@@ -19129,7 +19261,7 @@ export type operations = {
               /** Format: misskey:id */
               userId: string;
               name: string;
-              on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[];
+              on: (('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction') | string)[];
               url: string;
               secret: string;
               active: boolean;
@@ -19196,7 +19328,7 @@ export type operations = {
             /** Format: misskey:id */
             userId: string;
             name: string;
-            on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[];
+            on: (('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction') | string)[];
             url: string;
             secret: string;
             active: boolean;
@@ -19254,7 +19386,7 @@ export type operations = {
           url: string;
           /** @default */
           secret?: string;
-          on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[];
+          on: (('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction') | string)[];
           active: boolean;
         };
       };
@@ -25384,6 +25516,7 @@ export type operations = {
           allowPartial?: boolean;
           /** @default false */
           withFiles?: boolean;
+          includeSensitiveChannel?: boolean;
         };
       };
     };
diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts
index b690621e98d9..c5489a207ec1 100644
--- a/packages/misskey-js/src/consts.ts
+++ b/packages/misskey-js/src/consts.ts
@@ -9,6 +9,7 @@ export const followingVisibilities = ['public', 'followers', 'private'] as const
 export const followersVisibilities = ['public', 'followers', 'private'] as const;
 
 export const permissions = [
+	'read:admin:nirila-delete-user-log-access',
 	'read:account',
 	'write:account',
 	'read:blocks',
@@ -134,6 +135,7 @@ export const moderationLogTypes = [
 	'deleteAvatarDecoration',
 	'unsetUserAvatar',
 	'unsetUserBanner',
+	'makeNoteHome',
 ] as const;
 
 export type ModerationLogPayloads = {
@@ -326,4 +328,11 @@ export type ModerationLogPayloads = {
 		userHost: string | null;
 		fileId: string;
 	};
+	makeNoteHome: {
+		noteId: string;
+		noteUserId: string;
+		noteUserUsername: string;
+		noteUserHost: string | null;
+		note: any;
+	};
 };
diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts
index 35503d6d6fff..4373d53af506 100644
--- a/packages/misskey-js/src/entities.ts
+++ b/packages/misskey-js/src/entities.ts
@@ -134,6 +134,9 @@ export type ModerationLog = {
 } | {
 	type: 'unsetUserBanner';
 	info: ModerationLogPayloads['unsetUserBanner'];
+} | {
+	type: 'makeNoteHome';
+	info: ModerationLogPayloads['makeNoteHome'];
 });
 
 export type ServerStats = {