mirror of https://github.com/mastodon/mastodon
				
				
				
			Rewrite Atom generation from stream entries to use Ox instead of Nokogiri (#1124)
* Rewrite Atom generation from stream entries to use Ox instead of Nokogiri::Builder StreamEntry is now limited to only statuses, which allows some optimization. Removed extra queries on AccountsController#show. AtomSerializer instead of AtomBuilderHelper used in AccountsController#show, StreamEntriesController#show, StreamEntryRenderer and PubSubHubbub::DistributionWorker PubSubHubbub::DistributionWorker moves n+1 DomainBlock query to PubSubHubbub::DeliveryWorker instead. All Salmon slaps that aren't based on StreamEntry still use AtomBuilderHelper and Nokogiri * All Salmon slaps now use Ox instead of Nokogiri. No touch from status on accountpull/1129/head
							parent
							
								
									31597fd377
								
							
						
					
					
						commit
						6d6a429af8
					
				@ -0,0 +1,348 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class AtomSerializer
 | 
			
		||||
  include RoutingHelper
 | 
			
		||||
 | 
			
		||||
  class << self
 | 
			
		||||
    def render(element)
 | 
			
		||||
      document = Ox::Document.new(version: '1.0')
 | 
			
		||||
      document << element
 | 
			
		||||
      "<?xml version=\"1.0\"?>#{Ox.dump(element)}"
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def author(account)
 | 
			
		||||
    author = Ox::Element.new('author')
 | 
			
		||||
 | 
			
		||||
    uri = TagManager.instance.uri_for(account)
 | 
			
		||||
 | 
			
		||||
    append_element(author, 'id', uri)
 | 
			
		||||
    append_element(author, 'activity:object-type', TagManager::TYPES[:person])
 | 
			
		||||
    append_element(author, 'uri', uri)
 | 
			
		||||
    append_element(author, 'name', account.username)
 | 
			
		||||
    append_element(author, 'email', account.local? ? "#{account.acct}@#{Rails.configuration.x.local_domain}" : account.acct)
 | 
			
		||||
    append_element(author, 'summary', account.note)
 | 
			
		||||
    append_element(author, 'link', nil, rel: :alternate, type: 'text/html', href: TagManager.instance.url_for(account))
 | 
			
		||||
    append_element(author, 'link', nil, rel: :avatar, type: account.avatar_content_type, 'media:width': 120, 'media:height': 120, href: full_asset_url(account.avatar.url(:original)))
 | 
			
		||||
    append_element(author, 'link', nil, rel: :header, type: account.header_content_type, 'media:width': 700, 'media:height': 335, href: full_asset_url(account.header.url(:original)))
 | 
			
		||||
    append_element(author, 'poco:preferredUsername', account.username)
 | 
			
		||||
    append_element(author, 'poco:displayName', account.display_name) unless account.display_name.blank?
 | 
			
		||||
    append_element(author, 'poco:note', Formatter.instance.simplified_format(account).to_str) unless account.note.blank?
 | 
			
		||||
    append_element(author, 'mastodon:scope', account.locked? ? :private : :public)
 | 
			
		||||
 | 
			
		||||
    author
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def feed(account, stream_entries)
 | 
			
		||||
    feed = Ox::Element.new('feed')
 | 
			
		||||
 | 
			
		||||
    add_namespaces(feed)
 | 
			
		||||
 | 
			
		||||
    append_element(feed, 'id', account_url(account, format: 'atom'))
 | 
			
		||||
    append_element(feed, 'title', account.display_name)
 | 
			
		||||
    append_element(feed, 'subtitle', account.note)
 | 
			
		||||
    append_element(feed, 'updated', account.updated_at.iso8601)
 | 
			
		||||
    append_element(feed, 'logo', full_asset_url(account.avatar.url(:original)))
 | 
			
		||||
 | 
			
		||||
    feed << author(account)
 | 
			
		||||
 | 
			
		||||
    append_element(feed, 'link', nil, rel: :alternate, type: 'text/html', href: TagManager.instance.url_for(account))
 | 
			
		||||
    append_element(feed, 'link', nil, rel: :self, type: 'application/atom+xml', href: account_url(account, format: 'atom'))
 | 
			
		||||
    append_element(feed, 'link', nil, rel: :next, type: 'application/atom+xml', href: account_url(account, format: 'atom', max_id: stream_entries.last.id)) if stream_entries.size == 20
 | 
			
		||||
    append_element(feed, 'link', nil, rel: :hub, href: api_push_url)
 | 
			
		||||
    append_element(feed, 'link', nil, rel: :salmon, href: api_salmon_url(account.id))
 | 
			
		||||
 | 
			
		||||
    stream_entries.each do |stream_entry|
 | 
			
		||||
      feed << entry(stream_entry)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    feed
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def entry(stream_entry, root = false)
 | 
			
		||||
    entry = Ox::Element.new('entry')
 | 
			
		||||
 | 
			
		||||
    add_namespaces(entry) if root
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'id', TagManager.instance.unique_tag(stream_entry.created_at, stream_entry.activity_id, stream_entry.activity_type))
 | 
			
		||||
    append_element(entry, 'published', stream_entry.created_at.iso8601)
 | 
			
		||||
    append_element(entry, 'updated', stream_entry.updated_at.iso8601)
 | 
			
		||||
    append_element(entry, 'title', stream_entry&.status&.title)
 | 
			
		||||
    append_element(entry, 'activity:object-type', TagManager::TYPES[stream_entry.object_type])
 | 
			
		||||
    append_element(entry, 'activity:verb', TagManager::VERBS[stream_entry.verb])
 | 
			
		||||
 | 
			
		||||
    entry << object(stream_entry.target) if stream_entry.targeted?
 | 
			
		||||
 | 
			
		||||
    serialize_status_attributes(entry, stream_entry.status) unless stream_entry.status.nil?
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'link', nil, rel: :alternate, type: 'text/html', href: account_stream_entry_url(stream_entry.account, stream_entry))
 | 
			
		||||
    append_element(entry, 'link', nil, rel: :self, type: 'application/atom+xml', href: account_stream_entry_url(stream_entry.account, stream_entry, format: 'atom'))
 | 
			
		||||
    append_element(entry, 'thr:in-reply-to', nil, ref: TagManager.instance.uri_for(stream_entry.thread), href: TagManager.instance.url_for(stream_entry.thread)) if stream_entry.threaded?
 | 
			
		||||
 | 
			
		||||
    entry
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def object(status)
 | 
			
		||||
    object = Ox::Element.new('activity:object')
 | 
			
		||||
 | 
			
		||||
    append_element(object, 'id', TagManager.instance.uri_for(status))
 | 
			
		||||
    append_element(object, 'published', status.created_at.iso8601)
 | 
			
		||||
    append_element(object, 'updated', status.updated_at.iso8601)
 | 
			
		||||
    append_element(object, 'title', status.title)
 | 
			
		||||
 | 
			
		||||
    object << author(status.account)
 | 
			
		||||
 | 
			
		||||
    append_element(object, 'activity:object-type', TagManager::TYPES[status.object_type])
 | 
			
		||||
    append_element(object, 'activity:verb', TagManager::VERBS[status.verb])
 | 
			
		||||
 | 
			
		||||
    serialize_status_attributes(object, status)
 | 
			
		||||
 | 
			
		||||
    append_element(object, 'link', nil, rel: :alternate, type: 'text/html', href: TagManager.instance.url_for(status))
 | 
			
		||||
    append_element(object, 'thr:in-reply-to', nil, ref: TagManager.instance.uri_for(status.thread), href: TagManager.instance.url_for(status.thread)) if status.reply?
 | 
			
		||||
 | 
			
		||||
    object
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def follow_salmon(follow)
 | 
			
		||||
    entry = Ox::Element.new('entry')
 | 
			
		||||
    add_namespaces(entry)
 | 
			
		||||
 | 
			
		||||
    description = "#{follow.account.acct} started following #{follow.target_account.acct}"
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'id', TagManager.instance.unique_tag(follow.created_at, follow.id, 'Follow'))
 | 
			
		||||
    append_element(entry, 'title', description)
 | 
			
		||||
    append_element(entry, 'content', description, type: :html)
 | 
			
		||||
 | 
			
		||||
    entry << author(follow.account)
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
 | 
			
		||||
    append_element(entry, 'activity:verb', TagManager::VERBS[:follow])
 | 
			
		||||
 | 
			
		||||
    object = author(follow.target_account)
 | 
			
		||||
    object.value = 'activity:object'
 | 
			
		||||
 | 
			
		||||
    entry << object
 | 
			
		||||
    entry
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def follow_request_salmon(follow_request)
 | 
			
		||||
    entry = Ox::Element.new('entry')
 | 
			
		||||
    add_namespaces(entry)
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'id', TagManager.instance.unique_tag(follow_request.created_at, follow_request.id, 'FollowRequest'))
 | 
			
		||||
    append_element(entry, 'title', "#{follow_request.account.acct} requested to follow #{follow_request.target_account.acct}")
 | 
			
		||||
 | 
			
		||||
    entry << author(follow_request.account)
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
 | 
			
		||||
    append_element(entry, 'activity:verb', TagManager::VERBS[:request_friend])
 | 
			
		||||
 | 
			
		||||
    object = author(follow_request.target_account)
 | 
			
		||||
    object.value = 'activity:object'
 | 
			
		||||
 | 
			
		||||
    entry << object
 | 
			
		||||
    entry
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def authorize_follow_request_salmon(follow_request)
 | 
			
		||||
    entry = Ox::Element.new('entry')
 | 
			
		||||
    add_namespaces(entry)
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, follow_request.id, 'FollowRequest'))
 | 
			
		||||
    append_element(entry, 'title', "#{follow_request.target_account.acct} authorizes follow request by #{follow_request.account.acct}")
 | 
			
		||||
 | 
			
		||||
    entry << author(follow_request.target_account)
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
 | 
			
		||||
    append_element(entry, 'activity:verb', TagManager::VERBS[:authorize])
 | 
			
		||||
 | 
			
		||||
    object = Ox::Element.new('activity:object')
 | 
			
		||||
    object << author(follow_request.account)
 | 
			
		||||
 | 
			
		||||
    append_element(object, 'activity:object-type', TagManager::TYPES[:activity])
 | 
			
		||||
    append_element(object, 'activity:verb', TagManager::VERBS[:request_friend])
 | 
			
		||||
 | 
			
		||||
    inner_object = author(follow_request.target_account)
 | 
			
		||||
    inner_object.value = 'activity:object'
 | 
			
		||||
 | 
			
		||||
    object << inner_object
 | 
			
		||||
    entry  << object
 | 
			
		||||
    entry
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def reject_follow_request_salmon(follow_request)
 | 
			
		||||
    entry = Ox::Element.new('entry')
 | 
			
		||||
    add_namespaces(entry)
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, follow_request.id, 'FollowRequest'))
 | 
			
		||||
    append_element(entry, 'title', "#{follow_request.target_account.acct} rejects follow request by #{follow_request.account.acct}")
 | 
			
		||||
 | 
			
		||||
    entry << author(follow_request.target_account)
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
 | 
			
		||||
    append_element(entry, 'activity:verb', TagManager::VERBS[:reject])
 | 
			
		||||
 | 
			
		||||
    object = Ox::Element.new('activity:object')
 | 
			
		||||
    object << author(follow_request.account)
 | 
			
		||||
 | 
			
		||||
    append_element(object, 'activity:object-type', TagManager::TYPES[:activity])
 | 
			
		||||
    append_element(object, 'activity:verb', TagManager::VERBS[:request_friend])
 | 
			
		||||
 | 
			
		||||
    inner_object = author(follow_request.target_account)
 | 
			
		||||
    inner_object.value = 'activity:object'
 | 
			
		||||
 | 
			
		||||
    object << inner_object
 | 
			
		||||
    entry  << object
 | 
			
		||||
    entry
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def unfollow_salmon(follow)
 | 
			
		||||
    entry = Ox::Element.new('entry')
 | 
			
		||||
    add_namespaces(entry)
 | 
			
		||||
 | 
			
		||||
    description = "#{follow.account.acct} is no longer following #{follow.target_account.acct}"
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, follow.id, 'Follow'))
 | 
			
		||||
    append_element(entry, 'title', description)
 | 
			
		||||
    append_element(entry, 'content', description, type: :html)
 | 
			
		||||
 | 
			
		||||
    entry << author(follow.account)
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
 | 
			
		||||
    append_element(entry, 'activity:verb', TagManager::VERBS[:unfollow])
 | 
			
		||||
 | 
			
		||||
    object = author(follow.target_account)
 | 
			
		||||
    object.value = 'activity:object'
 | 
			
		||||
 | 
			
		||||
    entry << object
 | 
			
		||||
    entry
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def block_salmon(block)
 | 
			
		||||
    entry = Ox::Element.new('entry')
 | 
			
		||||
    add_namespaces(entry)
 | 
			
		||||
 | 
			
		||||
    description = "#{block.account.acct} no longer wishes to interact with #{block.target_account.acct}"
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, block.id, 'Block'))
 | 
			
		||||
    append_element(entry, 'title', description)
 | 
			
		||||
 | 
			
		||||
    entry << author(block.account)
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
 | 
			
		||||
    append_element(entry, 'activity:verb', TagManager::VERBS[:block])
 | 
			
		||||
 | 
			
		||||
    object = author(block.target_account)
 | 
			
		||||
    object.value = 'activity:object'
 | 
			
		||||
 | 
			
		||||
    entry << object
 | 
			
		||||
    entry
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def unblock_salmon(block)
 | 
			
		||||
    entry = Ox::Element.new('entry')
 | 
			
		||||
    add_namespaces(entry)
 | 
			
		||||
 | 
			
		||||
    description = "#{block.account.acct} no longer blocks #{block.target_account.acct}"
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, block.id, 'Block'))
 | 
			
		||||
    append_element(entry, 'title', description)
 | 
			
		||||
 | 
			
		||||
    entry << author(block.account)
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
 | 
			
		||||
    append_element(entry, 'activity:verb', TagManager::VERBS[:unblock])
 | 
			
		||||
 | 
			
		||||
    object = author(block.target_account)
 | 
			
		||||
    object.value = 'activity:object'
 | 
			
		||||
 | 
			
		||||
    entry << object
 | 
			
		||||
    entry
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def favourite_salmon(favourite)
 | 
			
		||||
    entry = Ox::Element.new('entry')
 | 
			
		||||
    add_namespaces(entry)
 | 
			
		||||
 | 
			
		||||
    description = "#{favourite.account.acct} favourited a status by #{favourite.status.account.acct}"
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'id', TagManager.instance.unique_tag(favourite.created_at, favourite.id, 'Favourite'))
 | 
			
		||||
    append_element(entry, 'title', description)
 | 
			
		||||
    append_element(entry, 'content', description, type: :html)
 | 
			
		||||
 | 
			
		||||
    entry << author(favourite.account)
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
 | 
			
		||||
    append_element(entry, 'activity:verb', TagManager::VERBS[:favorite])
 | 
			
		||||
 | 
			
		||||
    entry << object(favourite.status)
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'thr:in-reply-to', nil, ref: TagManager.instance.uri_for(favourite.status), href: TagManager.instance.url_for(favourite.status))
 | 
			
		||||
 | 
			
		||||
    entry
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def unfavourite_salmon(favourite)
 | 
			
		||||
    entry = Ox::Element.new('entry')
 | 
			
		||||
    add_namespaces(entry)
 | 
			
		||||
 | 
			
		||||
    description = "#{favourite.account.acct} no longer favourites a status by #{favourite.status.account.acct}"
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, favourite.id, 'Favourite'))
 | 
			
		||||
    append_element(entry, 'title', description)
 | 
			
		||||
    append_element(entry, 'content', description, type: :html)
 | 
			
		||||
 | 
			
		||||
    entry << author(favourite.account)
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
 | 
			
		||||
    append_element(entry, 'activity:verb', TagManager::VERBS[:unfavorite])
 | 
			
		||||
 | 
			
		||||
    entry << object(favourite.status)
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'thr:in-reply-to', nil, ref: TagManager.instance.uri_for(favourite.status), href: TagManager.instance.url_for(favourite.status))
 | 
			
		||||
 | 
			
		||||
    entry
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
  def append_element(parent, name, content = nil, attributes = {})
 | 
			
		||||
    element = Ox::Element.new(name)
 | 
			
		||||
    attributes.each { |k, v| element[k] = v.to_s }
 | 
			
		||||
    element << content.to_s unless content.nil?
 | 
			
		||||
    parent  << element
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def add_namespaces(parent)
 | 
			
		||||
    parent['xmlns']          = TagManager::XMLNS
 | 
			
		||||
    parent['xmlns:thr']      = TagManager::THR_XMLNS
 | 
			
		||||
    parent['xmlns:activity'] = TagManager::AS_XMLNS
 | 
			
		||||
    parent['xmlns:poco']     = TagManager::POCO_XMLNS
 | 
			
		||||
    parent['xmlns:media']    = TagManager::MEDIA_XMLNS
 | 
			
		||||
    parent['xmlns:ostatus']  = TagManager::OS_XMLNS
 | 
			
		||||
    parent['xmlns:mastodon'] = TagManager::MTDN_XMLNS
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def serialize_status_attributes(entry, status)
 | 
			
		||||
    append_element(entry, 'summary', status.spoiler_text) unless status.spoiler_text.blank?
 | 
			
		||||
    append_element(entry, 'content', Formatter.instance.format(status.reblog? ? status.reblog : status).to_str, type: 'html')
 | 
			
		||||
 | 
			
		||||
    status.mentions.each do |mentioned|
 | 
			
		||||
      append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': TagManager::TYPES[:person], href: TagManager.instance.uri_for(mentioned.account))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': TagManager::TYPES[:collection], href: TagManager::COLLECTIONS[:public]) if status.public_visibility?
 | 
			
		||||
 | 
			
		||||
    status.tags.each do |tag|
 | 
			
		||||
      append_element(entry, 'category', nil, term: tag.name)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'category', nil, term: 'nsfw') if status.sensitive?
 | 
			
		||||
 | 
			
		||||
    status.media_attachments.each do |media|
 | 
			
		||||
      append_element(entry, 'link', nil, rel: :enclosure, type: media.file_content_type, length: media.file_file_size, href: full_asset_url(media.file.url(:original, false)))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    append_element(entry, 'mastodon:scope', status.visibility)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,27 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
