Merge tag 'v2.7.2' into instance_only_statuses

This commit is contained in:
Renato "Lond" Cerqueira
2019-02-19 21:07:43 +01:00
76 changed files with 1440 additions and 577 deletions

View File

@ -4,6 +4,8 @@ class ActivityTracker
EXPIRE_AFTER = 90.days.seconds
class << self
include Redisable
def increment(prefix)
key = [prefix, current_week].join(':')
@ -20,10 +22,6 @@ class ActivityTracker
private
def redis
Redis.current
end
def current_week
Time.zone.today.cweek
end

View File

@ -2,6 +2,10 @@
class ActivityPub::Activity
include JsonLdHelper
include Redisable
SUPPORTED_TYPES = %w(Note).freeze
CONVERTED_TYPES = %w(Image Video Article Page).freeze
def initialize(json, account, **options)
@json = json
@ -70,8 +74,16 @@ class ActivityPub::Activity
@object_uri ||= value_or_id(@object)
end
def redis
Redis.current
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)
@ -123,6 +135,24 @@ class ActivityPub::Activity
redis.setex("delete_upon_arrival:#{@account.id}:#{uri}", 6.hours.seconds, uri)
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
fetch_remote_original_status
end
def fetch_remote_original_status
if object_uri.start_with?('http')
return if ActivityPub::TagManager.instance.local_uri?(object_uri)
@ -137,4 +167,21 @@ class ActivityPub::Activity
ensure
redis.del(key)
end
def fetch?
!@options[:delivery]
end
def followed_by_local_accounts?
@account.passive_relationships.exists?
end
def requested_through_relay?
@options[:relayed_through_account] && Relay.find_by(inbox_url: @options[:relayed_through_account].inbox_url)&.enabled?
end
def reject_payload!
Rails.logger.info("Rejected #{@json['type']} activity #{@json['id']} from #{@account.uri}#{@options[:relayed_through_account] && "via #{@options[:relayed_through_account].uri}"}")
nil
end
end

View File

@ -2,10 +2,11 @@
class ActivityPub::Activity::Announce < ActivityPub::Activity
def perform
original_status = status_from_uri(object_uri)
original_status ||= fetch_remote_original_status
return reject_payload! if delete_arrived_first?(@json['id']) || !related_to_local_activity?
return if original_status.nil? || delete_arrived_first?(@json['id']) || !announceable?(original_status)
original_status = status_from_object
return reject_payload! if original_status.nil? || !announceable?(original_status)
status = Status.find_by(account: @account, reblog: original_status)
@ -41,4 +42,12 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
def announceable?(status)
status.account_id == @account.id || status.public_visibility? || status.unlisted_visibility?
end
def related_to_local_activity?
followed_by_local_accounts? || requested_through_relay? || reblog_of_local_status?
end
def reblog_of_local_status?
status_from_uri(object_uri)&.account&.local?
end
end

View File

@ -1,12 +1,8 @@
# frozen_string_literal: true
class ActivityPub::Activity::Create < ActivityPub::Activity
SUPPORTED_TYPES = %w(Note).freeze
CONVERTED_TYPES = %w(Image Video Article Page).freeze
def perform
return if unsupported_object_type? || invalid_origin?(@object['id'])
return if Tombstone.exists?(uri: @object['id'])
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?
@ -318,22 +314,10 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
@object['nameMap'].is_a?(Hash) && !@object['nameMap'].empty?
end
def unsupported_object_type?
@object.is_a?(String) || !(supported_object_type? || converted_object_type?)
end
def unsupported_media_type?(mime_type)
mime_type.present? && !(MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES).include?(mime_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 skip_download?
return @skip_download if defined?(@skip_download)
@skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media?
@ -352,6 +336,25 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
!replied_to_status.nil? && replied_to_status.account.local?
end
def related_to_local_activity?
fetch? || followed_by_local_accounts? || requested_through_relay? ||
responds_to_followed_account? || addresses_local_accounts?
end
def responds_to_followed_account?
!replied_to_status.nil? && (replied_to_status.account.local? || replied_to_status.account.passive_relationships.exists?)
end
def addresses_local_accounts?
return true if @options[:delivered_to_account_id]
local_usernames = (as_array(@object['to']) + as_array(@object['cc'])).uniq.select { |uri| ActivityPub::TagManager.instance.local_uri?(uri) }.map { |uri| ActivityPub::TagManager.instance.uri_to_local_id(uri, :username) }
return false if local_usernames.empty?
Account.local.where(username: local_usernames).exists?
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])

