Class: SL::HistorySearch

Inherits:
Object
  • Object
show all
Defined in:
lib/searchlink/searches/history.rb,
lib/searchlink/searches/helpers/safari.rb,
lib/searchlink/searches/helpers/firefox.rb,
lib/searchlink/searches/helpers/chromium.rb

Overview

Chromium history search

Class Method Summary collapse

Class Method Details

.extract_chrome_bookmarks(json, urls = [], term = '') ⇒ Array

Extract chromium bookmarks from JSON file

Parameters:

  • json (String)

    The json data

  • urls (Array) (defaults to: [])

    The gathered urls, appended to recursively

  • term (String) (defaults to: '')

    The search term (optional)

Returns:

  • (Array)

    array of bookmarks



420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
# File 'lib/searchlink/searches/helpers/chromium.rb', line 420

def extract_chrome_bookmarks(json, urls = [], term = '')
  if json.instance_of?(Array)
    json.each { |item| urls = extract_chrome_bookmarks(item, urls, term) }
  elsif json.instance_of?(Hash)
    if json.key? 'children'
      urls = extract_chrome_bookmarks(json['children'], urls, term)
    elsif json['type'] == 'url'
      date = Time.at(json['date_added'].to_i / 1_000_000 + Time.new(1601, 0o1, 0o1).strftime('%s').to_i)
      url = { url: json['url'], title: json['name'], date: date }
      score = score_mark(url, term)

      if score > 7
        url[:score] = score
        urls << url
      end
    else
      json.each_value { |v| urls = extract_chrome_bookmarks(v, urls, term) }
    end
  else
    return urls
  end
  urls
end

.get_safari_bookmarks(parent, terms) ⇒ Array

Recursively parse bookmarks hash and score bookmarks

Parameters:

  • parent (Hash, Array)

    The parent bookmark item

  • terms (String)

    The search terms

Returns:

  • (Array)

    array of scored bookmarks



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/searchlink/searches/helpers/safari.rb', line 111

def get_safari_bookmarks(parent, terms)
  results = []

  if parent.is_a?(Array)
    parent.each do |c|
      if c.is_a?(Hash)
        if c.key?('Children')
          results.concat(get_safari_bookmarks(c['Children'], terms))
        elsif c.key?('URIDictionary')
          title = c['URIDictionary']['title']
          url = c['URLString']
          scored = score_bookmark({ url: url, title: title }, terms)

          results.push(scored) if scored[:score] > 7
        end
      end
    end
  elsif parent&.key?('Children')
    results.concat(get_safari_bookmarks(parent['Children'], terms))
  end

  results.sort_by { |h| [h[:score], h[:title].length * -1] }.reverse
end

.score_bookmark(mark, terms) ⇒ Object

Score bookmark for search term matches

Parameters:

  • mark (Hash)

    The bookmark

  • terms (String)

    The search terms



87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/searchlink/searches/helpers/safari.rb', line 87

def score_bookmark(mark, terms)
  score = if mark[:title].matches_exact(terms)
            12 + mark[:url].matches_score(terms, start_word: false)
          elsif mark[:url].matches_exact(terms)
            11
          elsif mark[:title].matches_score(terms) > 5
            mark[:title].matches_score(terms)
          elsif mark[:url].matches_score(terms, start_word: false)
            mark[:url].matches_score(terms, start_word: false)
          end

  { url: mark[:url], title: mark[:title], score: score }
end

.score_mark(mark, terms) ⇒ Object

Score bookmark for search term matches

Parameters:

  • mark (Hash)

    The bookmark

  • terms (String)

    The search terms



450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
# File 'lib/searchlink/searches/helpers/chromium.rb', line 450

def score_mark(mark, terms)
  return 0 unless mark[:url]

  if mark[:title]&.matches_exact(terms)
    12 + mark[:url].matches_score(terms, start_word: false)
  elsif mark[:url].matches_exact(terms)
    11
  elsif (mark[:title] && mark[:title].matches_score(terms) > 5) ||
        (mark[:saved_title] && mark[:saved_title].matches_score(terms) > 5)

    if mark[:saved_title]
      t = mark[:title]&.matches_score(terms)
      s = mark[:saved_title].matches_score(terms)
      return s if t.nil?

      [t, s].max
    else
      mark[:title]&.matches_score(terms)
    end
  elsif mark[:url].matches_score(terms, start_word: false)
    mark[:url].matches_score(terms, start_word: false)
  else
    0
  end
