hummingbird-me/kitsu-server

View on GitHub
app/services/embed_service.rb

Summary

Maintainability
A
0 mins
Test Coverage
F
50%
# frozen_string_literal: true

class EmbedService
  # User-Agent to use for the HTTP request.  We list facebookexternalhit and Twitterbot as
  # compatible to ensure we get served a version with the correct meta tags
  USER_AGENT = <<-UA.squish.freeze
    kitsubot/1.0 (+kitsu.io)
    facebookexternalhit/1.1 (compatible)
    Twitterbot/1.0 (compatible)
  UA
  # How long to wait for a response from the remote server
  EMBED_TIMEOUT = 5.seconds
  # How long to cache the URL data
  EXPIRY = 12.hours
  # List of Embedders to try (matched from top to bottom)
  EMBEDDERS = [
    # Site-specific Solutions
    KitsuEmbedder,      # kitsu.io
    NicoVideoEmbedder,  # nicovideo.jp
    GiphyMediaEmbedder, # media.giphy.com
    # Embed Standards
    OpenGraphEmbedder,   # Facebook OpenGraph data (http://ogp.me/)
    OembedEmbedder,      # oEmbed Data (http://oembed.com/)
    TwitterCardEmbedder, # Twitter Card data (https://dev.twitter.com/cards)
    # Generic Fallback Embeds
    MetaContentEmbedder, # Shitty keyword-stuffed meta tags
    ImageEmbedder,       # Embed direct image links
    GeneralUrlEmbedder   # Just stuff the URL in there
  ].freeze

  # @param url [String] the URL to generate an embed for
  def initialize(url)
    @url = url
  end

  # @see #to_json for a cached string of the JSON
  # @return [#to_json] the object output of the embed
  def as_json(*args)
    Rails.cache.fetch(cache_id, expires_in: EXPIRY) do
      embedder.as_json(*args)
    end
  rescue StandardError => e
    Sentry.capture_exception(e)
    {}
  end

  # @return [String] the JSON string for the embedded URL, using cache
  def to_json(*_args)
    as_json.to_json
  end

  # @return [Boolean] whether or not we found an embedder for the URL
  def match?
    embedder.present?
  end

  # @param url [String] the URL to load
  # @return [String] the body of the link's target
  def self.get(url)
    req = HTTP.follow
              .timeout(EMBED_TIMEOUT.to_i)
              .headers('User-Agent' => USER_AGENT)
              .get(url)
    req.body.to_s if req.status.success?
  end
  delegate :get, to: :class

  # @return [String] a caching ID generated from the list of Embedders
  # @private
  def self.cache_id
    # Join all the embedders' cache IDs and digest it
    @cache_id ||= Digest::MD5.hexdigest(EMBEDDERS.map(&:cache_id).join(','))
  end

  private

  # @return [Embedder] the embedder to handle this URL
  def embedder
    @embedder ||= find_embedder
  end

  # @return [String] a caching ID for the URL
  def cache_id
    @cache_id ||= "embeds-#{self.class.cache_id}/#{url_digest}"
  end

  # @return [String] an MD5 digest of the URL to use as a cache key
  def url_digest
    Digest::MD5.hexdigest(@url)
  end

  # @return [Embedder,nil] an instance of the first Embedder which matches the URL
  def find_embedder
    EMBEDDERS.each do |embedder|
      instance = embedder.new(@url, body)
      return instance if instance.match?
    end
    nil
  end

  # @return [String,nil] the body of the URL we're trying to embed
  def body
    @body ||= get(@url)
  end
end