Merge tag 'v3.0.0' into hometown-dev
This commit is contained in:
@ -5,7 +5,7 @@ class ActivityPub::Activity
|
||||
include Redisable
|
||||
|
||||
SUPPORTED_TYPES = %w(Note Question Article).freeze
|
||||
CONVERTED_TYPES = %w(Image Video Page).freeze
|
||||
CONVERTED_TYPES = %w(Image Audio Video Page).freeze
|
||||
|
||||
def initialize(json, account, **options)
|
||||
@json = json
|
||||
|
@ -40,7 +40,7 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
|
||||
end
|
||||
|
||||
def announceable?(status)
|
||||
status.account_id == @account.id || status.public_visibility? || status.unlisted_visibility?
|
||||
status.account_id == @account.id || status.distributable?
|
||||
end
|
||||
|
||||
def related_to_local_activity?
|
||||
|
@ -42,8 +42,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||
|
||||
resolve_thread(@status)
|
||||
fetch_replies(@status)
|
||||
check_for_spam
|
||||
distribute(@status)
|
||||
forward_for_reply if @status.public_visibility? || @status.unlisted_visibility?
|
||||
forward_for_reply if @status.distributable?
|
||||
end
|
||||
|
||||
def find_existing_status
|
||||
@ -199,12 +200,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||
def process_hashtag(tag)
|
||||
return if tag['name'].blank?
|
||||
|
||||
hashtag = tag['name'].gsub(/\A#/, '').mb_chars.downcase
|
||||
hashtag = Tag.where(name: hashtag).first_or_create!(name: hashtag)
|
||||
|
||||
return if @tags.include?(hashtag)
|
||||
|
||||
@tags << hashtag
|
||||
Tag.find_or_create_by_names(tag['name']) do |hashtag|
|
||||
@tags << hashtag unless @tags.include?(hashtag)
|
||||
end
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
nil
|
||||
end
|
||||
@ -243,22 +241,25 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||
media_attachments = []
|
||||
|
||||
as_array(@object['attachment']).each do |attachment|
|
||||
next if attachment['url'].blank?
|
||||
next if attachment['url'].blank? || media_attachments.size >= 4
|
||||
|
||||
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_attachments << media_attachment
|
||||
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_attachments << media_attachment
|
||||
|
||||
next if unsupported_media_type?(attachment['mediaType']) || skip_download?
|
||||
next if unsupported_media_type?(attachment['mediaType']) || skip_download?
|
||||
|
||||
media_attachment.file_remote_url = href
|
||||
media_attachment.save
|
||||
media_attachment.file_remote_url = href
|
||||
media_attachment.save
|
||||
rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError
|
||||
RedownloadMediaWorker.perform_in(rand(30..600).seconds, media_attachment.id)
|
||||
end
|
||||
end
|
||||
|
||||
media_attachments
|
||||
rescue Addressable::URI::InvalidURIError => e
|
||||
Rails.logger.debug e
|
||||
|
||||
Rails.logger.debug "Invalid URL in attachment: #{e}"
|
||||
media_attachments
|
||||
end
|
||||
|
||||
@ -283,25 +284,40 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||
items = @object['oneOf']
|
||||
end
|
||||
|
||||
voters_count = @object['votersCount']
|
||||
|
||||
@account.polls.new(
|
||||
multiple: multiple,
|
||||
expires_at: expires_at,
|
||||
options: items.map { |item| item['name'].presence || item['content'] }.compact,
|
||||
cached_tallies: items.map { |item| item.dig('replies', 'totalItems') || 0 }
|
||||
cached_tallies: items.map { |item| item.dig('replies', 'totalItems') || 0 },
|
||||
voters_count: voters_count
|
||||
)
|
||||
end
|
||||
|
||||
def poll_vote?
|
||||
return false if replied_to_status.nil? || replied_to_status.preloadable_poll.nil? || !replied_to_status.local? || !replied_to_status.preloadable_poll.options.include?(@object['name'])
|
||||
|
||||
unless replied_to_status.preloadable_poll.expired?
|
||||
replied_to_status.preloadable_poll.votes.create!(account: @account, choice: replied_to_status.preloadable_poll.options.index(@object['name']), uri: @object['id'])
|
||||
ActivityPub::DistributePollUpdateWorker.perform_in(3.minutes, replied_to_status.id) unless replied_to_status.preloadable_poll.hide_totals?
|
||||
end
|
||||
poll_vote! unless replied_to_status.preloadable_poll.expired?
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
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?
|
||||
poll.votes.create!(account: @account, choice: poll.options.index(@object['name']), uri: @object['id'])
|
||||
else
|
||||
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)
|
||||
@ -460,12 +476,31 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||
Account.local.where(username: local_usernames).exists?
|
||||
end
|
||||
|
||||
def check_for_spam
|
||||
SpamCheck.perform(@status)
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
rescue ActiveRecord::StaleObjectError
|
||||
poll.reload
|
||||
retry
|
||||
end
|
||||
|
||||
def lock_options
|
||||
{ redis: Redis.current, key: "create:#{@object['id']}" }
|
||||
end
|
||||
|
||||
def poll_lock_options
|
||||
{ redis: Redis.current, key: "vote:#{replied_to_status.poll_id}:#{@account.id}" }
|
||||
end
|
||||
end
|
||||
|
@ -13,8 +13,7 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
|
||||
|
||||
def delete_person
|
||||
lock_or_return("delete_in_progress:#{@account.id}") do
|
||||
SuspendAccountService.new.call(@account)
|
||||
@account.destroy!
|
||||
SuspendAccountService.new.call(@account, reserve_username: false)
|
||||
end
|
||||
end
|
||||
|
||||
@ -31,7 +30,7 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
|
||||
|
||||
return if @status.nil?
|
||||
|
||||
if @status.public_visibility? || @status.unlisted_visibility?
|
||||
if @status.distributable?
|
||||
forward_for_reply
|
||||
forward_for_reblogs
|
||||
end
|
||||
@ -70,7 +69,7 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
|
||||
end
|
||||
|
||||
def delete_now!
|
||||
RemoveStatusService.new.call(@status)
|
||||
RemoveStatusService.new.call(@status, redraft: false)
|
||||
end
|
||||
|
||||
def payload
|
||||
|
@ -8,7 +8,7 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
|
||||
|
||||
return if target_account.nil? || !target_account.local? || delete_arrived_first?(@json['id']) || @account.requested?(target_account)
|
||||
|
||||
if target_account.blocking?(@account) || target_account.domain_blocking?(@account.domain) || target_account.moved?
|
||||
if target_account.blocking?(@account) || target_account.domain_blocking?(@account.domain) || target_account.moved? || target_account.instance_actor?
|
||||
reject_follow_request!(target_account)
|
||||
return
|
||||
end
|
||||
@ -21,7 +21,7 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
|
||||
|
||||
follow_request = FollowRequest.create!(account: @account, target_account: target_account, uri: @json['id'])
|
||||
|
||||
if target_account.locked?
|
||||
if target_account.locked? || @account.silenced?
|
||||
NotifyService.new.call(target_account, follow_request)
|
||||
else
|
||||
AuthorizeFollowService.new.call(@account, target_account)
|
||||
|
@ -10,17 +10,16 @@ class ActivityPub::Activity::Move < ActivityPub::Activity
|
||||
|
||||
target_account = ActivityPub::FetchRemoteAccountService.new.call(target_uri)
|
||||
|
||||
return if target_account.nil? || !target_account.also_known_as.include?(origin_account.uri)
|
||||
if target_account.nil? || target_account.suspended? || !target_account.also_known_as.include?(origin_account.uri)
|
||||
unmark_as_processing!
|
||||
return
|
||||
end
|
||||
|
||||
# In case for some reason we didn't have a redirect for the profile already, set it
|
||||
origin_account.update(moved_to_account: target_account) if origin_account.moved_to_account_id.nil?
|
||||
origin_account.update(moved_to_account: target_account)
|
||||
|
||||
# Initiate a re-follow for each follower
|
||||
origin_account.followers.local.select(:id).find_in_batches do |follower_accounts|
|
||||
UnfollowFollowWorker.push_bulk(follower_accounts.map(&:id)) do |follower_account_id|
|
||||
[follower_account_id, origin_account.id, target_account.id]
|
||||
end
|
||||
end
|
||||
MoveWorker.perform_async(origin_account.id, target_account.id)
|
||||
end
|
||||
|
||||
private
|
||||
@ -40,4 +39,8 @@ class ActivityPub::Activity::Move < ActivityPub::Activity
|
||||
def mark_as_processing!
|
||||
redis.setex("move_in_progress:#{@account.id}", PROCESSING_COOLDOWN, true)
|
||||
end
|
||||
|
||||
def unmark_as_processing!
|
||||
redis.del("move_in_progress:#{@account.id}")
|
||||
end
|
||||
end
|
||||
|
@ -20,6 +20,8 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
|
||||
focal_point: { 'toot' => 'http://joinmastodon.org/ns#', 'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' } },
|
||||
identity_proof: { 'toot' => 'http://joinmastodon.org/ns#', 'IdentityProof' => 'toot:IdentityProof' },
|
||||
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' },
|
||||
}.freeze
|
||||
|
||||
def self.default_key_transform
|
||||
@ -31,21 +33,23 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
|
||||
end
|
||||
|
||||
def serializable_hash(options = nil)
|
||||
named_contexts = {}
|
||||
context_extensions = {}
|
||||
options = serialization_options(options)
|
||||
serialized_hash = serializer.serializable_hash(options)
|
||||
serialized_hash = serializer.serializable_hash(options.merge(named_contexts: named_contexts, context_extensions: context_extensions))
|
||||
serialized_hash = serialized_hash.select { |k, _| options[:fields].include?(k) } if options[:fields]
|
||||
serialized_hash = self.class.transform_key_casing!(serialized_hash, instance_options)
|
||||
|
||||
{ '@context' => serialized_context }.merge(serialized_hash)
|
||||
{ '@context' => serialized_context(named_contexts, context_extensions) }.merge(serialized_hash)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def serialized_context
|
||||
def serialized_context(named_contexts_map, context_extensions_map)
|
||||
context_array = []
|
||||
|
||||
serializer_options = serializer.send(:instance_options) || {}
|
||||
named_contexts = [:activitystreams] + serializer._named_contexts.keys + serializer_options.fetch(:named_contexts, {}).keys
|
||||
context_extensions = serializer._context_extensions.keys + serializer_options.fetch(:context_extensions, {}).keys
|
||||
named_contexts = [:activitystreams] + named_contexts_map.keys
|
||||
context_extensions = context_extensions_map.keys
|
||||
|
||||
named_contexts.each do |key|
|
||||
context_array << NAMED_CONTEXT_MAP[key]
|
||||
|
@ -27,4 +27,12 @@ class ActivityPub::Serializer < ActiveModel::Serializer
|
||||
_context_extensions[extension_name] = true
|
||||
end
|
||||
end
|
||||
|
||||
def serializable_hash(adapter_options = nil, options = {}, adapter_instance = self.class.serialization_adapter_instance)
|
||||
unless adapter_options&.fetch(:named_contexts, nil).nil?
|
||||
adapter_options[:named_contexts].merge!(_named_contexts)
|
||||
adapter_options[:context_extensions].merge!(_context_extensions)
|
||||
end
|
||||
super(adapter_options, options, adapter_instance)
|
||||
end
|
||||
end
|
||||
|
@ -17,7 +17,7 @@ class ActivityPub::TagManager
|
||||
|
||||
case target.object_type
|
||||
when :person
|
||||
short_account_url(target)
|
||||
target.instance_actor? ? about_more_url(instance_actor: true) : short_account_url(target)
|
||||
when :note, :comment, :activity
|
||||
return activity_account_status_url(target.account, target) if target.reblog?
|
||||
short_account_status_url(target.account, target)
|
||||
@ -29,7 +29,7 @@ class ActivityPub::TagManager
|
||||
|
||||
case target.object_type
|
||||
when :person
|
||||
account_url(target)
|
||||
target.instance_actor? ? instance_actor_url : account_url(target)
|
||||
when :note, :comment, :activity
|
||||
return activity_account_status_url(target.account, target) if target.reblog?
|
||||
account_status_url(target.account, target)
|
||||
@ -51,7 +51,7 @@ class ActivityPub::TagManager
|
||||
def replies_uri_for(target, page_params = nil)
|
||||
raise ArgumentError, 'target must be a local activity' unless %i(note comment activity).include?(target.object_type) && target.local?
|
||||
|
||||
replies_account_status_url(target.account, target, page_params)
|
||||
account_status_replies_url(target.account, target, page_params)
|
||||
end
|
||||
|
||||
# Primary audience of a status
|
||||
@ -119,6 +119,7 @@ class ActivityPub::TagManager
|
||||
|
||||
def uri_to_local_id(uri, param = :id)
|
||||
path_params = Rails.application.routes.recognize_path(uri)
|
||||
path_params[:username] = Rails.configuration.x.local_domain if path_params[:controller] == 'instance_actors'
|
||||
path_params[param]
|
||||
end
|
||||
|
||||
|
Reference in New Issue
Block a user