end

.search(search_type, search_terms, link_text) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/searchlink/searches/history.rb', line 49

def search(search_type, search_terms, link_text)
  str = search_type.match(/^h(([scfabe])([hb])?)*$/)[1]

  types = []
  while str&.length&.positive?
    if str =~ /^s([hb]*)/
      t = Regexp.last_match(1)
      if t.length > 1 || t.empty?
        types.push('safari_history')
        types.push('safari_bookmarks')
      elsif t == 'h'
        types.push('safari_history')
      elsif t == 'b'
        types.push('safari_bookmarks')
      end
      str.sub!(/^s([hb]*)/, '')
    end

    if str =~ /^c([hb]*)/
      t = Regexp.last_match(1)
      if t.length > 1 || t.empty?
        types.push('chrome_bookmarks')
        types.push('chrome_history')
      elsif t == 'h'
        types.push('chrome_history')
      elsif t == 'b'
        types.push('chrome_bookmarks')
      end
      str.sub!(/^c([hb]*)/, '')
    end

    if str =~ /^f([hb]*)$/
      t = Regexp.last_match(1)
      if t.length > 1 || t.empty?
        types.push('firefox_bookmarks')
        types.push('firefox_history')
      elsif t == 'h'
        types.push('firefox_history')
      elsif t == 'b'
        types.push('firefox_bookmarks')
      end
      str.sub!(/^f([hb]*)/, '')
    end

    if str =~ /^e([hb]*)$/
      t = Regexp.last_match(1)
      if t.length > 1 || t.empty?
        types.push('edge_bookmarks')
        types.push('edge_history')
      elsif t == 'h'
        types.push('edge_history')
      elsif t == 'b'
        types.push('edge_bookmarks')
      end
      str.sub!(/^e([hb]*)/, '')
    end

    if str =~ /^b([hb]*)$/
      t = Regexp.last_match(1)
      if t.length > 1 || t.empty?
        types.push('brave_bookmarks')
        types.push('brave_history')
      elsif t == 'h'
        types.push('brave_history')
      elsif t == 'b'
        types.push('brave_bookmarks')
      end
      str.sub!(/^b([hb]*)/, '')
    end

    next unless str =~ /^a([hb]*)$/

    t = Regexp.last_match(1)
    if t.length > 1 || t.empty?
      types.push('arc_bookmarks')
      types.push('arc_history')
    elsif t == 'h'
      types.push('arc_history')
    elsif t == 'b'
      types.push('arc_bookmarks')
    end
    str.sub!(/^a([hb]*)/, '')
  end

  url, title = search_history(search_terms, types)
  link_text = title if link_text == '' || link_text == search_terms
  [url, title, link_text]
end

.search_arc_bookmarks(term) ⇒ Array

Search Arc bookmarks

Parameters:

  • term (String)

    The search term

Returns:

  • (Array)

    single bookmark [url, title, date]



177
178
179
180
181
182
183
184
185
186
# File 'lib/searchlink/searches/helpers/chromium.rb', line 177

def search_arc_bookmarks(term)
  bookmarks_file = File.expand_path('~/Library/Application Support/Arc/StorableSidebar.json')

  if File.exist?(bookmarks_file)
    SL.notify('Searching Arc Bookmarks', term)
    return search_arc_json(bookmarks_file, term)
  end

  false
end

.search_arc_history(term) ⇒ Array

Search Arc history

Parameters:

  • term

    The search term

Returns:

  • (Array)

    Single bookmark, [url, title, date]



16
17
18
19
20
21
22
23
24
25
# File 'lib/searchlink/searches/helpers/chromium.rb', line 16

def search_arc_history(term)
  # Google history
  history_file = File.expand_path('~/Library/Application Support/Arc/User Data/Default/History')
  if File.exist?(history_file)
    SL.notify('Searching Arc History', term)
    search_chromium_history(history_file, term)
  else
    false
  end
end

.search_arc_json(bookmarks_file, term) ⇒ Array

Search Arc/JSON bookmarks

Parameters:

  • bookmarks_file (String)

    path to bookmarks file

  • term (String)

    the string to search for

