From 931bca204870a21e8029fccf07c6e1889f04cc2e Mon Sep 17 00:00:00 2001 From: mivirl Date: Tue, 14 Jul 2026 03:12:02 +0000 Subject: [PATCH] Display boosts, quotes, and replies in rss feeds Adds an additional `boosts` url parameter that enables returning boosts in the feed. The feed now returns the content of the referenced post for boosts, quotes, and replies in an html blockquote element. This is not done recursively, so quotes will only show the text content and not any nested quotes. Example rss feed url that displays boosts: https://mastodon.example/@admin/with_replies.rss?boosts=yes&limit=30 --- app/controllers/accounts_controller.rb | 2 +- app/helpers/formatting_helper.rb | 49 +++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 0deeb76e3ea..4fb2c231089 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -26,7 +26,7 @@ class AccountsController < ApplicationController expires_in 1.minute, public: true limit = params[:limit].present? ? [params[:limit].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE - @statuses = filtered_statuses.without_reblogs.limit(limit) + @statuses = params[:boosts].present? && params[:boosts] == 'yes' ? filtered_statuses.limit(limit) : filtered_statuses.without_reblogs.limit(limit) @statuses = preload_collection(@statuses, Status) end diff --git a/app/helpers/formatting_helper.rb b/app/helpers/formatting_helper.rb index 68aad4f6771..dbe71f2f538 100644 --- a/app/helpers/formatting_helper.rb +++ b/app/helpers/formatting_helper.rb @@ -27,9 +27,7 @@ module FormattingHelper module_function :extract_status_plain_text def status_content_format(status) - quoted_status = status.quote&.quoted_status if status.local? - - html_aware_format(status.text, status.local?, preloaded_accounts: [status.account] + (status.respond_to?(:active_mentions) ? status.active_mentions.map(&:account) : []), quoted_status: quoted_status) + html_aware_format(status.text, status.local?, preloaded_accounts: [status.account] + (status.respond_to?(:active_mentions) ? status.active_mentions.map(&:account) : [])) end def rss_status_content_format(status) @@ -61,7 +59,8 @@ module FormattingHelper def wrapped_status_content_format(status) safe_join [ rss_content_preroll(status), - status_content_format(status), + rss_referenced_status_content(status), + rss_status_content(status), rss_content_postroll(status), ] end @@ -82,6 +81,48 @@ module FormattingHelper ] end + def rss_referenced_status_content(status) + if status.reblog? + url = get_post_url(status.reblog) + tag.p('Boosted '.html_safe + get_reference_info(url, status.reblog)) + elsif status.reply? + referenced = Status.find_by(account_id: status.in_reply_to_account_id, id: status.in_reply_to_id) + return tag.p('Replied to unavailable post') if referenced.nil? || !referenced.distributable? + url = get_post_url(referenced) + tag.p('Replied to '.html_safe + get_reference_info(url, referenced) + tag.blockquote(status_content_format(referenced), cite: url)) + elsif status.with_quote? + case status.quote.state + when 'revoked' + return tag.p('Quoted post has been removed by author') + when 'rejected' + return tag.p('Quoted post is unavailable') + when 'pending' + return tag.p('Quoted post is pending') + end + url = get_post_url(status.quote.quoted_status) + tag.p('Quoted '.html_safe + get_reference_info(url, status.quote.quoted_status) + tag.blockquote(status_content_format(status.quote.quoted_status), cite: url)) + else + '' + end + end + + def rss_status_content(status) + if status.reblog + get_post_url(status.reblog) + return tag.blockquote(status_content_format(status.reblog)) + end + status_content_format(status) + end + + def get_post_url(status) + ActivityPub::TagManager.instance.url_for(status) || ActivityPub::TagManager.instance.uri_for(status) + end + + def get_reference_info(url, status) + pretty_name = tag.span(status.account.display_name.empty? ? status.account.username : status.account.display_name) + ' ('.html_safe + tag.span("@#{status.account.local? ? status.account.local_username_and_domain : status.account.pretty_acct}") + ')'.html_safe + tag.a(url, href: url) + ' by '.html_safe + pretty_name + end + def rss_content_postroll(status) return unless status.preloadable_poll