Merge tag 'v3.2.0' into hometown-dev
This commit is contained in:
@ -132,7 +132,7 @@ class ActivityPub::Activity
|
||||
end
|
||||
|
||||
def delete_arrived_first?(uri)
|
||||
redis.exists("delete_upon_arrival:#{@account.id}:#{uri}")
|
||||
redis.exists?("delete_upon_arrival:#{@account.id}:#{uri}")
|
||||
end
|
||||
|
||||
def delete_later!(uri)
|
||||
@ -157,6 +157,34 @@ class ActivityPub::Activity
|
||||
fetch_remote_original_status
|
||||
end
|
||||
|
||||
def dereference_object!
|
||||
return unless @object.is_a?(String)
|
||||
return if invalid_origin?(@object)
|
||||
|
||||
object = fetch_resource(@object, true, signed_fetch_account)
|
||||
return unless object.present? && object.is_a?(Hash) && supported_context?(object)
|
||||
|
||||
@object = object
|
||||
end
|
||||
|
||||
def signed_fetch_account
|
||||
first_mentioned_local_account || first_local_follower
|
||||
end
|
||||
|
||||
def first_mentioned_local_account
|
||||
audience = (as_array(@json['to']) + as_array(@json['cc'])).uniq
|
||||
local_usernames = audience.select { |uri| ActivityPub::TagManager.instance.local_uri?(uri) }
|
||||
.map { |uri| ActivityPub::TagManager.instance.uri_to_local_id(uri, :username) }
|
||||
|
||||
return if local_usernames.empty?
|
||||
|
||||
Account.local.where(username: local_usernames).first
|
||||
end
|
||||
|
||||
def first_local_follower
|
||||
@account.followers.local.first
|
||||
end
|
||||
|
||||
def follow_request_from_object
|
||||
@follow_request ||= FollowRequest.find_by(target_account: @account, uri: object_uri) unless object_uri.nil?
|
||||
end
|
||||
@ -185,7 +213,7 @@ class ActivityPub::Activity
|
||||
end
|
||||
|
||||
def followed_by_local_accounts?
|
||||
@account.passive_relationships.exists?
|
||||
@account.passive_relationships.exists? || @options[:relayed_through_account]&.passive_relationships&.exists?
|
||||
end
|
||||
|
||||
def requested_through_relay?
|
||||
|
@ -4,25 +4,32 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
|
||||
def perform
|
||||
return reject_payload! if delete_arrived_first?(@json['id']) || !related_to_local_activity?
|
||||
|
||||
original_status = status_from_object
|
||||
RedisLock.acquire(lock_options) do |lock|
|
||||
if lock.acquired?
|
||||
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?
|
||||
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
|
||||
)
|
||||
@status = Status.create!(
|
||||
account: @account,
|
||||
reblog: original_status,
|
||||
uri: @json['id'],
|
||||
created_at: @json['published'],
|
||||
override_timestamps: @options[:override_timestamps],
|
||||
visibility: visibility_from_audience
|
||||
)
|
||||
|
||||
distribute(status)
|
||||
status
|
||||
distribute(@status)
|
||||
else
|
||||
raise Mastodon::RaceConditionError
|
||||
end
|
||||
end
|
||||
|
||||
@status
|
||||
end
|
||||
|
||||
private
|
||||
@ -54,4 +61,8 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
|
||||
def reblog_of_local_status?
|
||||
status_from_uri(object_uri)&.account&.local?
|
||||
end
|
||||
|
||||
def lock_options
|
||||
{ redis: Redis.current, key: "announce:#{@object['id']}" }
|
||||
end
|
||||
end
|
||||
|
@ -4,7 +4,12 @@ class ActivityPub::Activity::Block < ActivityPub::Activity
|
||||
def perform
|
||||
target_account = account_from_uri(object_uri)
|
||||
|
||||
return if target_account.nil? || !target_account.local? || @account.blocking?(target_account)
|
||||
return if target_account.nil? || !target_account.local?
|
||||
|
||||
if @account.blocking?(target_account)
|
||||
@account.block_relationships.find_by(target_account: target_account).update(uri: @json['id']) if @json['id'].present?
|
||||
return
|
||||
end
|
||||
|
||||
UnfollowService.new.call(target_account, @account) if target_account.following?(@account)
|
||||
|
||||
|
@ -2,11 +2,52 @@
|
||||
|
||||
class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||
def perform
|
||||
dereference_object!
|
||||
|
||||
case @object['type']
|
||||
when 'EncryptedMessage'
|
||||
create_encrypted_message
|
||||
else
|
||||
create_status
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_encrypted_message
|
||||
return reject_payload! if invalid_origin?(@object['id']) || @options[:delivered_to_account_id].blank?
|
||||
|
||||
target_account = Account.find(@options[:delivered_to_account_id])
|
||||
target_device = target_account.devices.find_by(device_id: @object.dig('to', 'deviceId'))
|
||||
|
||||
return if target_device.nil?
|
||||
|
||||
target_device.encrypted_messages.create!(
|
||||
from_account: @account,
|
||||
from_device_id: @object.dig('attributedTo', 'deviceId'),
|
||||
type: @object['messageType'],
|
||||
body: @object['cipherText'],
|
||||
digest: @object.dig('digest', 'digestValue'),
|
||||
message_franking: message_franking.to_token
|
||||
)
|
||||
end
|
||||
|
||||
def message_franking
|
||||
MessageFranking.new(
|
||||
hmac: @object.dig('digest', 'digestValue'),
|
||||
original_franking: @object['messageFranking'],
|
||||
source_account_id: @account.id,
|
||||
target_account_id: @options[:delivered_to_account_id],
|
||||
timestamp: Time.now.utc
|
||||
)
|
||||
end
|
||||
|
||||
def create_status
|
||||
return reject_payload! if unsupported_object_type? || invalid_origin?(@object['id']) || Tombstone.exists?(uri: @object['id']) || !related_to_local_activity?
|
||||
|
||||
RedisLock.acquire(lock_options) do |lock|
|
||||
if lock.acquired?
|
||||
return if delete_arrived_first?(object_uri) || poll_vote?
|
||||
return if delete_arrived_first?(object_uri) || poll_vote? # rubocop:disable Lint/NonLocalExitFromIterator
|
||||
|
||||
@status = find_existing_status
|
||||
|
||||
@ -23,8 +64,6 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||
@status
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def audience_to
|
||||
@object['to'] || @json['to']
|
||||
end
|
||||
@ -253,12 +292,13 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||
|
||||
begin
|
||||
href = Addressable::URI.parse(attachment['url']).normalize.to_s
|
||||
media_attachment = MediaAttachment.create(account: @account, remote_url: href, description: attachment['name'].presence, focus: attachment['focalPoint'], blurhash: supported_blurhash?(attachment['blurhash']) ? attachment['blurhash'] : nil)
|
||||
media_attachment = MediaAttachment.create(account: @account, remote_url: href, thumbnail_remote_url: icon_url_from_attachment(attachment), description: attachment['summary'].presence || attachment['name'].presence, focus: attachment['focalPoint'], blurhash: supported_blurhash?(attachment['blurhash']) ? attachment['blurhash'] : nil)
|
||||
media_attachments << media_attachment
|
||||
|
||||
next if unsupported_media_type?(attachment['mediaType']) || skip_download?
|
||||
|
||||
media_attachment.file_remote_url = href
|
||||
media_attachment.download_file!
|
||||
media_attachment.download_thumbnail!
|
||||
media_attachment.save
|
||||
rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError
|
||||
RedownloadMediaWorker.perform_in(rand(30..600).seconds, media_attachment.id)
|
||||
@ -271,6 +311,13 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||
media_attachments
|
||||
end
|
||||
|
||||
def icon_url_from_attachment(attachment)
|
||||
url = attachment['icon'].is_a?(Hash) ? attachment['icon']['url'] : attachment['icon']
|
||||
Addressable::URI.parse(url).normalize.to_s if url.present?
|
||||
rescue Addressable::URI::InvalidURIError
|
||||
nil
|
||||
end
|
||||
|
||||
def process_poll
|
||||
return unless @object['type'] == 'Question' && (@object['anyOf'].is_a?(Array) || @object['oneOf'].is_a?(Array))
|
||||
|
||||
@ -314,6 +361,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||
def poll_vote!
|
||||
poll = replied_to_status.preloadable_poll
|
||||
already_voted = true
|
||||
|
||||
RedisLock.acquire(poll_lock_options) do |lock|
|
||||
if lock.acquired?
|
||||
already_voted = poll.votes.where(account: @account).exists?
|
||||
@ -322,20 +370,24 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||
raise Mastodon::RaceConditionError
|
||||
end
|
||||
end
|
||||
|
||||
increment_voters_count! unless already_voted
|
||||
ActivityPub::DistributePollUpdateWorker.perform_in(3.minutes, replied_to_status.id) unless replied_to_status.preloadable_poll.hide_totals?
|
||||
end
|
||||
|
||||
def resolve_thread(status)
|
||||
return unless status.reply? && status.thread.nil? && Request.valid_url?(in_reply_to_uri)
|
||||
|
||||
ThreadResolveWorker.perform_async(status.id, in_reply_to_uri)
|
||||
end
|
||||
|
||||
def fetch_replies(status)
|
||||
collection = @object['replies']
|
||||
return if collection.nil?
|
||||
|
||||
replies = ActivityPub::FetchRepliesService.new.call(status, collection, false)
|
||||
return unless replies.nil?
|
||||
|
||||
uri = value_or_id(collection)
|
||||
ActivityPub::FetchRepliesWorker.perform_async(status.id, uri) unless uri.nil?
|
||||
end
|
||||
@ -343,6 +395,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||
def conversation_from_uri(uri)
|
||||
return nil if uri.nil?
|
||||
return Conversation.find_by(id: OStatus::TagManager.instance.unique_tag_to_local_id(uri, 'Conversation')) if OStatus::TagManager.instance.local_id?(uri)
|
||||
|
||||
begin
|
||||
Conversation.find_or_create_by!(uri: uri)
|
||||
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
|
||||
@ -458,6 +511,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||
|
||||
def skip_download?
|
||||
return @skip_download if defined?(@skip_download)
|
||||
|
||||
@skip_download ||= DomainBlock.reject_media?(@account.domain)
|
||||
end
|
||||
|
||||
@ -490,11 +544,13 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||
|
||||
def forward_for_reply
|
||||
return unless @json['signature'].present? && reply_to_local?
|
||||
|
||||
ActivityPub::RawDistributionWorker.perform_async(Oj.dump(@json), replied_to_status.account_id, [@account.preferred_inbox_url])
|
||||
end
|
||||
|
||||
def increment_voters_count!
|
||||
poll = replied_to_status.preloadable_poll
|
||||
|
||||
unless poll.voters_count.nil?
|
||||
poll.voters_count = poll.voters_count + 1
|
||||
poll.save
|
||||
|
@ -33,7 +33,7 @@ class ActivityPub::Activity::Move < ActivityPub::Activity
|
||||
end
|
||||
|
||||
def processed?
|
||||
redis.exists("move_in_progress:#{@account.id}")
|
||||
redis.exists?("move_in_progress:#{@account.id}")
|
||||
end
|
||||
|
||||
def mark_as_processing!
|
||||
|
@ -13,11 +13,62 @@ class ActivityPub::Activity::Undo < ActivityPub::Activity
|
||||
undo_like
|
||||
when 'Block'
|
||||
undo_block
|
||||
when nil
|
||||
handle_reference
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_reference
|
||||
# Some implementations do not inline the object, and as we don't have a
|
||||
# global index, we have to guess what object it is.
|
||||
return if object_uri.nil?
|
||||
|
||||
try_undo_announce || try_undo_accept || try_undo_follow || try_undo_like || try_undo_block || delete_later!(object_uri)
|
||||
end
|
||||
|
||||
def try_undo_announce
|
||||
status = Status.where.not(reblog_of_id: nil).find_by(uri: object_uri, account: @account)
|
||||
if status.present?
|
||||
RemoveStatusService.new.call(status)
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def try_undo_accept
|
||||
# We can't currently handle `Undo Accept` as we don't record `Accept`'s uri
|
||||
false
|
||||
end
|
||||
|
||||
def try_undo_follow
|
||||
follow = @account.follow_requests.find_by(uri: object_uri) || @account.active_relationships.find_by(uri: object_uri)
|
||||
|
||||
if follow.present?
|
||||
follow.destroy
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def try_undo_like
|
||||
# There is an index on accounts, but an account may have *many* favs, so this may be too costly
|
||||
false
|
||||
end
|
||||
|
||||
def try_undo_block
|
||||
block = @account.block_relationships.find_by(uri: object_uri)
|
||||
if block.present?
|
||||
UnblockService.new.call(@account, block.target_account)
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def undo_announce
|
||||
return if object_uri.nil?
|
||||
|
||||
|
@ -4,6 +4,8 @@ class ActivityPub::Activity::Update < ActivityPub::Activity
|
||||
SUPPORTED_TYPES = %w(Application Group Organization Person Service).freeze
|
||||
|
||||
def perform
|
||||
dereference_object!
|
||||
|
||||
if equals_or_includes_any?(@object['type'], SUPPORTED_TYPES)
|
||||
update_account
|
||||
elsif equals_or_includes_any?(@object['type'], %w(Question))
|
||||
|
@ -22,6 +22,7 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
|
||||
blurhash: { 'toot' => 'http://joinmastodon.org/ns#', 'blurhash' => 'toot:blurhash' },
|
||||
discoverable: { 'toot' => 'http://joinmastodon.org/ns#', 'discoverable' => 'toot:discoverable' },
|
||||
voters_count: { 'toot' => 'http://joinmastodon.org/ns#', 'votersCount' => 'toot:votersCount' },
|
||||
olm: { 'toot' => 'http://joinmastodon.org/ns#', 'Device' => 'toot:Device', 'Ed25519Signature' => 'toot:Ed25519Signature', 'Ed25519Key' => 'toot:Ed25519Key', 'Curve25519Key' => 'toot:Curve25519Key', 'EncryptedMessage' => 'toot:EncryptedMessage', 'publicKeyBase64' => 'toot:publicKeyBase64', 'deviceId' => 'toot:deviceId', 'claim' => { '@type' => '@id', '@id' => 'toot:claim' }, 'fingerprintKey' => { '@type' => '@id', '@id' => 'toot:fingerprintKey' }, 'identityKey' => { '@type' => '@id', '@id' => 'toot:identityKey' }, 'devices' => { '@type' => '@id', '@id' => 'toot:devices' }, 'messageFranking' => 'toot:messageFranking', 'messageType' => 'toot:messageType', 'cipherText' => 'toot:cipherText' },
|
||||
}.freeze
|
||||
|
||||
def self.default_key_transform
|
||||
|
@ -72,16 +72,16 @@ class ActivityPub::TagManager
|
||||
account_ids = status.active_mentions.pluck(:account_id)
|
||||
to = status.account.followers.where(id: account_ids).each_with_object([]) do |account, result|
|
||||
result << uri_for(account)
|
||||
result << account.followers_url if account.group?
|
||||
result << account_followers_url(account) if account.group?
|
||||
end
|
||||
to.concat(FollowRequest.where(target_account_id: status.account_id, account_id: account_ids).each_with_object([]) do |request, result|
|
||||
result << uri_for(request.account)
|
||||
result << request.account.followers_url if request.account.group?
|
||||
result << account_followers_url(request.account) if request.account.group?
|
||||
end)
|
||||
else
|
||||
status.active_mentions.each_with_object([]) do |mention, result|
|
||||
result << uri_for(mention.account)
|
||||
result << mention.account.followers_url if mention.account.group?
|
||||
result << account_followers_url(mention.account) if mention.account.group?
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -110,16 +110,16 @@ class ActivityPub::TagManager
|
||||
account_ids = status.active_mentions.pluck(:account_id)
|
||||
cc.concat(status.account.followers.where(id: account_ids).each_with_object([]) do |account, result|
|
||||
result << uri_for(account)
|
||||
result << account.followers_url if account.group?
|
||||
result << account_followers_url(account) if account.group?
|
||||
end)
|
||||
cc.concat(FollowRequest.where(target_account_id: status.account_id, account_id: account_ids).each_with_object([]) do |request, result|
|
||||
result << uri_for(request.account)
|
||||
result << request.account.followers_url if request.account.group?
|
||||
result << account_followers_url(request.account) if request.account.group?
|
||||
end)
|
||||
else
|
||||
cc.concat(status.active_mentions.each_with_object([]) do |mention, result|
|
||||
result << uri_for(mention.account)
|
||||
result << mention.account.followers_url if mention.account.group?
|
||||
result << account_followers_url(mention.account) if mention.account.group?
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
Reference in New Issue
Block a user