Returns:

  • (Array)

    single bookmark [url, title, date]



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/searchlink/searches/helpers/chromium.rb', line 281

def search_arc_json(bookmarks_file, term)
  arc_bookmarks = JSON.parse(IO.read(bookmarks_file))

  exact_match = false
  match_phrases = []

  # If search terms start with ''term, only search for exact string matches
  if term =~ /^ *'/
    exact_match = true
    term.gsub!(/(^ *'+|'+ *$)/, '')
  elsif term =~ /%22(.*?)%22/
    match_phrases = term.scan(/%22(\S.*?\S)%22/)
    term.gsub!(/%22(\S.*?\S)%22/, '')
  end

  if arc_bookmarks
    bookmarks = []
    arc_bookmarks['sidebarSyncState']['items'].each do |mark|
      next if mark.is_a?(String)

      next unless mark['value']['childrenIds'].empty?

      next unless mark['value']['data']['tab']

      url = {
        url: mark['value']['data']['tab']['savedURL'],
        saved_title: mark['value']['data']['tab']['savedTitle'],
        title: mark['value']['title'],
        created: mark['value']['createdAt'].to_datetime,
        active: mark['value']['data']['tab']['timeLastActiveAt']&.to_datetime
      }

      score = score_mark(url, term)

      if score > 7
        url[:score] = score
        bookmarks << url
      end
    end

    unless bookmarks.empty?
      if exact_match
        bookmarks.delete_if do |bm|
          !(bm[:url].matches_exact(term) ||
          bm[:title].matches_exact(term) ||
          bm[:saved_title].matches_exact(term))
        end
      end

      if match_phrases
        match_phrases.map! { |phrase| phrase[0] }
        bookmarks.delete_if do |bm|
          matched = true
          match_phrases.each do |phrase|
            matched = false unless bm[:url].matches_exact(phrase) ||
                                   bm[:title].matches_exact(phrase) ||
                                   bm[:saved_title].matches_exact(phrase)
          end
          !matched
        end
      end

      return false if bookmarks.empty?

      lastest_bookmark = bookmarks.min_by { |u| u[:created] }

      return [lastest_bookmark[:url], lastest_bookmark[:title], lastest_bookmark[:date]]
    end
  end

  false
end

.search_brave_bookmarks(term) ⇒ Array

Search Brave bookmarks

Parameters:

  • term (String)

    The search term

Returns:

  • (Array)

    single bookmark [url, title, date]



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/searchlink/searches/helpers/chromium.rb', line 195

def search_brave_bookmarks(term)
  base = File.expand_path('~/Library/Application Support/BraveSoftware/Brave-Browser/')
  profiles = Dir.glob('**/Bookmarks', base: base)
  profiles.delete_if { |p| p =~ /^Snapshots/ }
  profiles.map! { |f| File.join(base, f) }

  res = false

  profiles.each do |bookmarks|
    next unless File.exist?(bookmarks)

    profile = bookmarks.match(%r{Browser/([^/]+)/})[1]

    SL.notify("Searching Brave Bookmarks for profile #{profile}", term)
    res = search_chromium_bookmarks(bookmarks, term)

    break if res
  end

  res
end

.search_brave_history(term) ⇒ Array

Search Brave history

Parameters:

  • term

    The search term

Returns:

  • (Array)

    Single bookmark, [url, title, date]



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/searchlink/searches/helpers/chromium.rb', line 33

def search_brave_history(term)
  base = File.expand_path('~/Library/Application Support/BraveSoftware/Brave-Browser/')
  profiles = Dir.glob('**/History', base: base)
  profiles.delete_if { |p| p =~ /^Snapshots/ }
  profiles.map! { |f| File.join(base, f) }

  res = false

  profiles.each do |bookmarks|
    next unless File.exist?(bookmarks)

    profile = bookmarks.match(%r{Browser/([^/]+)/})[1]

    SL.notify("Searching Brave History for profile #{profile}", term)
    res = search_chromium_history(bookmarks, term)

    break if res
  end

  res
end

.search_chrome_bookmarks(term) ⇒ Array

Search Chrome bookmarks

Parameters:

  • term (String)

    The search term

Returns:

  • (Array)

    single bookmark [url, title, date]



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/searchlink/searches/helpers/chromium.rb', line 252

