searchlink 2.3.59

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/bin/searchlink +84 -0
  3. data/lib/searchlink/array.rb +7 -0
  4. data/lib/searchlink/config.rb +230 -0
  5. data/lib/searchlink/curl/html.rb +482 -0
  6. data/lib/searchlink/curl/json.rb +90 -0
  7. data/lib/searchlink/curl.rb +7 -0
  8. data/lib/searchlink/help.rb +103 -0
  9. data/lib/searchlink/output.rb +270 -0
  10. data/lib/searchlink/parse.rb +668 -0
  11. data/lib/searchlink/plist.rb +213 -0
  12. data/lib/searchlink/search.rb +70 -0
  13. data/lib/searchlink/searches/amazon.rb +25 -0
  14. data/lib/searchlink/searches/applemusic.rb +123 -0
  15. data/lib/searchlink/searches/bitly.rb +50 -0
  16. data/lib/searchlink/searches/definition.rb +67 -0
  17. data/lib/searchlink/searches/duckduckgo.rb +167 -0
  18. data/lib/searchlink/searches/github.rb +245 -0
  19. data/lib/searchlink/searches/google.rb +67 -0
  20. data/lib/searchlink/searches/helpers/chromium.rb +318 -0
  21. data/lib/searchlink/searches/helpers/firefox.rb +135 -0
  22. data/lib/searchlink/searches/helpers/safari.rb +133 -0
  23. data/lib/searchlink/searches/history.rb +166 -0
  24. data/lib/searchlink/searches/hook.rb +77 -0
  25. data/lib/searchlink/searches/itunes.rb +97 -0
  26. data/lib/searchlink/searches/lastfm.rb +41 -0
  27. data/lib/searchlink/searches/lyrics.rb +91 -0
  28. data/lib/searchlink/searches/pinboard.rb +183 -0
  29. data/lib/searchlink/searches/social.rb +105 -0
  30. data/lib/searchlink/searches/software.rb +27 -0
  31. data/lib/searchlink/searches/spelling.rb +59 -0
  32. data/lib/searchlink/searches/spotlight.rb +28 -0
  33. data/lib/searchlink/searches/stackoverflow.rb +31 -0
  34. data/lib/searchlink/searches/tmdb.rb +52 -0
  35. data/lib/searchlink/searches/twitter.rb +46 -0
  36. data/lib/searchlink/searches/wikipedia.rb +33 -0
  37. data/lib/searchlink/searches/youtube.rb +48 -0
  38. data/lib/searchlink/searches.rb +194 -0
  39. data/lib/searchlink/semver.rb +140 -0
  40. data/lib/searchlink/string.rb +469 -0
  41. data/lib/searchlink/url.rb +153 -0
  42. data/lib/searchlink/util.rb +87 -0
  43. data/lib/searchlink/version.rb +93 -0
  44. data/lib/searchlink/which.rb +175 -0
  45. data/lib/searchlink.rb +66 -0
  46. data/lib/tokens.rb +3 -0
  47. metadata +299 -0
