searchlink 2.3.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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