def search_chrome_bookmarks(term)
  base = File.expand_path('~/Library/Application Support/Google/Chrome/')
  profiles = Dir.glob('**/Bookmarks', base: base)
  profiles.delete_if { |p| p =~ /^Snapshots/ }
  profiles.map! { |f| File.join(base, f) }

  res = false

  profiles.each do |bookmarks|
    if File.exist?(bookmarks)
      profile = bookmarks.match(%r{Chrome/([^/]+)/})[1]

      SL.notify("Searching Chrome Bookmarks for profile #{profile}", term)
      res = search_chromium_bookmarks(bookmarks, term)
      break if res
    end
  end

  res
end

.search_chrome_history(term) ⇒ Array

Search Chrome history

Parameters:

  • term

    The search term

Returns:

  • (Array)

    Single bookmark, [url, title, date]



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/searchlink/searches/helpers/chromium.rb', line 89

def search_chrome_history(term)
  # Google history
  base = File.expand_path('~/Library/Application Support/Google/Chrome/')
  profiles = Dir.glob('**/History', base: base)
  profiles.delete_if { |p| p =~ /^Snapshots/ }
  profiles.map! { |f| File.join(base, f) }

  # bookmarks_file = File.expand_path('~/Library/Application Support/Google/Chrome/Default/Bookmarks')

  res = false

  profiles.each do |bookmarks|
    next unless File.exist?(bookmarks)

    profile = bookmarks.match(%r{Chrome/([^/]+)/})[1]

    SL.notify("Searching Chrome History for profile #{profile}", term)
    res = search_chromium_history(bookmarks, term)

    break if res
  end

  res
end

.search_chromium_bookmarks(bookmarks_file, term) ⇒ Array

Generic chromium bookmark search

Parameters:

  • bookmarks_file (String)

    The path to bookmarks file for selected browser

  • term (String)

    The term

Returns:

  • (Array)

    single bookmark [url, title, date]



364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'lib/searchlink/searches/helpers/chromium.rb', line 364

def search_chromium_bookmarks(bookmarks_file, term)
  chrome_bookmarks = JSON.parse(IO.read(bookmarks_file))

  exact_match = false
  match_phrases = []

  # If search terms start with ''term, only search for exact string matches
  if term =~ /^ *'/
    exact_match = true
    term.gsub!(/(^ *'+|'+ *$)/, '')
  elsif term =~ /%22(.*?)%22/
    match_phrases = term.scan(/%22(\S.*?\S)%22/)
    term.gsub!(/%22(\S.*?\S)%22/, '')
  end

  if chrome_bookmarks
    roots = chrome_bookmarks['roots']

    urls = extract_chrome_bookmarks(roots, [], term)

    unless urls.empty?
      urls.delete_if { |bm| !(bm[:url].matches_exact(term) || bm[:title].matches_exact(term)) } if exact_match

      if match_phrases
        match_phrases.map! { |phrase| phrase[0] }
        urls.delete_if do |bm|
          matched = true
          match_phrases.each do |phrase|
            matched = false unless bm[:url].matches_exact(phrase) || bm[:title].matches_exact(phrase)
          end
          !matched
        end
      end

      return false if urls.empty?

      lastest_bookmark = urls.max_by { |u| u[:score] }

      return [lastest_bookmark[:url], lastest_bookmark[:title], lastest_bookmark[:date]]
    end
  end

  false
end

.search_chromium_history(history_file, term) ⇒ Array

Generic chromium history search

Parameters:

  • history_file (String)

    The history file path for the selected browser

  • term (String)

    The search term

Returns:

  • (Array)

    Single bookmark, [url, title, date]



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/searchlink/searches/helpers/chromium.rb', line 124

