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 :require_user!
|
||||
before_action :set_reblog, only: [:create]
|
||||
before_action :set_circle, only: [:create]
|
||||
|
||||
override_rate_limit_headers :create, family: :statuses
|
||||
|
||||
|
@ -42,7 +43,22 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
|
|||
not_found
|
||||
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
|
||||
params.permit(:visibility)
|
||||
params.permit(:visibility, :circle_id)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -153,7 +153,9 @@ class ActivityPub::Activity
|
|||
|
||||
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?
|
||||
actor_id = value_or_id(first_of_value(@object['attributedTo']))
|
||||
|
||||
|
@ -165,11 +167,16 @@ class ActivityPub::Activity
|
|||
fetch_remote_original_status
|
||||
end
|
||||
|
||||
def dereferenced?
|
||||
@dereferenced
|
||||
end
|
||||
|
||||
def dereference_object!
|
||||
return unless @object.is_a?(String)
|
||||
|
||||
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?
|
||||
end
|
||||
|
||||
|
@ -204,7 +211,12 @@ class ActivityPub::Activity
|
|||
def fetch_remote_original_status
|
||||
if object_uri.start_with?('http')
|
||||
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?
|
||||
::FetchRemoteStatusService.new.call(@object['url'])
|
||||
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}"}")
|
||||
nil
|
||||
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
|
||||
|
|
|
@ -5,28 +5,17 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
|
|||
return reject_payload! if delete_arrived_first?(@json['id']) || !related_to_local_activity?
|
||||
|
||||
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?
|
||||
|
||||
@status = Status.create!(
|
||||
account: @account,
|
||||
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)
|
||||
if @status.nil?
|
||||
process_status
|
||||
elsif @options[:delivered_to_account_id].present?
|
||||
postprocess_audience_and_deliver
|
||||
end
|
||||
|
||||
distribute(@status)
|
||||
end
|
||||
|
||||
@status
|
||||
|
@ -34,28 +23,47 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
|
|||
|
||||
private
|
||||
|
||||
def audience_to
|
||||
as_array(@json['to']).map { |x| value_or_id(x) }
|
||||
def process_status
|
||||
@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
|
||||
|
||||
def audience_cc
|
||||
as_array(@json['cc']).map { |x| value_or_id(x) }
|
||||
def process_status_params
|
||||
@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
|
||||
|
||||
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
|
||||
def attach_mentions(status)
|
||||
@mentions.each do |mention|
|
||||
mention.status = status
|
||||
mention.save
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def related_to_local_activity?
|
||||
|
@ -67,6 +75,6 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
|
|||
end
|
||||
|
||||
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
|
||||
|
|
|
@ -120,53 +120,6 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
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)
|
||||
@tags.each do |tag|
|
||||
status.tags << tag
|
||||
|
@ -384,23 +337,6 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
conversation
|
||||
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
|
||||
return @replied_to_status if defined?(@replied_to_status)
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ class Status < ApplicationRecord
|
|||
validates_with StatusLengthValidator
|
||||
validates_with DisallowedHashtagsValidator
|
||||
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_with ExpiresValidator, on: :create, if: :local?
|
||||
|
||||
|
|
|
@ -28,8 +28,21 @@ class ReblogService < BaseService
|
|||
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)
|
||||
ActivityPub::DistributionWorker.perform_async(reblog.id)
|
||||
|
||||
|
|
Loading…
Reference in a new issue