searchlink 2.3.84 → 2.3.85

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 78ae4255b0a3a287ce3ccf4b84db34597f8c2a566bf4e7cb0b4939a28f62cca1
4
- data.tar.gz: f654c706f574f6c23dca1a6714b2b52177944379fb1df00d34d9bac22e14104b
3
+ metadata.gz: 02b111da9ce13f8646d7357ae5560a1c6b84bb45fd506dca8fafe96e1c844bea
4
+ data.tar.gz: 02fd69545df789fdf1105dea4971b53f02bfc33ac0da20a3f25454c11f9a9ae7
5
5
  SHA512:
6
- metadata.gz: ecccb8b8f87a8aa891c9bbafffd7e40f9ecee5ee8042326fe8e0151d52e225797bcff745c7ec67b7f7dd8010fd5e32330673b3fe062c378873a74407b666205a
7
- data.tar.gz: 6a4ada563e8488646f0cc4fe5ab8c5414c906ead7560cafaf3e6b68fcdf9292a37c04ab8261970de6d5a3a3224dc135a3dba7bc2b2abc60160ca6181dbec512c
6
+ metadata.gz: 7af24cad60ca01ead8ab4a76f82c6d6fa9be6aa30752f088c23cfe387d9a3adf8f076e5e4d1f1dc25d9eeda8879d1ae7e83bbee3bcea250de6914dc67d35cf0c
7
+ data.tar.gz: '089fb329c4c7be4cf30bf929b8c4309374a0b8055f42eb2adeb2c62d29f7a9ef84d9e45527a5c0a13c7e0061345ee814cfd431bc6f33614ad034f8d8c970250a'
@@ -3,7 +3,8 @@
3
3
  module SL
4
4
  class << self
5
5
  attr_writer :titleize, :clipboard, :output, :footer, :line_num,
6
- :match_column, :match_length, :originput, :errors, :report, :printout
6
+ :match_column, :match_length, :originput, :errors, :report, :printout,
7
+ :shortener
7
8
 
8
9
  # Whether or not to add a title to the output
9
10
  def titleize
@@ -60,6 +61,16 @@ module SL
60
61
  @errors ||= {}
61
62
  end
62
63
 
64
+ # Stores query parameters
65
+ def query
66
+ @query ||= {}
67
+ end
68
+
69
+ # The shortener to use
70
+ def shortener
71
+ @shortener ||= :none
72
+ end
73
+
63
74
  # Posts macOS notifications
64
75
  #
65
76
  # @param title [String] The title of the notification
@@ -102,6 +113,19 @@ module SL
102
113
 
103
114
  title = title.gsub(/[ \t]+/, " ")
104
115
 
116
+ url.add_query_string!
117
+
118
+ url = case SL.shortener
119
+ when :isgd
120
+ SL::IsgdSearch.shorten(url)
121
+ when :tinyurl
122
+ SL::TinyurlSearch.shorten(url)
123
+ when :bitly
124
+ SL::BitlySearch.shorten(url)
125
+ else
126
+ url
127
+ end
128
+
105
129
  case type.to_sym
106
130
  when :ref_title