def search_chromium_history(history_file, term)
  tmpfile = "#{history_file}.tmp"
  FileUtils.cp(history_file, tmpfile)

  exact_match = false
  match_phrases = []

  # If search terms start with ''term, only search for exact string matches
  if term =~ /^ *'/
    exact_match = true
    term.gsub!(/(^ *'+|'+ *$)/, '')
  elsif term =~ /%22(.*?)%22/
    match_phrases = term.scan(/%22(\S.*?\S)%22/)
    term.gsub!(/%22(\S.*?\S)%22/, '')
  end

  terms = []
  terms.push("(url NOT LIKE '%search/?%'
             AND url NOT LIKE '%?q=%'
             AND url NOT LIKE '%?s=%'
             AND url NOT LIKE '%www.bing.com%'
             AND url NOT LIKE '%duckduckgo.com/?t%')")
  if exact_match
    terms.push("(url LIKE '%#{term.strip.downcase}%' OR title LIKE '%#{term.strip.downcase}%')")
  else
    terms.concat(term.split(/\s+/).map do |t|
      "(url LIKE '%#{t.strip.downcase}%' OR title LIKE '%#{t.strip.downcase}%')"
    end)
    terms.concat(match_phrases.map do |t|
      "(url LIKE '%#{t[0].strip.downcase}%' OR title LIKE '%#{t[0].strip.downcase}%')"
    end)
  end

  query = terms.join(' AND ')
  most_recent = `sqlite3 -json '#{tmpfile}' "select title, url,
  datetime(last_visit_time / 1000000 + (strftime('%s', '1601-01-01')), 'unixepoch') as datum
  from urls where #{query} order by datum desc limit 1 COLLATE NOCASE;"`.strip
  FileUtils.rm_f(tmpfile)
  return false if most_recent.strip.empty?

  bm = JSON.parse(most_recent)[0]

  date = Time.parse(bm['datum'])
  [bm['url'], bm['title'], date]
end

.search_edge_bookmarks(term) ⇒ Array

Search Ege bookmarks

Parameters:

  • term (String)

    The search term

Returns:

  • (Array)

    single bookmark [url, title, date]



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/searchlink/searches/helpers/chromium.rb', line 224

def search_edge_bookmarks(term)
  base = File.expand_path('~/Library/Application Support/Microsoft Edge')
  profiles = Dir.glob('**/Bookmarks', base: base)
  profiles.delete_if { |p| p =~ /^Snapshots/ }
  profiles.map! { |f| File.join(base, f) }

  res = false

  profiles.each do |bookmarks|
    next unless File.exist?(bookmarks)

    profile = bookmarks.match(%r{Edge/([^/]+)/})[1]

    SL.notify("Searching Edge Bookmarks for profile #{profile}", term)
    res = search_chromium_bookmarks(bookmarks, term)
    break if res
  end

  res
end

.search_edge_history(term) ⇒ Array

Search Edge history

Parameters:

  • term

    The search term

Returns:

  • (Array)

    Single bookmark, [url, title, date]



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/searchlink/searches/helpers/chromium.rb', line 61

def search_edge_history(term)
  base = File.expand_path('~/Library/Application Support/Microsoft Edge/')
  profiles = Dir.glob('**/History', base: base)
  profiles.delete_if { |p| p =~ /^Snapshots/ }
  profiles.map! { |f| File.join(base, f) }

  res = false

  profiles.each do |bookmarks|
    next unless File.exist?(bookmarks)

    profile = bookmarks.match(%r{Edge/([^/]+)/})[1]

    SL.notify("Searching Chrome History for profile #{profile}", term)
    res = search_chromium_history(bookmarks, term)

    break if res
  end

  res
end

.search_firefox_bookmarks(term) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/searchlink/searches/helpers/firefox.rb', line 73

