-
Notifications
You must be signed in to change notification settings - Fork 76
/
Copy pathbrowser_history.py
executable file
·121 lines (88 loc) · 3.21 KB
/
browser_history.py
1
2
3
4
5
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
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
#!/usr/bin/env python3
from datetime import datetime
from pathlib import Path
from subprocess import check_output
from typing import Optional
import filecmp
import logging
Browser = str
CHROME = 'chrome'
FIREFOX = 'firefox'
def get_logger():
return logging.getLogger('browser-history')
# TODO kython?
# TODO the with key?
def only(it):
values = list(it)
if len(values) == 1:
return values[0]
raise RuntimeError(f'Expected a single value: {values}')
def get_path(browser: Browser, profile: str='*') -> Path:
if browser == 'chrome':
bpath = Path('~/.config/google-chrome').expanduser()
dbs = bpath.glob(profile + '/History')
elif browser == 'firefox':
bpath = Path('~/.mozilla/firefox/').expanduser()
dbs = bpath.glob(profile + '/places.sqlite')
else:
raise RuntimeError(f'Unexpected browser {browser}')
ldbs = list(dbs)
if len(ldbs) == 1:
return ldbs[0]
raise RuntimeError(f'Expected single database, got {ldbs}. Perhaps you want to use --profile argument?')
def test_get_path():
get_path('chrome')
get_path('firefox', profile='*-release')
def atomic_copy(src: Path, dest: Path):
"""
Supposed to handle cases where the file is changed while we were copying it.
"""
import shutil
differs = True
while differs:
res = shutil.copy(src, dest)
differs = not filecmp.cmp(str(src), str(res))
def format_dt(dt: datetime) -> str:
return dt.strftime('%Y%m%d%H%M%S')
def backup_history(browser: Browser, to: Path, profile: str='*', pattern=None) -> Path:
assert to.is_dir()
logger = get_logger()
now = format_dt(datetime.utcnow())
path = get_path(browser, profile=profile)
pattern = path.stem + '-{}' + path.suffix if pattern is None else pattern
fname = pattern.format(now)
res = to / fname
logger.info('backing up to %s', res)
# if your chrome is open, database would normally be locked, so you can't just make a snapshot
# so we'll just copy it till it converge. bit paranoid, but should work
atomic_copy(path, res)
logger.info('done!')
return res
def test_backup_history(tmp_path):
tdir = Path(tmp_path)
backup_history(CHROME, tdir)
backup_history(FIREFOX, tdir, profile='*-release')
def guess_db_date(db: Path) -> str:
maxvisit = check_output([
'sqlite3',
'-csv',
db,
'SELECT max(datetime(((visits.visit_time/1000000)-11644473600), "unixepoch")) FROM visits;'
]).decode('utf8').strip().strip('"');
return format_dt(datetime.strptime(maxvisit, "%Y-%m-%d %H:%M:%S"))
def test_guess(tmp_path):
tdir = Path(tmp_path)
db = backup_history(CHROME, tdir)
guess_db_date(db)
def main():
logger = get_logger()
import argparse
p = argparse.ArgumentParser()
p.add_argument('--browser', type=Browser, required=True)
p.add_argument('--profile', type=str, default='*', help='Use to pick the correct profile to back up. If unspecified, will assume a single profile')
p.add_argument('--to', type=Path, required=True)
args = p.parse_args()
# TODO do I need pattern??
backup_history(browser=args.browser, to=args.to, profile=args.profile)
if __name__ == '__main__':
main()