searchlink 2.3.83 → 2.3.84
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/parse.rb +10 -4
- data/lib/searchlink/searches/bitly.rb +47 -22
- data/lib/searchlink/searches/github.rb +7 -2
- data/lib/searchlink/searches/google.rb +1 -1
- 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.rb +3 -0
- data/lib/searchlink/string.rb +24 -0
- data/lib/searchlink/version.rb +2 -2
- data/lib/tokens.rb +3 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 78ae4255b0a3a287ce3ccf4b84db34597f8c2a566bf4e7cb0b4939a28f62cca1
|
4
|
+
data.tar.gz: f654c706f574f6c23dca1a6714b2b52177944379fb1df00d34d9bac22e14104b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ecccb8b8f87a8aa891c9bbafffd7e40f9ecee5ee8042326fe8e0151d52e225797bcff745c7ec67b7f7dd8010fd5e32330673b3fe062c378873a74407b666205a
|
7
|
+
data.tar.gz: 6a4ada563e8488646f0cc4fe5ab8c5414c906ead7560cafaf3e6b68fcdf9292a37c04ab8261970de6d5a3a3224dc135a3dba7bc2b2abc60160ca6181dbec512c
|
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/parse.rb
CHANGED
@@ -386,10 +386,10 @@ module SL
|
|
386
386
|
m = Regexp.last_match
|
387
387
|
|
388
388
|
search_type = if m[1].nil?
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
389
|
+
SL::GoogleSearch.api_key? ? "gg" : "g"
|
390
|
+
else
|
391
|
+
m[1]
|
392
|
+
end
|
393
393
|
|
394
394
|
search_terms = m[2].gsub(/(^["']|["']$)/, "")
|
395
395
|
search_terms.strip!
|
@@ -414,6 +414,12 @@ module SL
|
|
414
414
|
link_only = true if search_terms =~ /!!\^?$/
|
415
415
|
|
416
416
|
search_terms = search_terms.sub(/(!!)?\^?(!!)?$/, "")
|
417
|
+
|
418
|
+
if search_type =~ /^(\S+\.)+\S+$/
|
419
|
+
search_type = "g"
|
420
|
+
search_terms = "site:#{m[1]} #{search_terms}"
|
421
|
+
end
|
422
|
+
|
417
423
|
elsif link_info =~ /^!/
|
418
424
|
search_word = link_info.match(/^!(\S+)/)
|
419
425
|
|
@@ -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,56 @@ 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
|
+
url.dup
|
53
|
+
|
54
|
+
headers = {
|
55
|
+
"Content-Type" => "application/json",
|
56
|
+
"Authorization" => "Bearer #{SL.config['bitly_access_token']}"
|
57
|
+
}
|
58
|
+
data_obj = {
|
59
|
+
"long_url" => url,
|
60
|
+
"domain" => domain
|
61
|
+
}
|
62
|
+
data = Curl::Json.new("https://api-ssl.bitly.com/v4/shorten", data: data_obj.to_json, headers: headers, symbolize_names: true)
|
63
|
+
|
64
|
+
return false unless data.json.key?(:link)
|
65
|
+
|
66
|
+
link = data.json[:link]
|
67
|
+
|
68
|
+
return false unless SL::URL.valid_link?(link)
|
69
|
+
|
70
|
+
link
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def bitly_config?
|
76
|
+
return true if SL.config["bitly_access_token"] && !SL.config["bitly_access_token"].empty?
|
77
|
+
|
78
|
+
SL.add_error("Bit.ly not configured", "Missing access token")
|
79
|
+
false
|
80
|
+
end
|
81
|
+
|
82
|
+
def format_response(link, original_url, link_text)
|
83
|
+
rtitle = SL::URL.title(original_url)
|
84
|
+
[link, rtitle, link_text == "" && !SL.titleize ? rtitle : link_text]
|
60
85
|
end
|
61
86
|
end
|
62
87
|
|
@@ -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
|
@@ -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
|
data/lib/searchlink/searches.rb
CHANGED
data/lib/searchlink/string.rb
CHANGED
@@ -157,6 +157,30 @@ module SL
|
|
157
157
|
input + append
|
158
158
|
end
|
159
159
|
|
160
|
+
##
|
161
|
+
## Append an affiliate string to a URL
|
162
|
+
##
|
163
|
+
## @param aff_string [String] The affiliate string
|
164
|
+
## @return [String] The URL with the affiliate string
|
165
|
+
##
|
166
|
+
## @see #append_affiliate_string!
|
167
|
+
##
|
168
|
+
def append_affiliate_string(aff_string)
|
169
|
+
separator = self =~ /\?/ ? "&" : "?"
|
170
|
+
"#{self}#{aff_string.sub(/^[?&]?/, separator)}"
|
171
|
+
end
|
172
|
+
|
173
|
+
## Destructively append an affiliate string to a URL
|
174
|
+
##
|
175
|
+
## @param aff_string [String] The affiliate string
|
176
|
+
## @return [String] The URL with the affiliate string
|
177
|
+
##
|
178
|
+
## @see #append_affiliate_string
|
179
|
+
##
|
180
|
+
def append_affiliate_string!(aff_string)
|
181
|
+
replace append_affiliate_string(aff_string)
|
182
|
+
end
|
183
|
+
|
160
184
|
##
|
161
185
|
## Remove the protocol from a URL
|
162
186
|
##
|
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.84"
|
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.84
|
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-27 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: base64
|
@@ -303,6 +303,7 @@ 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
|