@@ -0,0 +1,668 @@
1
+ module SL
2
+ class SearchLink
3
+ # Parse arguments in the input string
4
+ #
5
+ # @param string [String] the string to parse
6
+ # @param opt [Hash] the options to parse
7
+ # @option opt [Boolean] :only_meta (false) whether to skip flags
8
+ # @option opt [Boolean] :no_restore (false) whether to restore previous config
9
+ # @return [String] the parsed string
10
+ #
11
+ def parse_arguments(string, opt={})
12
+ input = string.dup
13
+ return "" if input.nil?
14
+
15
+ skip_flags = opt[:only_meta] || false
16
+ no_restore = opt[:no_restore] || false
17
+ restore_prev_config unless no_restore
18
+
19
+ input.parse_flags! unless skip_flags
20
+
21
+ options = %w[debug country_code inline prefix_random include_titles remove_seo validate_links]
22
+ options.each do |o|
23
+ if input =~ /^ *#{o}:\s+(\S+)$/
24
+ val = Regexp.last_match(1).strip
25
+ val = true if val =~ /true/i
26
+ val = false if val =~ /false/i
27
+ SL.config[o] = val
28
+ $stderr.print "\r\033[0KGlobal config: #{o} = #{SL.config[o]}\n" unless SILENT
29
+ end
30
+
31
+ next if skip_flags
32
+
33
+ while input =~ /^#{o}:\s+(.*?)$/ || input =~ /--(no-)?#{o}/
34
+ next unless input =~ /--(no-)?#{o}/ && !skip_flags
35
+
36
+ unless SL.prev_config.key? o
37
+ SL.prev_config[o] = SL.config[o]
38
+ bool = Regexp.last_match(1).nil? || Regexp.last_match(1) == '' ? true : false
39
+ SL.config[o] = bool
40
+ $stderr.print "\r\033[0KLine config: #{o} = #{SL.config[o]}\n" unless SILENT
41
+ end
42
+ input.sub!(/\s?--(no-)?#{o}/, '')
43
+ end
44
+ end
45
+ SL.clipboard ? string : input
46
+ end
47
+
48
+ # Parse commands from the given input string
49
+ #
50
+ # @param input [String] the input string
51
+ def parse_commands(input)
52
+ # Handle commands like help or docs
53
+ return unless input.strip =~ /^!?(h(elp)?|wiki|docs?|v(er(s(ion)?)?)?|up(date|grade))$/
54
+
55
+ case input.strip
56
+ when /^!?help$/i
57
+ if SILENT
58
+ help_dialog # %x{open http://brettterpstra.com/projects/searchlink/}
59
+ else
60
+ $stdout.puts SL.version_check.to_s
61
+ $stdout.puts 'See https://github.com/ttscoff/searchlink/wiki for help'
62
+ end
63
+ print input
64
+ when /^!?(wiki|docs)$/i
65
+ warn 'Opening wiki in browser'
66
+ `open https://github.com/ttscoff/searchlink/wiki`
67
+ when /^!?v(er(s(ion)?)?)?$/
68
+ print "[#{SL.version_check}]"
69
+ when /^!?up(date|grade)$/
70
+ SL.update_searchlink
71
+ print SL.output.join('')
72
+ end
73
+ Process.exit 0
74
+ end
75
+
76
+ def parse(input)
77
+ SL.output = []
78
+ return false if input.empty?
79
+
80
+ parse_arguments(input, { only_meta: true })
81
+ SL.originput = input.dup
82
+
83
+ parse_commands(input)
84
+
85
+ SL.config['inline'] = true if input.scan(/\]\(/).length == 1 && input.split(/\n/).length == 1
86
+ SL.errors = {}
87
+ SL.report = []
88
+
89
+ # Check for new version
90
+ latest_version = SL.new_version?
91
+ if latest_version
92
+ SL.add_output("<!-- v#{latest_version} available, run SearchLink on the word 'update' to install. -->")
93
+ end
94
+
95
+ links = {}
96
+ SL.footer = []
97
+ counter_links = 0
98
+ counter_errors = 0
99
+
100
+ input.sub!(/\n?<!-- Report:.*?-->\n?/m, '')
101
+ input.sub!(/\n?<!-- Errors:.*?-->\n?/m, '')
102
+
103
+ input.scan(/\[(.*?)\]:\s+(.*?)\n/).each { |match| links[match[1].strip] = match[0] }
104
+
105
+ prefix = if SL.config['prefix_random']
106
+ if input =~ /\[(\d{4}-)\d+\]: \S+/
107
+ Regexp.last_match(1)
108
+ else
109
+ format('%04d-', rand(9999))
110
+ end
111
+ else
112
+ ''
113
+ end
114
+
115
+ highest_marker = 0
116
+ input.scan(/^\s{,3}\[(?:#{prefix})?(\d+)\]: /).each do
117
+ m = Regexp.last_match
118
+ highest_marker = m[1].to_i if m[1].to_i > highest_marker
119
+ end
120
+
121
+ footnote_counter = 0
122
+ input.scan(/^\s{,3}\[\^(?:#{prefix})?fn(\d+)\]: /).each do
123
+ m = Regexp.last_match
124
+ footnote_counter = m[1].to_i if m[1].to_i > footnote_counter
125
+ end
126
+
127
+ if input =~ /\[(.*?)\]\((.*?)\)/
128
+ lines = input.split(/\n/)
129
+ out = []
130
+
131
+ total_links = input.scan(/\[(.*?)\]\((.*?)\)/).length
132
+ in_code_block = false
133
+ line_difference = 0
134
+ lines.each_with_index do |line, num|
135
+ SL.line_num = num - line_difference
136
+ cursor_difference = 0
137
+ # ignore links in code blocks
138
+ if line =~ /^( {4,}|\t+)[^*+\-]/
139
+ out.push(line)
140
+ next
141
+ end
142
+ if line =~ /^[~`]{3,}/
143
+ if in_code_block
144
+ in_code_block = false
145
+ out.push(line)
146
+ next
147
+ else
148
+ in_code_block = true
149
+ end
150
+ end
151
+ if in_code_block
152
+ out.push(line)
153
+ next
154
+ end
155
+
156
+ delete_line = false
157
+
158
+ search_count = 0
159
+
160
+ line.gsub!(/\[(.*?)\]\((.*?)\)/) do |match|
161
+ this_match = Regexp.last_match
162
+ SL.match_column = this_match.begin(0) - cursor_difference
163
+ match_string = this_match.to_s
164
+ SL.match_length = match_string.length
165
+ match_before = this_match.pre_match
166
+
167
+ invalid_search = false
168
+ ref_title = false
169
+
170
+ if match_before.scan(/(^|[^\\])`/).length.odd?
171
+ SL.add_report("Match '#{match_string}' within an inline code block")
172
+ invalid_search = true
173
+ end
174
+
175
+ counter_links += 1
176
+ unless SILENT
177
+ $stderr.print("\033[0K\rProcessed: #{counter_links} of #{total_links}, #{counter_errors} errors. ")
178
+ end
179
+
180
+ link_text = this_match[1] || ''
181
+ link_info = parse_arguments(this_match[2].strip).strip || ''
182
+
183
+ if link_text.strip == '' && link_info =~ /".*?"/
184
+ link_info.gsub!(/"(.*?)"/) do
185
+ m = Regexp.last_match(1)
186
+ link_text = m if link_text == ''
187
+ %("#")
188
+ end
189
+ end
190
+
191
+ link_info.gsub!(/<(.*?)>/) do
192
+ %(%22#{Regexp.last_match(1)}%22)
193
+ end
194
+
195
+ if link_info.strip =~ /:$/ && line.strip == match
196
+ ref_title = true
197
+ link_info.sub!(/\s*:\s*$/, '')
198
+ end
199
+
200
+ unless !link_text.empty? || !link_info.sub(/^[!\^]\S+/, '').strip.empty?
201
+ SL.add_error('No input', match)
202
+ counter_errors += 1
203
+ invalid_search = true
204
+ end
205
+
206
+ if link_info =~ /^!(\S+)/
207
+ search_type = Regexp.last_match(1)
208
+ unless SL::Searches.valid_search?(search_type) || search_type =~ /^(\S+\.)+\S+$/
209
+ SL.add_error("Invalid search#{SL::Searches.did_you_mean(search_type)}", match)
210
+ invalid_search = true
211
+ end
212
+ end
213
+
214
+ if invalid_search
215
+ match
216
+ elsif link_info =~ /^\^(.+)/
217
+ m = Regexp.last_match
218
+ if m[1].nil? || m[1] == ''
219
+ match
220
+ else
221
+ note = m[1].strip
222
+ footnote_counter += 1
223
+ ref = if !link_text.empty? && link_text.scan(/\s/).empty?
224
+ link_text
225
+ else
226
+ format('%<p>sfn%<c>04d', p: prefix, c: footnote_counter)
227
+ end
228
+ SL.add_footer "[^#{ref}]: #{note}"
229
+ res = "[^#{ref}]"
230
+ cursor_difference += (SL.match_length - res.length)
231
+ SL.match_length = res.length
232
+ SL.add_report("#{match_string} => Footnote #{ref}")
233
+ res
234
+ end
235
+ # Handle [](URL) and [%](URL), filling in title
236
+ elsif (link_text == '' || link_text == '%') && SL::URL.url?(link_info)
237
+ url = link_info
238
+ title = SL::URL.title(link_info)
239
+ link_text = title
240
+
241
+ if ref_title
242
+ unless links.key? url
243
+ links[url] = link_text
244
+ SL.add_footer SL.make_link(:ref_title, link_text, url, title: title, force_title: false)
245
+ end
246
+ delete_line = true
247
+ elsif SL.config['inline']
248
+ res = SL.make_link(:inline, link_text, url, title: title, force_title: false)
249
+ cursor_difference += SL.match_length - res.length
250
+ SL.match_length = res.length
251
+ SL.add_report("#{match_string} => #{url}")
252
+ res
253
+ else
254
+ unless links.key? url
255
+ highest_marker += 1
256
+ links[url] = format('%<pre>s%<m>04d', pre: prefix, m: highest_marker)
257
+ SL.add_footer SL.make_link(:ref_title, links[url], url, title: title, force_title: false)
258
+ end
259
+
260
+ type = SL.config['inline'] ? :inline : :ref_link
261
+ res = SL.make_link(type, link_text, links[url], title: false, force_title: false)
262
+ cursor_difference += SL.match_length - res.length
263
+ SL.match_length = res.length
264
+ SL.add_report("#{match_string} => #{url}")
265
+ res
266
+ end
267
+ elsif (link_text == '' && link_info == '') || SL::URL.url?(link_info)
268
+ SL.add_error('Invalid search', match) unless SL::URL.url?(link_info)
269
+ match
270
+ else
271
+ link_info = link_text if !link_text.empty? && link_info == ''
272
+
273
+ search_type = ''
274
+ search_terms = ''
275
+ link_only = false
276
+ SL.clipboard = false
277
+ SL.titleize = SL.config['empty_uses_page_title']
278
+
279
+ if link_info =~ /^(?:[!\^](\S+))\s*(.*)$/
280
+ m = Regexp.last_match
281
+
282
+ search_type = m[1].nil? ? (SL::GoogleSearch.test_for_key ? 'gg' : 'g') : m[1]
283
+
284
+ search_terms = m[2].gsub(/(^["']|["']$)/, '')
285
+ search_terms.strip!
286
+
287
+ # if the link text is just '%' replace with title regardless of config settings
288
+ if link_text == '%' && search_terms && !search_terms.empty?
289
+ SL.titleize = true
290
+ link_text = ''
291
+ end
292
+
293
+ search_terms = link_text if search_terms == ''
294
+
295
+ # if the input starts with a +, append it to the link text as the search terms
296
+ search_terms = "#{link_text} #{search_terms.strip.sub(/^\+\s*/, '')}" if search_terms.strip =~ /^\+[^+]/
297
+
298
+ # if the end of input contain "^", copy to clipboard instead of STDOUT
299
+ SL.clipboard = true if search_terms =~ /(!!)?\^(!!)?$/
300
+
301
+ # if the end of input contains "!!", only print the url
302
+ link_only = true if search_terms =~ /!!\^?$/
303
+
304
+ search_terms.sub!(/(!!)?\^?(!!)?$/,"")
305
+
306
+ elsif link_info =~ /^!/
307
+ search_word = link_info.match(/^!(\S+)/)
308
+
309
+ if search_word && SL::Searches.valid_search?(search_word[1])
310
+ search_type = search_word[1] unless search_word.nil?
311
+ search_terms = link_text
312
+ elsif search_word && search_word[1] =~ /^(\S+\.)+\S+$/
313
+ search_type = SL::GoogleSearch.test_for_key ? 'gg' : 'g'
314
+ puts SL::GoogleSearch.test_for_key
315
+ search_terms = "site:#{search_word[1]} #{link_text}"
316
+ else
317
+ SL.add_error("Invalid search#{SL::Searches.did_you_mean(search_word[1])}", match)
318
+ search_type = false
319
+ search_terms = false
320
+ end
321
+ elsif link_text && !link_text.empty? && (!link_info || link_info.empty?)
322
+ search_type = SL::GoogleSearch.test_for_key ? 'gg' : 'g'
323
+ search_terms = link_text
324
+ elsif link_info && !link_info.empty?
325
+ search_type = SL::GoogleSearch.test_for_key ? 'gg' : 'g'
326
+ search_terms = link_info
327
+ else
328
+ SL.add_error('Invalid search', match)
329
+ search_type = false
330
+ search_terms = false
331
+ end
332
+
333
+ if search_type && !search_terms.empty?
334
+ SL.config['custom_site_searches'].each do |k, v|
335
+ next unless search_type == k
336
+
337
+ link_text = search_terms if !SL.titleize && link_text == ''
338
+ v = parse_arguments(v, { no_restore: true })
339
+ if v =~ %r{^(/|http)}i
340
+ search_type = 'r'
341
+ tokens = v.scan(/\$term\d+[ds]?/).sort.uniq
342
+
343
+ if !tokens.empty?
344
+ highest_token = 0
345
+ tokens.each do |token|
346
+ if token =~ /(\d+)[ds]?$/ && Regexp.last_match(1).to_i > highest_token
347
+ highest_token = Regexp.last_match(1).to_i
348
+ end
349
+ end
350
+ terms_p = search_terms.split(/ +/)
351
+ if terms_p.length > highest_token
352
+ remainder = terms_p[highest_token - 1..-1].join(' ')
353
+ terms_p = terms_p[0..highest_token - 2]
354
+ terms_p.push(remainder)
355
+ end
356
+ tokens.each do |t|
357
+ next unless t =~ /(\d+)[ds]?$/
358
+
359
+ int = Regexp.last_match(1).to_i - 1
360
+ replacement = terms_p[int]
361
+ case t
362
+ when /d$/
363
+ replacement.downcase!
364
+ re_down = ''
365
+ when /s$/
366
+ replacement.slugify!
367
+ re_down = ''
368
+ else
369
+ re_down = '(?!d|s)'
370
+ end
371
+ v.gsub!(/#{Regexp.escape(t) + re_down}/, replacement.url_encode)
372
+ end
373
+ search_terms = v
374
+ else
375
+ search_terms = v.gsub(/\$term[ds]?/i) do |mtch|
376
+ search_terms.downcase! if mtch =~ /d$/i
377
+ search_terms.slugify! if mtch =~ /s$/i
378
+ search_terms.url_encode
379
+ end
380
+ end
381
+ else
382
+ search_type = SL::GoogleSearch.test_for_key ? 'gg' : 'g'
383
+ search_terms = "site:#{v} #{search_terms}"
384
+ end
385
+
386
+ break
387
+ end
388
+ end
389
+
390
+ if (search_type && search_terms) || url
391
+ # warn "Searching #{search_type} for #{search_terms}"
392
+ if (!url)
393
+ search_count += 1
394
+ url, title, link_text = do_search(search_type, search_terms, link_text, search_count)
395
+ end
396
+
397
+ if url
398
+ title = SL::URL.title(url) if SL.titleize && title == ''
399
+
400
+ link_text = title if link_text == '' && title
401
+ force_title = search_type =~ /def/ ? true : false
402
+
403
+ if link_only || search_type =~ /sp(ell)?/ || url == 'embed'
404
+ url = title if url == 'embed'
405
+ cursor_difference += SL.match_length - url.length
406
+ SL.match_length = url.length
407
+ SL.add_report("#{match_string} => #{url}")
408
+ url
409
+ elsif ref_title
410
+ unless links.key? url
411
+ links[url] = link_text
412
+ SL.add_footer SL.make_link(:ref_title, link_text, url, title: title, force_title: force_title)
413
+ end
414
+ delete_line = true
415
+ elsif SL.config['inline']
416
+ res = SL.make_link(:inline, link_text, url, title: title, force_title: force_title)
417
+ cursor_difference += SL.match_length - res.length
418
+ SL.match_length = res.length
419
+ SL.add_report("#{match_string} => #{url}")
420
+ res
421
+ else
422
+ unless links.key? url
423
+ highest_marker += 1
424
+ links[url] = format('%<pre>s%<m>04d', pre: prefix, m: highest_marker)
425
+ SL.add_footer SL.make_link(:ref_title, links[url], url, title: title, force_title: force_title)
426
+ end
427
+
428
+ type = SL.config['inline'] ? :inline : :ref_link
429
+ res = SL.make_link(type, link_text, links[url], title: false, force_title: force_title)
430
+ cursor_difference += SL.match_length - res.length
431
+ SL.match_length = res.length
432
+ SL.add_report("#{match_string} => #{url}")
433
+ res
434
+ end
435
+ else
436
+ SL.add_error('No results', "#{search_terms} (#{match_string})")
437
+ counter_errors += 1
438
+ match
439
+ end
440
+ else
441
+ SL.add_error('Invalid search', match)
442
+ counter_errors += 1
443
+ match
444
+ end
445
+ end
446
+ end
447
+ line_difference += 1 if delete_line
448
+ out.push(line) unless delete_line
449
+ delete_line = false
450
+ end
451
+ warn "\n" unless SILENT
452
+
453
+ input = out.delete_if { |l| l.strip =~ /^<!--DELETE-->$/ }.join("\n")
454
+
455
+ if SL.config['inline']
456
+ SL.add_output "#{input}\n"
457
+ SL.add_output "\n#{SL.print_footer}" unless SL.footer.empty?
458
+ elsif SL.footer.empty?
459
+ SL.add_output input
460
+ else
461
+ last_line = input.strip.split(/\n/)[-1]
462
+ case last_line
463
+ when /^\[.*?\]: http/
464
+ SL.add_output "#{input.rstrip}\n"
465
+ when /^\[\^.*?\]: /
466
+ SL.add_output input.rstrip
467
+ else
468
+ SL.add_output "#{input}\n\n"
469
+ end
470
+ SL.add_output "#{SL.print_footer}\n\n"
471
+ end
472
+
473
+ SL.line_num = nil
474
+ SL.add_report("Processed: #{total_links} links, #{counter_errors} errors.")
475
+ SL.print_report
476
+ SL.print_errors
477
+ else
478
+ link_only = false
479
+ SL.clipboard = false
480
+
481
+ res = parse_arguments(input.strip!).strip
482
+ input = res.nil? ? input.strip : res
483
+
484
+ # if the end of input contain "^", copy to clipboard instead of STDOUT
485
+ SL.clipboard = true if input =~ /\^[!~:\s]*$/
486
+
487
+ # if the end of input contains "!!", only print the url
488
+ link_only = true if input =~ /!![\^~:\s]*$/
489
+
490
+ reference_link = input =~ /:([!\^~\s]*)$/
491
+
492
+ # if end of input contains ~, pull url from clipboard
493
+ if input =~ /~[:\^!\s]*$/
494
+ input.sub!(/[:!\^\s~]*$/, '')
495
+ clipboard = `__CF_USER_TEXT_ENCODING=$UID:0x8000100:0x8000100 pbpaste`.strip
496
+ if SL::URL.url?(clipboard)
497
+ type = reference_link ? :ref_title : :inline
498
+ print SL.make_link(type, input.strip, clipboard)
499
+ else
500
+ print SL.originput
501
+ end
502
+ Process.exit
503
+ end
504
+
505
+ input.sub!(/[:!\^\s~]*$/, '')
506
+
507
+ ## Maybe if input is just a URL, convert it to a link
508
+ ## using hostname as text without doing search
509
+ if SL::URL.only_url?(input.strip)
510
+ type = reference_link ? :ref_title : :inline
511
+ url, title = SL::URL.url_to_link(input.strip, type)
512
+ print SL.make_link(type, title, url, title: false, force_title: false)
513
+ Process.exit
514
+ end
515
+
516
+ # check for additional search terms in parenthesis
517
+ additional_terms = ''
518
+ if input =~ /\((.*?)\)/
519
+ additional_terms = " #{Regexp.last_match(1).strip}"
520
+ input.sub!(/\(.*?\)/, '')
521
+ end
522
+
523
+ # Maybe detect "search + addition terms" and remove additional terms from link text?
524
+ # if input =~ /\+(.+?)$/
525
+ # additional_terms = "#{additional_terms} #{Regexp.last_match(1).strip}"
526
+ # input.sub!(/\+.*?$/, '').strip!
527
+ # end
528
+
529
+ link_text = false
530
+
531
+ if input =~ /"(.*?)"/
532
+ link_text = Regexp.last_match(1)
533
+ input.gsub!(/"(.*?)"/, '\1')
534
+ end
535
+
536
+ # remove quotes from terms, just in case
537
+ # input.sub!(/^(!\S+)?\s*(["'])(.*?)\2([\!\^]+)?$/, "\\1 \\3\\4")
538
+
539
+ case input
540
+ when /^!(\S+)\s+(.*)$/
541
+ type = Regexp.last_match(1)
542
+ link_info = Regexp.last_match(2).strip
543
+ link_text ||= link_info
544
+ terms = link_info + additional_terms
545
+ terms.strip!
546
+
547
+ if SL::Searches.valid_search?(type) || type =~ /^(\S+\.)+\S+$/
548
+ if type && terms && !terms.empty?
549
+ # Iterate through custom searches for a match, perform search if matched
550
+ SL.config['custom_site_searches'].each do |k, v|
551
+ next unless type == k
552
+
553
+ link_text = terms if link_text == ''
554
+ v = parse_arguments(v, { no_restore: true })
555
+ if v =~ %r{^(/|http)}i
556
+ type = 'r'
557
+ tokens = v.scan(/\$term\d+[ds]?/).sort.uniq
558
+
559
+ if !tokens.empty?
560
+ highest_token = 0
561
+ tokens.each do |token|
562
+ t = Regexp.last_match(1)
563
+ highest_token = t.to_i if token =~ /(\d+)d?$/ && t.to_i > highest_token
564
+ end
565
+ terms_p = terms.split(/ +/)
566
+ if terms_p.length > highest_token
567
+ remainder = terms_p[highest_token - 1..].join(' ')
568
+ terms_p = terms_p[0..highest_token - 2]
569
+ terms_p.push(remainder)
570
+ end
571
+ tokens.each do |t|
572
+ next unless t =~ /(\d+)d?$/
573
+
574
+ int = Regexp.last_match(1).to_i - 1
575
+ replacement = terms_p[int]
576
+
577
+ re_down = case t
578
+ when /d$/
579
+ replacement.downcase!
580
+ ''
581
+ when /s$/
582
+ replacement.slugify!
583
+ ''
584
+ else
585
+ '(?!d|s)'
586
+ end
587
+ v.gsub!(/#{Regexp.escape(t) + re_down}/, replacement.url_encode)
588
+ end
589
+ terms = v
590
+ else
591
+ terms = v.gsub(/\$term[ds]?/i) do |mtch|
592
+ terms.downcase! if mtch =~ /d$/i
593
+ terms.slugify! if mtch =~ /s$/i
594
+ terms.url_encode
595
+ end
596
+ end
597
+ else
598
+ type = SL::GoogleSearch.test_for_key ? 'gg' : 'g'
599
+ terms = "site:#{v} #{terms}"
600
+ end
601
+
602
+ break
603
+ end
604
+ end
605
+
606
+ # if contains TLD, use site-specific search
607
+ if type =~ /^(\S+\.)+\S+$/
608
+ terms = "site:#{type} #{terms}"
609
+ type = SL::GoogleSearch.test_for_key ? 'gg' : 'g'
610
+ end
611
+ search_count ||= 0
612
+ search_count += 1
613
+
614
+ url, title, link_text = do_search(type, terms, link_text, search_count)
615
+ else
616
+ SL.add_error("Invalid search#{SL::Searches.did_you_mean(type)}", input)
617
+ counter_errors += 1
618
+ end
619
+ # Social handle expansion
620
+ when /^([tfilm])?@(\S+)\s*$/
621
+ type = Regexp.last_match(1)
622
+ unless type
623
+ # If contains @ mid-handle, use Mastodon
624
+ if Regexp.last_match(2) =~ /[a-z0-9_]@[a-z0-9_.]+/i
625
+ type = 'm'
626
+ else
627
+ type = 't'
628
+ end
629
+ end
630
+ link_text = input.sub(/^[tfilm]/, '')
631
+ url, title = SL::SocialSearch.social_handle(type, link_text)
632
+ link_text = title
633
+ else
634
+ link_text ||= input
635
+ url, title, link_text = SL.ddg(input, link_text)
636
+ end
637
+
638
+ if url
639
+ if type =~ /sp(ell)?/
640
+ SL.add_output(url)
641
+ elsif link_only
642
+ SL.add_output(url)
643
+ elsif url == 'embed'
644
+ SL.add_output(title)
645
+ else
646
+ type = reference_link ? :ref_title : :inline
647
+
648
+ SL.add_output SL.make_link(type, link_text, url, title: title, force_title: false)
649
+ SL.print_errors
650
+ end
651
+ else
652
+ SL.add_error('No results', title)
653
+ SL.add_output SL.originput.chomp
654
+ SL.print_errors
655
+ end
656
+
657
+ if SL.clipboard
658
+ if SL.output == SL.originput
659
+ warn 'No results found'
660
+ else
661
+ `echo #{Shellwords.escape(SL.output.join(''))}|tr -d "\n"|pbcopy`
662
+ warn 'Results in clipboard'
663
+ end
664
+ end
665
+ end
666
+ end
667
+ end
668
+ end