Merge tag 'v3.1.4' into hometown-dev

This commit is contained in:
Darius Kazemi
2020-05-15 15:34:04 -07:00
1182 changed files with 15087 additions and 6745 deletions

View File

@ -15,6 +15,8 @@ class ActivityPub::TagManager
def url_for(target)
return target.url if target.respond_to?(:local?) && !target.local?
return unless target.respond_to?(:object_type)
case target.object_type
when :person
target.instance_actor? ? about_more_url(instance_actor: true) : short_account_url(target)

View File

@ -3,47 +3,53 @@
class DeliveryFailureTracker
FAILURE_DAYS_THRESHOLD = 7
def initialize(inbox_url)
@inbox_url = inbox_url
def initialize(url_or_host)
@host = url_or_host.start_with?('https://') || url_or_host.start_with?('http://') ? Addressable::URI.parse(url_or_host).normalized_host : url_or_host
end
def track_failure!
Redis.current.sadd(exhausted_deliveries_key, today)
Redis.current.sadd('unavailable_inboxes', @inbox_url) if reached_failure_threshold?
UnavailableDomain.create(domain: @host) if reached_failure_threshold?
end
def track_success!
Redis.current.del(exhausted_deliveries_key)
Redis.current.srem('unavailable_inboxes', @inbox_url)
UnavailableDomain.find_by(domain: @host)&.destroy
end
def days
Redis.current.scard(exhausted_deliveries_key) || 0
end
class << self
def filter(arr)
arr.reject(&method(:unavailable?))
end
def available?
!UnavailableDomain.where(domain: @host).exists?
end
def unavailable?(url)
Redis.current.sismember('unavailable_inboxes', url)
alias reset! track_success!
class << self
def without_unavailable(urls)
unavailable_domains_map = Rails.cache.fetch('unavailable_domains') { UnavailableDomain.pluck(:domain).each_with_object({}) { |domain, hash| hash[domain] = true } }
urls.reject do |url|
host = Addressable::URI.parse(url).normalized_host
unavailable_domains_map[host]
end
end
def available?(url)
!unavailable?(url)
new(url).available?
end
def track_inverse_success!(from_account)
new(from_account.inbox_url).track_success! if from_account.inbox_url.present?
new(from_account.shared_inbox_url).track_success! if from_account.shared_inbox_url.present?
def reset!(url)
new(url).reset!
end
end
private
def exhausted_deliveries_key
"exhausted_deliveries:#{@inbox_url}"
"exhausted_deliveries:#{@host}"
end
def today

View File

@ -7,6 +7,10 @@ class EntityCache
MAX_EXPIRATION = 7.days.freeze
def status(url)
Rails.cache.fetch(to_key(:status, url), expires_in: MAX_EXPIRATION) { FetchRemoteStatusService.new.call(url) }
end
def mention(username, domain)
Rails.cache.fetch(to_key(:mention, username, domain), expires_in: MAX_EXPIRATION) { Account.select(:id, :username, :domain, :url).find_remote(username, domain) }
end

View File

@ -8,6 +8,7 @@ module Mastodon
class LengthValidationError < ValidationError; end
class DimensionsValidationError < ValidationError; end
class RaceConditionError < Error; end
class RateLimitExceededError < Error; end
class UnexpectedResponseError < Error
def initialize(response = nil)

View File

@ -52,8 +52,10 @@ class LanguageDetector
def detect_language_code(text)
return if unreliable_input?(text)
result = @identifier.find_language(text)
iso6391(result.language.to_s).to_sym if result.reliable?
iso6391(result.language.to_s).to_sym if result&.reliable?
end
def iso6391(bcp47)

View File

@ -22,7 +22,12 @@ class ProofProvider::Keybase::ConfigSerializer < ActiveModel::Serializer
end
def logo
{ svg_black: full_asset_url(asset_pack_path('media/images/logo_transparent_black.svg')), svg_full: full_asset_url(asset_pack_path('media/images/logo.svg')) }
{
svg_black: full_asset_url(asset_pack_path('media/images/logo_transparent_black.svg')),
svg_white: full_asset_url(asset_pack_path('media/images/logo_transparent_white.svg')),
svg_full: full_asset_url(asset_pack_path('media/images/logo.svg')),
svg_full_darkmode: full_asset_url(asset_pack_path('media/images/logo.svg')),
}
end
def brand_color

64
app/lib/rate_limiter.rb Normal file
View File

@ -0,0 +1,64 @@
# frozen_string_literal: true
class RateLimiter
include Redisable
FAMILIES = {
follows: {
limit: 400,
period: 24.hours.freeze,
}.freeze,
statuses: {
limit: 300,
period: 3.hours.freeze,
}.freeze,
reports: {
limit: 400,
period: 24.hours.freeze,
}.freeze,
}.freeze
def initialize(by, options = {})
@by = by
@family = options[:family]
@limit = FAMILIES[@family][:limit]
@period = FAMILIES[@family][:period].to_i
end
def record!
count = redis.get(key)
if count.nil?
redis.set(key, 0)
redis.expire(key, (@period - (last_epoch_time % @period) + 1).to_i)
end
raise Mastodon::RateLimitExceededError if count.present? && count.to_i >= @limit
redis.incr(key)
end
def rollback!
redis.decr(key)
end
def to_headers(now = Time.now.utc)
{
'X-RateLimit-Limit' => @limit.to_s,
'X-RateLimit-Remaining' => (@limit - (redis.get(key) || 0).to_i).to_s,
'X-RateLimit-Reset' => (now + (@period - now.to_i % @period)).iso8601(6),
}
end
private
def key
@key ||= "rate_limit:#{@by.id}:#{@family}:#{(last_epoch_time / @period).to_i}"
end
def last_epoch_time
@last_epoch_time ||= Time.now.to_i
end
end

View File

@ -73,8 +73,6 @@ class Request
response.body_with_limit if http_client.persistent?
yield response if block_given?
rescue => e
raise e.class, e.message, e.backtrace[0]
ensure
http_client.close unless http_client.persistent?
end

38
app/lib/rss/serializer.rb Normal file
View File

@ -0,0 +1,38 @@
# frozen_string_literal: true
class RSS::Serializer
private
def render_statuses(builder, statuses)
statuses.each do |status|
builder.item do |item|
item.title(status_title(status))
.link(ActivityPub::TagManager.instance.url_for(status))
.pub_date(status.created_at)
.description(status.spoiler_text.presence || Formatter.instance.format(status, inline_poll_options: true).to_str)
status.media_attachments.each do |media|
item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size)
end
end
end
end
def status_title(status)
return "#{status.account.acct} deleted status" if status.destroyed?
preview = status.proper.spoiler_text.presence || status.proper.text
if preview.length > 30 || preview[0, 30].include?("\n")
preview = preview[0, 30]
preview = preview[0, preview.index("\n").presence || 30] + '…'
end
preview = "#{status.proper.spoiler_text.present? ? 'CW ' : ''}#{preview}#{status.proper.sensitive? ? ' (sensitive)' : ''}"
if status.reblog?
"#{status.account.acct} boosted #{status.reblog.account.acct}: #{preview}"
else
"#{status.account.acct}: #{preview}"
end
end
end

View File

@ -1,13 +1,24 @@
# frozen_string_literal: true
class SidekiqErrorHandler
BACKTRACE_LIMIT = 3
def call(*)
yield
rescue Mastodon::HostValidationError
# Do not retry
rescue => e
limit_backtrace_and_raise(e)
ensure
socket = Thread.current[:statsd_socket]
socket&.close
Thread.current[:statsd_socket] = nil
end
private
def limit_backtrace_and_raise(e)
e.set_backtrace(e.backtrace.first(BACKTRACE_LIMIT))
raise e
end
end