searchlink 2.3.83 → 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 +4 -4
- data/lib/searchlink/curl/html.rb +1 -1
- data/lib/searchlink/output.rb +33 -2
- data/lib/searchlink/parse.rb +220 -250
- data/lib/searchlink/searches/bitly.rb +46 -22
- data/lib/searchlink/searches/github.rb +7 -2
- data/lib/searchlink/searches/google.rb +1 -1
- data/lib/searchlink/searches/helpers/safari.rb +6 -1
- data/lib/searchlink/searches/history.rb +5 -6
- data/lib/searchlink/searches/isgd.rb +7 -10
- data/lib/searchlink/searches/linkding.rb +17 -3
- data/lib/searchlink/searches/setapp.rb +131 -0
- data/lib/searchlink/searches/tinyurl.rb +72 -0
- data/lib/searchlink/searches.rb +6 -0
- data/lib/searchlink/string.rb +91 -0
- data/lib/searchlink/version.rb +2 -2
- data/lib/tokens.rb +3 -1
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 02b111da9ce13f8646d7357ae5560a1c6b84bb45fd506dca8fafe96e1c844bea
|
4
|
+
data.tar.gz: 02fd69545df789fdf1105dea4971b53f02bfc33ac0da20a3f25454c11f9a9ae7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7af24cad60ca01ead8ab4a76f82c6d6fa9be6aa30752f088c23cfe387d9a3adf8f076e5e4d1f1dc25d9eeda8879d1ae7e83bbee3bcea250de6914dc67d35cf0c
|
7
|
+
data.tar.gz: '089fb329c4c7be4cf30bf929b8c4309374a0b8055f42eb2adeb2c62d29f7a9ef84d9e45527a5c0a13c7e0061345ee814cfd431bc6f33614ad034f8d8c970250a'
|
data/lib/searchlink/curl/html.rb
CHANGED
@@ -111,7 +111,7 @@ module Curl
|
|
111
111
|
def extract_tag_contents(tag, source: false)
|
112
112
|
return @body.scan(%r{<#{tag}.*?>(?:.*?</#{tag}>)?}) if source
|
113
113
|
|
114
|
-
@body.scan(/<#{tag}.*?>(.*?)</).map { |t| t[
|
114
|
+
@body.scan(/<#{tag}.*?>(.*?)</).map { |t| t[1] }
|
115
115
|
end
|
116
116
|
|
117
117
|
##
|
data/lib/searchlink/output.rb
CHANGED
@@ -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.
|
data/lib/searchlink/parse.rb
CHANGED
@@ -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
|
@@ -391,6 +206,8 @@ module SL
|
|
391
206
|
m[1]
|
392
207
|
end
|
393
208
|
|
209
|
+
search_type.extract_shortener!
|
210
|
+
|
394
211
|
search_terms = m[2].gsub(/(^["']|["']$)/, "")
|
395
212
|
search_terms.strip!
|
396
213
|
|
@@ -414,17 +231,22 @@ module SL
|
|
414
231
|
link_only = true if search_terms =~ /!!\^?$/
|
415
232
|
|
416
233
|
search_terms = search_terms.sub(/(!!)?\^?(!!)?$/, "")
|
234
|
+
|
235
|
+
if search_type =~ /^(\S+\.)+\S+$/
|
236
|
+
search_type = "g"
|
237
|
+
search_terms = "site:#{m[1]} #{search_terms}"
|
238
|
+
end
|
417
239
|
elsif link_info =~ /^!/
|
418
240
|
search_word = link_info.match(/^!(\S+)/)
|
419
|
-
|
420
|
-
if search_word && SL::Searches.valid_search?(
|
421
|
-
search_type =
|
241
|
+
st = search_word[1].extract_shortener
|
242
|
+
if search_word && SL::Searches.valid_search?(st)
|
243
|
+
search_type = st unless search_word.nil?
|
422
244
|
search_terms = @link_text
|
423
|
-
elsif search_word &&
|
245
|
+
elsif search_word && st =~ /^(\S+\.)+\S+$/
|
424
246
|
search_type = SL::GoogleSearch.api_key? ? "gg" : "g"
|
425
247
|
search_terms = "site:#{search_word[1]} #{@link_text}"
|
426
248
|
else
|
427
|
-
SL.add_error("Invalid search#{SL::Searches.did_you_mean(
|
249
|
+
SL.add_error("Invalid search#{SL::Searches.did_you_mean(st)}", match)
|
428
250
|
search_type = false
|
429
251
|
search_terms = false
|
430
252
|
end
|
@@ -444,6 +266,8 @@ module SL
|
|
444
266
|
search_type, search_terms = custom_search(search_type, search_terms)
|
445
267
|
end
|
446
268
|
|
269
|
+
SL.add_query(query) if query
|
270
|
+
|
447
271
|
if (search_type && search_terms) || @url
|
448
272
|
# warn "Searching #{search_type} for #{search_terms}"
|
449
273
|
|
@@ -538,12 +362,13 @@ module SL
|
|
538
362
|
SL.add_report("Processed: #{total_links} links, #{counter_errors} errors.")
|
539
363
|
SL.print_report
|
540
364
|
SL.print_errors
|
541
|
-
else
|
365
|
+
else # Assume single line input
|
542
366
|
link_only = false
|
543
367
|
SL.clipboard = false
|
544
368
|
|
545
369
|
res = parse_arguments(input.strip!).strip
|
546
370
|
input = res.nil? ? input.strip : res
|
371
|
+
query, input = input.extract_query({})
|
547
372
|
|
548
373
|
# if the end of input contain "^", copy to clipboard instead of STDOUT
|
549
374
|
SL.clipboard = true if input =~ /\^[!~:\s]*$/
|
@@ -602,8 +427,9 @@ module SL
|
|
602
427
|
|
603
428
|
case input
|
604
429
|
when /^!(\S+)\s+(.*)$/
|
605
|
-
type = Regexp.last_match(1)
|
430
|
+
type = Regexp.last_match(1).extract_shortener
|
606
431
|
link_info = Regexp.last_match(2).strip
|
432
|
+
|
607
433
|
@link_text ||= link_info
|
608
434
|
terms = link_info + additional_terms
|
609
435
|
terms.strip!
|
@@ -611,60 +437,7 @@ module SL
|
|
611
437
|
if SL::Searches.valid_search?(type) || type =~ /^(\S+\.)+\S+$/
|
612
438
|
if type && terms && !terms.empty?
|
613
439
|
# Iterate through custom searches for a match, perform search if matched
|
614
|
-
|
615
|
-
next unless type == k
|
616
|
-
|
617
|
-
@link_text = terms if @link_text == ""
|
618
|
-
v = parse_arguments(v, { no_restore: true })
|
619
|
-
if v =~ %r{^(/|http)}i
|
620
|
-
type = "r"
|
621
|
-
tokens = v.scan(/\$term\d+[ds]?/).sort.uniq
|
622
|
-
|
623
|
-
if !tokens.empty?
|
624
|
-
highest_token = 0
|
625
|
-
tokens.each do |token|
|
626
|
-
t = Regexp.last_match(1)
|
627
|
-
highest_token = t.to_i if token =~ /(\d+)d?$/ && t.to_i > highest_token
|
628
|
-
end
|
629
|
-
terms_p = terms.split(/ +/)
|
630
|
-
if terms_p.length > highest_token
|
631
|
-
remainder = terms_p[highest_token - 1..].join(" ")
|
632
|
-
terms_p = terms_p[0..highest_token - 2]
|
633
|
-
terms_p.push(remainder)
|
634
|
-
end
|
635
|
-
tokens.each do |t|
|
636
|
-
next unless t =~ /(\d+)d?$/
|
637
|
-
|
638
|
-
int = Regexp.last_match(1).to_i - 1
|
639
|
-
replacement = terms_p[int]
|
640
|
-
|
641
|
-
re_down = case t
|
642
|
-
when /d$/
|
643
|
-
replacement.downcase!
|
644
|
-
""
|
645
|
-
when /s$/
|
646
|
-
replacement.slugify!
|
647
|
-
""
|
648
|
-
else
|
649
|
-
"(?!d|s)"
|
650
|
-
end
|
651
|
-
v.gsub!(/#{Regexp.escape(t) + re_down}/, replacement.url_encode)
|
652
|
-
end
|
653
|
-
terms = v
|
654
|
-
else
|
655
|
-
terms = v.gsub(/\$term[ds]?/i) do |mtch|
|
656
|
-
terms.downcase! if mtch =~ /d$/i
|
657
|
-
terms.slugify! if mtch =~ /s$/i
|
658
|
-
terms.url_encode
|
659
|
-
end
|
660
|
-
end
|
661
|
-
else
|
662
|
-
type = SL::GoogleSearch.api_key? ? "gg" : "g"
|
663
|
-
terms = "site:#{v} #{terms}"
|
664
|
-
end
|
665
|
-
|
666
|
-
break
|
667
|
-
end
|
440
|
+
type, terms = custom_search(type, terms)
|
668
441
|
end
|
669
442
|
|
670
443
|
# if contains TLD, use site-specific search
|
@@ -675,6 +448,7 @@ module SL
|
|
675
448
|
@search_count ||= 0
|
676
449
|
@search_count += 1
|
677
450
|
|
451
|
+
SL.add_query(query) if query
|
678
452
|
@url, title, @link_text = do_search(type, terms, @link_text, @search_count)
|
679
453
|
else
|
680
454
|
SL.add_error("Invalid search#{SL::Searches.did_you_mean(type)}", input)
|
@@ -689,9 +463,11 @@ module SL
|
|
689
463
|
"t"
|
690
464
|
end
|
691
465
|
@link_text = input.sub(/^[tfilm]/, "")
|
466
|
+
SL.add_query(query) if query
|
692
467
|
@url, title = SL::SocialSearch.social_handle(type, @link_text)
|
693
468
|
@link_text = title
|
694
469
|
else
|
470
|
+
SL.add_query(query) if query
|
695
471
|
@link_text ||= input
|
696
472
|
@url, title, @link_text = SL.ddg(input, @link_text)
|
697
473
|
end
|
@@ -725,5 +501,199 @@ module SL
|
|
725
501
|
end
|
726
502
|
end
|
727
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
|
728
698
|
end
|
729
699
|
end
|
@@ -14,11 +14,12 @@ module SL
|
|
14
14
|
config: [
|
15
15
|
{
|
16
16
|
key: "bitly_access_token",
|
17
|
-
value: "
|
18
|
-
required:
|
17
|
+
value: "",
|
18
|
+
required: true,
|
19
19
|
description: "Generate an access token at https://app.bitly.com/settings/api/"
|
20
20
|
},
|
21
21
|
{
|
22
|
+
description: "Bit.ly domain (optional).",
|
22
23
|
key: "bitly_domain",
|
23
24
|
value: "bit.ly",
|
24
25
|
required: false
|
@@ -31,32 +32,55 @@ module SL
|
|
31
32
|
if SL::URL.url?(search_terms)
|
32
33
|
link = search_terms
|
33
34
|
else
|
34
|
-
link,
|
35
|
+
link, title, link_text = SL.ddg(search_terms, link_text)
|
35
36
|
end
|
36
37
|
|
37
|
-
url
|
38
|
-
link_text = title || url
|
39
|
-
[url, title, link_text]
|
40
|
-
end
|
38
|
+
url = shorten(link)
|
41
39
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
return [false, title]
|
40
|
+
unless url
|
41
|
+
SL.add_error("Result is not a valid URL", "URL error")
|
42
|
+
return [false, title, link_text]
|
46
43
|
end
|
47
44
|
|
45
|
+
format_response(url, link, link_text)
|
46
|
+
end
|
47
|
+
|
48
|
+
def shorten(url)
|
49
|
+
return false unless bitly_config?
|
50
|
+
|
48
51
|
domain = SL.config.key?("bitly_domain") ? SL.config["bitly_domain"] : "bit.ly"
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
52
|
+
|
53
|
+
headers = {
|
54
|
+
"Content-Type" => "application/json",
|
55
|
+
"Authorization" => "Bearer #{SL.config['bitly_access_token']}"
|
56
|
+
}
|
57
|
+
data_obj = {
|
58
|
+
"long_url" => url,
|
59
|
+
"domain" => domain
|
60
|
+
}
|
61
|
+
data = Curl::Json.new("https://api-ssl.bitly.com/v4/shorten", data: data_obj.to_json, headers: headers, symbolize_names: true)
|
62
|
+
|
63
|
+
return false unless data.json.key?(:link)
|
64
|
+
|
65
|
+
link = data.json[:link]
|
66
|
+
|
67
|
+
return false unless SL::URL.valid_link?(link)
|
68
|
+
|
69
|
+
link
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def bitly_config?
|
75
|
+
return true if SL.config["bitly_access_token"] && !SL.config["bitly_access_token"].empty?
|
76
|
+
|
77
|
+
SL.add_error("Bit.ly not configured", "Missing access token")
|
78
|
+
false
|
79
|
+
end
|
80
|
+
|
81
|
+
def format_response(link, original_url, link_text)
|
82
|
+
rtitle = SL::URL.title(original_url)
|
83
|
+
[link, rtitle, link_text == "" && !SL.titleize ? rtitle : link_text]
|
60
84
|
end
|
61
85
|
end
|
62
86
|
|
@@ -25,6 +25,9 @@ module SL
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def search(search_type, search_terms, link_text)
|
28
|
+
@auth_token = Secrets::GH_AUTH_TOKEN if defined? Secrets::GH_AUTH_TOKEN
|
29
|
+
@auth_token = SL.config["github_token"] if SL.config.key?("github_token") && !SL.config["github_token"].empty?
|
30
|
+
|
28
31
|
case search_type
|
29
32
|
when /^gist/
|
30
33
|
url, title, link_text = gist(search_terms, search_type, link_text)
|
@@ -46,7 +49,7 @@ module SL
|
|
46
49
|
"Accept" => "application/vnd.github+json",
|
47
50
|
"X-GitHub-Api-Version" => "2022-11-28"
|
48
51
|
}
|
49
|
-
headers["Authorization"] = "Bearer #{
|
52
|
+
headers["Authorization"] = "Bearer #{@auth_token}" if defined? @auth_token
|
50
53
|
|
51
54
|
url = "https://api.github.com/search/#{endpoint}?q=#{query.url_encode}&per_page=1&page=1&order=desc"
|
52
55
|
res = Curl::Json.new(url, headers: headers)
|
@@ -163,6 +166,8 @@ module SL
|
|
163
166
|
end
|
164
167
|
|
165
168
|
def filter_gists(gists, search_terms)
|
169
|
+
return false if gists.is_a?(Hash)
|
170
|
+
|
166
171
|
score = 0
|
167
172
|
gists.map! do |g|
|
168
173
|
{
|
@@ -238,7 +243,7 @@ module SL
|
|
238
243
|
end
|
239
244
|
|
240
245
|
# Assuming we retrieved a full gist URL
|
241
|
-
if url =~ %r{https://gist.github.com/(?:(?<user>[^/]+)/)?(?<id>[a-z0-9]+?)(?:[#/](?<file>(?:file-)?.*?))?$}
|
246
|
+
if url && url =~ %r{https://gist.github.com/(?:(?<user>[^/]+)/)?(?<id>[a-z0-9]+?)(?:[#/](?<file>(?:file-)?.*?))?$}
|
242
247
|
m = Regexp.last_match
|
243
248
|
user = m["user"]
|
244
249
|
id = m["id"]
|
@@ -43,7 +43,7 @@ module SL
|
|
43
43
|
return false
|
44
44
|
end
|
45
45
|
|
46
|
-
url = "https://customsearch.googleapis.com/customsearch/v1?cx=338419ee5ac894523&q=#{ERB::Util.url_encode(search_terms)}&num=1&key=#{@api_key}"
|
46
|
+
url = "https://customsearch.googleapis.com/customsearch/v1?cx=338419ee5ac894523&q=#{ERB::Util.url_encode(search_terms.gsub(/%22/, '"'))}&num=1&key=#{@api_key}"
|
47
47
|
json = Curl::Json.new(url).json
|
48
48
|
|
49
49
|
if json["error"] && json["error"]["code"].to_i == 429
|
@@ -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 -
|
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
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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[
|
@@ -17,29 +17,26 @@ module SL
|
|
17
17
|
if SL::URL.url?(search_terms)
|
18
18
|
link = search_terms
|
19
19
|
else
|
20
|
-
link,
|
20
|
+
link, title, link_text = SL.ddg(search_terms, link_text)
|
21
21
|
end
|
22
22
|
|
23
|
-
url
|
24
|
-
|
23
|
+
url = shorten(link)
|
24
|
+
title = SL::URL.title(link) if title.nil? || title.empty?
|
25
|
+
link_text = title if (link_text.nil? || link_text.empty?) && !SL.titleize
|
25
26
|
[url, title, link_text]
|
26
27
|
end
|
27
28
|
|
28
|
-
def
|
29
|
+
def shorten(url)
|
29
30
|
long_url = url.dup
|
30
31
|
|
31
32
|
data = Curl::Json.new("https://is.gd/create.php?format=json&url=#{CGI.escape(long_url)}", symbolize_names: true)
|
32
33
|
|
33
34
|
if data.json.key?("errorcode")
|
34
35
|
SL.add_error("Error creating is.gd url", data.json[:errorcode])
|
35
|
-
return
|
36
|
+
return false
|
36
37
|
end
|
37
38
|
|
38
|
-
|
39
|
-
rtitle = SL::URL.title(long_url)
|
40
|
-
title = rtitle
|
41
|
-
link_text = rtitle if link_text == "" && !SL.titleize
|
42
|
-
[link, title, link_text]
|
39
|
+
data.json[:shorturl]
|
43
40
|
end
|
44
41
|
end
|
45
42
|
|
@@ -10,6 +10,20 @@ module SL
|
|
10
10
|
trigger: "(ld|ding)",
|
11
11
|
searches: [
|
12
12
|
[%w[ld ding], "Linkding Bookmark Search"]
|
13
|
+
],
|
14
|
+
config: [
|
15
|
+
{
|
16
|
+
description: "Linkding server URL.",
|
17
|
+
key: "linkding_server",
|
18
|
+
value: "''",
|
19
|
+
required: true
|
20
|
+
},
|
21
|
+
{
|
22
|
+
description: "Linkding API key.\nYou can find your api key here: https://your_server/settings/integrations",
|
23
|
+
key: "linkding_api_key",
|
24
|
+
value: "''",
|
25
|
+
required: true
|
26
|
+
}
|
13
27
|
]
|
14
28
|
}
|
15
29
|
end
|
@@ -108,7 +122,7 @@ module SL
|
|
108
122
|
cache
|
109
123
|
end
|
110
124
|
|
111
|
-
# Search
|
125
|
+
# Search linkding bookmarks
|
112
126
|
# Begin query with '' to force exact matching (including description text)
|
113
127
|
# Regular matching searches for each word of query and scores the bookmarks
|
114
128
|
# exact matches in title get highest score
|
@@ -120,13 +134,13 @@ module SL
|
|
120
134
|
#
|
121
135
|
# Exact matching is case and punctuation insensitive
|
122
136
|
def search(_, search_terms, link_text)
|
123
|
-
unless SL.config["linkding_server"]
|
137
|
+
unless SL.config["linkding_server"] && !SL.config["linkding_server"].empty?
|
124
138
|
SL.add_error("Missing Linkding server",
|
125
139
|
"add it to your configuration (linkding_server: https://YOUR_SERVER)")
|
126
140
|
return false
|
127
141
|
end
|
128
142
|
|
129
|
-
unless SL.config["linkding_api_key"]
|
143
|
+
unless SL.config["linkding_api_key"] && !SL.config["linkding_api_key"].empty?
|
130
144
|
SL.add_error("Missing Linkding API token",
|
131
145
|
"Find your api key at https://your_server/settings/integrations and add it
|
132
146
|
to your configuration (linkding_api_key: YOURKEY)")
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Setapp Plugin
|
4
|
+
# Takes an app name or Setapp URL, adds an affiliate string,
|
5
|
+
# and outputs a URL, optionally shortened with is.gd or bit.ly
|
6
|
+
#
|
7
|
+
# Optional config:
|
8
|
+
#
|
9
|
+
# ```yaml
|
10
|
+
# setapp_affiliate_string: xxxxxxxxx # see below
|
11
|
+
# bitly_domain: bit.ly # or custom domain
|
12
|
+
# bitly_access_token: xxxxxxxxxxxx # see below
|
13
|
+
# ```
|
14
|
+
#
|
15
|
+
# To get your Setapp affiliate string
|
16
|
+
#
|
17
|
+
# 1. You must have a Setapp affiliate account through impact.com
|
18
|
+
# 2. Generate a campaign url for an app landing page
|
19
|
+
# 3. Follow the short link provided
|
20
|
+
# 4. The browser URL bar will now show the expanded link
|
21
|
+
# 5. Copy everything after the & symbol in the url to
|
22
|
+
# the `setapp_affiliate_string` config line
|
23
|
+
#
|
24
|
+
# Run a search with !set, !seti, or !setb. The input can either be
|
25
|
+
# a Setapp app landing page url, or an app name, e.g. `!seti marked`,
|
26
|
+
# `[jump desktop](!seti)`, or `!setb https://setapp.com/apps/marked`
|
27
|
+
#
|
28
|
+
module SL
|
29
|
+
# is.gd link shortening
|
30
|
+
class SetappSearch
|
31
|
+
class << self
|
32
|
+
def settings
|
33
|
+
{
|
34
|
+
trigger: "set[ib]?",
|
35
|
+
searches: [
|
36
|
+
["set", "Setapp Link with optional affiliate string"],
|
37
|
+
["seti", "Shorten Setapp Affiliate Link with is.gd"],
|
38
|
+
["setb", "Shorten Setapp Affiliate Link with bit.ly"]
|
39
|
+
],
|
40
|
+
config: [
|
41
|
+
{
|
42
|
+
description: "Setapp affiliate string (optional).\nYou can find your affiliate string here: https://www.impact.com/affiliate/links",
|
43
|
+
key: "setapp_affiliate_string",
|
44
|
+
value: "''",
|
45
|
+
required: false
|
46
|
+
}
|
47
|
+
]
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def search(search_type, search_terms, link_text)
|
52
|
+
link = build_link(search_terms)
|
53
|
+
link, rtitle, link_text = process_link(link, search_terms, link_text)
|
54
|
+
return [false, rtitle, link_text] unless valid_setapp_url?(link)
|
55
|
+
|
56
|
+
process_search_type(search_type, link, rtitle, link_text)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def build_link(search_terms)
|
62
|
+
return search_terms if SL::URL.url?(search_terms)
|
63
|
+
|
64
|
+
"https://setapp.com/apps/#{CGI.escape(search_terms.gsub(/ \d+$/, '').gsub(/ +/, '-').downcase)}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def process_link(link, search_terms, link_text)
|
68
|
+
SL::URL.valid_link?(link, 2) && [link, search_terms, link_text] || SL.ddg("site:setapp.com #{search_terms}", link_text)
|
69
|
+
end
|
70
|
+
|
71
|
+
def process_search_type(search_type, link, title, link_text)
|
72
|
+
link_text = link_text.nil? || link_text.empty? ? Curl::Html.new(link).title.sub(/ on Setapp.*/, "") : link_text
|
73
|
+
link = build_affiliate_url(link)
|
74
|
+
case search_type
|
75
|
+
when "set"
|
76
|
+
format_response(link, link_text)
|
77
|
+
when "seti"
|
78
|
+
shorten(:isgd, link, title, link_text)
|
79
|
+
when "setb"
|
80
|
+
shorten(:bitly, link, title, link_text)
|
81
|
+
else
|
82
|
+
SL.add_error("Invalid search type", "Search error")
|
83
|
+
[false, title, link_text]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def shorten(type, url, title = nil, link_text = nil)
|
88
|
+
return [false, title, link_text] unless valid_setapp_url?(url)
|
89
|
+
|
90
|
+
shortened_url = case type
|
91
|
+
when :isgd
|
92
|
+
SL::IsgdSearch.shorten(url)
|
93
|
+
when :bitly
|
94
|
+
SL::BitlySearch.shorten(url)
|
95
|
+
end
|
96
|
+
return [false, title, link_text] unless shortened_url
|
97
|
+
|
98
|
+
format_response(shortened_url, link_text)
|
99
|
+
end
|
100
|
+
|
101
|
+
def valid_setapp_url?(url)
|
102
|
+
return true if SL::URL.valid_link?(url, 2) && url =~ %r{^https://setapp.com}
|
103
|
+
|
104
|
+
SL.add_error("URL is not a valid Setapp link", "URL error")
|
105
|
+
false
|
106
|
+
end
|
107
|
+
|
108
|
+
def valid_affiliate_config?
|
109
|
+
return true if SL.config.key?("setapp_affiliate_string") && !SL.config["setapp_affiliate_string"].empty?
|
110
|
+
|
111
|
+
# SL.add_error("Setapp affiliate string not configured", "Missing affiliate string")
|
112
|
+
false
|
113
|
+
end
|
114
|
+
|
115
|
+
def build_affiliate_url(url)
|
116
|
+
return url unless valid_affiliate_config?
|
117
|
+
|
118
|
+
separator = url =~ /\?/ ? "&" : "?"
|
119
|
+
"#{url}#{SL.config['setapp_affiliate_string'].sub(/^[?&]?/, separator)}"
|
120
|
+
end
|
121
|
+
|
122
|
+
def format_response(link, link_text)
|
123
|
+
title = SL::URL.title(link)
|
124
|
+
|
125
|
+
[link, title, link_text.nil? || link_text.empty? && !SL.titleize ? title : link_text]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
SL::Searches.register "setapp", :search, self
|
130
|
+
end
|
131
|
+
end
|
@@ -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
|
data/lib/searchlink/searches.rb
CHANGED
@@ -218,6 +218,9 @@ require_relative "searches/lastfm"
|
|
218
218
|
# import
|
219
219
|
require_relative "searches/pinboard"
|
220
220
|
|
221
|
+
# import
|
222
|
+
require_relative "searches/setapp"
|
223
|
+
|
221
224
|
# import
|
222
225
|
require_relative "searches/social"
|
223
226
|
|
@@ -245,5 +248,8 @@ require_relative "searches/youtube"
|
|
245
248
|
# import
|
246
249
|
require_relative "searches/stackoverflow"
|
247
250
|
|
251
|
+
# import
|
252
|
+
require_relative "searches/tinyurl"
|
253
|
+
|
248
254
|
# import
|
249
255
|
require_relative "searches/linkding"
|
data/lib/searchlink/string.rb
CHANGED
@@ -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
|
@@ -157,6 +224,30 @@ module SL
|
|
157
224
|
input + append
|
158
225
|
end
|
159
226
|
|
227
|
+
##
|
228
|
+
## Append an affiliate string to a URL
|
229
|
+
##
|
230
|
+
## @param aff_string [String] The affiliate string
|
231
|
+
## @return [String] The URL with the affiliate string
|
232
|
+
##
|
233
|
+
## @see #append_affiliate_string!
|
234
|
+
##
|
235
|
+
def append_affiliate_string(aff_string)
|
236
|
+
separator = self =~ /\?/ ? "&" : "?"
|
237
|
+
"#{self}#{aff_string.sub(/^[?&]?/, separator)}"
|
238
|
+
end
|
239
|
+
|
240
|
+
## Destructively append an affiliate string to a URL
|
241
|
+
##
|
242
|
+
## @param aff_string [String] The affiliate string
|
243
|
+
## @return [String] The URL with the affiliate string
|
244
|
+
##
|
245
|
+
## @see #append_affiliate_string
|
246
|
+
##
|
247
|
+
def append_affiliate_string!(aff_string)
|
248
|
+
replace append_affiliate_string(aff_string)
|
249
|
+
end
|
250
|
+
|
160
251
|
##
|
161
252
|
## Remove the protocol from a URL
|
162
253
|
##
|
data/lib/searchlink/version.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module SL
|
4
|
-
VERSION =
|
4
|
+
VERSION = "2.3.85"
|
5
5
|
end
|
6
6
|
|
7
7
|
# Main module
|
@@ -47,7 +47,7 @@ module SL
|
|
47
47
|
if defined? Secrets::GH_AUTH_TOKEN
|
48
48
|
headers["Authorization"] = "Bearer #{Secrets::GH_AUTH_TOKEN}"
|
49
49
|
elsif SL.config["github_token"]
|
50
|
-
headers["Authorization"] = "Bearer #{SL.
|
50
|
+
headers["Authorization"] = "Bearer #{SL.config["github_token"]}"
|
51
51
|
end
|
52
52
|
|
53
53
|
url = "https://api.github.com/repos/ttscoff/searchlink/releases/latest"
|
data/lib/tokens.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Secrets
|
2
|
-
GH_AUTH_TOKEN =
|
4
|
+
GH_AUTH_TOKEN = "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"
|
3
5
|
end
|
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.
|
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-
|
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.
|
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.
|
151
|
+
version: 3.7.2
|
152
152
|
- !ruby/object:Gem::Dependency
|
153
153
|
name: rake
|
154
154
|
requirement: !ruby/object:Gem::Requirement
|
@@ -303,11 +303,13 @@ files:
|
|
303
303
|
- lib/searchlink/searches/linkding.rb
|
304
304
|
- lib/searchlink/searches/lyrics.rb
|
305
305
|
- lib/searchlink/searches/pinboard.rb
|
306
|
+
- lib/searchlink/searches/setapp.rb
|
306
307
|
- lib/searchlink/searches/social.rb
|
307
308
|
- lib/searchlink/searches/software.rb
|
308
309
|
- lib/searchlink/searches/spelling.rb
|
309
310
|
- lib/searchlink/searches/spotlight.rb
|
310
311
|
- lib/searchlink/searches/stackoverflow.rb
|
312
|
+
- lib/searchlink/searches/tinyurl.rb
|
311
313
|
- lib/searchlink/searches/tmdb.rb
|
312
314
|
- lib/searchlink/searches/twitter.rb
|
313
315
|
- lib/searchlink/searches/wikipedia.rb
|