* When self-boosting, embed original toot into Announce serialization * Process unknown self-boosts from Announce object if it is more than an URI * Add some self-boost specs * Only serialize private toots in self-Announcesmaster
@@ -4,6 +4,9 @@ class ActivityPub::Activity | |||
include JsonLdHelper | |||
include Redisable | |||
SUPPORTED_TYPES = %w(Note).freeze | |||
CONVERTED_TYPES = %w(Image Video Article Page).freeze | |||
def initialize(json, account, **options) | |||
@json = json | |||
@account = account | |||
@@ -71,6 +74,18 @@ class ActivityPub::Activity | |||
@object_uri ||= value_or_id(@object) | |||
end | |||
def unsupported_object_type? | |||
@object.is_a?(String) || !(supported_object_type? || converted_object_type?) | |||
end | |||
def supported_object_type? | |||
equals_or_includes_any?(@object['type'], SUPPORTED_TYPES) | |||
end | |||
def converted_object_type? | |||
equals_or_includes_any?(@object['type'], CONVERTED_TYPES) | |||
end | |||
def distribute(status) | |||
crawl_links(status) | |||
@@ -120,6 +135,23 @@ class ActivityPub::Activity | |||
redis.setex("delete_upon_arrival:#{@account.id}:#{uri}", 6.hours.seconds, uri) | |||
end | |||
def status_from_object | |||
# If the status is already known, return it | |||
status = status_from_uri(object_uri) | |||
return status unless status.nil? | |||
# If the boosted toot is embedded and it is a self-boost, handle it like a Create | |||
unless unsupported_object_type? | |||
actor_id = value_or_id(first_of_value(@object['attributedTo'])) || @account.uri | |||
if actor_id == @account.uri | |||
return ActivityPub::Activity.factory({ 'type' => 'Create', 'actor' => actor_id, 'object' => @object }, @account).perform | |||
end | |||
end | |||
# If the status is not from the actor, try to fetch it | |||
return fetch_remote_original_status if value_or_id(first_of_value(@json['attributedTo'])) == @account.uri | |||
end | |||
def fetch_remote_original_status | |||
if object_uri.start_with?('http') | |||
return if ActivityPub::TagManager.instance.local_uri?(object_uri) | |||
@@ -2,9 +2,7 @@ | |||
class ActivityPub::Activity::Announce < ActivityPub::Activity | |||
def perform | |||
original_status = status_from_uri(object_uri) | |||
original_status ||= fetch_remote_original_status | |||
original_status = status_from_object | |||
return if original_status.nil? || delete_arrived_first?(@json['id']) || !announceable?(original_status) | |||
status = Status.find_by(account: @account, reblog: original_status) | |||
@@ -1,9 +1,6 @@ | |||
# frozen_string_literal: true | |||
class ActivityPub::Activity::Create < ActivityPub::Activity | |||
SUPPORTED_TYPES = %w(Note).freeze | |||
CONVERTED_TYPES = %w(Image Video Article Page).freeze | |||
def perform | |||
return if unsupported_object_type? || invalid_origin?(@object['id']) | |||
return if Tombstone.exists?(uri: @object['id']) | |||
@@ -318,22 +315,10 @@ class ActivityPub::Activity::Create < ActivityPub::Activity | |||
@object['nameMap'].is_a?(Hash) && !@object['nameMap'].empty? | |||
end | |||
def unsupported_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? | |||
equals_or_includes_any?(@object['type'], SUPPORTED_TYPES) | |||
end | |||
def converted_object_type? | |||
equals_or_includes_any?(@object['type'], CONVERTED_TYPES) | |||
end | |||
def skip_download? | |||
return @skip_download if defined?(@skip_download) | |||
@skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media? | |||
@@ -3,8 +3,8 @@ | |||
class ActivityPub::ActivitySerializer < ActiveModel::Serializer | |||
attributes :id, :type, :actor, :published, :to, :cc | |||
has_one :proper, key: :object, serializer: ActivityPub::NoteSerializer, unless: :announce? | |||
attribute :proper_uri, key: :object, if: :announce? | |||
has_one :proper, key: :object, serializer: ActivityPub::NoteSerializer, unless: :owned_announce? | |||
attribute :proper_uri, key: :object, if: :owned_announce? | |||
attribute :atom_uri, if: :announce? | |||
def id | |||
@@ -42,4 +42,8 @@ class ActivityPub::ActivitySerializer < ActiveModel::Serializer | |||
def announce? | |||
object.reblog? | |||
end | |||
def owned_announce? | |||
announce? && object.account == object.proper.account && object.proper.private_visibility? | |||
end | |||
end |
@@ -1,7 +1,7 @@ | |||
require 'rails_helper' | |||
RSpec.describe ActivityPub::Activity::Announce do | |||
let(:sender) { Fabricate(:account) } | |||
let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers') } | |||
let(:recipient) { Fabricate(:account) } | |||
let(:status) { Fabricate(:status, account: recipient) } | |||
@@ -11,19 +11,60 @@ RSpec.describe ActivityPub::Activity::Announce do | |||
id: 'foo', | |||
type: 'Announce', | |||
actor: ActivityPub::TagManager.instance.uri_for(sender), | |||
object: ActivityPub::TagManager.instance.uri_for(status), | |||
object: object_json, | |||
}.with_indifferent_access | |||
end | |||
describe '#perform' do | |||
subject { described_class.new(json, sender) } | |||
subject { described_class.new(json, sender) } | |||
before do | |||
sender.update(uri: ActivityPub::TagManager.instance.uri_for(sender)) | |||
end | |||
describe '#perform' do | |||
before do | |||
subject.perform | |||
end | |||
it 'creates a reblog by sender of status' do | |||
expect(sender.reblogged?(status)).to be true | |||
context 'a known status' do | |||
let(:object_json) do | |||
ActivityPub::TagManager.instance.uri_for(status) | |||
end | |||
it 'creates a reblog by sender of status' do | |||
expect(sender.reblogged?(status)).to be true | |||
end | |||
end | |||
context 'self-boost of a previously unknown status with missing attributedTo' do | |||
let(:object_json) do | |||
{ | |||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, | |||
type: 'Note', | |||
content: 'Lorem ipsum', | |||
to: 'http://example.com/followers', | |||
} | |||
end | |||
it 'creates a reblog by sender of status' do | |||
expect(sender.reblogged?(sender.statuses.first)).to be true | |||
end | |||
end | |||
context 'self-boost of a previously unknown status with correct attributedTo' do | |||
let(:object_json) do | |||
{ | |||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, | |||
type: 'Note', | |||
content: 'Lorem ipsum', | |||
attributedTo: ActivityPub::TagManager.instance.uri_for(sender), | |||
to: 'http://example.com/followers', | |||
} | |||
end | |||
it 'creates a reblog by sender of status' do | |||
expect(sender.reblogged?(sender.statuses.first)).to be true | |||
end | |||
end | |||
end | |||
end |