From a9b22883dc249c5311e2225ebe054925b6558328 Mon Sep 17 00:00:00 2001
From: KMY <tt@kmycode.net>
Date: Sun, 24 Sep 2023 21:42:16 +0900
Subject: [PATCH 1/3] #13 Turn searchability private if account is silenced

---
 app/models/status.rb       |  1 +
 spec/models/status_spec.rb | 11 ++++++++++-
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/app/models/status.rb b/app/models/status.rb
index 9f0299a511..719ba3eeda 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -426,6 +426,7 @@ class Status < ApplicationRecord
   def compute_searchability
     local = account.local?
 
+    return 'private' if public_searchability? && account.silenced?
     return 'direct' if unsupported_searchability?
     return searchability if local && !searchability.nil?
     return 'direct' if local || [:public, :private, :direct, :limited].exclude?(account.searchability.to_sym)
diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb
index 16210592f4..89f90fc751 100644
--- a/spec/models/status_spec.rb
+++ b/spec/models/status_spec.rb
@@ -120,7 +120,8 @@ RSpec.describe Status do
     let(:account_searchability) { :public }
     let(:status_searchability) { :public }
     let(:account_domain) { 'example.com' }
-    let(:account) { Fabricate(:account, domain: account_domain, searchability: account_searchability) }
+    let(:silenced_at) { nil }
+    let(:account) { Fabricate(:account, domain: account_domain, searchability: account_searchability, silenced_at: silenced_at) }
 
     context 'when public-public' do
       it 'returns public' do
@@ -128,6 +129,14 @@ RSpec.describe Status do
       end
     end
 
+    context 'when public-public but silenced' do
+      let(:silenced_at) { Time.now.utc }
+
+      it 'returns private' do
+        expect(subject.compute_searchability).to eq 'private'
+      end
+    end
+
     context 'when public-private' do
       let(:status_searchability) { :private }
 

From 385ac0c1a2dad447e9f6e225dfa5d87d2170c51c Mon Sep 17 00:00:00 2001
From: KMY <tt@kmycode.net>
Date: Mon, 25 Sep 2023 18:32:11 +0900
Subject: [PATCH 2/3] Add posting with limited visibility test

---
 app/services/post_status_service.rb       |  1 +
 spec/services/post_status_service_spec.rb | 21 +++++++++++++++++++++
 2 files changed, 22 insertions(+)

diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index a549ab1c78..f48555245e 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -94,6 +94,7 @@ class PostStatusService < BaseService
   end
 
   def load_circle
+    raise ArgumentError if @options[:visibility] == 'limited' && @options[:circle_id].nil?
     return unless @options[:visibility] == 'circle' || (@options[:visibility] == 'limited' && @options[:circle_id].present?)
 
     @circle = @options[:circle_id].present? && Circle.find(@options[:circle_id])
diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb
index 3b953b9fc1..a2cf6fbfb0 100644
--- a/spec/services/post_status_service_spec.rb
+++ b/spec/services/post_status_service_spec.rb
@@ -206,6 +206,27 @@ RSpec.describe PostStatusService, type: :service do
     expect(status.mentioned_accounts.first.id).to eq circle_account.id
   end
 
+  it 'circle post with limited visibility' do
+    account = Fabricate(:account)
+    circle_account = Fabricate(:account)
+    circle = Fabricate(:circle, account: account)
+    text = 'This is an English text.'
+
+    circle_account.follow!(account)
+    circle.accounts << circle_account
+    status = subject.call(account, text: text, visibility: 'limited', circle_id: circle.id)
+
+    expect(status.visibility).to eq 'limited'
+    expect(status.limited_scope).to eq 'circle'
+  end
+
+  it 'limited visibility and empty circle' do
+    account = Fabricate(:account)
+    text = 'This is an English text.'
+
+    expect { subject.call(account, text: text, visibility: 'limited') }.to raise_exception ActiveRecord::RecordInvalid
+  end
+
   it 'safeguards mentions' do
     account = Fabricate(:account)
     mentioned_account = Fabricate(:account, username: 'alice')