View File

@ -4,6 +4,7 @@ require 'singleton'
class FeedManager
include Singleton
include Redisable
MAX_ITEMS = 400
@ -35,7 +36,7 @@ class FeedManager
def unpush_from_home(account, status)
return false unless remove_from_feed(:home, account.id, status)
Redis.current.publish("timeline:#{account.id}", Oj.dump(event: :delete, payload: status.id.to_s))
redis.publish("timeline:#{account.id}", Oj.dump(event: :delete, payload: status.id.to_s))
true
end
@ -53,7 +54,7 @@ class FeedManager
def unpush_from_list(list, status)
return false unless remove_from_feed(:list, list.id, status)
Redis.current.publish("timeline:list:#{list.id}", Oj.dump(event: :delete, payload: status.id.to_s))
redis.publish("timeline:list:#{list.id}", Oj.dump(event: :delete, payload: status.id.to_s))
true
end
@ -142,10 +143,6 @@ class FeedManager
private
def redis
Redis.current
end
def push_update_required?(timeline_id)
redis.exists("subscribed:#{timeline_id}")
end

View File

@ -99,7 +99,7 @@ class Formatter
end
def encode_and_link_urls(html, accounts = nil, options = {})
entities = Extractor.extract_entities_with_indices(html, extract_url_without_protocol: false)
entities = utf8_friendly_extractor(html, extract_url_without_protocol: false)
if accounts.is_a?(Hash)
options = accounts
@ -199,6 +199,53 @@ class Formatter
result.flatten.join
end
UNICODE_ESCAPE_BLACKLIST_RE = /\p{Z}|\p{P}/
def utf8_friendly_extractor(text, options = {})
old_to_new_index = [0]
escaped = text.chars.map do |c|
output = begin
if c.ord.to_s(16).length > 2 && UNICODE_ESCAPE_BLACKLIST_RE.match(c).nil?
CGI.escape(c)
else
c
end
end
old_to_new_index << old_to_new_index.last + output.length
output
end.join
# Note: I couldn't obtain list_slug with @user/list-name format
# for mention so this requires additional check
special = Extractor.extract_urls_with_indices(escaped, options).map do |extract|
# exactly one of :url, :hashtag, :screen_name, :cashtag keys is present
key = (extract.keys & [:url, :hashtag, :screen_name, :cashtag]).first
new_indices = [
old_to_new_index.find_index(extract[:indices].first),
old_to_new_index.find_index(extract[:indices].last),
]
has_prefix_char = [:hashtag, :screen_name, :cashtag].include?(key)
value_indices = [
new_indices.first + (has_prefix_char ? 1 : 0), # account for #, @ or $
new_indices.last - 1,
]
next extract.merge(
:indices => new_indices,
key => text[value_indices.first..value_indices.last]
)
end
standard = Extractor.extract_entities_with_indices(text, options)
Extractor.remove_overlapping_entities(special + standard)
end
def link_to_url(entity, options = {})
url = Addressable::URI.parse(entity[:url])
html_attrs = { target: '_blank', rel: 'nofollow noopener' }

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
class OStatus::Activity::Base
include Redisable
def initialize(xml, account = nil, **options)
@xml = xml
@account = account
@ -66,8 +68,4 @@ class OStatus::Activity::Base
Status.find_by(uri: uri)
end
end
def redis
Redis.current
end
end

View File

@ -11,6 +11,8 @@ class PotentialFriendshipTracker
}.freeze
class << self
include Redisable
def record(account_id, target_account_id, action)
return if account_id == target_account_id
@ -31,11 +33,5 @@ class PotentialFriendshipTracker
return [] if account_ids.empty?
Account.searchable.where(id: account_ids)
end
private
def redis
Redis.current
end
end
end