138 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Plaintext
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			138 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Plaintext
		
	
	
		
			Executable file
		
	
	
	
	
#!/usr/bin/env cached-nix-shell
 | 
						|
#! nix-shell -p ruby_2_7 rubyPackages_2_7.sqlite3 -i "ruby -S" --quiet
 | 
						|
 | 
						|
# Open your browser bookmarks or history in your default browser. Currently
 | 
						|
# works with Firefox, Brave, and qutebrowser.
 | 
						|
 | 
						|
require 'shellwords'
 | 
						|
require 'sqlite3'
 | 
						|
require 'json'
 | 
						|
require 'fileutils'
 | 
						|
require 'cgi'
 | 
						|
 | 
						|
def rofi items, *args
 | 
						|
  cmd = [*%w{rofi -dmenu -theme theme/bookmarkmenu.rasi -markup-rows -multi-select -p browser -i}, *args]
 | 
						|
  IO.popen(cmd.compact.shelljoin, "r+") do |pipe|
 | 
						|
    pipe.puts items.keys.join "\n"
 | 
						|
    pipe.close_write
 | 
						|
    (pipe.gets || "").split "\n"
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
def db file, command
 | 
						|
  copy = "/tmp/browsermenu-db-#{File.basename(file)}"
 | 
						|
  begin
 | 
						|
    # Copy the sqlite database because history/bookmark info is usually small
 | 
						|
    # and may be locked by an open browser.
 | 
						|
    unless File.exists? copy
 | 
						|
      FileUtils.cp file, copy
 | 
						|
      FileUtils.chmod 0400, copy, verbose: true
 | 
						|
    end
 | 
						|
    results = []
 | 
						|
    db = SQLite3::Database.open copy
 | 
						|
    db.execute(command).each { |r| results.push r }
 | 
						|
    results
 | 
						|
  ensure
 | 
						|
    File.delete copy
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
def walk_json children
 | 
						|
  result = []
 | 
						|
  for c in children
 | 
						|
    if c['children']
 | 
						|
      result += walk_json c['children']
 | 
						|
    else
 | 
						|
      result.push [ c['name'], c['url'] ]
 | 
						|
    end
 | 
						|
  end
 | 
						|
  result
 | 
						|
end
 | 
						|
 | 
						|
def get_bookmarks browser
 | 
						|
  bookmarks = []
 | 
						|
  case browser
 | 
						|
  when :firefox
 | 
						|
    # In my nixos dotfiles I hardcode the default profile to $USER.profile,
 | 
						|
    # which makes this lookup easier, but means this script won't work on any
 | 
						|
    # other system.
 | 
						|
    user = ENV['USER']
 | 
						|
    ["#{ENV['HOME']}/.mozilla/firefox/#{user}.default",
 | 
						|
     "#{ENV['XDG_CONFIG_HOME']}/firefox/.mozilla/firefox/#{user}.default",
 | 
						|
     "#{ENV['XDG_DATA_HOME']}/mozilla/firefox/#{user}.default"].each do |dir|
 | 
						|
      if Dir.exists? dir
 | 
						|
        db "#{dir}/places.sqlite",
 | 
						|
           ["SELECT DISTINCT mb.title, mp.url FROM moz_bookmarks mb",
 | 
						|
            "INNER JOIN moz_places mp ON mp.id = mb.fk",
 | 
						|
            "WHERE mb.type = 1 AND mb.title IS NOT NULL",
 | 
						|
            "ORDER BY mp.frecency DESC",
 | 
						|
            "LIMIT 200"].join(' ')
 | 
						|
      end
 | 
						|
    end
 | 
						|
  when :brave
 | 
						|
    f = File.read "#{ENV['XDG_CONFIG_HOME']}/BraveSoftware/Brave-Browser/Default/Bookmarks"
 | 
						|
    data = JSON.parse f
 | 
						|
    walk_json data["roots"]["bookmark_bar"]["children"]
 | 
						|
  when :qutebrowser
 | 
						|
    bookmarks = File.readlines "#{ENV['XDG_CONFIG_HOME']}/qutebrowser/bookmarks/urls"
 | 
						|
    bookmarks.map { |s| s.strip.split(' ', 2).reverse }
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
def get_history browser
 | 
						|
  case browser
 | 
						|
  when :firefox
 | 
						|
    # In my nixos dotfiles I hardcode the default profile to $USER.profile,
 | 
						|
    # which makes this lookup easier, but means this script won't work on any
 | 
						|
    # other system.
 | 
						|
    user = ENV['USER']
 | 
						|
    ["#{ENV['HOME']}/.mozilla/firefox/#{user}.default",
 | 
						|
     "#{ENV['XDG_CONFIG_HOME']}/firefox/.mozilla/firefox/#{user}.default",
 | 
						|
     "#{ENV['XDG_DATA_HOME']}/mozilla/firefox/#{user}.default"].each do |dir|
 | 
						|
      if Dir.exists? dir
 | 
						|
        return db "#{dir}/places.sqlite",
 | 
						|
                  ["SELECT title, url",
 | 
						|
                   "FROM moz_places",
 | 
						|
                   "WHERE title<>''",
 | 
						|
                   "ORDER BY last_visit_date DESC"].join(' ')
 | 
						|
      end
 | 
						|
    end
 | 
						|
    return []
 | 
						|
  when :brave
 | 
						|
    db "#{ENV['XDG_CONFIG_HOME']}/BraveSoftware/Brave-Browser/Default/History",
 | 
						|
       "SELECT title, url FROM urls GROUP BY url ORDER BY last_visit_time DESC LIMIT 200"
 | 
						|
  when :qutebrowser
 | 
						|
    db "#{ENV['XDG_DATA_HOME']}/qutebrowser/history.sqlite",
 | 
						|
       ["SELECT title, url FROM History",
 | 
						|
        "WHERE title != ''",
 | 
						|
        "GROUP BY url",
 | 
						|
        "ORDER BY atime DESC"].join(' ')
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
def list urls
 | 
						|
  items = urls.map do |x|
 | 
						|
    x.map! { |x| CGI.escapeHTML x }
 | 
						|
    [x[0].empty? ?
 | 
						|
       "<b>#{x[1]}</b>" :
 | 
						|
       "<b>#{x[0]}</b> <span size=\"x-small\">#{x[1]}</span>",
 | 
						|
     x[1]]
 | 
						|
  end.to_h
 | 
						|
  selected = rofi items
 | 
						|
  selected.map { |key| items[key] }
 | 
						|
end
 | 
						|
 | 
						|
type, browser = *ARGV
 | 
						|
browser = (browser || ENV['BROWSER']).to_sym
 | 
						|
urls = if type == 'history'
 | 
						|
  get_history browser
 | 
						|
else
 | 
						|
  get_bookmarks browser
 | 
						|
end
 | 
						|
urls = list urls
 | 
						|
if urls.empty?
 | 
						|
  puts "No URL selected. Aborting..."
 | 
						|
  exit 1
 | 
						|
end
 | 
						|
Process.detach fork { exec ENV['BROWSER'], *urls }
 |