searchlink 2.3.84 → 2.3.86

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.
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SL
4
+ class PopupSearch
5
+ class << self
6
+ def settings
7
+ {
8
+ trigger: "pop(up)?[gaw]?",
9
+ searches: [
10
+ ["pop", "Popup DuckDuckGo Search"],
11
+ ["popa", "Popup Amazon Search"],
12
+ ["popg", "Popup Google Search"],
13
+ ["popw", "Popup Wikipedia Search"]
14
+ ],
15
+ }
16
+ end
17
+
18
+ def search(type, search_terms, link_text)
19
+ return [false, false, link_text] unless workflow_exist?
20
+
21
+ term = search_terms.url_encode
22
+ url = case type
23
+ when /g$/
24
+ "https://www.google.com/search?hl=en&q=#{term}"
25
+ when /a$/
26
+ "https://www.amazon.com/s?k=#{term}"
27
+ when /b$/
28
+ "https://www.bing.com/search?q=#{term}"
29
+ when /w$/
30
+ "https://en.wikipedia.org/w/index.php?search=#{term}&title=Special%3ASearch&ns0=1"
31
+ else
32
+ "https://duckduckgo.com/?q=#{term}&ia=web"
33
+ end
34
+
35
+ path = File.expand_path("~/Library/Services/Preview URL.workflow")
36
+ res = `automator -i "#{url}" "#{path}"`.strip
37
+
38
+ begin
39
+ if res.empty?
40
+ SL.add_error("Canceled", "Popup Search Cancelled")
41
+ return [false, false, link_text]
42
+ end
43
+
44
+ title = SL::URL.title(res)
45
+
46
+ link_text = title if link_text == "" && !SL.titleize
47
+
48
+ [res, title, link_text]
49
+ rescue StandardError
50
+ false
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def workflow_exist?
57
+ unless File.exist?(File.expand_path("~/Library/Services/Preview URL.workflow"))
58
+ SL.add_error("Missing Service", "Preview URL Service not installed")
59
+ return false
60
+ end
61
+
62
+ true
63
+ end
64
+ end
65
+
66
+ SL::Searches.register "popup", :search, self
67
+ end
68
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ # import
4
+ require_relative "shorteners/bitly"
5
+
6
+ # import
7
+ require_relative "shorteners/isgd"
8
+
9
+ # import
10
+ require_relative "shorteners/tinyurl"
11
+
12
+ module SL
13
+ class Shortener
14
+ class << self
15
+ # Displays an AppleScript dialog to confirm shortening a URL.
16
+ #
17
+ # @param url [String] The URL to confirm shortening for.
18
+ # @return [Boolean] True if the user confirms, false otherwise.
19
+ #
20
+ def confirm?(url, title: "Confirm URL?")
21
+ if File.exist?(File.expand_path("~/Library/Services/Preview URL.workflow"))
22
+ cmd = %(osascript -e "display dialog \\"#{url}\\" with title \\"#{title}\\" buttons {\\"Cancel\\", \\"Confirm\\", \\"Preview\\"}")
23
+
24
+ res = `#{cmd}`.strip
25
+
26
+ if res =~ /Preview/
27
+ path = File.expand_path("~/Library/Services/Preview URL.workflow")
28
+ res = `automator -i "#{url}" "#{path}"`.strip
29
+
30
+ return res.empty? ? false : res
31
+ else
32
+ return res =~ /Confirm/
33
+ end
34
+ end
35
+
36
+ res = system(%(osascript -e "display dialog \"#{url}\" with title \"#{title}\" buttons {\"Cancel\", \"Confirm\"}"))
37
+
38
+ res == 0
39
+ end
40
+
41
+ # Shortens a URL using the specified shortener.
42
+ #
43
+ # @param url [String] The URL to shorten.
44
+ # @param shortener [Symbol] The shortener to use (:tinyurl, :bitly, :isgd).
45
+ # @return [String] The shortened URL.
46
+ # @raise [ArgumentError] If the shortener is unknown.
47
+ #
48
+ # @example
49
+ # SL::Shortener.shorten('http://example.com', :tinyurl)
50
+ #
51
+ def shorten(url, shortener)
52
+ # Check if the URL is already shortened
53
+ return url unless SL::URL.url?(url)
54
+
55
+ known_shorteners = %i[tinyurl bitly isgd]
56
+ return url unless known_shorteners.include?(shortener)
57
+
58
+ # Confirm shortening the URL
59
+ res = SL::Shortener.confirm?(url)
60
+
61
+ return url unless res
62
+
63
+ url = res if res.is_a?(String)
64
+
65
+ return url unless SL::URL.url?(url)
66
+
67
+ # Use the shortener to shorten the URL
68
+ case shortener
69
+ when :tinyurl
70
+ SL::TinyurlSearch.shorten(url)
71
+ when :bitly
72
+ SL::BitlySearch.shorten(url)
73
+ when :isgd
74
+ SL::IsgdSearch.shorten(url)
75
+ else
76
+ raise ArgumentError, "Unknown shortener"
77
+ end
78
+ end
79
+ end
80
+ end
81
+ 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",
@@ -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
@@ -43,9 +43,9 @@ module SL
43
43
  #
44
44
  def available_searches_html
45
45
  searches = plugins[:search]
46
- .flat_map { |_, plugin| plugin[:searches] }
47
- .reject { |s| s[1].nil? }
48
- .sort_by { |s| s[0].is_a?(Array) ? s[0][0] : s[0] }
46
+ .flat_map { |_, plugin| plugin[:searches] }
47
+ .reject { |s| s[1].nil? }
48
+ .sort_by { |s| s[0].is_a?(Array) ? s[0][0] : s[0] }
49
49
  out = ['<table id="searches">',
50
50
  "<thead><td>Shortcut</td><td>Search Type</td></thead>",
51
51
  "<tbody>"]
@@ -53,7 +53,7 @@ module SL
53
53
  searches.each do |s|
54
54
  out << "<tr>
55
55
  <td>
56
- <code>!#{s[0].is_a?(Array) ? "#{s[0][0]} (#{s[0][1..].join(',')})" : s[0]}
56
+ <code>!#{s[0].is_a?(Array) ? "#{s[0][0]} (#{s[0][1..].join(",")})" : s[0]}
57
57
  </code>
58
58
  </td><td>#{s[1]}</td></tr>"
59
59
  end
@@ -72,10 +72,10 @@ module SL
72
72
 
73
73
  searches.each do |s|
74
74
  shortcut = if s[0].is_a?(Array)
75
- "#{s[0][0]} (#{s[0][1..].join(',')})"
76
- else
77
- s[0]
78
- end
75
+ "#{s[0][0]} (#{s[0][1..].join(",")})"
76
+ else
77
+ s[0]
78
+ end
79
79
 
80
80
  out << "!#{shortcut}#{shortcut.spacer}#{s[1]}"
81
81
  end
@@ -95,7 +95,7 @@ module SL
95
95
 
96
96
  def did_you_mean(term)
97
97
  matches = best_search_match(term)
98
- matches.empty? ? "" : ", did you mean #{matches.map { |m| "!#{m}" }.join(', ')}?"
98
+ matches.empty? ? "" : ", did you mean #{matches.map { |m| "!#{m}" }.join(", ")}?"
99
99
  end
100
100
 
101
101
  def valid_searches
@@ -106,7 +106,7 @@ module SL
106
106
 
107
107
  def valid_search?(term)
108
108
  valid = false
109
- valid = true if term =~ /^(#{valid_searches.join('|')})$/
109
+ valid = true if term =~ /^(#{valid_searches.join("|")})$/
110
110
  valid = true if SL.config["custom_site_searches"].keys.include? term
111
111
  # SL.notify("Invalid search#{did_you_mean(term)}", term) unless valid
112
112
  valid
@@ -124,7 +124,7 @@ module SL
124
124
  trigger: settings.fetch(:trigger, title).normalize_trigger,
125
125
  searches: settings[:searches],
126
126
  config: settings[:config],
127
- class: klass
127
+ class: klass,
128
128
  }
129
129
  end
130
130
 
@@ -155,11 +155,11 @@ module SL
155
155
  config = IO.read(file)
156
156
 
157
157
  cfg = case ext
158
- when /^y/i
159
- YAML.safe_load(config)
160
- else
161
- JSON.parse(config)
162
- end
158
+ when /^y/i
159
+ YAML.safe_load(config)
160
+ else
161
+ JSON.parse(config)
162
+ end
163
163
  cfg["filename"] = File.basename(file)
164
164
  cfg["path"] = file.shorten_path
165
165
  SL::ScriptSearch.new(cfg)
@@ -169,10 +169,12 @@ module SL
169
169
  def do_search(search_type, search_terms, link_text, timeout: SL.config["timeout"])
170
170
  plugins[:search].each_value do |plugin|
171
171
  trigger = plugin[:trigger].gsub(/(^\^|\$$)/, "")
172
- if search_type =~ /^#{trigger}$/
173
- search = proc { plugin[:class].search(search_type, search_terms, link_text) }
174
- return SL::Util.search_with_timeout(search, timeout)
175
- end
172
+ next unless search_type =~ /^#{trigger}$/
173
+
174
+ search = proc { plugin[:class].search(search_type, search_terms, link_text) }
175
+ SL.config["skip_timeout"] = true if trigger =~ /^pop/
176
+
177
+ return SL::Util.search_with_timeout(search, timeout)
176
178
  end
177
179
  end
178
180
  end
@@ -188,8 +190,8 @@ require_relative "searches/itunes"
188
190
  # import
189
191
  require_relative "searches/amazon"
190
192
 
191
- # import
192
- require_relative "searches/bitly"
193
+ #import
194
+ require_relative "searches/shortener"
193
195
 
194
196
  # import
195
197
  require_relative "searches/definition"
@@ -209,15 +211,15 @@ require_relative "searches/history"
209
211
  # import
210
212
  require_relative "searches/hook"
211
213
 
212
- # import
213
- require_relative "searches/isgd"
214
-
215
214
  # import
216
215
  require_relative "searches/lastfm"
217
216
 
218
217
  # import
219
218
  require_relative "searches/pinboard"
220
219
 
220
+ # import
221
+ require_relative "searches/popup"
222
+
221
223
  # import
222
224
  require_relative "searches/setapp"
223
225
 
@@ -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
@@ -78,27 +145,29 @@ module SL
78
145
 
79
146
  # parse command line flags into long options
80
147
  def parse_flags
81
- gsub(/(\+\+|--)([dirtvs]+)\b/) do
148
+ gsub(/(\+\+|--)([dirtvsc]+)\b/) do
82
149
  m = Regexp.last_match
83
150
  bool = m[1] == "++" ? "" : "no-"
84
151
  output = " "
85
152
  m[2].split("").each do |arg|
86
153
  output += case arg
87
- when "d"
88
- "--#{bool}debug "
89
- when "i"
90
- "--#{bool}inline "
91
- when "r"
92
- "--#{bool}prefix_random "
93
- when "t"
94
- "--#{bool}include_titles "
95
- when "v"
96
- "--#{bool}validate_links "
97
- when "s"
98
- "--#{bool}remove_seo "
99
- else
100
- ""
101
- end
154
+ when "c"
155
+ "--#{bool}confirm"
156
+ when "d"
157
+ "--#{bool}debug "
158
+ when "i"
159
+ "--#{bool}inline "
160
+ when "r"
161
+ "--#{bool}prefix_random "
162
+ when "t"
163
+ "--#{bool}include_titles "
164
+ when "v"
165
+ "--#{bool}validate_links "
166
+ when "s"
167
+ "--#{bool}remove_seo "
168
+ else
169
+ ""
170
+ end
102
171
  end
103
172
 
104
173
  output
@@ -241,7 +310,7 @@ module SL
241
310
  "‘" => "’",
242
311
  "[" => "]",
243
312
  "(" => ")",
244
- "<" => ">"
313
+ "<" => ">",
245
314
  }
246
315
 
247
316
  left_punct = []
@@ -311,7 +380,7 @@ module SL
311
380
  # p_re = path.path_elements.map{|seg| seg.downcase.split(//).join('.?') }.join('|')
312
381
  # re_parts.push(p_re) if p_re.length > 0
313
382
 
314
- site_re = "(#{re_parts.join('|')})"
383
+ site_re = "(#{re_parts.join("|")})"
315
384
 
316
385
  dead_switch = 0
317
386
 
@@ -336,14 +405,14 @@ module SL
336
405
  end
337
406
 
338
407
  title = if parts.empty?
339
- longest
340
- elsif parts.length < 2
341
- parts.join(sep)
342
- elsif parts.length > 2
343
- parts.longest_element.strip
344
- else
345
- parts.join(sep)
346
- end
408
+ longest
409
+ elsif parts.length < 2
410
+ parts.join(sep)
411
+ elsif parts.length > 2
412
+ parts.longest_element.strip
413
+ else
414
+ parts.join(sep)
415
+ end
347
416
  end
348
417
  dead_switch += 1
349
418
  end
@@ -354,7 +423,7 @@ module SL
354
423
  return self
355
424
  end
356
425
 
357
- seps = Regexp.new(" *[#{seo_title_separators.map { |s| Regexp.escape(s) }.join('')}] +")
426
+ seps = Regexp.new(" *[#{seo_title_separators.map { |s| Regexp.escape(s) }.join("")}] +")
358
427
  if title =~ seps
359
428
  seo_parts = title.split(seps)
360
429
  title = seo_parts.longest_element.strip if seo_parts.length.positive?
@@ -457,13 +526,13 @@ module SL
457
526
  (1..n).each do |j|
458
527
  (1..m).each do |i|
459
528
  d[i][j] = if s[i - 1] == t[j - 1] # adjust index into string
460
- d[i - 1][j - 1] # no operation required
461
- else
462
- [d[i - 1][j] + 1, # deletion
463
- d[i][j - 1] + 1, # insertion
464
- d[i - 1][j - 1] + 1 # substitution
465
- ].min
466
- end
529
+ d[i - 1][j - 1] # no operation required
530
+ else
531
+ [d[i - 1][j] + 1, # deletion
532
+ d[i][j - 1] + 1, # insertion
533
+ d[i - 1][j - 1] + 1 # substitution
534
+ ].min
535
+ end
467
536
  end
468
537
  end
469
538
  d[m][n]
@@ -476,7 +545,7 @@ module SL
476
545
  ##
477
546
  def matches_exact(string)
478
547
  comp = gsub(/[^a-z0-9 ]/i, "")
479
- comp =~ /\b#{string.gsub(/[^a-z0-9 ]/i, '').split(/ +/).map { |s| Regexp.escape(s) }.join(' +')}/i
548
+ comp =~ /\b#{string.gsub(/[^a-z0-9 ]/i, "").split(/ +/).map { |s| Regexp.escape(s) }.join(" +")}/i
480
549
  end
481
550
 
482
551
  ##
@@ -524,7 +593,7 @@ module SL
524
593
  def to_rx_array(separator: " ", start_word: true)
525
594
  bound = start_word ? '\b' : ""
526
595
  str = gsub(/(#{separator})+/, separator)
527
- str.split(/#{separator}/).map { |arg| /#{bound}#{arg.gsub(/[^a-z0-9]/i, '.?')}/i }
596
+ str.split(/#{separator}/).map { |arg| /#{bound}#{arg.gsub(/[^a-z0-9]/i, ".?")}/i }
528
597
  end
529
598
 
530
599
  ##
@@ -146,6 +146,8 @@ module SL
146
146
  # end
147
147
 
148
148
  begin
149
+ return File.basename(url) if url =~ /\.(gif|jpe?g|png|web[mp]|bmp|svg|tiff?|pdf|avif)$/i
150
+
149
151
  if url =~ %r{https://(amzn.to|(www\.)?amazon\.com)/}
150
152
  final_url = follow_redirects(url)
151
153
  m = final_url.match(%r{https://www.amazon.com/(.*?)/dp/})
@@ -56,9 +56,10 @@ module SL
56
56
  ## @return [Array] url, title, link_text
57
57
  ##
58
58
  def search_with_timeout(search, timeout)
59
- url = nil
60
- title = nil
61
- link_text = nil
59
+ if SL.config["skip_timeout"] || SL.config["confirm"]
60
+ url, title, link_text = search.call
61
+ return [url, title, link_text]
62
+ end
62
63
 
63
64
  begin
64
65
  Timeout.timeout(timeout) do
@@ -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.86'
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.86
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-31 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
@@ -287,7 +287,6 @@ files:
287
287
  - lib/searchlink/searches.rb
288
288
  - lib/searchlink/searches/amazon.rb
289
289
  - lib/searchlink/searches/applemusic.rb
290
- - lib/searchlink/searches/bitly.rb
291
290
  - lib/searchlink/searches/definition.rb
292
291
  - lib/searchlink/searches/duckduckgo.rb
293
292
  - lib/searchlink/searches/github.rb
@@ -297,13 +296,17 @@ files:
297
296
  - lib/searchlink/searches/helpers/safari.rb
298
297
  - lib/searchlink/searches/history.rb
299
298
  - lib/searchlink/searches/hook.rb
300
- - lib/searchlink/searches/isgd.rb
301
299
  - lib/searchlink/searches/itunes.rb
302
300
  - lib/searchlink/searches/lastfm.rb
303
301
  - lib/searchlink/searches/linkding.rb
304
302
  - lib/searchlink/searches/lyrics.rb
305
303
  - lib/searchlink/searches/pinboard.rb
304
+ - lib/searchlink/searches/popup.rb
306
305
  - lib/searchlink/searches/setapp.rb
306
+ - lib/searchlink/searches/shortener.rb
307
+ - lib/searchlink/searches/shorteners/bitly.rb
308
+ - lib/searchlink/searches/shorteners/isgd.rb
309
+ - lib/searchlink/searches/shorteners/tinyurl.rb
307
310
  - lib/searchlink/searches/social.rb
308
311
  - lib/searchlink/searches/software.rb
309
312
  - lib/searchlink/searches/spelling.rb