commit: 4c6b5dbe96b565d3db3cbf0912f3b9911bc3922a
parent: 85e97ecab6db67faefb64557af9b2271d2b23735
Author: Eugen Rochko <eugen@zeonfederated.com>
Date: Thu, 30 Nov 2017 04:06:20 +0100
Add semi-support for Video/Image objects in ActivityPub (#5848)
* Add semi-support for Video/Image objects in ActivityPub
Video and Image objects will create corresponding status records
with manually crafted text contents (title + URL)
* Extract html-url-finding logic into JsonLdHelper
* Fallback to id when url missing, extract supported object types
Diffstat:
6 files changed, 103 insertions(+), 26 deletions(-)
diff --git a/app/helpers/jsonld_helper.rb b/app/helpers/jsonld_helper.rb
@@ -9,6 +9,24 @@ module JsonLdHelper
value.is_a?(Array) ? value.first : value
end
+ # The url attribute can be a string, an array of strings, or an array of objects.
+ # The objects could include a mimeType. Not-included mimeType means it's text/html.
+ def url_to_href(value, preferred_type = nil)
+ single_value = if value.is_a?(Array) && !value.first.is_a?(String)
+ value.find { |link| preferred_type.nil? || ((link['mimeType'].presence || 'text/html') == preferred_type) }
+ elsif value.is_a?(Array)
+ value.first
+ else
+ value
+ end
+
+ if single_value.nil? || single_value.is_a?(String)
+ single_value
+ else
+ single_value['href']
+ end
+ end
+
def as_array(value)
value.is_a?(Array) ? value : [value]
end
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
@@ -1,6 +1,9 @@
# frozen_string_literal: true
class ActivityPub::Activity::Create < ActivityPub::Activity
+ SUPPORTED_TYPES = %w(Article Note).freeze
+ CONVERTED_TYPES = %w(Image Video).freeze
+
def perform
return if delete_arrived_first?(object_uri) || unsupported_object_type?
@@ -41,7 +44,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
url: object_url || @object['id'],
account: @account,
text: text_from_content || '',
- language: language_from_content,
+ language: detected_language,
spoiler_text: @object['summary'] || '',
created_at: @options[:override_timestamps] ? nil : @object['published'],
reply: @object['inReplyTo'].present?,
@@ -165,40 +168,62 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
end
def text_from_content
+ return Formatter.instance.linkify([text_from_name, object_url || @object['id']].join(' ')) if converted_object_type?
+
if @object['content'].present?
@object['content']
- elsif language_map?
+ elsif content_language_map?
@object['contentMap'].values.first
end
end
- def language_from_content
- return LanguageDetector.instance.detect(text_from_content, @account) unless language_map?
- @object['contentMap'].keys.first
+ def text_from_name
+ if @object['name'].present?
+ @object['name']
+ elsif name_language_map?
+ @object['nameMap'].values.first
+ end
+ end
+
+ def detected_language
+ if content_language_map?
+ @object['contentMap'].keys.first
+ elsif name_language_map?
+ @object['nameMap'].keys.first
+ elsif supported_object_type?
+ LanguageDetector.instance.detect(text_from_content, @account)
+ end
end
def object_url
return if @object['url'].blank?
-
- value = first_of_value(@object['url'])
-
- return value if value.is_a?(String)
-
- value['href']
+ url_to_href(@object['url'], 'text/html')
end
- def language_map?
+ def content_language_map?
@object['contentMap'].is_a?(Hash) && !@object['contentMap'].empty?
end
+ def name_language_map?
+ @object['nameMap'].is_a?(Hash) && !@object['nameMap'].empty?
+ end
+
def unsupported_object_type?
- @object.is_a?(String) || !%w(Article Note).include?(@object['type'])
+ @object.is_a?(String) || !(supported_object_type? || converted_object_type?)
end
def unsupported_media_type?(mime_type)
mime_type.present? && !(MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES).include?(mime_type)
end
+ def supported_object_type?
+ SUPPORTED_TYPES.include?(@object['type'])
+ end
+
+ def converted_object_type?
+ CONVERTED_TYPES.include?(@object['type'])
+ end
+
def skip_download?
return @skip_download if defined?(@skip_download)
@skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media?
diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb
@@ -51,12 +51,7 @@ class Formatter
def simplified_format(account)
return reformat(account.note).html_safe unless account.local? # rubocop:disable Rails/OutputSafety
-
- html = encode_and_link_urls(account.note)
- html = simple_format(html, {}, sanitize: false)
- html = html.delete("\n")
-
- html.html_safe # rubocop:disable Rails/OutputSafety
+ linkify(account.note)
end
def sanitize(html, config)
@@ -69,6 +64,14 @@ class Formatter
html.html_safe # rubocop:disable Rails/OutputSafety
end
+ def linkify(text)
+ html = encode_and_link_urls(text)
+ html = simple_format(html, {}, sanitize: false)
+ html = html.delete("\n")
+
+ html.html_safe # rubocop:disable Rails/OutputSafety
+ end
+
private
def encode(html)
diff --git a/app/services/activitypub/fetch_remote_status_service.rb b/app/services/activitypub/fetch_remote_status_service.rb
@@ -42,7 +42,7 @@ class ActivityPub::FetchRemoteStatusService < BaseService
end
def expected_type?
- %w(Note Article).include? @json['type']
+ (ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES).include? @json['type']
end
def needs_update(actor)
diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb
@@ -107,12 +107,7 @@ class ActivityPub::ProcessAccountService < BaseService
def url
return if @json['url'].blank?
-
- value = first_of_value(@json['url'])
-
- return value if value.is_a?(String)
-
- value['href']
+ url_to_href(@json['url'], 'text/html')
end
def outbox_total_items
diff --git a/spec/services/activitypub/fetch_remote_status_service_spec.rb b/spec/services/activitypub/fetch_remote_status_service_spec.rb
@@ -1,6 +1,8 @@
require 'rails_helper'
RSpec.describe ActivityPub::FetchRemoteStatusService do
+ include ActionView::Helpers::TextHelper
+
let(:sender) { Fabricate(:account) }
let(:recipient) { Fabricate(:account) }
let(:valid_domain) { Rails.configuration.x.local_domain }
@@ -19,6 +21,7 @@ RSpec.describe ActivityPub::FetchRemoteStatusService do
describe '#call' do
before do
+ stub_request(:head, 'https://example.com/watch?v=12345').to_return(status: 404, body: '')
subject.call(object[:id], prefetched_body: Oj.dump(object))
end
@@ -32,5 +35,38 @@ RSpec.describe ActivityPub::FetchRemoteStatusService do
expect(status.text).to eq 'Lorem ipsum'
end
end
+
+ context 'with Video object' do
+ let(:object) do
+ {
+ '@context': 'https://www.w3.org/ns/activitystreams',
+ id: "https://#{valid_domain}/@foo/1234",
+ type: 'Video',
+ name: 'Nyan Cat 10 hours remix',
+ attributedTo: ActivityPub::TagManager.instance.uri_for(sender),
+ url: [
+ {
+ type: 'Link',
+ mimeType: 'application/x-bittorrent',
+ href: 'https://example.com/12345.torrent',
+ },
+
+ {
+ type: 'Link',
+ mimeType: 'text/html',
+ href: 'https://example.com/watch?v=12345',
+ },
+ ],
+ }
+ end
+
+ it 'creates status' do
+ status = sender.statuses.first
+
+ expect(status).to_not be_nil
+ expect(status.url).to eq 'https://example.com/watch?v=12345'
+ expect(strip_tags(status.text)).to eq 'Nyan Cat 10 hours remix https://example.com/watch?v=12345'
+ end
+ end
end
end