def search_firefox_bookmarks(term)
  # Firefox history
  base = File.expand_path('~/Library/Application Support/Firefox/Profiles')
  Dir.chdir(base)
  profile = Dir.glob('*default-release')
  return false unless profile

  src = File.join(base, profile[0], 'places.sqlite')

  exact_match = false
  match_phrases = []

  # If search terms start with ''term, only search for exact string matches
  if term =~ /^ *'/
    exact_match = true
    term.gsub!(/(^ *'+|'+ *$)/, '')
  elsif term =~ /%22(.*?)%22/
    match_phrases = term.scan(/%22(\S.*?\S)%22/)
    term.gsub!(/%22(\S.*?\S)%22/, '')
  end

  if File.exist?(src)
    SL.notify('Searching Firefox Bookmarks', term)
    tmpfile = "#{src}.tmp"
    FileUtils.cp(src, tmpfile)

    terms = []
    terms.push("(h.url NOT LIKE '%search/?%'
               AND h.url NOT LIKE '%?q=%'
               AND h.url NOT LIKE '%?s=%'
               AND h.url NOT LIKE '%duckduckgo.com/?t%')")
    if exact_match
      terms.push("(h.url LIKE '%#{term.strip.downcase}%' OR h.title LIKE '%#{term.strip.downcase}%')")
    else
      terms.concat(term.split(/\s+/).map do |t|
        "(h.url LIKE '%#{t.strip.downcase}%' OR h.title LIKE '%#{t.strip.downcase}%')"
      end)
      terms.concat(match_phrases.map do |t|
        "(h.url LIKE '%#{t[0].strip.downcase}%' OR h.title LIKE '%#{t[0].strip.downcase}%')"
      end)
    end

    query = terms.join(' AND ')

    most_recent = `sqlite3 -json '#{tmpfile}' "select h.url, b.title,
    datetime(b.dateAdded/1000000, 'unixepoch', 'localtime') as datum
    FROM moz_places h JOIN moz_bookmarks b ON h.id = b.fk
    where #{query} order by datum desc limit 1 COLLATE NOCASE;"`.strip
    FileUtils.rm_f(tmpfile)

    return false if most_recent.strip.empty?

    bm = JSON.parse(most_recent)[0]

    date = Time.parse(bm['datum'])
    score = score_mark({ url: bm['url'], title: bm['title'] }, term)
    [bm['url'], bm['title'], date, score]
  else
    false
  end
end

.search_firefox_history(term) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/searchlink/searches/helpers/firefox.rb', line 6

def search_firefox_history(term)
  # Firefox history
  base = File.expand_path('~/Library/Application Support/Firefox/Profiles')
  Dir.chdir(base)
  profile = Dir.glob('*default-release')
  return false unless profile

  src = File.join(base, profile[0], 'places.sqlite')

  exact_match = false
  match_phrases = []

  # If search terms start with ''term, only search for exact string matches
  case term
  when /^ *'/
    exact_match = true
    term.gsub!(/(^ *'+|'+ *$)/, '')
  when /%22(.*?)%22/
    match_phrases = term.scan(/%22(\S.*?\S)%22/)
    term.gsub!(/%22(\S.*?\S)%22/, '')
  end

  if File.exist?(src)
    SL.notify('Searching Firefox History', term)
    tmpfile = "#{src}.tmp"
    FileUtils.cp(src, tmpfile)

    terms = []
    terms.push("(moz_places.url NOT LIKE '%search/?%'
               AND moz_places.url NOT LIKE '%?q=%'
               AND moz_places.url NOT LIKE '%?s=%'
               AND moz_places.url NOT LIKE '%duckduckgo.com/?t%')")
    if exact_match
      terms.push("(moz_places.url LIKE '%#{term.strip.downcase}%' OR moz_places.title LIKE '%#{term.strip.downcase}%')")
    else
      terms.concat(term.split(/\s+/).map do |t|
        "(moz_places.url LIKE '%#{t.strip.downcase}%' OR moz_places.title LIKE '%#{t.strip.downcase}%')"
      end)
      terms.concat(match_phrases.map do |t|
        "(moz_places.url LIKE '%#{t[0].strip.downcase}%' OR moz_places.title LIKE '%#{t[0].strip.downcase}%')"
      end)
    end
    query = terms.join(' AND ')
    most_recent = `sqlite3 -json '#{tmpfile}' "select moz_places.title, moz_places.url,
    datetime(moz_historyvisits.visit_date/1000000, 'unixepoch', 'localtime') as datum
    from moz_places, moz_historyvisits where moz_places.id = moz_historyvisits.place_id
    and #{query} order by datum desc limit 1 COLLATE NOCASE;"`.strip
    FileUtils.rm_f(tmpfile)

    return false if most_recent.strip.empty?

    marks = JSON.parse(most_recent)

    marks.map! do |bm|
      date = Time.parse(bm['datum'])
      score = score_mark({ url: bm['url'], title: bm['title'] }, term)
      { url: bm['url'], title: bm['title'], date: date, score: score }
    end

    m = marks.max_by { |m| [m[:url].length * -1, m[:score]] }

    [m[:url], m[:title], m[:date]]
  else
    false
  end
end

.search_history(term, types = []) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/searchlink/searches/history.rb', line 138

