├── Chrome History.alfredworkflow ├── README.md ├── chrome_history_sample.jpeg ├── chrome_history_usage.jpeg └── src └── chrome_history_workflow.py /Chrome History.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandrea57/alfred-chromehistory/d22957483d11443ff8455798d1a421cde2134115/Chrome History.alfredworkflow -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # alfred-chromehistory 2 | Chrome History to search chrome history with keyword and open url in browser. 3 | 4 | ## Usage 5 | 1. Trigger Alfred (normally ⌥Spac) 6 | 2. Type hi and then enter history's keywords (part of url or title) 7 | 3. Multiple keywords split with white space 8 | 9 | ## Sample 10 | ![](https://raw.githubusercontent.com/iandrea57/alfred-chromehistory/master/chrome_history_usage.jpeg) 11 | ![](https://raw.githubusercontent.com/iandrea57/alfred-chromehistory/master/chrome_history_sample.jpeg) 12 | -------------------------------------------------------------------------------- /chrome_history_sample.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandrea57/alfred-chromehistory/d22957483d11443ff8455798d1a421cde2134115/chrome_history_sample.jpeg -------------------------------------------------------------------------------- /chrome_history_usage.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandrea57/alfred-chromehistory/d22957483d11443ff8455798d1a421cde2134115/chrome_history_usage.jpeg -------------------------------------------------------------------------------- /src/chrome_history_workflow.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | chrome history v0.1 6 | 7 | Author: iandrea57 8 | Host: https://github.com/iandrea57 9 | ''' 10 | 11 | import itertools 12 | import sqlite3 13 | import datetime 14 | import cgi 15 | import os 16 | import sys 17 | reload(sys) 18 | sys.setdefaultencoding("utf-8") 19 | 20 | DEBUG_MODE = False #debug mode 21 | HISTORY_DB_PATH = os.path.expanduser('~') + '/Library/Application Support/Google/Chrome/Default/History' #you can command `chrome://version/` in chrome to find the path 22 | 23 | class History: 24 | ''' 25 | This is chrome history class, field from table: urls 26 | contains field: id, url, title, visit_count, last_visit_time 27 | ''' 28 | 29 | id = 0 30 | url = '' 31 | title = '' 32 | visit_count = 0 33 | last_visit_time = None 34 | def __init__(self, id, url, title, visit_count, last_visit_time): 35 | self.id = id 36 | if url is not None: 37 | self.url = url 38 | if title is not None: 39 | self.title = title 40 | self.visit_count = visit_count 41 | self.last_visit_time = datetime.datetime.fromtimestamp(last_visit_time/1000000-11644473600) 42 | 43 | def __str__(self): 44 | ''' 45 | in DEBUG_MODE: return all field value 46 | normally: return workflow_xml item format info 47 | ''' 48 | if DEBUG_MODE: 49 | return 'id=%d, url=%s, title=%s, visit_count=%d, last_visit_time=%s' % (self.id, self.url, self.title, self.visit_count, self.last_visit_time) 50 | else: 51 | return '' + cgi.escape(self.title) + '' + cgi.escape(str(self.last_visit_time) + ' ' + self.url) + 'icon.png' 52 | 53 | 54 | def backup_db(path): 55 | ''' 56 | backup chrome's History database file daily (named 'History.bak.%y-%m-%d', previous backup file will be removed) 57 | ''' 58 | time_day_str = datetime.datetime.now().strftime('%Y-%m-%d') 59 | backup_suffix = '.bak' 60 | backup_path = path + backup_suffix + "." + time_day_str 61 | if not os.path.exists(backup_path): 62 | os.system('rm ' + path.replace(' ', '\\ ') + backup_suffix + '.*') 63 | os.system('cp -a ' + path.replace(' ', '\\ ') + ' ' + backup_path.replace(' ', '\\ ')) 64 | return backup_path 65 | 66 | 67 | def get_conn(path): 68 | ''' 69 | get sqlite3 database connect 70 | ''' 71 | conn = None 72 | if path is not None and os.path.exists(path) and os.path.isfile(path): 73 | conn = sqlite3.connect(path) 74 | conn.text_factory = str 75 | if DEBUG_MODE: 76 | print 'sqlite connect path', path 77 | return conn 78 | 79 | 80 | def get_cursor(conn): 81 | ''' 82 | get sqlite3 database cursor 83 | ''' 84 | if conn is not None: 85 | return conn.cursor() 86 | else: 87 | return None 88 | 89 | 90 | def fetchall(conn, sql, data=None): 91 | ''' 92 | fetchall data from sqlite3 database by sql and param 93 | ''' 94 | result = None 95 | if sql is not None and sql != '': 96 | cursor = get_cursor(conn) 97 | if cursor is not None: 98 | if DEBUG_MODE: 99 | print('execute sql:[{}], param:[{}]'.format(sql, data)) 100 | if data is not None: 101 | cursor.execute(sql, data) 102 | else: 103 | cursor.execute(sql) 104 | result = cursor.fetchall() 105 | cursor.close() 106 | return result 107 | 108 | 109 | def build_history_list(result): 110 | ''' 111 | build chrome history data list from sqlite3 query result arrays 112 | ''' 113 | history_list = [] 114 | if result is not None and len(result) > 0: 115 | for row in result: 116 | history_list.append(History(row[0], row[1], row[2], row[3], row[4])) 117 | return history_list 118 | 119 | 120 | def print_history_list(history_list): 121 | ''' 122 | print chrome history list 123 | in DEBUG_MODE: print all field value 124 | normally: print workflow_xml item format info 125 | ''' 126 | if DEBUG_MODE: 127 | for history in history_list: 128 | print history 129 | else: 130 | workflow_xml = '' 131 | for history in history_list: 132 | workflow_xml += str(history) 133 | workflow_xml += '' 134 | print workflow_xml 135 | 136 | 137 | def split_keywords(keywords, title_param_max_count): 138 | ''' 139 | split keywords for param ${title}, ${url} 140 | ''' 141 | dim_keywords = [] 142 | for i in range(title_param_max_count+1): 143 | for split_tuple in itertools.combinations(keywords, i): 144 | dim_keywords.append([split_tuple, tuple(set(keywords).difference(set(split_tuple)))]) 145 | if DEBUG_MODE: 146 | print 'dim_keywords:', dim_keywords 147 | return dim_keywords 148 | 149 | 150 | def query_from_db(conn, keywords): 151 | ''' 152 | query chrome history list from sqlite3 by keywords 153 | ''' 154 | like_keywords = [] 155 | for keyword in keywords: 156 | like_keywords.append('%' + keyword + '%') 157 | dim_keywords = split_keywords(like_keywords, 2) 158 | flat_keywords = [] 159 | sql = 'select id, url, title, visit_count, last_visit_time from urls where ' 160 | for i in range(len(dim_keywords)): 161 | if i > 0: 162 | sql += ' or ' 163 | sub_sql = '' 164 | title_keywords = dim_keywords[i][0] 165 | url_keywords = dim_keywords[i][1] 166 | flat_keywords += title_keywords + url_keywords 167 | if len(title_keywords) > 0: 168 | for url_keyword in title_keywords: 169 | if sub_sql != '': 170 | sub_sql += ' and ' 171 | sub_sql += 'title like ?' 172 | if len(url_keywords) > 0: 173 | for url_keyword in url_keywords: 174 | if sub_sql != '': 175 | sub_sql += ' and ' 176 | sub_sql += 'url like ?' 177 | sql += sub_sql 178 | sql += ' order by visit_count desc, last_visit_time desc, id desc limit 50' 179 | result = fetchall(conn, sql, tuple(flat_keywords)) 180 | return result 181 | 182 | 183 | def query(content): 184 | ''' 185 | query chrome history list by content and print result 186 | ''' 187 | keywords = content.split() 188 | if DEBUG_MODE: 189 | print 'keywords:', keywords 190 | path = backup_db(HISTORY_DB_PATH) 191 | conn = get_conn(path) 192 | result = query_from_db(conn, keywords) 193 | history_list = build_history_list(result) 194 | print_history_list(history_list) 195 | 196 | 197 | if __name__ == '__main__': 198 | if DEBUG_MODE: 199 | query(u'dev bee') 200 | else: 201 | query(u'{query}') --------------------------------------------------------------------------------