searchlink 2.3.59
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|