about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/statuses_controller.rb53
-rw-r--r--app/lib/activitypub/tag_manager.rb6
-rw-r--r--app/serializers/activitypub/note_serializer.rb10
-rw-r--r--config/routes.rb1
4 files changed, 67 insertions, 3 deletions
diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb
index 15d59fd89..3686bd9fd 100644
--- a/app/controllers/statuses_controller.rb
+++ b/app/controllers/statuses_controller.rb
@@ -18,6 +18,7 @@ class StatusesController < ApplicationController
   before_action :redirect_to_original, only: [:show]
   before_action :set_referrer_policy_header, only: [:show]
   before_action :set_cache_headers
+  before_action :set_replies, only: [:replies]
 
   content_security_policy only: :embed do |p|
     p.frame_ancestors(false)
@@ -63,8 +64,37 @@ class StatusesController < ApplicationController
     render 'stream_entries/embed', layout: 'embedded'
   end
 
+  def replies
+    skip_session!
+
+    render json: replies_collection_presenter,
+           serializer: ActivityPub::CollectionSerializer,
+           adapter: ActivityPub::Adapter,
+           content_type: 'application/activity+json',
+           skip_activities: true
+  end
+
   private
 
+  def replies_collection_presenter
+    page = ActivityPub::CollectionPresenter.new(
+      id: replies_account_status_url(@account, @status, page_params),
+      type: :unordered,
+      part_of: replies_account_status_url(@account, @status),
+      next: next_page,
+      items: @replies.map { |status| status.local ? status : status.id }
+    )
+    if page_requested?
+      page
+    else
+      ActivityPub::CollectionPresenter.new(
+        id: replies_account_status_url(@account, @status),
+        type: :unordered,
+        first: page
+      )
+    end
+  end
+
   def create_descendant_thread(starting_depth, statuses)
     depth = starting_depth + statuses.size
     if depth < DESCENDANTS_DEPTH_LIMIT
@@ -174,4 +204,27 @@ class StatusesController < ApplicationController
     return if @status.public_visibility? || @status.unlisted_visibility?
     response.headers['Referrer-Policy'] = 'origin'
   end
+
+  def page_requested?
+    params[:page] == 'true'
+  end
+
+  def set_replies
+    @replies = page_params[:other_accounts] ? Status.where.not(account_id: @account.id) : @account.statuses
+    @replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted])
+    @replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
+  end
+
+  def next_page
+    last_reply = @replies.last
+    return if last_reply.nil?
+    same_account = last_reply.account_id == @account.id
+    return unless same_account || @replies.size == DESCENDANTS_LIMIT
+    same_account = false unless @replies.size == DESCENDANTS_LIMIT
+    replies_account_status_url(@account, @status, page: true, min_id: last_reply.id, other_accounts: !same_account)
+  end
+
+  def page_params
+    { page: true, other_accounts: params[:other_accounts], min_id: params[:min_id] }.compact
+  end
 end
diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb
index be3a562d0..892bb9974 100644
--- a/app/lib/activitypub/tag_manager.rb
+++ b/app/lib/activitypub/tag_manager.rb
@@ -48,6 +48,12 @@ class ActivityPub::TagManager
     activity_account_status_url(target.account, target)
   end
 
+  def replies_uri_for(target, page_params = nil)
+    raise ArgumentError, 'target must be a local activity' unless %i(note comment activity).include?(target.object_type) && target.local?
+
+    replies_account_status_url(target.account, target, page_params)
+  end
+
   # Primary audience of a status
   # Public statuses go out to primarily the public collection
   # Unlisted and private statuses go out primarily to the followers collection
diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb
index 6b0978ad3..4aab993a9 100644
--- a/app/serializers/activitypub/note_serializer.rb
+++ b/app/serializers/activitypub/note_serializer.rb
@@ -13,7 +13,7 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer
   has_many :media_attachments, key: :attachment
   has_many :virtual_tags, key: :tag
 
-  has_one :replies, serializer: ActivityPub::CollectionSerializer
+  has_one :replies, serializer: ActivityPub::CollectionSerializer, if: :local?
 
   def id
     ActivityPub::TagManager.instance.uri_for(object)
@@ -36,12 +36,16 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer
   end
 
   def replies
+    replies = object.self_replies(5).pluck(:id, :uri)
+    last_id = replies.last&.first
     ActivityPub::CollectionPresenter.new(
       type: :unordered,
+      id: ActivityPub::TagManager.instance.replies_uri_for(object),
       first: ActivityPub::CollectionPresenter.new(
         type: :unordered,
-        page: true,
-        items: object.self_replies(5).pluck(:uri)
+        part_of: ActivityPub::TagManager.instance.replies_uri_for(object),
+        items: replies.map(&:second),
+        next: last_id ? ActivityPub::TagManager.instance.replies_uri_for(object, page: true, min_id: last_id) : nil
       )
     )
   end
diff --git a/config/routes.rb b/config/routes.rb
index ded62981d..9a83d0f88 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -56,6 +56,7 @@ Rails.application.routes.draw do
       member do
         get :activity
         get :embed
+        get :replies
       end
     end