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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/bin/searchlink +84 -0
  3. data/lib/searchlink/array.rb +7 -0
  4. data/lib/searchlink/config.rb +230 -0
  5. data/lib/searchlink/curl/html.rb +482 -0
  6. data/lib/searchlink/curl/json.rb +90 -0
  7. data/lib/searchlink/curl.rb +7 -0
  8. data/lib/searchlink/help.rb +103 -0
  9. data/lib/searchlink/output.rb +270 -0
  10. data/lib/searchlink/parse.rb +668 -0
  11. data/lib/searchlink/plist.rb +213 -0
  12. data/lib/searchlink/search.rb +70 -0
  13. data/lib/searchlink/searches/amazon.rb +25 -0
  14. data/lib/searchlink/searches/applemusic.rb +123 -0
  15. data/lib/searchlink/searches/bitly.rb +50 -0
  16. data/lib/searchlink/searches/definition.rb +67 -0
  17. data/lib/searchlink/searches/duckduckgo.rb +167 -0
  18. data/lib/searchlink/searches/github.rb +245 -0
  19. data/lib/searchlink/searches/google.rb +67 -0
  20. data/lib/searchlink/searches/helpers/chromium.rb +318 -0
  21. data/lib/searchlink/searches/helpers/firefox.rb +135 -0
  22. data/lib/searchlink/searches/helpers/safari.rb +133 -0
  23. data/lib/searchlink/searches/history.rb +166 -0
  24. data/lib/searchlink/searches/hook.rb +77 -0
  25. data/lib/searchlink/searches/itunes.rb +97 -0
  26. data/lib/searchlink/searches/lastfm.rb +41 -0
  27. data/lib/searchlink/searches/lyrics.rb +91 -0
  28. data/lib/searchlink/searches/pinboard.rb +183 -0
  29. data/lib/searchlink/searches/social.rb +105 -0
  30. data/lib/searchlink/searches/software.rb +27 -0
  31. data/lib/searchlink/searches/spelling.rb +59 -0
  32. data/lib/searchlink/searches/spotlight.rb +28 -0
  33. data/lib/searchlink/searches/stackoverflow.rb +31 -0
  34. data/lib/searchlink/searches/tmdb.rb +52 -0
  35. data/lib/searchlink/searches/twitter.rb +46 -0
  36. data/lib/searchlink/searches/wikipedia.rb +33 -0
  37. data/lib/searchlink/searches/youtube.rb +48 -0
  38. data/lib/searchlink/searches.rb +194 -0
  39. data/lib/searchlink/semver.rb +140 -0
  40. data/lib/searchlink/string.rb +469 -0
  41. data/lib/searchlink/url.rb +153 -0
  42. data/lib/searchlink/util.rb +87 -0
  43. data/lib/searchlink/version.rb +93 -0
  44. data/lib/searchlink/which.rb +175 -0
  45. data/lib/searchlink.rb +66 -0
  46. data/lib/tokens.rb +3 -0
  47. metadata +299 -0
@@ -0,0 +1,213 @@
1
+ # = plist
2
+ #
3
+ # Copyright 2006-2010 Ben Bleything and Patrick May
4
+ # Distributed under the MIT License
5
+ module Plist ; end
6
+
7
+ # === Load a plist file
8
+ # This is the main point of the library:
9
+ #
10
+ # r = Plist::parse_xml( filename_or_xml )
11
+ module Plist
12
+ def Plist::parse_xml( filename_or_xml )
13
+ listener = Listener.new
14
+ parser = StreamParser.new(filename_or_xml, listener)
15
+ parser.parse
16
+ listener.result
17
+ end
18
+
19
+ class Listener
20
+ attr_accessor :result, :open
21
+
22
+ def initialize
23
+ @result = nil
24
+ @open = Array.new
25
+ end
26
+
27
+ def tag_start(name, attributes)
28
+ @open.push PTag::mappings[name].new
29
+ end
30
+
31
+ def text( contents )
32
+ @open.last.text = contents if @open.last
33
+ end
34
+
35
+ def tag_end(name)
36
+ last = @open.pop
37
+ if @open.empty?
38
+ @result = last.to_ruby
39
+ else
40
+ @open.last.children.push last
41
+ end
42
+ end
43
+ end
44
+
45
+ class StreamParser
46
+ def initialize( plist_data_or_file, listener )
47
+ if plist_data_or_file.respond_to? :read
48
+ @xml = plist_data_or_file.read
49
+ elsif File.exists? plist_data_or_file
50
+ @xml = File.read( plist_data_or_file )
51
+ else
52
+ @xml = plist_data_or_file
53
+ end
54
+
55
+ @listener = listener
56
+ end
57
+
58
+ TEXT = /([^<]+)/
59
+ XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>*/um
60
+ DOCTYPE_PATTERN = /\s*<!DOCTYPE\s+(.*?)(\[|>)/um
61
+ COMMENT_START = /\A<!--/u
62
+ COMMENT_END = /.*?-->/um
63
+
64
+ def parse
65
+ plist_tags = PTag::mappings.keys.join('|')
66
+ start_tag = /<(#{plist_tags})([^>]*)>/i
67
+ end_tag = /<\/(#{plist_tags})[^>]*>/i
68
+
69
+ require 'strscan'
70
+
71
+ @scanner = StringScanner.new(@xml)
72
+ until @scanner.eos?
73
+ if @scanner.scan(COMMENT_START)
74
+ @scanner.scan(COMMENT_END)
75
+ elsif @scanner.scan(XMLDECL_PATTERN)
76
+ elsif @scanner.scan(DOCTYPE_PATTERN)
77
+ elsif @scanner.scan(start_tag)
78
+ @listener.tag_start(@scanner[1], nil)
79
+ if (@scanner[2] =~ /\/$/)
80
+ @listener.tag_end(@scanner[1])
81
+ end
82
+ elsif @scanner.scan(TEXT)
83
+ @listener.text(@scanner[1])
84
+ elsif @scanner.scan(end_tag)
85
+ @listener.tag_end(@scanner[1])
86
+ else
87
+ raise "Unimplemented element"
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ class PTag
94
+ @@mappings = { }
95
+ def PTag::mappings
96
+ @@mappings
97
+ end
98
+
99
+ def PTag::inherited( sub_class )
100
+ key = sub_class.to_s.downcase
101
+ key.gsub!(/^plist::/, '' )
102
+ key.gsub!(/^p/, '') unless key == "plist"
103
+
104
+ @@mappings[key] = sub_class
105
+ end
106
+
107
+ attr_accessor :text, :children
108
+ def initialize
109
+ @children = Array.new
110
+ end
111
+
112
+ def to_ruby
113
+ raise "Unimplemented: " + self.class.to_s + "#to_ruby on #{self.inspect}"
114
+ end
115
+ end
116
+
117
+ class PList < PTag
118
+ def to_ruby
119
+ children.first.to_ruby if children.first
120
+ end
121
+ end
122
+
123
+ class PDict < PTag
124
+ def to_ruby
125
+ dict = Hash.new
126
+ key = nil
127
+
128
+ children.each do |c|
129
+ if key.nil?
130
+ key = c.to_ruby
131
+ else
132
+ dict[key] = c.to_ruby
133
+ key = nil
134
+ end
135
+ end
136
+
137
+ dict
138
+ end
139
+ end
140
+
141
+ class PKey < PTag
142
+ def to_ruby
143
+ CGI::unescapeHTML(text || '')
144
+ end
145
+ end
146
+
147
+ class PString < PTag
148
+ def to_ruby
149
+ CGI::unescapeHTML(text || '')
150
+ end
151
+ end
152
+
153
+ class PArray < PTag
154
+ def to_ruby
155
+ children.collect do |c|
156
+ c.to_ruby
157
+ end
158
+ end
159
+ end
160
+
161
+ class PInteger < PTag
162
+ def to_ruby
163
+ text.to_i
164
+ end
165
+ end
166
+
167
+ class PTrue < PTag
168
+ def to_ruby
169
+ true
170
+ end
171
+ end
172
+
173
+ class PFalse < PTag
174
+ def to_ruby
175
+ false
176
+ end
177
+ end
178
+
179
+ class PReal < PTag
180
+ def to_ruby
181
+ text.to_f
182
+ end
183
+ end
184
+
185
+ require 'date'
186
+ class PDate < PTag
187
+ def to_ruby
188
+ DateTime.parse(text)
189
+ end
190
+ end
191
+
192
+ require 'base64'
193
+ class PData < PTag
194
+ def to_ruby
195
+ data = Base64.decode64(text.gsub(/\s+/, ''))
196
+
197
+ begin
198
+ return Marshal.load(data)
199
+ rescue Exception
200
+ io = StringIO.new
201
+ io.write data
202
+ io.rewind
203
+ return io
204
+ end
205
+ end
206
+ end
207
+ end
208
+
209
+
210
+ # module Plist
211
+ # VERSION = '3.1.0'
212
+ # end
213
+
@@ -0,0 +1,70 @@
1
+ # Main SearchLink class
2
+ module SL
3
+ include URL
4
+
5
+ class SearchLink
6
+ include Plist
7
+
8
+ attr_reader :originput, :output, :clipboard
9
+
10
+ private
11
+
12
+ #
13
+ # Run a search
14
+ #
15
+ # @param[String] search_type The search type (abbreviation)
16
+ # @param[String] search_terms The search terms
17
+ # @param[String] link_text The link text
18
+ # @param[Integer] search_count The current search count
19
+ #
20
+ # @return [Array] [Url, link, text]
21
+ #
22
+ def do_search(search_type, search_terms, link_text = '', search_count = 0)
23
+ if (search_count % 5).zero?
24
+ SL.notify('Throttling for 5s')
25
+ sleep 5
26
+ end
27
+
28
+ description = SL::Searches.description_for_search(search_type)
29
+
30
+ SL.notify(description, search_terms)
31
+ return [false, search_terms, link_text] if search_terms.empty?
32
+
33
+ if SL::Searches.valid_search?(search_type)
34
+ url, title, link_text = SL::Searches.do_search(search_type, search_terms, link_text)
35
+ else
36
+ case search_type
37
+ when /^r$/ # simple replacement
38
+ if SL.config['validate_links'] && !SL::URL.valid_link?(search_terms)
39
+ return [false, "Link not valid: #{search_terms}", link_text]
40
+ end
41
+
42
+ title = SL::URL.title(search_terms) || search_terms
43
+
44
+ link_text = title if link_text == ''
45
+ return [search_terms, title, link_text]
46
+ else
47
+ if search_terms
48
+ if search_type =~ /.+?\.\w{2,}$/
49
+ url, title, link_text = SL.ddg(%(site:#{search_type} #{search_terms}), link_text)
50
+ else
51
+ url, title, link_text = SL.ddg(search_terms, link_text)
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ if link_text == ''
58
+ link_text = SL.titleize ? title : search_terms
59
+ end
60
+
61
+ if url && SL.config['validate_links'] && !SL::URL.valid_link?(url) && search_type !~ /^sp(ell)?/
62
+ [false, "Not found: #{url}", link_text]
63
+ elsif !url
64
+ [false, "No results: #{url}", link_text]
65
+ else
66
+ [url, title, link_text]
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,25 @@
1
+ module SL
2
+ # Amazon Search
3
+ class AmazonSearch
4
+ class << self
5
+ def settings
6
+ {
7
+ trigger: 'a',
8
+ searches: [
9
+ ['a', 'Amazon Search']
10
+ ]
11
+ }
12
+ end
13
+
14
+ def search(_, search_terms, link_text)
15
+ az_url, = SL.ddg("site:amazon.com #{search_terms}", link_text)
16
+ url, title = SL::URL.amazon_affiliatize(az_url, SL.config['amazon_partner'])
17
+ title ||= search_terms
18
+
19
+ [url, title, link_text]
20
+ end
21
+ end
22
+
23
+ SL::Searches.register 'amazon', :search, self
24
+ end
25
+ end
@@ -0,0 +1,123 @@
1
+ # title: Apple Music Search
2
+ # description: Search Apple Music
3
+ module SL
4
+ # Apple Music Search
5
+ class AppleMusicSearch
6
+ class << self
7
+ def settings
8
+ {
9
+ trigger: 'am(pod|art|alb|song)?e?',
10
+ searches: [
11
+ ['am', 'Apple Music'],
12
+ ['ampod', 'Apple Music Podcast'],
13
+ ['amart', 'Apple Music Artist'],
14
+ ['amalb', 'Apple Music Album'],
15
+ ['amsong', 'Apple Music Song'],
16
+ ['amalbe', 'Apple Music Album Embed'],
17
+ ['amsong', 'Apple Music Song Embed']
18
+ ]
19
+ }
20
+ end
21
+
22
+ def search(search_type, search_terms, link_text)
23
+ stype = search_type.downcase.sub(/^am/, '')
24
+ otype = :link
25
+ if stype =~ /e$/
26
+ otype = :embed
27
+ stype.sub!(/e$/, '')
28
+ end
29
+ result = case stype
30
+ when /^pod$/
31
+ applemusic(search_terms, 'podcast')
32
+ when /^art$/
33
+ applemusic(search_terms, 'music', 'musicArtist')
34
+ when /^alb$/
35
+ applemusic(search_terms, 'music', 'album')
36
+ when /^song$/
37
+ applemusic(search_terms, 'music', 'musicTrack')
38
+ else
39
+ applemusic(search_terms)
40
+ end
41
+
42
+ return [false, "Not found: #{search_terms}", link_text] unless result
43
+
44
+ # {:type=>,:id=>,:url=>,:title=>}
45
+ if otype == :embed && result[:type] =~ /(album|song)/
46
+ url = 'embed'
47
+ if result[:type] =~ /song/
48
+ link = %(https://embed.music.apple.com/#{SL.config['country_code'].downcase}/album/#{result[:album]}?i=#{result[:id]}&app=music#{SL.config['itunes_affiliate']})
49
+ height = 150
50
+ else
51
+ link = %(https://embed.music.apple.com/#{SL.config['country_code'].downcase}/album/#{result[:id]}?app=music#{SL.config['itunes_affiliate']})
52
+ height = 450
53
+ end
54
+
55
+ title = [
56
+ %(<iframe src="#{link}" allow="autoplay *; encrypted-media *;"),
57
+ %(frameborder="0" height="#{height}"),
58
+ %(style="width:100%;max-width:660px;overflow:hidden;background:transparent;"),
59
+ %(sandbox="allow-forms allow-popups allow-same-origin),
60
+ %(allow-scripts allow-top-navigation-by-user-activation"></iframe>)
61
+ ].join(' ')
62
+ else
63
+ url = result[:url]
64
+ title = result[:title]
65
+ end
66
+ [url, title, link_text]
67
+ end
68
+
69
+ # Search apple music
70
+ # terms => search terms (unescaped)
71
+ # media => music, podcast
72
+ # entity => optional: artist, song, album, podcast
73
+ # returns {:type=>,:id=>,:url=>,:title}
74
+ def applemusic(terms, media = 'music', entity = '')
75
+ url = "http://itunes.apple.com/search?term=#{terms.url_encode}&country=#{SL.config['country_code']}&media=#{media}&entity=#{entity}"
76
+ page = Curl::Json.new(url, compressed: true, symbolize_names: true)
77
+ json = page.json
78
+ return false unless json[:resultCount]&.positive?
79
+
80
+ output = process_result(json[:results][0])
81
+
82
+ return false if output.empty?
83
+
84
+ output
85
+ end
86
+
87
+ def process_result(result)
88
+ output = {}
89
+ aff = SL.config['itunes_affiliate']
90
+
91
+ case result[:wrapperType]
92
+ when 'track'
93
+ if result[:kind] == 'podcast'
94
+ output[:type] = 'podcast'
95
+ output[:id] = result[:collectionId]
96
+ output[:url] = result[:collectionViewUrl].to_am + aff
97
+ output[:title] = result[:collectionName]
98
+ else
99
+ output[:type] = 'song'
100
+ output[:album] = result[:collectionId]
101
+ output[:id] = result[:trackId]
102
+ output[:url] = result[:trackViewUrl].to_am + aff
103
+ output[:title] = "#{result[:trackName]} by #{result[:artistName]}"
104
+ end
105
+ when 'collection'
106
+ output[:type] = 'album'
107
+ output[:id] = result[:collectionId]
108
+ output[:url] = result[:collectionViewUrl].to_am + aff
109
+ output[:title] = "#{result[:collectionName]} by #{result[:artistName]}"
110
+ when 'artist'
111
+ output[:type] = 'artist'
112
+ output[:id] = result[:artistId]
113
+ output[:url] = result[:artistLinkUrl].to_am + aff
114
+ output[:title] = result[:artistName]
115
+ end
116
+
117
+ output
118
+ end
119
+ end
120
+
121
+ SL::Searches.register 'applemusic', :search, self
122
+ end
123
+ end
@@ -0,0 +1,50 @@
1
+ module SL
2
+ # Bit.ly link shortening
3
+ class BitlySearch
4
+ class << self
5
+ def settings
6
+ {
7
+ trigger: 'b(l|itly)',
8
+ searches: [
9
+ ['bl', 'bit.ly Shorten'],
10
+ ['bitly', 'bit.ly shorten']
11
+ ]
12
+ }
13
+ end
14
+
15
+ def search(_, search_terms, link_text)
16
+ if SL::URL.url?(search_terms)
17
+ link = search_terms
18
+ else
19
+ link, rtitle = SL.ddg(search_terms, link_text)
20
+ end
21
+
22
+ url, title = bitly_shorten(link, rtitle)
23
+ link_text = title || url
24
+ [url, title, link_text]
25
+ end
26
+
27
+ def bitly_shorten(url, title = nil)
28
+ unless SL.config.key?('bitly_access_token') && !SL.config['bitly_access_token'].empty?
29
+ SL.add_error('Bit.ly not configured', 'Missing access token')
30
+ return [false, title]
31
+ end
32
+
33
+ domain = SL.config.key?('bitly_domain') ? SL.config['bitly_domain'] : 'bit.ly'
34
+ long_url = url.dup
35
+ curl = TTY::Which.which('curl')
36
+ cmd = [
37
+ %(#{curl} -SsL -H 'Authorization: Bearer #{SL.config['bitly_access_token']}'),
38
+ %(-H 'Content-Type: application/json'),
39
+ '-X POST', %(-d '{ "long_url": "#{url}", "domain": "#{domain}" }'), 'https://api-ssl.bitly.com/v4/shorten'
40
+ ]
41
+ data = JSON.parse(`#{cmd.join(' ')}`.strip)
42
+ link = data['link']
43
+ title ||= SL::URL.title(long_url)
44
+ [link, title]
45
+ end
46
+ end
47
+
48
+ SL::Searches.register 'bitly', :search, self
49
+ end
50
+ end
@@ -0,0 +1,67 @@
1
+ module SL
2
+ # Dictionary Definition Search
3
+ class DefinitionSearch
4
+ class << self
5
+ # Returns a hash of settings for the search
6
+ #
7
+ # @return [Hash] the settings for the search
8
+ #
9
+ def settings
10
+ {
11
+ trigger: 'def(?:ine)?',
12
+ searches: [
13
+ ['def', 'Dictionary Definition'],
14
+ ['define', nil]
15
+ ]
16
+ }
17
+ end
18
+
19
+ # Searches for a definition of the given terms
20
+ #
21
+ # @param _ [String] unused
22
+ # @param search_terms [String] the terms to
23
+ # search for
24
+ # @param link_text [String] the text to use
25
+ # for the link
26
+ # @return [Array] the url, title, and link text for the
27
+ # search
28
+ #
29
+ def search(_, search_terms, link_text)
30
+ fix = SL.spell(search_terms)
31
+
32
+ if fix && search_terms.downcase != fix.downcase
33
+ SL.add_error('Spelling', "Spelling altered for '#{search_terms}' to '#{fix}'")
34
+ search_terms = fix
35
+ link_text = fix
36
+ end
37
+
38
+ url, title = define(search_terms)
39
+
40
+ url ? [url, title, link_text] : [false, false, link_text]
41
+ end
42
+
43
+ # Searches for a definition of the given terms
44
+ #
45
+ # @param terms [String] the terms to search for
46
+ # @return [Array] the url and title for the search
47
+ #
48
+ def define(terms)
49
+ def_url = "https://www.wordnik.com/words/#{terms.url_encode}"
50
+ curl = TTY::Which.which('curl')
51
+ body = `#{curl} -sSL '#{def_url}'`
52
+ if body =~ /id="define"/
53
+ first_definition = body.match(%r{(?mi)(?:id="define"[\s\S]*?<li>)([\s\S]*?)</li>})[1]
54
+ parts = first_definition.match(%r{<abbr title="partOfSpeech">(.*?)</abbr> (.*?)$})
55
+ return [def_url, "(#{parts[1]}) #{parts[2]}".gsub(%r{</?.*?>}, '').strip]
56
+ end
57
+
58
+ false
59
+ rescue StandardError
60
+ false
61
+ end
62
+ end
63
+
64
+ # Registers the search with the SL::Searches module
65
+ SL::Searches.register 'definition', :search, self
66
+ end
67
+ end