-
Notifications
You must be signed in to change notification settings - Fork 76
/
Copy pathbrowser_history.py
executable file
·130 lines (94 loc) · 3.54 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
122
123
124
125
126
127
128
129
130
#!/usr/bin/env python3
DEPRECATION = 'NOTE: this is DEPRECATED! Please use https://github.com/purarue/browserexport instead'
from datetime import datetime, timezone
from pathlib import Path
from subprocess import check_output
import filecmp
import logging
import warnings
import sys
warnings.warn(DEPRECATION, DeprecationWarning)
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.now(tz=timezone.utc))
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)
warnings.warn(DEPRECATION, DeprecationWarning)
logger.error("This script is DEPRECATED! Exiting with error code so that the use notices")
sys.exit(44)
if __name__ == '__main__':
main()