1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
# 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
|