Files
danbooru/app/logical/iqdb_proxy.rb
evazion d946a84480 iqdb: download files inside danbooru, not inside iqdb.
Instead of sending IQDB the image url and letting it download the file,
download the file on Danbooru's end and send IQDB the downloaded file.

This fixes several issues:

* Some sites need the referer header set to avoid hotlink protection
  when downloading the file. Danbooru knows how to deal with this but
  IQDB doesn't.

* We need to enforce certain restrictions when downloading files,
  including setting max filesize limits, setting max timeouts, and not
  allowing downloads from forbidden IPs (to avoid SSRF attacks).
  Danbooru knows how to handle these things but IQDB doesn't.
2019-10-26 15:02:07 -05:00

64 lines
2.3 KiB
Ruby

class IqdbProxy
class Error < StandardError; end
def self.enabled?
Danbooru.config.iqdbs_server.present?
end
def self.download(url, type)
download = Downloads::File.new(url)
file, strategy = download.download!(url: download.send(type))
file
end
def self.search(params)
raise NotImplementedError, "the IQDBs service isn't configured" unless enabled?
limit = params[:limit]&.to_i&.clamp(1, 1000) || 20
similarity = params[:similarity]&.to_f&.clamp(0.0, 100.0) || 0.0
high_similarity = params[:high_similarity]&.to_f&.clamp(0.0, 100.0) || 65.0
if params[:file].present?
file = params[:file]
results = query(file: file, limit: limit)
elsif params[:url].present?
file = download(params[:url], :preview_url)
results = query(file: file, limit: limit)
elsif params[:image_url].present?
file = download(params[:image_url], :image_url)
results = query(file: file, limit: limit)
elsif params[:post_id].present?
url = Post.find(params[:post_id]).preview_file_url
results = query(url: url, limit: limit)
else
results = []
end
results = results.select { |result| result["score"] >= similarity }.take(limit)
matches = decorate_posts(results)
high_similarity_matches, low_similarity_matches = matches.partition { |match| match["score"] >= high_similarity }
[high_similarity_matches, low_similarity_matches, matches]
ensure
file.try(:close)
end
def self.query(params)
response = HTTParty.post("#{Danbooru.config.iqdbs_server}/similar", body: params, **Danbooru.config.httparty_options)
raise Error, "IQDB error: #{response.code} #{response.message}" unless response.success?
raise Error, "IQDB error: #{response.parsed_response["error"]}" if response.parsed_response.is_a?(Hash)
raise Error, "IQDB error: #{response.parsed_response.first}" if response.parsed_response.try(:first).is_a?(String)
response.parsed_response
end
def self.decorate_posts(json)
post_ids = json.map { |match| match["post_id"] }
posts = Post.where(id: post_ids).group_by(&:id).transform_values(&:first)
json.map do |match|
post = posts.fetch(match["post_id"], nil)
match.with_indifferent_access.merge(post: post) if post
end.compact
end
end