107
131
  %(\n[#{text}]: #{url}#{title})
@@ -141,7 +165,6 @@ module SL
141
165
  #
142
166
  def print_footer
143
167
  unless SL.footer.empty?
144
-
145
168
  footnotes = []
146
169
  SL.footer.delete_if do |note|
147
170
  note.strip!
@@ -202,6 +225,14 @@ module SL
202
225
  SL.errors[type].push("(#{position}): #{str}")
203
226
  end
204
227
 
228
+ # Add to query string
229
+ # @param hsh [Hash] The queries to add
230
+ # @return [nil]
231
+ def add_query(hsh)
232
+ SL.query ||= {}
233
+ SL.query.merge!(hsh)
234
+ end
235
+
205
236
  # Prints the report.
206
237
  #
207
238
  # @return [String] The report.
@@ -2,194 +2,6 @@
2
2
 
3
3
  module SL
4
4
  class SearchLink
5
- # Parse arguments in the input string
6
- #
7
- # @param string [String] the string to parse
8
- # @param opt [Hash] the options to parse
9
- # @option opt [Boolean] :only_meta (false) whether to skip flags
10
- # @option opt [Boolean] :no_restore (false) whether to restore previous config
11
- # @return [String] the parsed string
12
- #
13
- def parse_arguments(string, opt = {})
14
- input = string.dup
15
- return "" if input.nil?
16
-
17
- skip_flags = opt[:only_meta] || false
18
- no_restore = opt[:no_restore] || false
19
- restore_prev_config unless no_restore
20
-
21
- input.parse_flags! unless skip_flags
22
-
23
- options = %w[debug country_code inline prefix_random include_titles remove_seo validate_links complete_bare]
24
- options.each do |o|
25
- if input =~ /^ *#{o}:\s+(\S+)$/
26
- val = Regexp.last_match(1).strip
27
-
28
- if val.is_a?(String)
29
- value = true if val =~ /true/i
30
- value = false if val =~ /false/i
31
- end
32
- val = value if value
33
- SL.config[o] = val
34
- warn "\r\033[0KGlobal config: #{o} = #{SL.config[o]}\n" unless SILENT
35
- end
36
-
37
- next if skip_flags
38
-
39
- while input =~ /^#{o}:\s+(.*?)$/ || input =~ /--(no-)?#{o}/
40
- next unless input =~ /--(no-)?#{o}/ && !skip_flags
41
-
42
- unless SL.prev_config.key? o
43
- SL.prev_config[o] = SL.config[o]
44
- bool = Regexp.last_match(1).nil? || Regexp.last_match(1) == "" ? true : false
45
- SL.config[o] = bool
46
- $stderr.print "\r\033[0KLine config: #{o} = #{SL.config[o]}\n" unless SILENT
47
- end
48
- input.sub!(/\s?--(no-)?#{o}/, "")
49
- end
50
- end
51
- SL.clipboard ? string : input
52
- end
53
-
54
- # Parse commands from the given input string
55
- #
56
- # @param input [String] the input string
57
- def parse_commands(input)
58
- # Handle commands like help or docs
59
- return unless input.strip =~ /^!?(h(elp)?|wiki|docs?|v(er(s(ion)?)?)?|up(date|grade))$/
60
-
61
- case input.strip
62
- when /^!?help$/i
63
- if SILENT
64
- help_dialog
65
- else
66
- $stdout.puts SL.version_check.to_s
67
- $stdout.puts "See https://github.com/ttscoff/searchlink/wiki for help"
68
- end
69
- print input
70
- when /^!?(wiki|docs)$/i
71
- warn "Opening wiki in browser"
72
- `open https://github.com/ttscoff/searchlink/wiki`
73
- when /^!?v(er(s(ion)?)?)?$/
74
- print "[#{SL.version_check}]"
75
- when /^!?up(date|grade)$/
76
- SL.update_searchlink
77
- print SL.output.join("")
78
- end
79
- Process.exit 0
80
- end
81
-
82
- def create_footnote(mtch)
83
- if mtch[1].nil? || mtch[1] == ""
84
- match
85
- else
86
- note = mtch[1].strip
87
- @footnote_counter += 1
88
- ref = if !@link_text.empty? && @link_text.scan(/\s/).empty?
89
- @link_text
90
- else
91
- format("%<p>sfn%<c>04d", p: @prefix, c: @footnote_counter)
92
- end
93
- SL.add_footer "[^#{ref}]: #{note}"
94
- res = "[^#{ref}]"
95
- @cursor_difference += (SL.match_length - res.length)
96
- SL.match_length = res.length
97
- SL.add_report("#{@match_string} => Footnote #{ref}")
98
- res
99
- end
100
- end
101
-
102
- def add_title(link_info)
103
- @url = link_info
104
- title = SL::URL.title(@url)
105
- @link_text = title
106
-
107
- if @ref_title
108
- unless @links.key? @url
109
- @links[@url] = @link_text
110
- SL.add_footer SL.make_link(:ref_title, @link_text, @url, title: title, force_title: false)
111
- end
112
- @delete_line = true
113
- elsif SL.config["inline"]
114
- res = SL.make_link(:inline, @link_text, @url, title: title, force_title: false)
115
- @cursor_difference += SL.match_length - res.length
116
- SL.match_length = res.length
117
- SL.add_report("#{@match_string} => #{@url}")
118
- res
119
- else
120
- unless @links.key? @url
121
- @highest_marker += 1
122
- @links[@url] = format("%<pre>s%<m>04d", pre: @prefix, m: @highest_marker)
123
- SL.add_footer SL.make_link(:ref_title, @links[@url], @url, title: title, force_title: false)
124
- end
125
-
126
- type = SL.config["inline"] ? :inline : :ref_link
127
- res = SL.make_link(type, @link_text, @links[@url], title: false, force_title: false)
128
- @cursor_difference += SL.match_length - res.length
129
- SL.match_length = res.length
130
- SL.add_report("#{@match_string} => #{@url}")
131
- res
132
- end
133
- end
134
-
135
- def custom_search(search_type, search_terms)
136
- SL.config["custom_site_searches"].each do |k, v|
137
- next unless search_type == k
138
-
139
- @link_text = search_terms if !SL.titleize && @link_text == ""
140
- v = parse_arguments(v, { no_restore: true })
141
- if v =~ %r{^(/|http)}i
142
- search_type = "r"
143
- tokens = v.scan(/\$term\d+[ds]?/).sort.uniq
144
-
145
- if !tokens.empty?
146
- highest_token = 0
147
- tokens.each do |token|
148
- if token =~ /(\d+)[ds]?$/ && Regexp.last_match(1).to_i > highest_token
149
- highest_token = Regexp.last_match(1).to_i
150
- end
151
- end
152
- terms_p = search_terms.split(/ +/)
153
- if terms_p.length > highest_token
154
- remainder = terms_p[highest_token - 1..].join(" ")
155
- terms_p = terms_p[0..highest_token - 2]
156
- terms_p.push(remainder)
157
- end
158
- tokens.each do |t|
159
- next unless t =~ /(\d+)[ds]?$/
160
-
161
- int = Regexp.last_match(1).to_i - 1
162
- replacement = terms_p[int]
163
- case t
164
- when /d$/
165
- replacement.downcase!
166
- re_down = ""
167
- when /s$/
168
- replacement.slugify!
169
- re_down = ""
170
- else
171
- re_down = "(?!d|s)"
172
- end
173
- v.gsub!(/#{Regexp.escape(t) + re_down}/, replacement.url_encode)
174
- end
175
- search_terms = v
176
- else
177
- search_terms = v.gsub(/\$term[ds]?/i) do |mtch|
178
- search_terms.downcase! if mtch =~ /d$/i
179
- search_terms.slugify! if mtch =~ /s$/i
180
- search_terms.url_encode
181
- end
182
- end
183
- else
184
- search_type = SL::GoogleSearch.api_key? ? "gg" : "g"
185
- search_terms = "site:#{v} #{search_terms}"
186
- end
187
-
188
- break
189
- end
190
- [search_type, search_terms]
191
- end
192
-
193
5
  def parse(input)
194
6
  SL.output = []
195
7
  return false if input.empty?
@@ -202,6 +14,7 @@ module SL
202
14
  SL.config["inline"] = true if input.scan(/\]\(/).length == 1 && input.split(/\n/).length == 1
203
15
  SL.errors = {}
204
16
  SL.report = []
17
+ SL.shortener = :none
205
18
 
206
19
  # Check for new version
207
20
  latest_version = SL.new_version?
@@ -274,6 +87,7 @@ module SL
274
87
  end
275
88
  end
276
89
 
90
+ # Handle links in the form of [text](url) or [text](url "title")
277
91
  if input =~ /\[(.*?)\]\((.*?)\)/
278
92
  lines = input.split(/\n/)
279
93
  out = []
@@ -330,6 +144,7 @@ module SL
330
144
 
331
145
  @link_text = this_match[1] || ""
332
146
  link_info = parse_arguments(this_match[2].strip).strip || ""
147
+ query, link_info = link_info.extract_query({})
333
148
 
334
149
  if @link_text.strip == "" && link_info =~ /".*?"/
335
150
  link_info.gsub!(/"(.*?)"/) do
@@ -355,7 +170,7 @@ module SL
355
170
  end
356
171
 
357
172
  if link_info =~ /^!(\S+)/
358
- search_type = Regexp.last_match(1)
173
+ search_type = Regexp.last_match(1).extract_shortener
359
174
  unless SL::Searches.valid_search?(search_type) || search_type =~ /^(\S+\.)+\S+$/
360
175
  SL.add_error("Invalid search#{SL::Searches.did_you_mean(search_type)}", match)
361
176
  invalid_search = true
@@ -386,10 +201,12 @@ module SL
386
201
  m = Regexp.last_match
387
202
 
388
203
  search_type = if m[1].nil?
389
- SL::GoogleSearch.api_key? ? "gg" : "g"
390
- else
391
- m[1]
392
- end
204
+ SL::GoogleSearch.api_key? ? "gg" : "g"
205
+ else
206
+ m[1]
207
+ end
208
+
209
+ search_type.extract_shortener!
393
210
 
394
211
  search_terms = m[2].gsub(/(^["']|["']$)/, "")
395
212
  search_terms.strip!
@@ -419,18 +236,17 @@ module SL
419
236
  search_type = "g"
420
237
  search_terms = "site:#{m[1]} #{search_terms}"
421
238
  end
422
-
423
239
  elsif link_info =~ /^!/
424
240
  search_word = link_info.match(/^!(\S+)/)
425
-
426
- if search_word && SL::Searches.valid_search?(search_word[1])
427
- search_type = search_word[1] unless search_word.nil?
241
+ st = search_word[1].extract_shortener
242
+ if search_word && SL::Searches.valid_search?(st)
243
+ search_type = st unless search_word.nil?
428
244
  search_terms = @link_text
429
- elsif search_word && search_word[1] =~ /^(\S+\.)+\S+$/
245
+ elsif search_word && st =~ /^(\S+\.)+\S+$/
430
246
  search_type = SL::GoogleSearch.api_key? ? "gg" : "g"
431
247
  search_terms = "site:#{search_word[1]} #{@link_text}"
432
248
  else
433
- SL.add_error("Invalid search#{SL::Searches.did_you_mean(search_word[1])}", match)
249
+ SL.add_error("Invalid search#{SL::Searches.did_you_mean(st)}", match)
434
250
  search_type = false
435
251
  search_terms = false
436
252
  end
@@ -450,6 +266,8 @@ module SL
450
266
  search_type, search_terms = custom_search(search_type, search_terms)
451
267
  end
452
268
 
269
+ SL.add_query(query) if query
270
+
453
271
  if (search_type && search_terms) || @url
454
272
  # warn "Searching #{search_type} for #{search_terms}"
455
273
 
@@ -544,12 +362,13 @@ module SL
544
362
  SL.add_report("Processed: #{total_links} links, #{counter_errors} errors.")
545
363
  SL.print_report
546
364
  SL.print_errors
547
- else
365
+ else # Assume single line input
548
366
  link_only = false
549
367
  SL.clipboard = false
550
368
 
551
369
  res = parse_arguments(input.strip!).strip
552
370
  input = res.nil? ? input.strip : res
371
+ query, input = input.extract_query({})
553
372
 
554
373
  # if the end of input contain "^", copy to clipboard instead of STDOUT
555
374
  SL.clipboard = true if input =~ /\^[!~:\s]*$/
@@ -608,8 +427,9 @@ module SL
608
427
 
609
428
  case input
610
429
  when /^!(\S+)\s+(.*)$/
611
- type = Regexp.last_match(1)
430
+ type = Regexp.last_match(1).extract_shortener
612
431
  link_info = Regexp.last_match(2).strip
432
+
613
433
  @link_text ||= link_info
614
434
  terms = link_info + additional_terms
615
435
  terms.strip!
@@ -617,60 +437,7 @@ module SL
617
437
  if SL::Searches.valid_search?(type) || type =~ /^(\S+\.)+\S+$/
618
438
  if type && terms && !terms.empty?
619
439
  # Iterate through custom searches for a match, perform search if matched
620
- SL.config["custom_site_searches"].each do |k, v|
621
- next unless type == k
622
-
623
- @link_text = terms if @link_text == ""
624
- v = parse_arguments(v, { no_restore: true })
625
- if v =~ %r{^(/|http)}i
626
- type = "r"
627
- tokens = v.scan(/\$term\d+[ds]?/).sort.uniq
628
-
629
- if !tokens.empty?
630
- highest_token = 0
631
- tokens.each do |token|
632
- t = Regexp.last_match(1)
633
- highest_token = t.to_i if token =~ /(\d+)d?$/ && t.to_i > highest_token
634
- end
635
- terms_p = terms.split(/ +/)
636
- if terms_p.length > highest_token
637
- remainder = terms_p[highest_token - 1..].join(" ")
638
- terms_p = terms_p[0..highest_token - 2]
639
- terms_p.push(remainder)
640
- end
641
- tokens.each do |t|
642
- next unless t =~ /(\d+)d?$/
643
-
644
- int = Regexp.last_match(1).to_i - 1
645
- replacement = terms_p[int]
646
-
647
- re_down = case t
648
- when /d$/
649
- replacement.downcase!
650
- ""
651
- when /s$/
652
- replacement.slugify!
653
- ""
654
- else
655
- "(?!d|s)"
656
- end
657
- v.gsub!(/#{Regexp.escape(t) + re_down}/, replacement.url_encode)
658
- end
659
- terms = v
660
- else
661
- terms = v.gsub(/\$term[ds]?/i) do |mtch|
662
- terms.downcase! if mtch =~ /d$/i
663
- terms.slugify! if mtch =~ /s$/i
664
- terms.url_encode
665
- end
666
- end
667
- else
668
- type = SL::GoogleSearch.api_key? ? "gg" : "g"
669
- terms = "site:#{v} #{terms}"
670
- end
671
-
672
- break
673
- end
440
+ type, terms = custom_search(type, terms)
674
441
  end
675
442
 
676
443
  # if contains TLD, use site-specific search
@@ -681,6 +448,7 @@ module SL
681
448
  @search_count ||= 0
682
449
  @search_count += 1
683
450
 
451
+ SL.add_query(query) if query
684
452
  @url, title, @link_text = do_search(type, terms, @link_text, @search_count)
685
453
  else
686
454
  SL.add_error("Invalid search#{SL::Searches.did_you_mean(type)}", input)
@@ -695,9 +463,11 @@ module SL
695
463
  "t"
696
464
  end
697
465
  @link_text = input.sub(/^[tfilm]/, "")
466
+ SL.add_query(query) if query
698
467
  @url, title = SL::SocialSearch.social_handle(type, @link_text)
699
468
  @link_text = title
700
469
  else
470
+ SL.add_query(query) if query
701
471
  @link_text ||= input
702
472
  @url, title, @link_text = SL.ddg(input, @link_text)
703
473
  end
@@ -731,5 +501,199 @@ module SL
731
501
  end
732
502
  end
733
503
  end
504
+
505
+ private
506
+
507
+ def add_title(link_info)
508
+ @url = link_info
509
+ title = SL::URL.title(@url)
510
+ @link_text = title
511
+
512
+ if @ref_title
513
+ unless @links.key? @url
514
+ @links[@url] = @link_text
515
+ SL.add_footer SL.make_link(:ref_title, @link_text, @url, title: title, force_title: false)
516
+ end
517
+ @delete_line = true
518
+ elsif SL.config["inline"]
519
+ res = SL.make_link(:inline, @link_text, @url, title: title, force_title: false)
520
+ @cursor_difference += SL.match_length - res.length
521
+ SL.match_length = res.length
522
+ SL.add_report("#{@match_string} => #{@url}")
523
+ res
524
+ else
525
+ unless @links.key? @url
526
+ @highest_marker += 1
527
+ @links[@url] = format("%<pre>s%<m>04d", pre: @prefix, m: @highest_marker)
528
+ SL.add_footer SL.make_link(:ref_title, @links[@url], @url, title: title, force_title: false)
529
+ end
530
+
531
+ type = SL.config["inline"] ? :inline : :ref_link
532
+ res = SL.make_link(type, @link_text, @links[@url], title: false, force_title: false)
533
+ @cursor_difference += SL.match_length - res.length
534
+ SL.match_length = res.length
535
+ SL.add_report("#{@match_string} => #{@url}")
536
+ res
537
+ end
538
+ end
539
+
540
+ # Parse arguments in the input string
541
+ #
542
+ # @param string [String] the string to parse
543
+ # @param opt [Hash] the options to parse
544
+ # @option opt [Boolean] :only_meta (false) whether to skip flags
545
+ # @option opt [Boolean] :no_restore (false) whether to restore previous config
546
+ # @return [String] the parsed string
547
+ #
548
+ def parse_arguments(string, opt = {})
549
+ input = string.dup
550
+ return "" if input.nil?
551
+
552
+ skip_flags = opt[:only_meta] || false
553
+ no_restore = opt[:no_restore] || false
554
+ restore_prev_config unless no_restore
555
+
556
+ input.parse_flags! unless skip_flags
557
+
558
+ options = %w[debug country_code inline prefix_random include_titles remove_seo validate_links complete_bare]
559
+ options.each do |o|
560
+ if input =~ /^ *#{o}:\s+(\S+)$/
561
+ val = Regexp.last_match(1).strip
562
+
563
+ if val.is_a?(String)
564
+ value = true if val =~ /true/i
565
+ value = false if val =~ /false/i
566
+ end
567
+ val = value if value
568
+ SL.config[o] = val
569
+ warn "\r\033[0KGlobal config: #{o} = #{SL.config[o]}\n" unless SILENT
570
+ end
571
+
572
+ next if skip_flags
573
+
574
+ while input =~ /^#{o}:\s+(.*?)$/ || input =~ /--(no-)?#{o}/
575
+ next unless input =~ /--(no-)?#{o}/ && !skip_flags
576
+
577
+ unless SL.prev_config.key? o
578
+ SL.prev_config[o] = SL.config[o]
579
+ bool = Regexp.last_match(1).nil? || Regexp.last_match(1) == "" ? true : false
580
+ SL.config[o] = bool
581
+ $stderr.print "\r\033[0KLine config: #{o} = #{SL.config[o]}\n" unless SILENT
582
+ end
583
+ input.sub!(/\s?--(no-)?#{o}/, "")
584
+ end
585
+ end
586
+ SL.clipboard ? string : input
587
+ end
588
+
589
+ # Parse commands from the given input string
590
+ #
591
+ # @param input [String] the input string
592
+ def parse_commands(input)
593
+ # Handle commands like help or docs
594
+ return unless input.strip =~ /^!?(h(elp)?|wiki|docs?|v(er(s(ion)?)?)?|up(date|grade))$/
595
+
596
+ case input.strip
597
+ when /^!?help$/i
598
+ if SILENT
599
+ help_dialog
600
+ else
601
+ $stdout.puts SL.version_check.to_s
602
+ $stdout.puts "See https://github.com/ttscoff/searchlink/wiki for help"
603
+ end
604
+ print input
605
+ when /^!?(wiki|docs)$/i
606
+ warn "Opening wiki in browser"
607
+ `open https://github.com/ttscoff/searchlink/wiki`
608
+ when /^!?v(er(s(ion)?)?)?$/
609
+ print "[#{SL.version_check}]"
610
+ when /^!?up(date|grade)$/
611
+ SL.update_searchlink
612
+ print SL.output.join("")
613
+ end
614
+ Process.exit 0
615
+ end
616
+
617
+ def create_footnote(mtch)
618
+ if mtch[1].nil? || mtch[1] == ""
619
+ match
620
+ else
621
+ note = mtch[1].strip
622
+ @footnote_counter += 1
623
+ ref = if !@link_text.empty? && @link_text.scan(/\s/).empty?
624
+ @link_text
625
+ else
626
+ format("%<p>sfn%<c>04d", p: @prefix, c: @footnote_counter)
627
+ end
628
+ SL.add_footer "[^#{ref}]: #{note}"
629
+ res = "[^#{ref}]"
630
+ @cursor_difference += (SL.match_length - res.length)
631
+ SL.match_length = res.length
632
+ SL.add_report("#{@match_string} => Footnote #{ref}")
633
+ res
634
+ end
635
+ end
636
+
637
+ def custom_search(search_type, search_terms)
638
+ SL.config["custom_site_searches"].each do |k, v|
639
+ next unless search_type == k
640
+
641
+ @link_text = search_terms if !SL.titleize && @link_text == ""
642
+ v = parse_arguments(v, { no_restore: true })
643
+ query, v = v.extract_query({})
644
+
645
+ SL.add_query(query)
646
+
647
+ if v =~ %r{^(/|http)}i
648
+ search_type = "r"
649
+ tokens = v.scan(/\$term\d+[ds]?/).sort.uniq
650
+
651
+ if !tokens.empty?
652
+ highest_token = 0
653
+ tokens.each do |token|
654
+ if token =~ /(\d+)[ds]?$/ && Regexp.last_match(1).to_i > highest_token
655
+ highest_token = Regexp.last_match(1).to_i
656
+ end
657
+ end
658
+ terms_p = search_terms.split(/ +/)
659
+ if terms_p.length > highest_token
660
+ remainder = terms_p[highest_token - 1..].join(" ")
661
+ terms_p = terms_p[0..highest_token - 2]
662
+ terms_p.push(remainder)
663
+ end
664
+ tokens.each do |t|
665
+ next unless t =~ /(\d+)[ds]?$/
666
+
667
+ int = Regexp.last_match(1).to_i - 1
668
+ replacement = terms_p[int]
669
+ case t
670
+ when /d$/
671
+ replacement.downcase!
672
+ re_down = ""
673
+ when /s$/
674
+ replacement.slugify!
675
+ re_down = ""
676
+ else
677
+ re_down = "(?!d|s)"
678
+ end
679
+ v.gsub!(/#{Regexp.escape(t) + re_down}/, replacement.url_encode)
680
+ end
681
+ search_terms = v
682
+ else
683
+ search_terms = v.gsub(/\$term[ds]?/i) do |mtch|
684
+ search_terms.downcase! if mtch =~ /d$/i
685
+ search_terms.slugify! if mtch =~ /s$/i
686
+ search_terms.url_encode
687
+ end
688
+ end
689
+ else
690
+ search_type = SL::GoogleSearch.api_key? ? "gg" : "g"
691
+ search_terms = "site:#{v} #{search_terms}"
692
+ end
693
+
694
+ break
695
+ end
696
+ [search_type, search_terms]
697
+ end
734
698
  end
735
699
  end
@@ -49,7 +49,6 @@ module SL
49
49
  return false unless bitly_config?
50
50
 
51
51
  domain = SL.config.key?("bitly_domain") ? SL.config["bitly_domain"] : "bit.ly"
52
- url.dup
53
52
 
54
53
  headers = {
55
54
  "Content-Type" => "application/json",
@@ -68,8 +68,13 @@ module SL
68
68
  ## @return [Array] [url, title, date]
69
69
  ##
70
70
  def search_safari_bookmarks(terms)
71
- data = `plutil -convert xml1 -o - ~/Library/Safari/Bookmarks.plist`.strip
71
+ data = `plutil -extract Children xml1 -o - ~/Library/Safari/Bookmarks.plist`.strip
72
+
73
+ data.gsub!(%r{<key>Data</key>\s+<data>(.+?)</data>\n}m, "")
74
+ data.gsub!(/\t/, "")
75
+
72
76
  parent = Plist.parse_xml(data)
77
+
73
78
  results = get_safari_bookmarks(parent, terms)
74
79
  return false if results.empty?
75
80
 
@@ -45,12 +45,11 @@ module SL
45
45
  ],
46
46
  config: [
47
47
  {
48
- description:
49
- ["Remove or comment (with #) history searches you don't want",
50
- "performed by `!h`. You can force-enable them per search, e.g.",
51
- "`!hsh` (Safari History only), `!hcb` (Chrome Bookmarks only)",
52
- "etc. Multiple types can be strung together: !hshcb (Safari",
53
- "History and Chrome bookmarks)"].join(" "),
48
+ description: ["Remove or comment (with #) history searches you don't want",
49
+ "performed by `!h`. You can force-enable them per search, e.g.",
50
+ "`!hsh` (Safari History only), `!hcb` (Chrome Bookmarks only)",
51
+ "etc. Multiple types can be strung together: !hshcb (Safari",
52
+ "History and Chrome bookmarks)"].join(" "),
54
53
  required: false,
55
54
  key: "history_types",
56
55
  value: %w[
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SL
4
+ # is.gd link shortening
5
+ class TinyurlSearch
6
+ class << self
7
+ def settings
8
+ {
9
+ trigger: "tiny",
10
+ searches: [
11
+ ["tiny", "TinyURL Shorten"]
12
+ ],
13
+ config: [
14
+ {
15
+ key: "tinyurl_access_token",
16
+ value: "",
17
+ required: true,
18
+ description: "Generate a tinyurl API key at https://tinyurl.ph/developers (login required)"
19
+ }
20
+ ]
21
+ }
22
+ end
23
+
24
+ def search(_, search_terms, link_text)
25
+ if SL::URL.url?(search_terms)
26
+ link = search_terms
27
+ else
28
+ link, title, link_text = SL.ddg(search_terms, link_text)
29
+ end
30
+
31
+ url = shorten(link)
32
+ title = SL::URL.title(link) if title.nil? || title.empty?
33
+ link_text = title if (link_text.nil? || link_text.empty?) && !SL.titleize
34
+ format_response(url, link, link_text)
35
+ end
36
+
37
+ def shorten(url)
38
+ return false unless tinyurl_config?
39
+
40
+ headers = {
41
+ "Content-Type" => "application/json",
42
+ "Authorization" => "Bearer #{SL.config['tinyurl_access_token']}"
43
+ }
44
+ data_obj = {
45
+ "url" => url
46
+ }
47
+ data = Curl::Json.new("https://tinyurl.ph/api/url/add", data: data_obj.to_json, headers: headers, symbolize_names: true)
48
+
49
+ if data.json[:error].positive?
50
+ SL.add_error("Error creating tinyurl", data.json[:error])
51
+ return false
52
+ end
53
+
54
+ data.json[:shorturl]
55
+ end
56
+
57
+ def tinyurl_config?
58
+ return true if SL.config["tinyurl_access_token"] && !SL.config["tinyurl_access_token"].empty?
59
+
60
+ SL.add_error("TinyURL not configured", "Missing access token")
61
+ false
62
+ end
63
+
64
+ def format_response(link, original_url, link_text)
65
+ rtitle = SL::URL.title(original_url)
66
+ [link, rtitle, link_text == "" && !SL.titleize ? rtitle : link_text]
67
+ end
68
+ end
69
+
70
+ SL::Searches.register "tiny", :search, self
71
+ end
72
+ end
@@ -248,5 +248,8 @@ require_relative "searches/youtube"
248
248
  # import
249
249
  require_relative "searches/stackoverflow"
250
250
 
251
+ # import
252
+ require_relative "searches/tinyurl"
253
+
251
254
  # import
252
255
  require_relative "searches/linkding"
@@ -37,6 +37,73 @@ module SL
37
37
  replace scrub
38
38
  end
39
39
 
40
+ # Extract query string from search string
41
+ def extract_query(known_queries = {})
42
+ string = gsub(/\?((\S+?)=(\S+?)(?=&|$|\s))+/) do |mtch|
43
+ tokens = mtch.sub(/^\?/, "").split("&")
44
+ tokens.each do |token|
45
+ key, value = token.split("=")
46
+
47
+ known_queries[key] = value
48
+ end
49
+
50
+ ""
51
+ end.gsub(/ +/, " ").strip
52
+
53
+ [known_queries, string]
54
+ end
55
+
56
+ # Extract a shortner from a string
57
+ def extract_shortener
58
+ return self unless self =~ /_[ibt]$/i
59
+
60
+ shortener = split(/_/).last
61
+ SL.shortener = case shortener
62
+ when /i/i
63
+ :isgd
64
+ when /b/i
65
+ :bitly
66
+ when /t/i
67
+ :tinyurl
68
+ else
69
+ :none
70
+ end
71
+
72
+ sub(/_[ibt]$/i, "")
73
+ end
74
+
75
+ # Destructive version of #extract_shortener
76
+ # @see #extract_shortener
77
+ # @return [String] The string without the shortener
78
+ def extract_shortener!
79
+ replace extract_shortener
80
+ end
81
+
82
+ # Format and append a query string
83
+ #
84
+ # @return [String] The formatted query string
85
+ #
86
+ def add_query_string
87
+ return self if SL.query.empty?
88
+
89
+ query = SL.query.map { |k, v| "#{k}=#{v}" }.join("&")
90
+
91
+ query = if self =~ /\?[^= ]+=\S+/
92
+ "&#{query}"
93
+ else
94
+ "?#{query}"
95
+ end
96
+
97
+ "#{self}#{query}"
98
+ end
99
+
100
+ # Destructive version of #add_query_string
101
+ # @see #add_query_string
102
+ # @return [String] The formatted query string
103
+ def add_query_string!
104
+ replace add_query_string
105
+ end
106
+
40
107
  # URL Encode string
41
108
  #
42
109
  # @return [String] url encoded string
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SL
4
- VERSION = "2.3.84"
4
+ VERSION = "2.3.85"
5
5
  end
6
6
 
7
7
  # Main module
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: searchlink
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.84
4
+ version: 2.3.85
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-03-27 00:00:00.000000000 Z
10
+ date: 2025-03-30 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64
@@ -141,14 +141,14 @@ dependencies:
141
141
  requirements:
142
142
  - - "~>"
143
143
  - !ruby/object:Gem::Version
144
- version: 3.7.1
144
+ version: 3.7.2
145
145
  type: :development
146
146
  prerelease: false
147
147
  version_requirements: !ruby/object:Gem::Requirement
148
148
  requirements:
149
149
  - - "~>"
150
150
  - !ruby/object:Gem::Version
151
- version: 3.7.1
151
+ version: 3.7.2
152
152
  - !ruby/object:Gem::Dependency
153
153
  name: rake
154
154
  requirement: !ruby/object:Gem::Requirement
@@ -309,6 +309,7 @@ files:
309
309
  - lib/searchlink/searches/spelling.rb
310
310
  - lib/searchlink/searches/spotlight.rb
311
311
  - lib/searchlink/searches/stackoverflow.rb
312
+ - lib/searchlink/searches/tinyurl.rb
312
313
  - lib/searchlink/searches/tmdb.rb
313
314
  - lib/searchlink/searches/twitter.rb
314
315
  - lib/searchlink/searches/wikipedia.rb