From 2f21aa5193f21b8f3a62539973b6820323415555 Mon Sep 17 00:00:00 2001
From: KMY <tt@kmycode.net>
Date: Mon, 25 Sep 2023 19:39:05 +0900
Subject: [PATCH 3/3] #15 Add misskey fork indexable support

---
 .../activitypub/process_account_service.rb    |  8 ++++-
 spec/models/status_spec.rb                    |  8 +++++
 .../process_account_service_spec.rb           | 35 +++++++++++++++++++
 3 files changed, 50 insertions(+), 1 deletion(-)

diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb
index 18277034cf..d2ba697545 100644
--- a/app/services/activitypub/process_account_service.rb
+++ b/app/services/activitypub/process_account_service.rb
@@ -265,7 +265,7 @@ class ActivityPub::ProcessAccountService < BaseService
       bio = searchability_from_bio
       return bio unless bio.nil?
 
-      return misskey_software? ? :public : :direct
+      return misskey_software? ? misskey_searchability_from_indexable : :direct
     end
 
     if audience_searchable_by.any? { |uri| ActivityPub::TagManager.instance.public_collection?(uri) }
@@ -297,6 +297,12 @@ class ActivityPub::ProcessAccountService < BaseService
     searchability
   end
 
+  def misskey_searchability_from_indexable
+    return :public if @json['indexable'].nil?
+
+    @json['indexable'] ? :public : :limited
+  end
+
   def instance_info
     @instance_info ||= InstanceInfo.find_by(domain: @domain)
   end
diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb
index 89f90fc751..6462307594 100644
--- a/spec/models/status_spec.rb
+++ b/spec/models/status_spec.rb
@@ -169,6 +169,14 @@ RSpec.describe Status do
       end
     end
 
+    context 'when limited-public' do
+      let(:account_searchability) { :limited }
+
+      it 'returns limited' do
+        expect(subject.compute_searchability).to eq 'limited'
+      end
+    end
+
     context 'when private-limited' do
       let(:account_searchability) { :private }
       let(:status_searchability) { :limited }
diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb
index 1ea3b6491b..b23aa1cea3 100644
--- a/spec/services/activitypub/process_account_service_spec.rb
+++ b/spec/services/activitypub/process_account_service_spec.rb
@@ -11,6 +11,7 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do
     let(:software) { 'mastodon' }
     let(:searchable_by) { 'https://www.w3.org/ns/activitystreams#Public' }
     let(:sender_bio) { '' }
+    let(:indexable) { nil }
     let(:payload) do
       {
         id: 'https://foo.test',
@@ -18,6 +19,7 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do
         inbox: 'https://foo.test/inbox',
         followers: 'https://example.com/followers',
         searchableBy: searchable_by,
+        indexable: indexable,
         summary: sender_bio,
       }.with_indifferent_access
     end
@@ -73,6 +75,39 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do
       it 'searchability is public' do
         expect(subject.searchability).to eq 'public'
       end
+
+      context 'with true indexable' do
+        let(:indexable) { true }
+
+        it 'searchability is public' do
+          expect(subject.searchability).to eq 'public'
+        end
+      end
+
+      context 'with false indexable' do
+        let(:indexable) { false }
+
+        it 'searchability is limited' do
+          expect(subject.searchability).to eq 'limited'
+        end
+      end
+
+      context 'with no-indexable key' do
+        let(:payload) do
+          {
+            id: 'https://foo.test',
+            type: 'Actor',
+            inbox: 'https://foo.test/inbox',
+            followers: 'https://example.com/followers',
+            searchableBy: searchable_by,
+            summary: sender_bio,
+          }.with_indifferent_access
+        end
+
+        it 'searchability is public' do
+          expect(subject.searchability).to eq 'public'
+        end
+      end
     end
 
     context 'with bio' do