discourse/lib/oneboxer.rb

177 lines
4.6 KiB
Ruby

require_dependency "onebox/discourse_onebox_sanitize_config"
require_dependency 'final_destination'
Dir["#{Rails.root}/lib/onebox/engine/*_onebox.rb"].sort.each { |f| require f }
module Oneboxer
# keep reloaders happy
unless defined? Oneboxer::Result
Result = Struct.new(:doc, :changed) do
def to_html
doc.to_html
end
def changed?
changed
end
end
end
def self.ignore_redirects
@ignore_redirects ||= ['http://www.dropbox.com', 'http://store.steampowered.com', Discourse.base_url]
end
def self.force_get_hosts
@force_get_hosts ||= ['http://us.battle.net']
end
def self.preview(url, options = nil)
options ||= {}
invalidate(url) if options[:invalidate_oneboxes]
onebox_raw(url)[:preview]
end
def self.onebox(url, options = nil)
options ||= {}
invalidate(url) if options[:invalidate_oneboxes]
onebox_raw(url)[:onebox]
end
def self.cached_onebox(url)
if c = Rails.cache.read(onebox_cache_key(url))
c[:onebox]
end
rescue => e
invalidate(url)
Rails.logger.warn("invalid cached onebox for #{url} #{e}")
""
end
def self.cached_preview(url)
if c = Rails.cache.read(onebox_cache_key(url))
c[:preview]
end
rescue => e
invalidate(url)
Rails.logger.warn("invalid cached preview for #{url} #{e}")
""
end
def self.invalidate(url)
Rails.cache.delete(onebox_cache_key(url))
end
# Parse URLs out of HTML, returning the document when finished.
def self.each_onebox_link(string_or_doc)
doc = string_or_doc
doc = Nokogiri::HTML::fragment(doc) if doc.is_a?(String)
onebox_links = doc.search("a.onebox")
if onebox_links.present?
onebox_links.each do |link|
yield(link['href'], link) if link['href'].present?
end
end
doc
end
def self.append_source_topic_id(url, topic_id)
# hack urls to create proper expansions
if url =~ Regexp.new("^#{Discourse.base_url.gsub(".", "\\.")}.*$", true)
uri = URI.parse(url) rescue nil
if uri && uri.path
route = Rails.application.routes.recognize_path(uri.path) rescue nil
if route && route[:controller] == 'topics'
url += (url =~ /\?/ ? "&" : "?") + "source_topic_id=#{topic_id}"
end
end
end
url
end
def self.apply(string_or_doc, args = nil)
doc = string_or_doc
doc = Nokogiri::HTML::fragment(doc) if doc.is_a?(String)
changed = false
each_onebox_link(doc) do |url, element|
if args && args[:topic_id]
url = append_source_topic_id(url, args[:topic_id])
end
onebox, _preview = yield(url, element)
if onebox
parsed_onebox = Nokogiri::HTML::fragment(onebox)
next unless parsed_onebox.children.count > 0
# special logic to strip empty p elements
if element.parent &&
element.parent.node_name &&
element.parent.node_name.downcase == "p" &&
element.parent.children.count == 1
element = element.parent
end
changed = true
element.swap parsed_onebox.to_html
end
end
Result.new(doc, changed)
end
def self.is_previewing?(user_id)
$redis.get(preview_key(user_id)) == "1"
end
def self.preview_onebox!(user_id)
$redis.setex(preview_key(user_id), 1.minute, "1")
end
def self.onebox_previewed!(user_id)
$redis.del(preview_key(user_id))
end
def self.engine(url)
Onebox::Matcher.new(url).oneboxed
end
private
def self.preview_key(user_id)
"onebox:preview:#{user_id}"
end
def self.blank_onebox
{ preview: "", onebox: "" }
end
def self.onebox_cache_key(url)
"onebox__#{url}"
end
def self.onebox_raw(url)
Rails.cache.fetch(onebox_cache_key(url), expires_in: 1.day) do
fd = FinalDestination.new(url, ignore_redirects: ignore_redirects, force_get_hosts: force_get_hosts)
uri = fd.resolve
return blank_onebox if uri.blank? || SiteSetting.onebox_domains_blacklist.include?(uri.hostname)
options = {
cache: {},
max_width: 695,
sanitize_config: Sanitize::Config::DISCOURSE_ONEBOX
}
options[:cookie] = fd.cookie if fd.cookie
r = Onebox.preview(uri.to_s, options)
{ onebox: r.to_s, preview: r.try(:placeholder_html).to_s }
end
rescue => e
# no point warning here, just cause we have an issue oneboxing a url
# we can later hunt for failed oneboxes by searching logs if needed
Rails.logger.info("Failed to onebox #{url} #{e} #{e.backtrace}")
# return a blank hash, so rest of the code works
blank_onebox
end
end