Nokogiri::XML::Builder.new do |xml|
 | 
			
		||||
  feed(xml) do
 | 
			
		||||
    simple_id  xml, account_url(@account, format: 'atom')
 | 
			
		||||
    title      xml, @account.display_name
 | 
			
		||||
    subtitle   xml, @account.note
 | 
			
		||||
    updated_at xml, stream_updated_at
 | 
			
		||||
    logo       xml, full_asset_url(@account.avatar.url(:original))
 | 
			
		||||
 | 
			
		||||
    author(xml) do
 | 
			
		||||
      include_author xml, @account
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    link_alternate xml, TagManager.instance.url_for(@account)
 | 
			
		||||
    link_self      xml, account_url(@account, format: 'atom')
 | 
			
		||||
    link_next      xml, account_url(@account, format: 'atom', max_id: @entries.last.id) if @entries.size == 20
 | 
			
		||||
    link_hub       xml, api_push_url
 | 
			
		||||
    link_salmon    xml, api_salmon_url(@account.id)
 | 
			
		||||
 | 
			
		||||
    @entries.each do |stream_entry|
 | 
			
		||||
      entry(xml, false) do
 | 
			
		||||
        include_entry xml, stream_entry
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end.to_xml
 | 
			
		||||
@ -1,9 +0,0 @@
 | 
			
		||||
Nokogiri::XML::Builder.new do |xml|
 | 
			
		||||
  entry(xml, true) do
 | 
			
		||||
    author(xml) do
 | 
			
		||||
      include_author xml, @stream_entry.account
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    include_entry xml, @stream_entry
 | 
			
		||||
  end
 | 
			
		||||
end.to_xml
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue