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,318 @@
1
+ ##
2
+ ## Chromium (Chrome, Arc, Brave, Edge) search methods
3
+ ##
4
+ module SL
5
+ # Chromium history search
6
+ class HistorySearch
7
+ class << self
8
+ ## Search Arc history
9
+ ##
10
+ ## @param term The search term
11
+ ##
12
+ ## @return [Array] Single bookmark, [url, title, date]
13
+ ##
14
+ def search_arc_history(term)
15
+ # Google history
16
+ history_file = File.expand_path('~/Library/Application Support/Arc/User Data/Default/History')
17
+ if File.exist?(history_file)
18
+ SL.notify('Searching Arc History', term)
19
+ search_chromium_history(history_file, term)
20
+ else
21
+ false
22
+ end
23
+ end
24
+
25
+ ## Search Brave history
26
+ ##
27
+ ## @param term The search term
28
+ ##
29
+ ## @return [Array] Single bookmark, [url, title, date]
30
+ ##
31
+ def search_brave_history(term)
32
+ # Google history
33
+ history_file = File.expand_path('~/Library/Application Support/BraveSoftware/Brave-Browser/Default/History')
34
+ if File.exist?(history_file)
35
+ SL.notify('Searching Brave History', term)
36
+ search_chromium_history(history_file, term)
37
+ else
38
+ false
39
+ end
40
+ end
41
+
42
+ ## Search Edge history
43
+ ##
44
+ ## @param term The search term
45
+ ##
46
+ ## @return [Array] Single bookmark, [url, title, date]
47
+ ##
48
+ def search_edge_history(term)
49
+ # Google history
50
+ history_file = File.expand_path('~/Library/Application Support/Microsoft Edge/Default/History')
51
+ if File.exist?(history_file)
52
+ SL.notify('Searching Edge History', term)
53
+ search_chromium_history(history_file, term)
54
+ else
55
+ false
56
+ end
57
+ end
58
+
59
+ ## Search Chrome history
60
+ ##
61
+ ## @param term The search term
62
+ ##
63
+ ## @return [Array] Single bookmark, [url, title, date]
64
+ ##
65
+ def search_chrome_history(term)
66
+ # Google history
67
+ history_file = File.expand_path('~/Library/Application Support/Google/Chrome/Default/History')
68
+ if File.exist?(history_file)
69
+ SL.notify('Searching Chrome History', term)
70
+ search_chromium_history(history_file, term)
71
+ else
72
+ false
73
+ end
74
+ end
75
+
76
+ ##
77
+ ## Generic chromium history search
78
+ ##
79
+ ## @param history_file [String] The history file
80
+ ## path for the selected
81
+ ## browser
82
+ ## @param term [String] The search term
83
+ ##
84
+ ## @return [Array] Single bookmark, [url, title, date]
85
+ ##
86
+ def search_chromium_history(history_file, term)
87
+ tmpfile = "#{history_file}.tmp"
88
+ FileUtils.cp(history_file, tmpfile)
89
+
90
+ exact_match = false
91
+ match_phrases = []
92
+
93
+ # If search terms start with ''term, only search for exact string matches
94
+ if term =~ /^ *'/
95
+ exact_match = true
96
+ term.gsub!(/(^ *'+|'+ *$)/, '')
97
+ elsif term =~ /%22(.*?)%22/
98
+ match_phrases = term.scan(/%22(\S.*?\S)%22/)
99
+ term.gsub!(/%22(\S.*?\S)%22/, '')
100
+ end
101
+
102
+ terms = []
103
+ terms.push("(url NOT LIKE '%search/?%'
104
+ AND url NOT LIKE '%?q=%'
105
+ AND url NOT LIKE '%?s=%'
106
+ AND url NOT LIKE '%duckduckgo.com/?t%')")
107
+ if exact_match
108
+ terms.push("(url LIKE '%#{term.strip.downcase}%' OR title LIKE '%#{term.strip.downcase}%')")
109
+ else
110
+ terms.concat(term.split(/\s+/).map do |t|
111
+ "(url LIKE '%#{t.strip.downcase}%' OR title LIKE '%#{t.strip.downcase}%')"
112
+ end)
113
+ terms.concat(match_phrases.map do |t|
114
+ "(url LIKE '%#{t[0].strip.downcase}%' OR title LIKE '%#{t[0].strip.downcase}%')"
115
+ end)
116
+ end
117
+
118
+ query = terms.join(' AND ')
119
+ most_recent = `sqlite3 -json '#{tmpfile}' "select title, url,
120
+ datetime(last_visit_time / 1000000 + (strftime('%s', '1601-01-01')), 'unixepoch') as datum
121
+ from urls where #{query} order by datum desc limit 1 COLLATE NOCASE;"`.strip
122
+ FileUtils.rm_f(tmpfile)
123
+ return false if most_recent.strip.empty?
124
+
125
+ bm = JSON.parse(most_recent)[0]
126
+
127
+ date = Time.parse(bm['datum'])
128
+ [bm['url'], bm['title'], date]
129
+ end
130
+
131
+ ##
132
+ ## Search Arc bookmarks
133
+ ##
134
+ ## @param term [String] The search term
135
+ ##
136
+ ## @return [Array] single bookmark [url, title, date]
137
+ ##
138
+ def search_arc_bookmarks(term)
139
+ bookmarks_file = File.expand_path('~/Library/Application Support/Arc/User Data/Default/Bookmarks')
140
+
141
+ if File.exist?(bookmarks_file)
142
+ SL.notify('Searching Brave Bookmarks', term)
143
+ return search_chromium_bookmarks(bookmarks_file, term)
144
+ end
145
+
146
+ false
147
+ end
148
+
149
+ ##
150
+ ## Search Brave bookmarks
151
+ ##
152
+ ## @param term [String] The search term
153
+ ##
154
+ ## @return [Array] single bookmark [url, title, date]
155
+ ##
156
+ def search_brave_bookmarks(term)
157
+ bookmarks_file = File.expand_path('~/Library/Application Support/BraveSoftware/Brave-Browser/Default/Bookmarks')
158
+
159
+ if File.exist?(bookmarks_file)
160
+ SL.notify('Searching Brave Bookmarks', term)
161
+ return search_chromium_bookmarks(bookmarks_file, term)
162
+ end
163
+
164
+ false
165
+ end
166
+
167
+ ##
168
+ ## Search Ege bookmarks
169
+ ##
170
+ ## @param term [String] The search term
171
+ ##
172
+ ## @return [Array] single bookmark [url, title, date]
173
+ ##
174
+ def search_edge_bookmarks(term)
175
+ bookmarks_file = File.expand_path('~/Library/Application Support/Microsoft Edge/Default/Bookmarks')
176
+
177
+ if File.exist?(bookmarks_file)
178
+ SL.notify('Searching Edge Bookmarks', term)
179
+ return search_chromium_bookmarks(bookmarks_file, term)
180
+ end
181
+
182
+ false
183
+ end
184
+
185
+ ##
186
+ ## Search Chrome bookmarks
187
+ ##
188
+ ## @param term [String] The search term
189
+ ##
190
+ ## @return [Array] single bookmark [url, title, date]
191
+ ##
192
+ def search_chrome_bookmarks(term)
193
+ bookmarks_file = File.expand_path('~/Library/Application Support/Google/Chrome/Default/Bookmarks')
194
+
195
+ if File.exist?(bookmarks_file)
196
+ SL.notify('Searching Chrome Bookmarks', term)
197
+ return search_chromium_bookmarks(bookmarks_file, term)
198
+ end
199
+
200
+ false
201
+ end
202
+
203
+ ##
204
+ ## Generic chromium bookmark search
205
+ ##
206
+ ## @param bookmarks_file [String] The path to
207
+ ## bookmarks file for
208
+ ## selected browser
209
+ ## @param term [String] The term
210
+ ##
211
+ ## @return [Array] single bookmark [url, title, date]
212
+ ##
213
+ def search_chromium_bookmarks(bookmarks_file, term)
214
+ chrome_bookmarks = JSON.parse(IO.read(bookmarks_file))
215
+
216
+ exact_match = false
217
+ match_phrases = []
218
+
219
+ # If search terms start with ''term, only search for exact string matches
220
+ if term =~ /^ *'/
221
+ exact_match = true
222
+ term.gsub!(/(^ *'+|'+ *$)/, '')
223
+ elsif term =~ /%22(.*?)%22/
224
+ match_phrases = term.scan(/%22(\S.*?\S)%22/)
225
+ term.gsub!(/%22(\S.*?\S)%22/, '')
226
+ end
227
+
228
+ if chrome_bookmarks
229
+ roots = chrome_bookmarks['roots']
230
+
231
+ urls = extract_chrome_bookmarks(roots, [], term)
232
+
233
+ unless urls.empty?
234
+ urls.delete_if { |bm| !(bm[:url].matches_exact(term) || bm[:title].matches_exact(term)) } if exact_match
235
+
236
+ if match_phrases
237
+ match_phrases.map! { |phrase| phrase[0] }
238
+ urls.delete_if do |bm|
239
+ matched = true
240
+ match_phrases.each do |phrase|
241
+ matched = false unless bm[:url].matches_exact(phrase) || bm[:title].matches_exact(phrase)
242
+ end
243
+ !matched
244
+ end
245
+ end
246
+
247
+ return false if urls.empty?
248
+
249
+ lastest_bookmark = urls.max_by { |u| u[:score] }
250
+
251
+ return [lastest_bookmark[:url], lastest_bookmark[:title], lastest_bookmark[:date]]
252
+ end
253
+ end
254
+
255
+ false
256
+ end
257
+
258
+ ##
259
+ ## Extract chromium bookmarks from JSON file
260
+ ##
261
+ ## @param json [String] The json data
262
+ ## @param urls [Array] The gathered urls,
263
+ ## appended to recursively
264
+ ## @param term [String] The search term
265
+ ## (optional)
266
+ ##
267
+ ## @return [Array] array of bookmarks
268
+ ##
269
+ def extract_chrome_bookmarks(json, urls = [], term = '')
270
+ if json.instance_of?(Array)
271
+ json.each { |item| urls = extract_chrome_bookmarks(item, urls, term) }
272
+ elsif json.instance_of?(Hash)
273
+ if json.key? 'children'
274
+ urls = extract_chrome_bookmarks(json['children'], urls, term)
275
+ elsif json['type'] == 'url'
276
+ date = Time.at(json['date_added'].to_i / 1000000 + (Time.new(1601, 01, 01).strftime('%s').to_i))
277
+ url = { url: json['url'], title: json['name'], date: date }
278
+ score = score_mark(url, term)
279
+
280
+ if score > 7
281
+ url[:score] = score
282
+ urls << url
283
+ end
284
+ else
285
+ json.each { |_, v| urls = extract_chrome_bookmarks(v, urls, term) }
286
+ end
287
+ else
288
+ return urls
289
+ end
290
+ urls
291
+ end
292
+
293
+ ##
294
+ ## Score bookmark for search term matches
295
+ ##
296
+ ## @param mark [Hash] The bookmark
297
+ ## @param terms [String] The search terms
298
+ ##
299
+ def score_mark(mark, terms)
300
+ return 0 unless mark[:url]
301
+
302
+ score = if mark[:title] && mark[:title].matches_exact(terms)
303
+ 12 + mark[:url].matches_score(terms, start_word: false)
304
+ elsif mark[:url].matches_exact(terms)
305
+ 11
306
+ elsif mark[:title] && mark[:title].matches_score(terms) > 5
307
+ mark[:title].matches_score(terms)
308
+ elsif mark[:url].matches_score(terms, start_word: false)
309
+ mark[:url].matches_score(terms, start_word: false)
310
+ else
311
+ 0
312
+ end
313
+
314
+ score
315
+ end
316
+ end
317
+ end
318
+ end
@@ -0,0 +1,135 @@
1
+ module SL
2
+ class HistorySearch
3
+ class << self
4
+ def search_firefox_history(term)
5
+ # Firefox history
6
+ base = File.expand_path('~/Library/Application Support/Firefox/Profiles')
7
+ Dir.chdir(base)
8
+ profile = Dir.glob('*default-release')
9
+ return false unless profile
10
+
11
+ src = File.join(base, profile[0], 'places.sqlite')
12
+
13
+ exact_match = false
14
+ match_phrases = []
15
+
16
+ # If search terms start with ''term, only search for exact string matches
17
+ case term
18
+ when /^ *'/
19
+ exact_match = true
20
+ term.gsub!(/(^ *'+|'+ *$)/, '')
21
+ when /%22(.*?)%22/
22
+ match_phrases = term.scan(/%22(\S.*?\S)%22/)
23
+ term.gsub!(/%22(\S.*?\S)%22/, '')
24
+ end
25
+
26
+ if File.exist?(src)
27
+ SL.notify('Searching Firefox History', term)
28
+ tmpfile = "#{src}.tmp"
29
+ FileUtils.cp(src, tmpfile)
30
+
31
+ terms = []
32
+ terms.push("(moz_places.url NOT LIKE '%search/?%'
33
+ AND moz_places.url NOT LIKE '%?q=%'
34
+ AND moz_places.url NOT LIKE '%?s=%'
35
+ AND moz_places.url NOT LIKE '%duckduckgo.com/?t%')")
36
+ if exact_match
37
+ terms.push("(moz_places.url LIKE '%#{term.strip.downcase}%' OR moz_places.title LIKE '%#{term.strip.downcase}%')")
38
+ else
39
+ terms.concat(term.split(/\s+/).map do |t|
40
+ "(moz_places.url LIKE '%#{t.strip.downcase}%' OR moz_places.title LIKE '%#{t.strip.downcase}%')"
41
+ end)
42
+ terms.concat(match_phrases.map do |t|
43
+ "(moz_places.url LIKE '%#{t[0].strip.downcase}%' OR moz_places.title LIKE '%#{t[0].strip.downcase}%')"
44
+ end)
45
+ end
46
+ query = terms.join(' AND ')
47
+ most_recent = `sqlite3 -json '#{tmpfile}' "select moz_places.title, moz_places.url,
48
+ datetime(moz_historyvisits.visit_date/1000000, 'unixepoch', 'localtime') as datum
49
+ from moz_places, moz_historyvisits where moz_places.id = moz_historyvisits.place_id
50
+ and #{query} order by datum desc limit 1 COLLATE NOCASE;"`.strip
51
+ FileUtils.rm_f(tmpfile)
52
+
53
+ return false if most_recent.strip.empty?
54
+
55
+ marks = JSON.parse(most_recent)
56
+
57
+ marks.map! do |bm|
58
+ date = Time.parse(bm['datum'])
59
+ score = score_mark({url: bm['url'], title: bm['title']}, term)
60
+ { url: bm['url'], title: bm['title'], date: date, score: score }
61
+ end
62
+
63
+
64
+ m = marks.sort_by { |m| [m[:url].length * -1, m[:score]] }.last
65
+
66
+ [m[:url], m[:title], m[:date]]
67
+ else
68
+ false
69
+ end
70
+ end
71
+
72
+ def search_firefox_bookmarks(term)
73
+ # Firefox history
74
+ base = File.expand_path('~/Library/Application Support/Firefox/Profiles')
75
+ Dir.chdir(base)
76
+ profile = Dir.glob('*default-release')
77
+ return false unless profile
78
+
79
+ src = File.join(base, profile[0], 'places.sqlite')
80
+
81
+ exact_match = false
82
+ match_phrases = []
83
+
84
+ # If search terms start with ''term, only search for exact string matches
85
+ if term =~ /^ *'/
86
+ exact_match = true
87
+ term.gsub!(/(^ *'+|'+ *$)/, '')
88
+ elsif term =~ /%22(.*?)%22/
89
+ match_phrases = term.scan(/%22(\S.*?\S)%22/)
90
+ term.gsub!(/%22(\S.*?\S)%22/, '')
91
+ end
92
+
93
+ if File.exist?(src)
94
+ SL.notify('Searching Firefox Bookmarks', term)
95
+ tmpfile = "#{src}.tmp"
96
+ FileUtils.cp(src, tmpfile)
97
+
98
+ terms = []
99
+ terms.push("(h.url NOT LIKE '%search/?%'
100
+ AND h.url NOT LIKE '%?q=%'
101
+ AND h.url NOT LIKE '%?s=%'
102
+ AND h.url NOT LIKE '%duckduckgo.com/?t%')")
103
+ if exact_match
104
+ terms.push("(h.url LIKE '%#{term.strip.downcase}%' OR h.title LIKE '%#{term.strip.downcase}%')")
105
+ else
106
+ terms.concat(term.split(/\s+/).map do |t|
107
+ "(h.url LIKE '%#{t.strip.downcase}%' OR h.title LIKE '%#{t.strip.downcase}%')"
108
+ end)
109
+ terms.concat(match_phrases.map do |t|
110
+ "(h.url LIKE '%#{t[0].strip.downcase}%' OR h.title LIKE '%#{t[0].strip.downcase}%')"
111
+ end)
112
+ end
113
+
114
+ query = terms.join(' AND ')
115
+
116
+ most_recent = `sqlite3 -json '#{tmpfile}' "select h.url, b.title,
117
+ datetime(b.dateAdded/1000000, 'unixepoch', 'localtime') as datum
118
+ FROM moz_places h JOIN moz_bookmarks b ON h.id = b.fk
119
+ where #{query} order by datum desc limit 1 COLLATE NOCASE;"`.strip
120
+ FileUtils.rm_f(tmpfile)
121
+
122
+ return false if most_recent.strip.empty?
123
+
124
+ bm = JSON.parse(most_recent)[0]
125
+
126
+ date = Time.parse(bm['datum'])
127
+ score = score_mark({url: bm['url'], title: bm['title']}, term)
128
+ [bm['url'], bm['title'], date, score]
129
+ else
130
+ false
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,133 @@
1
+ module SL
2
+ class HistorySearch
3
+ class << self
4
+ # Search Safari history for terms
5
+ #
6
+ # @param term The search term
7
+ #
8
+ def search_safari_history(term)
9
+ # Safari
10
+ src = File.expand_path('~/Library/Safari/History.db')
11
+ if File.exist?(src)
12
+ SL.notify('Searching Safari History', term)
13
+
14
+ exact_match = false
15
+ match_phrases = []
16
+
17
+ # If search terms start with ''term, only search for exact string matches
18
+ if term =~ /^ *'/
19
+ exact_match = true
20
+ term.gsub!(/(^ *'+|'+ *$)/, '')
21
+ elsif term =~ /%22(.*?)%22/
22
+ match_phrases = term.scan(/%22(\S.*?\S)%22/)
23
+ term.gsub!(/%22(\S.*?\S)%22/, '')
24
+ end
25
+
26
+ terms = []
27
+ terms.push("(url NOT LIKE '%search/?%'
28
+ AND url NOT LIKE '%?q=%' AND url NOT LIKE '%?s=%'
29
+ AND url NOT LIKE '%duckduckgo.com/?t%')")
30
+ if exact_match
31
+ terms.push("(url LIKE '%#{term.strip.downcase}%' OR title LIKE '%#{term.strip.downcase}%')")
32
+ else
33
+ terms.concat(term.split(/\s+/).map do |t|
34
+ "(url LIKE '%#{t.strip.downcase}%' OR title LIKE '%#{t.strip.downcase}%')"
35
+ end)
36
+ terms.concat(match_phrases.map do |t|
37
+ "(url LIKE '%#{t[0].strip.downcase}%' OR title LIKE '%#{t[0].strip.downcase}%')"
38
+ end)
39
+ end
40
+
41
+ query = terms.join(' AND ')
42
+
43
+ cmd = %(sqlite3 -json '#{src}' "select title, url,
44
+ datetime(visit_time/1000000, 'unixepoch', 'localtime') as datum
45
+ from history_visits INNER JOIN history_items ON history_items.id = history_visits.history_item
46
+ where #{query} order by datum desc limit 1 COLLATE NOCASE;")
47
+
48
+ most_recent = `#{cmd}`.strip
49
+
50
+ return false if most_recent.strip.empty?
51
+
52
+ bm = JSON.parse(most_recent)[0]
53
+ date = Time.parse(bm['datum'])
54
+ [bm['url'], bm['title'], date]
55
+ else
56
+ false
57
+ end
58
+ end
59
+
60
+ ##
61
+ ## Search Safari bookmarks for relevant search terms
62
+ ##
63
+ ## @param terms [String] The search terms
64
+ ##
65
+ ## @return [Array] [url, title, date]
66
+ ##
67
+ def search_safari_bookmarks(terms)
68
+ data = `plutil -convert xml1 -o - ~/Library/Safari/Bookmarks.plist`.strip
69
+ parent = Plist.parse_xml(data)
70
+ results = get_safari_bookmarks(parent, terms)
71
+ return false if results.empty?
72
+
73
+ result = results.max_by { |res| [res[:score], res[:title].length] }
74
+
75
+ [result[:url], result[:title], Time.now]
76
+ end
77
+
78
+ ##
79
+ ## Score bookmark for search term matches
80
+ ##
81
+ ## @param mark [Hash] The bookmark
82
+ ## @param terms [String] The search terms
83
+ ##
84
+ def score_bookmark(mark, terms)
85
+ score = if mark[:title].matches_exact(terms)
86
+ 12 + mark[:url].matches_score(terms, start_word: false)
87
+ elsif mark[:url].matches_exact(terms)
88
+ 11
89
+ elsif mark[:title].matches_score(terms) > 5
90
+ mark[:title].matches_score(terms)
91
+ elsif mark[:url].matches_score(terms, start_word: false)
92
+ mark[:url].matches_score(terms, start_word: false)
93
+ end
94
+
95
+ { url: mark[:url], title: mark[:title], score: score }
96
+ end
97
+
98
+ ##
99
+ ## Recursively parse bookmarks hash and score
100
+ ## bookmarks
101
+ ##
102
+ ## @param parent [Hash, Array] The parent
103
+ ## bookmark item
104
+ ## @param terms [String] The search terms
105
+ ##
106
+ ## @return [Array] array of scored bookmarks
107
+ ##
108
+ def get_safari_bookmarks(parent, terms)
109
+ results = []
110
+
111
+ if parent.is_a?(Array)
112
+ parent.each do |c|
113
+ if c.is_a?(Hash)
114
+ if c.key?('Children')
115
+ results.concat(get_safari_bookmarks(c['Children'], terms))
116
+ elsif c.key?('URIDictionary')
117
+ title = c['URIDictionary']['title']
118
+ url = c['URLString']
119
+ scored = score_bookmark({ url: url, title: title }, terms)
120
+
121
+ results.push(scored) if scored[:score] > 7
122
+ end
123
+ end
124
+ end
125
+ elsif parent&.key?('Children')
126
+ results.concat(get_safari_bookmarks(parent['Children'], terms))
127
+ end
128
+
129
+ results.sort_by { |h| [h[:score], h[:title].length * -1] }.reverse
130
+ end
131
+ end
132
+ end
133
+ end