Alternative handling of private self-boosts (#9998)
* 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-Announces
This commit is contained in:
parent
737ac4b59d
commit
6c11f0f8cf
5 changed files with 86 additions and 26 deletions
|
@ -4,6 +4,9 @@ class ActivityPub::Activity
|
||||||
include JsonLdHelper
|
include JsonLdHelper
|
||||||
include Redisable
|
include Redisable
|
||||||
|
|
||||||
|
SUPPORTED_TYPES = %w(Note).freeze
|
||||||
|
CONVERTED_TYPES = %w(Image Video Article Page).freeze
|
||||||
|
|
||||||
def initialize(json, account, **options)
|
def initialize(json, account, **options)
|
||||||
@json = json
|
@json = json
|
||||||
@account = account
|
@account = account
|
||||||
|
@ -71,6 +74,18 @@ class ActivityPub::Activity
|
||||||
@object_uri ||= value_or_id(@object)
|
@object_uri ||= value_or_id(@object)
|
||||||
end
|
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)
|
def distribute(status)
|
||||||
crawl_links(status)
|
crawl_links(status)
|
||||||
|
|
||||||
|
@ -120,6 +135,23 @@ class ActivityPub::Activity
|
||||||
redis.setex("delete_upon_arrival:#{@account.id}:#{uri}", 6.hours.seconds, uri)
|
redis.setex("delete_upon_arrival:#{@account.id}:#{uri}", 6.hours.seconds, uri)
|
||||||
end
|
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
|
def fetch_remote_original_status
|
||||||
if object_uri.start_with?('http')
|
if object_uri.start_with?('http')
|
||||||
return if ActivityPub::TagManager.instance.local_uri?(object_uri)
|
return if ActivityPub::TagManager.instance.local_uri?(object_uri)
|
||||||
|
|
|
@ -2,9 +2,7 @@
|
||||||
|
|
||||||
class ActivityPub::Activity::Announce < ActivityPub::Activity
|
class ActivityPub::Activity::Announce < ActivityPub::Activity
|
||||||
def perform
|
def perform
|
||||||
original_status = status_from_uri(object_uri)
|
original_status = status_from_object
|
||||||
original_status ||= fetch_remote_original_status
|
|
||||||
|
|
||||||
return if original_status.nil? || delete_arrived_first?(@json['id']) || !announceable?(original_status)
|
return if original_status.nil? || delete_arrived_first?(@json['id']) || !announceable?(original_status)
|
||||||
|
|
||||||
status = Status.find_by(account: @account, reblog: original_status)
|
status = Status.find_by(account: @account, reblog: original_status)
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ActivityPub::Activity::Create < ActivityPub::Activity
|
class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
SUPPORTED_TYPES = %w(Note).freeze
|
|
||||||
CONVERTED_TYPES = %w(Image Video Article Page).freeze
|
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
return if unsupported_object_type? || invalid_origin?(@object['id'])
|
return if unsupported_object_type? || invalid_origin?(@object['id'])
|
||||||
return if Tombstone.exists?(uri: @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?
|
@object['nameMap'].is_a?(Hash) && !@object['nameMap'].empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
def unsupported_object_type?
|
|
||||||
@object.is_a?(String) || !(supported_object_type? || converted_object_type?)
|
|
||||||
end
|
|
||||||
|
|
||||||
def unsupported_media_type?(mime_type)
|
def unsupported_media_type?(mime_type)
|
||||||
mime_type.present? && !(MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES).include?(mime_type)
|
mime_type.present? && !(MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES).include?(mime_type)
|
||||||
end
|
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?
|
def skip_download?
|
||||||
return @skip_download if defined?(@skip_download)
|
return @skip_download if defined?(@skip_download)
|
||||||
@skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media?
|
@skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media?
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
class ActivityPub::ActivitySerializer < ActiveModel::Serializer
|
class ActivityPub::ActivitySerializer < ActiveModel::Serializer
|
||||||
attributes :id, :type, :actor, :published, :to, :cc
|
attributes :id, :type, :actor, :published, :to, :cc
|
||||||
|
|
||||||
has_one :proper, key: :object, serializer: ActivityPub::NoteSerializer, unless: :announce?
|
has_one :proper, key: :object, serializer: ActivityPub::NoteSerializer, unless: :owned_announce?
|
||||||
attribute :proper_uri, key: :object, if: :announce?
|
attribute :proper_uri, key: :object, if: :owned_announce?
|
||||||
attribute :atom_uri, if: :announce?
|
attribute :atom_uri, if: :announce?
|
||||||
|
|
||||||
def id
|
def id
|
||||||
|
@ -42,4 +42,8 @@ class ActivityPub::ActivitySerializer < ActiveModel::Serializer
|
||||||
def announce?
|
def announce?
|
||||||
object.reblog?
|
object.reblog?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def owned_announce?
|
||||||
|
announce? && object.account == object.proper.account && object.proper.private_visibility?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe ActivityPub::Activity::Announce do
|
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(:recipient) { Fabricate(:account) }
|
||||||
let(:status) { Fabricate(:status, account: recipient) }
|
let(:status) { Fabricate(:status, account: recipient) }
|
||||||
|
|
||||||
|
@ -11,19 +11,60 @@ RSpec.describe ActivityPub::Activity::Announce do
|
||||||
id: 'foo',
|
id: 'foo',
|
||||||
type: 'Announce',
|
type: 'Announce',
|
||||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||||
object: ActivityPub::TagManager.instance.uri_for(status),
|
object: object_json,
|
||||||
}.with_indifferent_access
|
}.with_indifferent_access
|
||||||
end
|
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
|
before do
|
||||||
subject.perform
|
subject.perform
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates a reblog by sender of status' do
|
context 'a known status' do
|
||||||
expect(sender.reblogged?(status)).to be true
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue