The code powering m.abunchtell.com https://m.abunchtell.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

349 lines
14 KiB

  1. # frozen_string_literal: true
  2. class AtomSerializer
  3. include RoutingHelper
  4. class << self
  5. def render(element)
  6. document = Ox::Document.new(version: '1.0')
  7. document << element
  8. ('<?xml version="1.0"?>' + Ox.dump(element)).force_encoding('UTF-8')
  9. end
  10. end
  11. def author(account)
  12. author = Ox::Element.new('author')
  13. uri = TagManager.instance.uri_for(account)
  14. append_element(author, 'id', uri)
  15. append_element(author, 'activity:object-type', TagManager::TYPES[:person])
  16. append_element(author, 'uri', uri)
  17. append_element(author, 'name', account.username)
  18. append_element(author, 'email', account.local? ? "#{account.acct}@#{Rails.configuration.x.local_domain}" : account.acct)
  19. append_element(author, 'summary', account.note)
  20. append_element(author, 'link', nil, rel: :alternate, type: 'text/html', href: TagManager.instance.url_for(account))
  21. 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)))
  22. 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)))
  23. append_element(author, 'poco:preferredUsername', account.username)
  24. append_element(author, 'poco:displayName', account.display_name) unless account.display_name.blank?
  25. append_element(author, 'poco:note', Formatter.instance.simplified_format(account).to_str) unless account.note.blank?
  26. append_element(author, 'mastodon:scope', account.locked? ? :private : :public)
  27. author
  28. end
  29. def feed(account, stream_entries)
  30. feed = Ox::Element.new('feed')
  31. add_namespaces(feed)
  32. append_element(feed, 'id', account_url(account, format: 'atom'))
  33. append_element(feed, 'title', account.display_name)
  34. append_element(feed, 'subtitle', account.note)
  35. append_element(feed, 'updated', account.updated_at.iso8601)
  36. append_element(feed, 'logo', full_asset_url(account.avatar.url(:original)))
  37. feed << author(account)
  38. append_element(feed, 'link', nil, rel: :alternate, type: 'text/html', href: TagManager.instance.url_for(account))
  39. append_element(feed, 'link', nil, rel: :self, type: 'application/atom+xml', href: account_url(account, format: 'atom'))
  40. 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
  41. append_element(feed, 'link', nil, rel: :hub, href: api_push_url)
  42. append_element(feed, 'link', nil, rel: :salmon, href: api_salmon_url(account.id))
  43. stream_entries.each do |stream_entry|
  44. feed << entry(stream_entry)
  45. end
  46. feed
  47. end
  48. def entry(stream_entry, root = false)
  49. entry = Ox::Element.new('entry')
  50. add_namespaces(entry) if root
  51. append_element(entry, 'id', TagManager.instance.unique_tag(stream_entry.created_at, stream_entry.activity_id, stream_entry.activity_type))
  52. append_element(entry, 'published', stream_entry.created_at.iso8601)
  53. append_element(entry, 'updated', stream_entry.updated_at.iso8601)
  54. append_element(entry, 'title', stream_entry&.status&.title)
  55. append_element(entry, 'activity:object-type', TagManager::TYPES[stream_entry.object_type])
  56. append_element(entry, 'activity:verb', TagManager::VERBS[stream_entry.verb])
  57. entry << object(stream_entry.target) if stream_entry.targeted?
  58. serialize_status_attributes(entry, stream_entry.status) unless stream_entry.status.nil?
  59. append_element(entry, 'link', nil, rel: :alternate, type: 'text/html', href: account_stream_entry_url(stream_entry.account, stream_entry))
  60. append_element(entry, 'link', nil, rel: :self, type: 'application/atom+xml', href: account_stream_entry_url(stream_entry.account, stream_entry, format: 'atom'))
  61. 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?
  62. entry
  63. end
  64. def object(status)
  65. object = Ox::Element.new('activity:object')
  66. append_element(object, 'id', TagManager.instance.uri_for(status))
  67. append_element(object, 'published', status.created_at.iso8601)
  68. append_element(object, 'updated', status.updated_at.iso8601)
  69. append_element(object, 'title', status.title)
  70. object << author(status.account)
  71. append_element(object, 'activity:object-type', TagManager::TYPES[status.object_type])
  72. append_element(object, 'activity:verb', TagManager::VERBS[status.verb])
  73. serialize_status_attributes(object, status)
  74. append_element(object, 'link', nil, rel: :alternate, type: 'text/html', href: TagManager.instance.url_for(status))
  75. 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?
  76. object
  77. end
  78. def follow_salmon(follow)
  79. entry = Ox::Element.new('entry')
  80. add_namespaces(entry)
  81. description = "#{follow.account.acct} started following #{follow.target_account.acct}"
  82. append_element(entry, 'id', TagManager.instance.unique_tag(follow.created_at, follow.id, 'Follow'))
  83. append_element(entry, 'title', description)
  84. append_element(entry, 'content', description, type: :html)
  85. entry << author(follow.account)
  86. append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
  87. append_element(entry, 'activity:verb', TagManager::VERBS[:follow])
  88. object = author(follow.target_account)
  89. object.value = 'activity:object'
  90. entry << object
  91. entry
  92. end
  93. def follow_request_salmon(follow_request)
  94. entry = Ox::Element.new('entry')
  95. add_namespaces(entry)
  96. append_element(entry, 'id', TagManager.instance.unique_tag(follow_request.created_at, follow_request.id, 'FollowRequest'))
  97. append_element(entry, 'title', "#{follow_request.account.acct} requested to follow #{follow_request.target_account.acct}")
  98. entry << author(follow_request.account)
  99. append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
  100. append_element(entry, 'activity:verb', TagManager::VERBS[:request_friend])
  101. object = author(follow_request.target_account)
  102. object.value = 'activity:object'
  103. entry << object
  104. entry
  105. end
  106. def authorize_follow_request_salmon(follow_request)
  107. entry = Ox::Element.new('entry')
  108. add_namespaces(entry)
  109. append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, follow_request.id, 'FollowRequest'))
  110. append_element(entry, 'title', "#{follow_request.target_account.acct} authorizes follow request by #{follow_request.account.acct}")
  111. entry << author(follow_request.target_account)
  112. append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
  113. append_element(entry, 'activity:verb', TagManager::VERBS[:authorize])
  114. object = Ox::Element.new('activity:object')
  115. object << author(follow_request.account)
  116. append_element(object, 'activity:object-type', TagManager::TYPES[:activity])
  117. append_element(object, 'activity:verb', TagManager::VERBS[:request_friend])
  118. inner_object = author(follow_request.target_account)
  119. inner_object.value = 'activity:object'
  120. object << inner_object
  121. entry << object
  122. entry
  123. end
  124. def reject_follow_request_salmon(follow_request)
  125. entry = Ox::Element.new('entry')
  126. add_namespaces(entry)
  127. append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, follow_request.id, 'FollowRequest'))
  128. append_element(entry, 'title', "#{follow_request.target_account.acct} rejects follow request by #{follow_request.account.acct}")
  129. entry << author(follow_request.target_account)
  130. append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
  131. append_element(entry, 'activity:verb', TagManager::VERBS[:reject])
  132. object = Ox::Element.new('activity:object')
  133. object << author(follow_request.account)
  134. append_element(object, 'activity:object-type', TagManager::TYPES[:activity])
  135. append_element(object, 'activity:verb', TagManager::VERBS[:request_friend])
  136. inner_object = author(follow_request.target_account)
  137. inner_object.value = 'activity:object'
  138. object << inner_object
  139. entry << object
  140. entry
  141. end
  142. def unfollow_salmon(follow)
  143. entry = Ox::Element.new('entry')
  144. add_namespaces(entry)
  145. description = "#{follow.account.acct} is no longer following #{follow.target_account.acct}"
  146. append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, follow.id, 'Follow'))
  147. append_element(entry, 'title', description)
  148. append_element(entry, 'content', description, type: :html)
  149. entry << author(follow.account)
  150. append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
  151. append_element(entry, 'activity:verb', TagManager::VERBS[:unfollow])
  152. object = author(follow.target_account)
  153. object.value = 'activity:object'
  154. entry << object
  155. entry
  156. end
  157. def block_salmon(block)
  158. entry = Ox::Element.new('entry')
  159. add_namespaces(entry)
  160. description = "#{block.account.acct} no longer wishes to interact with #{block.target_account.acct}"
  161. append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, block.id, 'Block'))
  162. append_element(entry, 'title', description)
  163. entry << author(block.account)
  164. append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
  165. append_element(entry, 'activity:verb', TagManager::VERBS[:block])
  166. object = author(block.target_account)
  167. object.value = 'activity:object'
  168. entry << object
  169. entry
  170. end
  171. def unblock_salmon(block)
  172. entry = Ox::Element.new('entry')
  173. add_namespaces(entry)
  174. description = "#{block.account.acct} no longer blocks #{block.target_account.acct}"
  175. append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, block.id, 'Block'))
  176. append_element(entry, 'title', description)
  177. entry << author(block.account)
  178. append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
  179. append_element(entry, 'activity:verb', TagManager::VERBS[:unblock])
  180. object = author(block.target_account)
  181. object.value = 'activity:object'
  182. entry << object
  183. entry
  184. end
  185. def favourite_salmon(favourite)
  186. entry = Ox::Element.new('entry')
  187. add_namespaces(entry)
  188. description = "#{favourite.account.acct} favourited a status by #{favourite.status.account.acct}"
  189. append_element(entry, 'id', TagManager.instance.unique_tag(favourite.created_at, favourite.id, 'Favourite'))
  190. append_element(entry, 'title', description)
  191. append_element(entry, 'content', description, type: :html)
  192. entry << author(favourite.account)
  193. append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
  194. append_element(entry, 'activity:verb', TagManager::VERBS[:favorite])
  195. entry << object(favourite.status)
  196. append_element(entry, 'thr:in-reply-to', nil, ref: TagManager.instance.uri_for(favourite.status), href: TagManager.instance.url_for(favourite.status))
  197. entry
  198. end
  199. def unfavourite_salmon(favourite)
  200. entry = Ox::Element.new('entry')
  201. add_namespaces(entry)
  202. description = "#{favourite.account.acct} no longer favourites a status by #{favourite.status.account.acct}"
  203. append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, favourite.id, 'Favourite'))
  204. append_element(entry, 'title', description)
  205. append_element(entry, 'content', description, type: :html)
  206. entry << author(favourite.account)
  207. append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
  208. append_element(entry, 'activity:verb', TagManager::VERBS[:unfavorite])
  209. entry << object(favourite.status)
  210. append_element(entry, 'thr:in-reply-to', nil, ref: TagManager.instance.uri_for(favourite.status), href: TagManager.instance.url_for(favourite.status))
  211. entry
  212. end
  213. private
  214. def append_element(parent, name, content = nil, attributes = {})
  215. element = Ox::Element.new(name)
  216. attributes.each { |k, v| element[k] = v.to_s }
  217. element << content.to_s unless content.nil?
  218. parent << element
  219. end
  220. def add_namespaces(parent)
  221. parent['xmlns'] = TagManager::XMLNS
  222. parent['xmlns:thr'] = TagManager::THR_XMLNS
  223. parent['xmlns:activity'] = TagManager::AS_XMLNS
  224. parent['xmlns:poco'] = TagManager::POCO_XMLNS
  225. parent['xmlns:media'] = TagManager::MEDIA_XMLNS
  226. parent['xmlns:ostatus'] = TagManager::OS_XMLNS
  227. parent['xmlns:mastodon'] = TagManager::MTDN_XMLNS
  228. end
  229. def serialize_status_attributes(entry, status)
  230. append_element(entry, 'summary', status.spoiler_text) unless status.spoiler_text.blank?
  231. append_element(entry, 'content', Formatter.instance.format(status.reblog? ? status.reblog : status).to_str, type: 'html')
  232. status.mentions.each do |mentioned|
  233. append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': TagManager::TYPES[:person], href: TagManager.instance.uri_for(mentioned.account))
  234. end
  235. append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': TagManager::TYPES[:collection], href: TagManager::COLLECTIONS[:public]) if status.public_visibility?
  236. status.tags.each do |tag|
  237. append_element(entry, 'category', nil, term: tag.name)
  238. end
  239. append_element(entry, 'category', nil, term: 'nsfw') if status.sensitive?
  240. status.media_attachments.each do |media|
  241. 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)))
  242. end
  243. append_element(entry, 'mastodon:scope', status.visibility)
  244. end
  245. end