class Onebox::Engine::YoutubeOnebox

Public Instance Methods

parse_embed_response() click to toggle source
# File lib/onebox/engine/youtube_onebox.rb, line 16
def parse_embed_response
  return unless video_id
  return @parse_embed_response if defined?(@parse_embed_response)

  embed_url = "https://www.youtube.com/embed/#{video_id}"
  @embed_doc ||= Onebox::Helpers.fetch_html_doc(embed_url)

  begin
    script_tag = @embed_doc.xpath('//script').find { |tag| tag.to_s.include?('ytcfg.set') }.to_s
    match = script_tag.to_s.match(/ytcfg\.set\((?<json>.*)\)/)

    yt_json = ::JSON.parse(match[:json])
    renderer = ::JSON.parse(yt_json['PLAYER_VARS']['embedded_player_response'])['embedPreview']['thumbnailPreviewRenderer']

    title = renderer['title']['runs'].first['text']

    image = "https://img.youtube.com/vi/#{video_id}/hqdefault.jpg"
  rescue
    return
  end

  @parse_embed_response = { image: image, title: title }
end
placeholder_html() click to toggle source
# File lib/onebox/engine/youtube_onebox.rb, line 40
def placeholder_html
  if video_id || list_id
    result = parse_embed_response
    result ||= get_opengraph.data

    "<img src='#{result[:image]}' width='#{WIDTH}' height='#{HEIGHT}' title='#{result[:title]}'>"
  else
    to_html
  end
end
to_html() click to toggle source
# File lib/onebox/engine/youtube_onebox.rb, line 51
      def to_html
        if video_id
          <<-HTML
            <iframe
              src="https://www.youtube.com/embed/#{video_id}?#{embed_params}"
              width="#{WIDTH}"
              height="#{HEIGHT}"
              frameborder="0"
              allowfullscreen
            ></iframe>
          HTML
        elsif list_id
          <<-HTML
            <iframe
              src="https://www.youtube.com/embed/videoseries?list=#{list_id}&wmode=transparent&rel=0&autohide=1&showinfo=1&enablejsapi=1"
              width="#{WIDTH}"
              height="#{HEIGHT}"
              frameborder="0"
              allowfullscreen
            ></iframe>
          HTML
        else
          # for channel pages
          html = Onebox::Engine::AllowlistedGenericOnebox.new(@url, @timeout).to_html
          return if Onebox::Helpers.blank?(html)
          html.gsub!(/['"]\/\//, "https://")
          html
        end
      end
video_title() click to toggle source
# File lib/onebox/engine/youtube_onebox.rb, line 81
def video_title
  @video_title ||= begin
    result = parse_embed_response || get_opengraph.data
    result[:title]
  end
end

Private Instance Methods

embed_params() click to toggle source
# File lib/onebox/engine/youtube_onebox.rb, line 119
def embed_params
  p = { 'feature' => 'oembed', 'wmode' => 'opaque' }

  p['list'] = list_id if list_id

  # Parse timestrings, and assign the result as a start= parameter
  start = if params['start']
    params['start']
  elsif params['t']
    params['t']
  elsif uri.fragment && uri.fragment.start_with?('t=')
    # referencing uri is safe here because any throws were already caught by video_id returning nil
    # remove the t= from the start
    uri.fragment[2..-1]
  end

  p['start'] = parse_timestring(start)        if start
  p['end']   = parse_timestring params['end'] if params['end']

  # Official workaround for looping videos
  # https://developers.google.com/youtube/player_parameters#loop
  # use params.include? so that you can just add "&loop"
  if params.include?('loop')
    p['loop'] = 1
    p['playlist'] = video_id
  end

  # https://developers.google.com/youtube/player_parameters#rel
  p['rel'] = 0 if params.include?('rel')

  # https://developers.google.com/youtube/player_parameters#enablejsapi
  p['enablejsapi'] = params['enablejsapi'] if params.include?('enablejsapi')

  URI.encode_www_form(p)
end
list_id() click to toggle source
# File lib/onebox/engine/youtube_onebox.rb, line 111
def list_id
  @list_id ||= sanitize_yt_id(params['list'])
end
params() click to toggle source
# File lib/onebox/engine/youtube_onebox.rb, line 161
def params
  return {} unless uri.query
  # This mapping is necessary because CGI.parse returns a hash of keys to arrays.
  # And *that* is necessary because querystrings support arrays, so they
  # force you to deal with it to avoid security issues that would pop up
  # if one day it suddenly gave you an array.
  #
  # However, we aren't interested. Just take the first one.
  @params ||= begin
    p = {}
    CGI.parse(uri.query).each { |k, v| p[k] = v.first }
    p
  end
rescue
  {}
end
parse_timestring(string) click to toggle source
# File lib/onebox/engine/youtube_onebox.rb, line 155
def parse_timestring(string)
  if string =~ /(\d+h)?(\d+m)?(\d+s?)?/
    ($1.to_i * 3600) + ($2.to_i * 60) + $3.to_i
  end
end
sanitize_yt_id(raw) click to toggle source
# File lib/onebox/engine/youtube_onebox.rb, line 115
def sanitize_yt_id(raw)
  raw&.match?(/\A[\w-]+\z/) ? raw : nil
end
video_id() click to toggle source
# File lib/onebox/engine/youtube_onebox.rb, line 90
def video_id
  @video_id ||= begin
    id = nil

    # http://youtu.be/afyK1HSFfgw
    if uri.host["youtu.be"]
      id = uri.path[/\/([\w\-]+)/, 1]
    end

    # https://www.youtube.com/embed/vsF0K3Ou1v0
    if uri.path["/embed/"]
      id ||= uri.path[/\/embed\/([\w\-]+)/, 1]
    end

    # https://www.youtube.com/watch?v=Z0UISCEe52Y
    id ||= params['v']

    sanitize_yt_id(id)
  end
end