Add support for limited Announce Activity
This commit is contained in:
parent
c3114f9a5e
commit
5ea43e505b
6 changed files with 155 additions and 102 deletions
|
@ -6,6 +6,7 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }
|
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
before_action :set_reblog, only: [:create]
|
before_action :set_reblog, only: [:create]
|
||||||
|
before_action :set_circle, only: [:create]
|
||||||
|
|
||||||
override_rate_limit_headers :create, family: :statuses
|
override_rate_limit_headers :create, family: :statuses
|
||||||
|
|
||||||
|
@ -42,7 +43,22 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
|
||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_circle
|
||||||
|
reblog_params[:circle] = begin
|
||||||
|
if reblog_params[:visibility] == 'mutual'
|
||||||
|
reblog_params[:visibility] = 'limited'
|
||||||
|
current_account
|
||||||
|
elsif reblog_params[:circle_id].blank?
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
current_account.owned_circles.find(reblog_params[:circle_id])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render json: { error: I18n.t('statuses.errors.circle_not_found') }, status: 404
|
||||||
|
end
|
||||||
|
|
||||||
def reblog_params
|
def reblog_params
|
||||||
params.permit(:visibility)
|
params.permit(:visibility, :circle_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -153,7 +153,9 @@ class ActivityPub::Activity
|
||||||
|
|
||||||
return status unless status.nil?
|
return status unless status.nil?
|
||||||
|
|
||||||
# If the boosted toot is embedded and it is a self-boost, handle it like a Create
|
dereference_object!
|
||||||
|
|
||||||
|
# If the boosted toot is embedded and it is a self-boost or dereferenced, handle it like a Create
|
||||||
unless unsupported_object_type?
|
unless unsupported_object_type?
|
||||||
actor_id = value_or_id(first_of_value(@object['attributedTo']))
|
actor_id = value_or_id(first_of_value(@object['attributedTo']))
|
||||||
|
|
||||||
|
@ -165,11 +167,16 @@ class ActivityPub::Activity
|
||||||
fetch_remote_original_status
|
fetch_remote_original_status
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def dereferenced?
|
||||||
|
@dereferenced
|
||||||
|
end
|
||||||
|
|
||||||
def dereference_object!
|
def dereference_object!
|
||||||
return unless @object.is_a?(String)
|
return unless @object.is_a?(String)
|
||||||
|
|
||||||
dereferencer = ActivityPub::Dereferencer.new(@object, permitted_origin: @account.uri, signature_account: signed_fetch_account)
|
dereferencer = ActivityPub::Dereferencer.new(@object, permitted_origin: @account.uri, signature_account: signed_fetch_account)
|
||||||
|
|
||||||
|
@dereferenced = !dereferencer.object.nil?
|
||||||
@object = dereferencer.object unless dereferencer.object.nil?
|
@object = dereferencer.object unless dereferencer.object.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -204,7 +211,12 @@ class ActivityPub::Activity
|
||||||
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)
|
||||||
ActivityPub::FetchRemoteStatusService.new.call(object_uri, id: true, on_behalf_of: @account.followers.local.first)
|
|
||||||
|
if dereferenced?
|
||||||
|
ActivityPub::FetchRemoteStatusService.new.call(object_uri, id: true, prefetched_body: @object)
|
||||||
|
else
|
||||||
|
ActivityPub::FetchRemoteStatusService.new.call(object_uri, id: true, on_behalf_of: @account.followers.local.first)
|
||||||
|
end
|
||||||
elsif @object['url'].present?
|
elsif @object['url'].present?
|
||||||
::FetchRemoteStatusService.new.call(@object['url'])
|
::FetchRemoteStatusService.new.call(@object['url'])
|
||||||
end
|
end
|
||||||
|
@ -242,4 +254,72 @@ class ActivityPub::Activity
|
||||||
Rails.logger.info("Rejected #{@json['type']} activity #{@json['id']} from #{@account.uri}#{@options[:relayed_through_account] && "via #{@options[:relayed_through_account].uri}"}")
|
Rails.logger.info("Rejected #{@json['type']} activity #{@json['id']} from #{@account.uri}#{@options[:relayed_through_account] && "via #{@options[:relayed_through_account].uri}"}")
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def audience_to
|
||||||
|
as_array(@json['to']).map { |x| value_or_id(x) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def audience_cc
|
||||||
|
as_array(@json['cc']).map { |x| value_or_id(x) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_audience
|
||||||
|
conversation_uri = value_or_id(@object['context'])
|
||||||
|
|
||||||
|
(audience_to + audience_cc).uniq.each do |audience|
|
||||||
|
next if ActivityPub::TagManager.instance.public_collection?(audience) || audience == conversation_uri
|
||||||
|
|
||||||
|
# Unlike with tags, there is no point in resolving accounts we don't already
|
||||||
|
# know here, because silent mentions would only be used for local access
|
||||||
|
# control anyway
|
||||||
|
account = account_from_uri(audience)
|
||||||
|
|
||||||
|
next if account.nil? || @mentions.any? { |mention| mention.account_id == account.id }
|
||||||
|
|
||||||
|
@mentions << Mention.new(account: account, silent: true)
|
||||||
|
|
||||||
|
# If there is at least one silent mention, then the status can be considered
|
||||||
|
# as a limited-audience status, and not strictly a direct message, but only
|
||||||
|
# if we considered a direct message in the first place
|
||||||
|
@params[:visibility] = :limited if @params[:visibility] == :direct
|
||||||
|
end
|
||||||
|
|
||||||
|
# If the payload was delivered to a specific inbox, the inbox owner must have
|
||||||
|
# access to it, unless they already have access to it anyway
|
||||||
|
return if @options[:delivered_to_account_id].nil? || @mentions.any? { |mention| mention.account_id == @options[:delivered_to_account_id] }
|
||||||
|
|
||||||
|
@mentions << Mention.new(account_id: @options[:delivered_to_account_id], silent: true)
|
||||||
|
|
||||||
|
@params[:visibility] = :limited if @params[:visibility] == :direct
|
||||||
|
end
|
||||||
|
|
||||||
|
def postprocess_audience_and_deliver
|
||||||
|
return if @status.mentions.find_by(account_id: @options[:delivered_to_account_id])
|
||||||
|
|
||||||
|
delivered_to_account = Account.find(@options[:delivered_to_account_id])
|
||||||
|
|
||||||
|
@status.mentions.create(account: delivered_to_account, silent: true)
|
||||||
|
@status.update(visibility: :limited) if @status.direct_visibility?
|
||||||
|
|
||||||
|
return unless delivered_to_account.following?(@account)
|
||||||
|
|
||||||
|
FeedInsertWorker.perform_async(@status.id, delivered_to_account.id, :home)
|
||||||
|
end
|
||||||
|
|
||||||
|
def visibility_from_audience
|
||||||
|
if audience_to.any? { |to| ActivityPub::TagManager.instance.public_collection?(to) }
|
||||||
|
:public
|
||||||
|
elsif audience_cc.any? { |cc| ActivityPub::TagManager.instance.public_collection?(cc) }
|
||||||
|
:unlisted
|
||||||
|
elsif audience_to.include?(@account.followers_url)
|
||||||
|
:private
|
||||||
|
else
|
||||||
|
:direct
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def audience_includes?(account)
|
||||||
|
uri = ActivityPub::TagManager.instance.uri_for(account)
|
||||||
|
audience_to.include?(uri) || audience_cc.include?(uri)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,28 +5,17 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
|
||||||
return reject_payload! if delete_arrived_first?(@json['id']) || !related_to_local_activity?
|
return reject_payload! if delete_arrived_first?(@json['id']) || !related_to_local_activity?
|
||||||
|
|
||||||
lock_or_fail("announce:#{@object['id']}") do
|
lock_or_fail("announce:#{@object['id']}") do
|
||||||
original_status = status_from_object
|
@original_status = status_from_object
|
||||||
|
|
||||||
return reject_payload! if original_status.nil? || !announceable?(original_status)
|
return reject_payload! if @original_status.nil? || !announceable?(@original_status)
|
||||||
|
|
||||||
@status = Status.find_by(account: @account, reblog: original_status)
|
@status = Status.find_by(account: @account, reblog: @original_status)
|
||||||
|
|
||||||
return @status unless @status.nil?
|
if @status.nil?
|
||||||
|
process_status
|
||||||
@status = Status.create!(
|
elsif @options[:delivered_to_account_id].present?
|
||||||
account: @account,
|
postprocess_audience_and_deliver
|
||||||
reblog: original_status,
|
|
||||||
uri: @json['id'],
|
|
||||||
created_at: @json['published'],
|
|
||||||
override_timestamps: @options[:override_timestamps],
|
|
||||||
visibility: visibility_from_audience
|
|
||||||
)
|
|
||||||
|
|
||||||
original_status.tags.each do |tag|
|
|
||||||
tag.use!(@account)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
distribute(@status)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@status
|
@status
|
||||||
|
@ -34,28 +23,47 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def audience_to
|
def process_status
|
||||||
as_array(@json['to']).map { |x| value_or_id(x) }
|
@mentions = []
|
||||||
|
@params = {}
|
||||||
|
|
||||||
|
process_status_params
|
||||||
|
process_audience
|
||||||
|
|
||||||
|
ApplicationRecord.transaction do
|
||||||
|
@status = Status.create!(@params)
|
||||||
|
attach_mentions(@status)
|
||||||
|
end
|
||||||
|
|
||||||
|
@original_status.tags.each do |tag|
|
||||||
|
tag.use!(@account)
|
||||||
|
end
|
||||||
|
|
||||||
|
distribute(@status)
|
||||||
end
|
end
|
||||||
|
|
||||||
def audience_cc
|
def process_status_params
|
||||||
as_array(@json['cc']).map { |x| value_or_id(x) }
|
@params = begin
|
||||||
|
{
|
||||||
|
account: @account,
|
||||||
|
reblog: @original_status,
|
||||||
|
uri: @json['id'],
|
||||||
|
created_at: @json['published'],
|
||||||
|
override_timestamps: @options[:override_timestamps],
|
||||||
|
visibility: visibility_from_audience
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def visibility_from_audience
|
def attach_mentions(status)
|
||||||
if audience_to.any? { |to| ActivityPub::TagManager.instance.public_collection?(to) }
|
@mentions.each do |mention|
|
||||||
:public
|
mention.status = status
|
||||||
elsif audience_cc.any? { |cc| ActivityPub::TagManager.instance.public_collection?(cc) }
|
mention.save
|
||||||
:unlisted
|
|
||||||
elsif audience_to.include?(@account.followers_url)
|
|
||||||
:private
|
|
||||||
else
|
|
||||||
:direct
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def announceable?(status)
|
def announceable?(status)
|
||||||
status.account_id == @account.id || status.distributable? || @account.group? && (status.mentioning?(@account) || status.account.mutual?(@account))
|
status.account_id == @account.id || (@account.group? && dereferenced?) || status.distributable? || status.account.mutual?(@account)
|
||||||
end
|
end
|
||||||
|
|
||||||
def related_to_local_activity?
|
def related_to_local_activity?
|
||||||
|
@ -67,6 +75,6 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
|
||||||
end
|
end
|
||||||
|
|
||||||
def reblog_of_local_status?
|
def reblog_of_local_status?
|
||||||
status_from_uri(object_uri)&.account&.local?
|
ActivityPub::TagManager.instance.local_uri?(object_uri) && status_from_uri(object_uri)&.account&.local?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -120,53 +120,6 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_audience
|
|
||||||
conversation_uri = value_or_id(@object['context'])
|
|
||||||
|
|
||||||
(audience_to + audience_cc).uniq.each do |audience|
|
|
||||||
next if ActivityPub::TagManager.instance.public_collection?(audience) || audience == conversation_uri
|
|
||||||
|
|
||||||
# Unlike with tags, there is no point in resolving accounts we don't already
|
|
||||||
# know here, because silent mentions would only be used for local access
|
|
||||||
# control anyway
|
|
||||||
account = account_from_uri(audience)
|
|
||||||
|
|
||||||
next if account.nil? || @mentions.any? { |mention| mention.account_id == account.id }
|
|
||||||
|
|
||||||
@mentions << Mention.new(account: account, silent: true)
|
|
||||||
|
|
||||||
# If there is at least one silent mention, then the status can be considered
|
|
||||||
# as a limited-audience status, and not strictly a direct message, but only
|
|
||||||
# if we considered a direct message in the first place
|
|
||||||
next unless @params[:visibility] == :direct
|
|
||||||
|
|
||||||
@params[:visibility] = :limited
|
|
||||||
end
|
|
||||||
|
|
||||||
# If the payload was delivered to a specific inbox, the inbox owner must have
|
|
||||||
# access to it, unless they already have access to it anyway
|
|
||||||
return if @options[:delivered_to_account_id].nil? || @mentions.any? { |mention| mention.account_id == @options[:delivered_to_account_id] }
|
|
||||||
|
|
||||||
@mentions << Mention.new(account_id: @options[:delivered_to_account_id], silent: true)
|
|
||||||
|
|
||||||
return unless @params[:visibility] == :direct
|
|
||||||
|
|
||||||
@params[:visibility] = :limited
|
|
||||||
end
|
|
||||||
|
|
||||||
def postprocess_audience_and_deliver
|
|
||||||
return if @status.mentions.find_by(account_id: @options[:delivered_to_account_id])
|
|
||||||
|
|
||||||
delivered_to_account = Account.find(@options[:delivered_to_account_id])
|
|
||||||
|
|
||||||
@status.mentions.create(account: delivered_to_account, silent: true)
|
|
||||||
@status.update(visibility: :limited) if @status.direct_visibility?
|
|
||||||
|
|
||||||
return unless delivered_to_account.following?(@account)
|
|
||||||
|
|
||||||
FeedInsertWorker.perform_async(@status.id, delivered_to_account.id, :home)
|
|
||||||
end
|
|
||||||
|
|
||||||
def attach_tags(status)
|
def attach_tags(status)
|
||||||
@tags.each do |tag|
|
@tags.each do |tag|
|
||||||
status.tags << tag
|
status.tags << tag
|
||||||
|
@ -384,23 +337,6 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
conversation
|
conversation
|
||||||
end
|
end
|
||||||
|
|
||||||
def visibility_from_audience
|
|
||||||
if audience_to.any? { |to| ActivityPub::TagManager.instance.public_collection?(to) }
|
|
||||||
:public
|
|
||||||
elsif audience_cc.any? { |cc| ActivityPub::TagManager.instance.public_collection?(cc) }
|
|
||||||
:unlisted
|
|
||||||
elsif audience_to.include?(@account.followers_url)
|
|
||||||
:private
|
|
||||||
else
|
|
||||||
:direct
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def audience_includes?(account)
|
|
||||||
uri = ActivityPub::TagManager.instance.uri_for(account)
|
|
||||||
audience_to.include?(uri) || audience_cc.include?(uri)
|
|
||||||
end
|
|
||||||
|
|
||||||
def replied_to_status
|
def replied_to_status
|
||||||
return @replied_to_status if defined?(@replied_to_status)
|
return @replied_to_status if defined?(@replied_to_status)
|
||||||
|
|
||||||
|
|
|
@ -89,7 +89,7 @@ class Status < ApplicationRecord
|
||||||
validates_with StatusLengthValidator
|
validates_with StatusLengthValidator
|
||||||
validates_with DisallowedHashtagsValidator
|
validates_with DisallowedHashtagsValidator
|
||||||
validates :reblog, uniqueness: { scope: :account }, if: :reblog?
|
validates :reblog, uniqueness: { scope: :account }, if: :reblog?
|
||||||
validates :visibility, exclusion: { in: %w(direct limited) }, if: :reblog?
|
validates :visibility, exclusion: { in: %w(direct) }, if: :reblog?
|
||||||
validates :quote_visibility, inclusion: { in: %w(public unlisted) }, if: :quote?
|
validates :quote_visibility, inclusion: { in: %w(public unlisted) }, if: :quote?
|
||||||
validates_with ExpiresValidator, on: :create, if: :local?
|
validates_with ExpiresValidator, on: :create, if: :local?
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,21 @@ class ReblogService < BaseService
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: visibility, rate_limit: options[:with_rate_limit])
|
circle = begin
|
||||||
|
if visibility == 'mutual'
|
||||||
|
visibility = 'limited'
|
||||||
|
account
|
||||||
|
else
|
||||||
|
options[:circle]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ApplicationRecord.transaction do
|
||||||
|
reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: visibility, rate_limit: options[:with_rate_limit])
|
||||||
|
reblog.capability_tokens.create! if reblog.limited_visibility?
|
||||||
|
end
|
||||||
|
|
||||||
|
ProcessMentionsService.new.call(reblog, circle)
|
||||||
DistributionWorker.perform_async(reblog.id)
|
DistributionWorker.perform_async(reblog.id)
|
||||||
ActivityPub::DistributionWorker.perform_async(reblog.id)
|
ActivityPub::DistributionWorker.perform_async(reblog.id)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue