Merge tag 'v3.1.4' into hometown-dev
This commit is contained in:
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
64
app/lib/rate_limiter.rb
Normal 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
|
||||
@ -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
38
app/lib/rss/serializer.rb
Normal 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
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user