searchlink 2.3.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/searchlink +84 -0
- data/lib/searchlink/array.rb +7 -0
- data/lib/searchlink/config.rb +230 -0
- data/lib/searchlink/curl/html.rb +482 -0
- data/lib/searchlink/curl/json.rb +90 -0
- data/lib/searchlink/curl.rb +7 -0
- data/lib/searchlink/help.rb +103 -0
- data/lib/searchlink/output.rb +270 -0
- data/lib/searchlink/parse.rb +668 -0
- data/lib/searchlink/plist.rb +213 -0
- data/lib/searchlink/search.rb +70 -0
- data/lib/searchlink/searches/amazon.rb +25 -0
- data/lib/searchlink/searches/applemusic.rb +123 -0
- data/lib/searchlink/searches/bitly.rb +50 -0
- data/lib/searchlink/searches/definition.rb +67 -0
- data/lib/searchlink/searches/duckduckgo.rb +167 -0
- data/lib/searchlink/searches/github.rb +245 -0
- data/lib/searchlink/searches/google.rb +67 -0
- data/lib/searchlink/searches/helpers/chromium.rb +318 -0
- data/lib/searchlink/searches/helpers/firefox.rb +135 -0
- data/lib/searchlink/searches/helpers/safari.rb +133 -0
- data/lib/searchlink/searches/history.rb +166 -0
- data/lib/searchlink/searches/hook.rb +77 -0
- data/lib/searchlink/searches/itunes.rb +97 -0
- data/lib/searchlink/searches/lastfm.rb +41 -0
- data/lib/searchlink/searches/lyrics.rb +91 -0
- data/lib/searchlink/searches/pinboard.rb +183 -0
- data/lib/searchlink/searches/social.rb +105 -0
- data/lib/searchlink/searches/software.rb +27 -0
- data/lib/searchlink/searches/spelling.rb +59 -0
- data/lib/searchlink/searches/spotlight.rb +28 -0
- data/lib/searchlink/searches/stackoverflow.rb +31 -0
- data/lib/searchlink/searches/tmdb.rb +52 -0
- data/lib/searchlink/searches/twitter.rb +46 -0
- data/lib/searchlink/searches/wikipedia.rb +33 -0
- data/lib/searchlink/searches/youtube.rb +48 -0
- data/lib/searchlink/searches.rb +194 -0
- data/lib/searchlink/semver.rb +140 -0
- data/lib/searchlink/string.rb +469 -0
- data/lib/searchlink/url.rb +153 -0
- data/lib/searchlink/util.rb +87 -0
- data/lib/searchlink/version.rb +93 -0
- data/lib/searchlink/which.rb +175 -0
- data/lib/searchlink.rb +66 -0
- data/lib/tokens.rb +3 -0
- metadata +299 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
module SL
|
|
2
|
+
class SocialSearch
|
|
3
|
+
class << self
|
|
4
|
+
def settings
|
|
5
|
+
{
|
|
6
|
+
trigger: '@[tfilm]',
|
|
7
|
+
searches: [
|
|
8
|
+
['@t', 'Twitter Handle'],
|
|
9
|
+
['@f', 'Facebook Handle'],
|
|
10
|
+
['@i', 'Instagram Handle'],
|
|
11
|
+
['@l', 'LinkedIn Handle'],
|
|
12
|
+
['@m', 'Mastodon Handle']
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def search(search_type, search_terms, link_text = '')
|
|
18
|
+
type = case search_type
|
|
19
|
+
when /^@t/ # twitter-ify username
|
|
20
|
+
unless search_terms.strip =~ /^@?[0-9a-z_$]+$/i
|
|
21
|
+
return [false, "#{search_terms} is not a valid Twitter handle", link_text]
|
|
22
|
+
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
't'
|
|
26
|
+
when /^@fb?/ # fb-ify username
|
|
27
|
+
unless search_terms.strip =~ /^@?[0-9a-z_]+$/i
|
|
28
|
+
return [false, "#{search_terms} is not a valid Facebook username", link_text]
|
|
29
|
+
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
'f'
|
|
33
|
+
when /^@i/ # intagramify username
|
|
34
|
+
unless search_terms.strip =~ /^@?[0-9a-z_]+$/i
|
|
35
|
+
return [false, "#{search_terms} is not a valid Instagram username", link_text]
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
'i'
|
|
40
|
+
when /^@l/ # linked-inify username
|
|
41
|
+
unless search_terms.strip =~ /^@?[0-9a-z_]+$/i
|
|
42
|
+
return [false, "#{search_terms} is not a valid LinkedIn username", link_text]
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
'l'
|
|
47
|
+
when /^@m/ # mastodonify username
|
|
48
|
+
unless search_terms.strip =~ /^@?[0-9a-z_]+@[0-9a-z_.]+$/i
|
|
49
|
+
return [false, "#{search_terms} is not a valid Mastodon username", link_text]
|
|
50
|
+
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
'm'
|
|
54
|
+
else
|
|
55
|
+
't'
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
url, title = social_handle(type, search_terms)
|
|
59
|
+
link_text = title if link_text == ''
|
|
60
|
+
[url, title, link_text]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def template_social(user, url, service)
|
|
64
|
+
template = SL.config['social_template'].dup
|
|
65
|
+
|
|
66
|
+
template.sub!(/%user%/, user)
|
|
67
|
+
template.sub!(/%service%/, service)
|
|
68
|
+
template.sub!(/%url%/, url.sub(%r{^https?://(www\.)?}, '').sub(%r{/$}, ''))
|
|
69
|
+
|
|
70
|
+
template
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def social_handle(type, term)
|
|
74
|
+
handle = term.sub(/^@/, '').strip
|
|
75
|
+
|
|
76
|
+
case type
|
|
77
|
+
when /^t/i
|
|
78
|
+
url = "https://twitter.com/#{handle}"
|
|
79
|
+
title = template_social(handle, url, 'Twitter')
|
|
80
|
+
when /^f/i
|
|
81
|
+
url = "https://www.facebook.com/#{handle}"
|
|
82
|
+
title = template_social(handle, url, 'Facebook')
|
|
83
|
+
when /^l/i
|
|
84
|
+
url = "https://www.linkedin.com/in/#{handle}/"
|
|
85
|
+
title = template_social(handle, url, 'LinkedIn')
|
|
86
|
+
when /^i/i
|
|
87
|
+
url = "https://www.instagram.com/#{handle}/"
|
|
88
|
+
title = template_social(handle, url, 'Instagram')
|
|
89
|
+
when /^m/i
|
|
90
|
+
parts = handle.split(/@/)
|
|
91
|
+
return [false, term] unless parts.count == 2
|
|
92
|
+
|
|
93
|
+
url = "https://#{parts[1]}/@#{parts[0]}"
|
|
94
|
+
title = template_social(handle, url, 'Mastodon')
|
|
95
|
+
else
|
|
96
|
+
[false, term]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
[url, title]
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
SL::Searches.register 'social', :search, self
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module SL
|
|
2
|
+
# Software Search
|
|
3
|
+
class SoftwareSearch
|
|
4
|
+
class << self
|
|
5
|
+
def settings
|
|
6
|
+
{
|
|
7
|
+
trigger: 's',
|
|
8
|
+
searches: [
|
|
9
|
+
['s', 'Software Search']
|
|
10
|
+
]
|
|
11
|
+
}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def search(_, search_terms, link_text)
|
|
15
|
+
excludes = %w[apple.com postmates.com download.cnet.com softpedia.com softonic.com macupdate.com]
|
|
16
|
+
search_url = %(#{excludes.map { |x| "-site:#{x}" }.join(' ')} #{search_terms} app)
|
|
17
|
+
|
|
18
|
+
url, title, link_text = SL.ddg(search_url, link_text)
|
|
19
|
+
link_text = title if link_text == '' && !SL.titleize
|
|
20
|
+
|
|
21
|
+
[url, title, link_text]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
SL::Searches.register 'software', :search, self
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module SL
|
|
2
|
+
# Spelling Search
|
|
3
|
+
class SpellSearch
|
|
4
|
+
class << self
|
|
5
|
+
def settings
|
|
6
|
+
{
|
|
7
|
+
trigger: 'sp(?:ell)?',
|
|
8
|
+
searches: [
|
|
9
|
+
%w[sp Spelling],
|
|
10
|
+
['spell', nil]
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def search(_, search_terms, link_text)
|
|
16
|
+
title = SL.spell(search_terms)
|
|
17
|
+
|
|
18
|
+
[title, title, link_text]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
SL::Searches.register 'spelling', :search, self
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
def spell(phrase)
|
|
27
|
+
aspell = if File.exist?('/usr/local/bin/aspell')
|
|
28
|
+
'/usr/local/bin/aspell'
|
|
29
|
+
elsif File.exist?('/opt/homebrew/bin/aspell')
|
|
30
|
+
'/opt/homebrew/bin/aspell'
|
|
31
|
+
else
|
|
32
|
+
`which aspell`.strip
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
if aspell.nil? || aspell.empty?
|
|
36
|
+
SL.add_error('Missing aspell', 'Install aspell in to allow spelling corrections')
|
|
37
|
+
return false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
words = phrase.split(/\b/)
|
|
41
|
+
output = ''
|
|
42
|
+
words.each do |w|
|
|
43
|
+
if w =~ /[A-Za-z]+/
|
|
44
|
+
spell_res = `echo "#{w}" | #{aspell} --sug-mode=bad-spellers -C pipe | head -n 2 | tail -n 1`
|
|
45
|
+
if spell_res.strip == "\*"
|
|
46
|
+
output += w
|
|
47
|
+
else
|
|
48
|
+
spell_res.sub!(/.*?: /, '')
|
|
49
|
+
results = spell_res.split(/, /).delete_if { |word| phrase =~ /^[a-z]/ && word =~ /[A-Z]/ }
|
|
50
|
+
output += results[0]
|
|
51
|
+
end
|
|
52
|
+
else
|
|
53
|
+
output += w
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
output
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module SL
|
|
2
|
+
# Spotlight file search
|
|
3
|
+
class SpotlightSearch
|
|
4
|
+
class << self
|
|
5
|
+
def settings
|
|
6
|
+
{
|
|
7
|
+
trigger: 'file',
|
|
8
|
+
searches: [
|
|
9
|
+
['file', 'Spotlight Search']
|
|
10
|
+
]
|
|
11
|
+
}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def search(_, search_terms, link_text)
|
|
15
|
+
query = search_terms.gsub(/%22/, '"')
|
|
16
|
+
matches = `mdfind '#{query}' 2>/dev/null`.strip.split(/\n/)
|
|
17
|
+
res = matches.sort_by { |r| File.basename(r).length }.first
|
|
18
|
+
return [false, query, link_text] if res.strip.empty?
|
|
19
|
+
|
|
20
|
+
title = File.basename(res)
|
|
21
|
+
link_text = title if link_text.strip.empty? || link_text == search_terms
|
|
22
|
+
["file://#{res.strip.gsub(/ /, '%20')}", title, link_text]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
SL::Searches.register 'spotlight', :search, self
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module SL
|
|
2
|
+
# Stack Overflow search
|
|
3
|
+
class StackOverflowSearch
|
|
4
|
+
class << self
|
|
5
|
+
def settings
|
|
6
|
+
{
|
|
7
|
+
trigger: 'soa?',
|
|
8
|
+
searches: [
|
|
9
|
+
['so', 'StackOverflow Search'],
|
|
10
|
+
['soa', 'StackOverflow Accepted Answer']
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def search(search_type, search_terms, link_text)
|
|
16
|
+
url, title, link_text = SL.ddg("site:stackoverflow.com #{search_terms}", link_text)
|
|
17
|
+
link_text = title if link_text == '' && !SL.titleize
|
|
18
|
+
|
|
19
|
+
if search_type =~ /a$/
|
|
20
|
+
body = `curl -SsL #{url}`.strip
|
|
21
|
+
m = body.match(/id="(?<id>answer-\d+)"[^>]+accepted-answer/)
|
|
22
|
+
url = "#{url}##{m['id']}" if m
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
[url, title, link_text]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
SL::Searches.register 'stackoverflow', :search, self
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module SL
|
|
2
|
+
# The Movie Database search
|
|
3
|
+
class TMDBSearch
|
|
4
|
+
class << self
|
|
5
|
+
def settings
|
|
6
|
+
{
|
|
7
|
+
trigger: 'tmdb[amt]?',
|
|
8
|
+
searches: [
|
|
9
|
+
['tmdb', 'TMDB Multi Search'],
|
|
10
|
+
['tmdba', 'TMDB Actor Search'],
|
|
11
|
+
['tmdbm', 'TMDB Movie Search'],
|
|
12
|
+
['tmdbt', 'TMDB TV Search']
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def search(search_type, terms, link_text)
|
|
18
|
+
type = case search_type
|
|
19
|
+
when /t$/
|
|
20
|
+
'tv'
|
|
21
|
+
when /m$/
|
|
22
|
+
'movie'
|
|
23
|
+
when /a$/
|
|
24
|
+
'person'
|
|
25
|
+
else
|
|
26
|
+
'multi'
|
|
27
|
+
end
|
|
28
|
+
body = `/usr/bin/curl -sSL 'https://api.themoviedb.org/3/search/#{type}?query=#{terms.url_encode}&api_key=2bd76548656d92517f14d64766e87a02'`
|
|
29
|
+
data = JSON.parse(body)
|
|
30
|
+
if data.key?('results') && data['results'].count.positive?
|
|
31
|
+
res = data['results'][0]
|
|
32
|
+
type = res['media_type'] if type == 'multi'
|
|
33
|
+
id = res['id']
|
|
34
|
+
url = "https://www.themoviedb.org/#{type}/#{id}"
|
|
35
|
+
title = res['name']
|
|
36
|
+
title ||= res['title']
|
|
37
|
+
title ||= terms
|
|
38
|
+
else
|
|
39
|
+
url, title, link_text = SL.ddg("site:imdb.com #{terms}", link_text)
|
|
40
|
+
|
|
41
|
+
return false unless url
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
link_text = title if link_text == '' && !SL.titleize
|
|
45
|
+
|
|
46
|
+
[url, title, link_text]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
SL::Searches.register 'tmdb', :search, self
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module SL
|
|
2
|
+
class TwitterSearch
|
|
3
|
+
class << self
|
|
4
|
+
def settings
|
|
5
|
+
{
|
|
6
|
+
trigger: 'te',
|
|
7
|
+
searches: [
|
|
8
|
+
['te', 'Twitter Embed']
|
|
9
|
+
]
|
|
10
|
+
}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def search(search_type, search_terms, link_text)
|
|
14
|
+
if SL::URL.url?(search_terms) && search_terms =~ %r{^https://twitter.com/}
|
|
15
|
+
url, title = twitter_embed(search_terms)
|
|
16
|
+
else
|
|
17
|
+
SL.add_error('Invalid Tweet URL', "#{search_terms} is not a valid link to a tweet or timeline")
|
|
18
|
+
url = false
|
|
19
|
+
title = false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
[url, title, link_text]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def twitter_embed(tweet)
|
|
26
|
+
res = `curl -sSL 'https://publish.twitter.com/oembed?url=#{tweet.url_encode}'`.strip
|
|
27
|
+
if res
|
|
28
|
+
begin
|
|
29
|
+
json = JSON.parse(res)
|
|
30
|
+
url = 'embed'
|
|
31
|
+
title = json['html']
|
|
32
|
+
rescue StandardError
|
|
33
|
+
SL.add_error('Tweet Error', 'Error retrieving tweet')
|
|
34
|
+
url = false
|
|
35
|
+
title = tweet
|
|
36
|
+
end
|
|
37
|
+
else
|
|
38
|
+
return [false, 'Error retrieving tweet']
|
|
39
|
+
end
|
|
40
|
+
return [url, title]
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
SL::Searches.register 'twitter', :search, self
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module SL
|
|
2
|
+
class WikipediaSearch
|
|
3
|
+
class << self
|
|
4
|
+
def settings
|
|
5
|
+
{
|
|
6
|
+
trigger: 'wiki',
|
|
7
|
+
searches: [
|
|
8
|
+
['wiki', 'Wikipedia Search']
|
|
9
|
+
]
|
|
10
|
+
}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def search(_, search_terms, link_text)
|
|
14
|
+
## Hack to scrape wikipedia result
|
|
15
|
+
body = `/usr/bin/curl -sSL 'https://en.wikipedia.org/wiki/Special:Search?search=#{search_terms.url_encode}&go=Go'`
|
|
16
|
+
return false unless body
|
|
17
|
+
|
|
18
|
+
body = body.force_encoding('utf-8') if RUBY_VERSION.to_f > 1.9
|
|
19
|
+
|
|
20
|
+
begin
|
|
21
|
+
title = body.match(/"wgTitle":"(.*?)"/)[1]
|
|
22
|
+
url = body.match(/<link rel="canonical" href="(.*?)"/)[1]
|
|
23
|
+
rescue StandardError
|
|
24
|
+
return false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
[url, title, link_text]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
SL::Searches.register 'wikipedia', :search, self
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module SL
|
|
2
|
+
# YouTube Search/Linking
|
|
3
|
+
class YouTubeSearch
|
|
4
|
+
YOUTUBE_RX = %r{(?:youtu\.be/|youtube\.com/watch\?v=)?(?<id>[a-z0-9_\-]+)$}i.freeze
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
def settings
|
|
8
|
+
{
|
|
9
|
+
trigger: 'yte?',
|
|
10
|
+
searches: [
|
|
11
|
+
['yt', 'YouTube Search'],
|
|
12
|
+
['yte', 'YouTube Embed']
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def search(search_type, search_terms, link_text)
|
|
18
|
+
if SL::URL.url?(search_terms) && search_terms =~ YOUTUBE_RX
|
|
19
|
+
url = search_terms
|
|
20
|
+
elsif search_terms =~ /^[a-z0-9_\-]+$/i
|
|
21
|
+
url = "https://youtube.com/watch?v=#{search_terms}"
|
|
22
|
+
else
|
|
23
|
+
url, title = SL.ddg("site:youtube.com #{search_terms}", link_text)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
url, title = embed_for_url(url) if search_type =~ /e$/
|
|
27
|
+
|
|
28
|
+
[url, title]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def embed_for_url(url)
|
|
32
|
+
return unless url =~ YOUTUBE_RX
|
|
33
|
+
|
|
34
|
+
id = Regexp.last_match('id')
|
|
35
|
+
title = [
|
|
36
|
+
%(<iframe width="560" height="315" src="https://www.youtube.com/embed/#{id}"),
|
|
37
|
+
%(title="YouTube video player" frameborder="0"),
|
|
38
|
+
%(allow="accelerometer; autoplay; clipboard-write; encrypted-media;),
|
|
39
|
+
%(gyroscope; picture-in-picture; web-share"),
|
|
40
|
+
%(allowfullscreen></iframe>)
|
|
41
|
+
].join(' ')
|
|
42
|
+
['embed', title]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
SL::Searches.register 'youtube', :search, self
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
module SL
|
|
2
|
+
module Searches
|
|
3
|
+
class << self
|
|
4
|
+
def plugins
|
|
5
|
+
@plugins ||= {}
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def load_searches
|
|
9
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'searches', '*.rb')).sort.each { |f| require f }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
# Register a plugin with the plugin manager
|
|
14
|
+
#
|
|
15
|
+
# @param [String, Array] title title or array of titles
|
|
16
|
+
# @param [Symbol] type plugin type (:search)
|
|
17
|
+
# @param [Class] klass class that handles plugin actions. Search plugins
|
|
18
|
+
# must have a #settings and a #search method
|
|
19
|
+
#
|
|
20
|
+
def register(title, type, klass)
|
|
21
|
+
Array(title).each { |t| register_plugin(t, type, klass) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def description_for_search(search_type)
|
|
25
|
+
description = "#{search_type} search"
|
|
26
|
+
plugins[:search].each do |_, plugin|
|
|
27
|
+
s = plugin[:searches].select { |s| s[0] == search_type }
|
|
28
|
+
unless s.empty?
|
|
29
|
+
description = s[0][1]
|
|
30
|
+
break
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
description
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
#
|
|
37
|
+
# Output an HTML table of available searches
|
|
38
|
+
#
|
|
39
|
+
# @return [String] Table HTML
|
|
40
|
+
#
|
|
41
|
+
def available_searches_html
|
|
42
|
+
searches = plugins[:search]
|
|
43
|
+
.flat_map { |_, plugin| plugin[:searches] }
|
|
44
|
+
.reject { |s| s[1].nil? }
|
|
45
|
+
.sort_by { |s| s[0] }
|
|
46
|
+
out = ['<table id="searches">',
|
|
47
|
+
'<thead><td>Shortcut</td><td>Search Type</td></thead>',
|
|
48
|
+
'<tbody>']
|
|
49
|
+
searches.each { |s| out << "<tr><td><code>!#{s[0]}</code></td><td>#{s[1]}</td></tr>" }
|
|
50
|
+
out.concat(['</tbody>', '</table>']).join("\n")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
#
|
|
54
|
+
# Aligned list of available searches
|
|
55
|
+
#
|
|
56
|
+
# @return [String] Aligned list of searches
|
|
57
|
+
#
|
|
58
|
+
def available_searches
|
|
59
|
+
searches = []
|
|
60
|
+
plugins[:search].each { |_, plugin| searches.concat(plugin[:searches].delete_if { |s| s[1].nil? }) }
|
|
61
|
+
out = ''
|
|
62
|
+
searches.each { |s| out += "!#{s[0]}#{s[0].spacer}#{s[1]}\n" }
|
|
63
|
+
out
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def best_search_match(term)
|
|
67
|
+
searches = all_possible_searches.dup
|
|
68
|
+
searches.select { |s| s.matches_score(term, separator: '', start_word: false) > 8 }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def all_possible_searches
|
|
72
|
+
searches = []
|
|
73
|
+
plugins[:search].each { |_, plugin| plugin[:searches].each { |s| searches.push(s[0]) } }
|
|
74
|
+
searches.concat(SL.config['custom_site_searches'].keys)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def did_you_mean(term)
|
|
78
|
+
matches = best_search_match(term)
|
|
79
|
+
matches.empty? ? '' : ", did you mean #{matches.map { |m| "!#{m}" }.join(', ')}?"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def valid_searches
|
|
83
|
+
searches = []
|
|
84
|
+
plugins[:search].each { |_, plugin| searches.push(plugin[:trigger]) }
|
|
85
|
+
searches
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def valid_search?(term)
|
|
89
|
+
valid = false
|
|
90
|
+
valid = true if term =~ /^(#{valid_searches.join('|')})$/
|
|
91
|
+
valid = true if SL.config['custom_site_searches'].keys.include? term
|
|
92
|
+
# SL.notify("Invalid search#{did_you_mean(term)}", term) unless valid
|
|
93
|
+
valid
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def register_plugin(title, type, klass)
|
|
97
|
+
raise StandardError, "Plugin #{title} has no settings method" unless klass.respond_to? :settings
|
|
98
|
+
|
|
99
|
+
settings = klass.settings
|
|
100
|
+
|
|
101
|
+
raise StandardError, "Plugin #{title} has no search method" unless klass.respond_to? :search
|
|
102
|
+
|
|
103
|
+
plugins[type] ||= {}
|
|
104
|
+
plugins[type][title] = {
|
|
105
|
+
trigger: settings.fetch(:trigger, title).normalize_trigger,
|
|
106
|
+
searches: settings[:searches],
|
|
107
|
+
class: klass
|
|
108
|
+
}
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def load_custom
|
|
112
|
+
plugins_folder = File.expand_path('~/.local/searchlink/plugins')
|
|
113
|
+
return unless File.directory?(plugins_folder)
|
|
114
|
+
|
|
115
|
+
Dir.glob(File.join(plugins_folder, '**/*.rb')).sort.each do |plugin|
|
|
116
|
+
require plugin
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def do_search(search_type, search_terms, link_text, timeout: SL.config['timeout'])
|
|
121
|
+
plugins[:search].each do |_title, plugin|
|
|
122
|
+
trigger = plugin[:trigger].gsub(/(^\^|\$$)/, '')
|
|
123
|
+
if search_type =~ /^#{trigger}$/
|
|
124
|
+
search = proc { plugin[:class].search(search_type, search_terms, link_text) }
|
|
125
|
+
return SL::Util.search_with_timeout(search, timeout)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# import
|
|
134
|
+
require_relative 'searches/applemusic'
|
|
135
|
+
|
|
136
|
+
# import
|
|
137
|
+
require_relative 'searches/itunes'
|
|
138
|
+
|
|
139
|
+
# import
|
|
140
|
+
require_relative 'searches/amazon'
|
|
141
|
+
|
|
142
|
+
# import
|
|
143
|
+
require_relative 'searches/bitly'
|
|
144
|
+
|
|
145
|
+
# import
|
|
146
|
+
require_relative 'searches/definition'
|
|
147
|
+
|
|
148
|
+
# import
|
|
149
|
+
require_relative 'searches/duckduckgo'
|
|
150
|
+
|
|
151
|
+
# import
|
|
152
|
+
require_relative 'searches/github'
|
|
153
|
+
|
|
154
|
+
# import
|
|
155
|
+
require_relative 'searches/google'
|
|
156
|
+
|
|
157
|
+
# import
|
|
158
|
+
require_relative 'searches/history'
|
|
159
|
+
|
|
160
|
+
# import
|
|
161
|
+
require_relative 'searches/hook'
|
|
162
|
+
|
|
163
|
+
# import
|
|
164
|
+
require_relative 'searches/lastfm'
|
|
165
|
+
|
|
166
|
+
# import
|
|
167
|
+
require_relative 'searches/pinboard'
|
|
168
|
+
|
|
169
|
+
# import
|
|
170
|
+
require_relative 'searches/social'
|
|
171
|
+
|
|
172
|
+
# import
|
|
173
|
+
require_relative 'searches/software'
|
|
174
|
+
|
|
175
|
+
# import
|
|
176
|
+
require_relative 'searches/spelling'
|
|
177
|
+
|
|
178
|
+
# import
|
|
179
|
+
require_relative 'searches/spotlight'
|
|
180
|
+
|
|
181
|
+
# import
|
|
182
|
+
require_relative 'searches/tmdb'
|
|
183
|
+
|
|
184
|
+
# import
|
|
185
|
+
require_relative 'searches/twitter'
|
|
186
|
+
|
|
187
|
+
# import
|
|
188
|
+
require_relative 'searches/wikipedia'
|
|
189
|
+
|
|
190
|
+
# import
|
|
191
|
+
require_relative 'searches/youtube'
|
|
192
|
+
|
|
193
|
+
# import
|
|
194
|
+
require_relative 'searches/stackoverflow'
|