def search_history(term, types = [])
  if types.empty?
    return false unless SL.config['history_types']

    types = SL.config['history_types']
  end

  results = []

  if !types.empty?
    types.each do |type|
      url, title, date = send("search_#{type}", term)

      results << { 'url' => url, 'title' => title, 'date' => date } if url
    end

    if results.empty?
      false
    else
      out = results.sort_by! { |r| r['date'] }.last
      [out['url'], out['title']]
    end
  else
    false
  end
end

.search_safari_bookmarks(terms) ⇒ Array

Search Safari bookmarks for relevant search terms

Parameters:

  • terms (String)

    The search terms

Returns:

  • (Array)
    url, title, date


70
71
72
73
74
75
76
77
78
79
# File 'lib/searchlink/searches/helpers/safari.rb', line 70

def search_safari_bookmarks(terms)
  data = `plutil -convert xml1 -o - ~/Library/Safari/Bookmarks.plist`.strip
  parent = Plist.parse_xml(data)
  results = get_safari_bookmarks(parent, terms)
  return false if results.empty?

  result = results.max_by { |res| [res[:score], res[:title].length] }

  [result[:url], result[:title], Time.now]
end

.search_safari_history(term) ⇒ Object

Search Safari history for terms

Parameters:

  • term

    The search term



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/searchlink/searches/helpers/safari.rb', line 10

def search_safari_history(term)
  # Safari
  src = File.expand_path('~/Library/Safari/History.db')
  if File.exist?(src)
    SL.notify('Searching Safari History', term)

    exact_match = false
    match_phrases = []

    # If search terms start with ''term, only search for exact string matches
    if term =~ /^ *'/
      exact_match = true
      term.gsub!(/(^ *'+|'+ *$)/, '')
    elsif term =~ /%22(.*?)%22/
      match_phrases = term.scan(/%22(\S.*?\S)%22/)
      term.gsub!(/%22(\S.*?\S)%22/, '')
    end

    terms = []
    terms.push("(url NOT LIKE '%search/?%'
               AND url NOT LIKE '%?q=%' AND url NOT LIKE '%?s=%'
               AND url NOT LIKE '%/search?%'
               AND url NOT LIKE '%duckduckgo.com/?t%')")
    if exact_match
      terms.push("(url LIKE '%#{term.strip.downcase}%' OR title LIKE '%#{term.strip.downcase}%')")
    else
      terms.concat(term.split(/\s+/).map do |t|
        "(url LIKE '%#{t.strip.downcase}%' OR title LIKE '%#{t.strip.downcase}%')"
      end)
      terms.concat(match_phrases.map do |t|
        "(url LIKE '%#{t[0].strip.downcase}%' OR title LIKE '%#{t[0].strip.downcase}%')"
      end)
    end

    query = terms.join(' AND ')

    cmd = %(sqlite3 -json '#{src}' "select title, url,
    datetime(visit_time/1000000, 'unixepoch', 'localtime') as datum
    from history_visits INNER JOIN history_items ON history_items.id = history_visits.history_item
    where #{query} order by datum desc limit 1 COLLATE NOCASE;")

    most_recent = `#{cmd}`.strip

    return false if most_recent.strip.empty?

    bm = JSON.parse(most_recent)[0]
    date = Time.parse(bm['datum'])
    [bm['url'], bm['title'], date]
  else
    false
  end
end

.settingsObject



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/searchlink/searches/history.rb', line 16

def settings
  {
    trigger: 'h(([scfabe])([hb])?)*',
    searches: [
      ['h', 'Browser History/Bookmark Search'],
      ['hsh', 'Safari History Search'],
      ['hsb', 'Safari Bookmark Search'],
      ['hshb', nil],
      ['hsbh', nil],
      ['hch', 'Chrome History Search'],
      ['hcb', 'Chrome Bookmark Search'],
      ['hchb', nil],
      ['hcbh', nil],
      ['hfh', 'Firefox History Search'],
      ['hfb', 'Firefox Bookmark Search'],
      ['hfhb', nil],
      ['hfbh', nil],
      ['hah', 'Arc History Search'],
      ['hab', 'Arc Bookmark Search'],
      ['hahb', nil],
      ['habh', nil],
      ['hbh', 'Brave History Search'],
      ['hbb', 'Brave Bookmark Search'],
      ['hbhb', nil],
      ['hbbh', nil],
      ['heh', 'Edge History Search'],
      ['heb', 'Edge Bookmark Search'],
      ['hehb', nil],
      ['hebh', nil]
    ]
  }
end