# frozen_string_literal: true require 'tempfile' require 'fancy_searchable/parsing/search_parsing_error' require 'booru/image_processing' class SearchController < ApplicationController include ImageUpload include SortParams before_action :setup_pagination_and_tags skip_authorization_check def index set_image_filter @search_query = params[:q] unless @search_query.present? @title = 'Search' respond_to do |format| format.html { render } format.json { head :ok } end return end # JSON API kludge @include_deleted = params.key?(:deleted) if params[:format] == 'json' @title = "Searching for #{@search_query}" if @include_deleted == :only @title += ' (Deleted/Merged Only)' elsif @include_deleted @title += ' (Including Deleted/Merged)' end begin loader = PostLoader.new(default_image_filter_options.merge(sorts: parse_sort)) @search = loader.search(@search_query) rescue FancySearchable::Parsing::SearchParsingError => ex @search_failed = ex respond_to do |format| format.html { render } format.json { render json: {}, status: :bad_request } end else # Rely on ES to filter out deleted images. begin if params[:random_image] && (random_image_id = find_random_image_id) return respond_to do |format| format.html { redirect_to post_path(random_image_id, scope_key) } format.json { render json: { id: random_image_id } } end else @posts = @search.records(includes: [:tags, :user]) @image_ids = @search.results.map { |h| h[:_id].to_i } # A small hack for detecting single tag searches and candidates for unspoilering. search_body = @search.search.definition[:body][:query][:bool][:filter] # If wildcards are present, detect the single tags (so they don't get spoilered) search_body += @search.search.definition[:body][:query][:bool][:must] if @search.search.definition[:body][:query][:bool][:must].is_a?(Array) setup_ignoredtaglist search_body @interactions = PostQuery.interactions(@image_ids.compact.uniq, current_user.id) if current_user end rescue Elasticsearch::Transport::Transport::Errors::BadRequest => e puts e @posts = nil @search_failed = "Processing error. Please report this query if this error persists." end respond_to do |format| format.html { render } format.json { render json: { search: @posts.map(&:as_json), total: @search.total_count, interactions: (@interactions || []) } } end end end def dump authorize! :manage, Post @search_query = params[:q] unless @search_query.present? return render plain: 'No query specified', status: :bad_request end set_image_filter begin loader = PostLoader.new(default_image_filter_options.merge(sorts: parse_sort, size: 65535)) @search = loader.search(@search_query) rescue FancySearchable::Parsing::SearchParsingError => ex render plain: ex, status: :internal_server_error else @posts = @search.records(includes: [:tags, :user]) send_data @posts.as_json.to_json, type: 'application/json', disposition: :attachment, filename: "booru-dump-#{Time.now.to_i}.json" end end def reverse @title = 'Reverse Search' if request.post? || params[:scraper_url].present? scraped = scraped_media file = if scraped && scraped[:url] && scraped[:type] == 'image' Tempfile.new.tap do |t| t.binmode t.write Booru::Scrapers.fetch_image(scraped[:url]) t.close end elsif params[:image].present? params[:image] else flash[:error] = 'We could not find an image at the URL you provided' redirect_to search_reverse_path && return end begin processor = Booru::ImageProcessing.processor_for_file(file.path) processor.generate_rasterized intensities = ImageIntensities.file(processor.rasterized, type: (processor.is_a?(Booru::ImageProcessing::JpegProcessor) ? :jpeg : :png)) # FIXME: Hack! rescue StandardError => e Rails.logger.error "SearchController#reverse, except: #{e}" flash[:error] = 'The file you uploaded could not be analyzed!' redirect_to search_reverse_path return end dist = params[:fuzziness].to_f rescue 0.25 dist = dist.clamp(0.1, 5.0) @matches = PostQuery.duplicates(intensities, dist) respond_to do |format| format.html format.json { render json: { search: @matches.map(&:as_json), total: @matches.count } } end end end def syntax @title = 'Search syntax documentation' respond_to do |format| format.html { render } format.json { head :ok } end end private def find_random_image_id loader = PostLoader.new(default_image_filter_options.merge(sample: true)) @search = loader.search(@search_query) @search.results.empty? ? false : @search.results[0].id end # TODO: Clean up this mess of rescues and bizarre logic def setup_ignoredtaglist(search_body) # Okay, so this is not an "ignored tag list". # This is a "list of tags that the user explicitly searched for" - it is used so that they are not spoilered in # search results, as well as for displaying the tag info bar at the top. @ignoredtaglist = [] @ignoredtaglist = search_body.map { |t| t.dig(:term, :'namespaced_tags.name') }.compact rescue [] @ignoredtaglist = search_body.map { |t| t.dig(:bool, :must) }.compact.flatten.map { |t| t.dig(:term, :'namespaced_tags.name') } rescue [] if @ignoredtaglist.empty? @ignoredtaglist = @ignoredtaglist.map { |t| t.is_a?(Hash) ? t[:value] : t } @ignoredtaglist = Tag.includes(:implied_tags, :aliased_tag).where(name: @ignoredtaglist).map { |t| t.aliased_tag || t } end end