This repository was archived by the owner on Jan 23, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathkeeper.py
More file actions
176 lines (136 loc) · 5.93 KB
/
Copy pathkeeper.py
File metadata and controls
176 lines (136 loc) · 5.93 KB
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
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
169
170
171
172
173
174
175
176
import pathlib
import configparser
import keyring
import gkeepapi
NOTE_FILE_EXTENSION = ".txt"
LIST_FILE_EXTENSION = ".todo"
class Keeper:
def __init__(self):
self.keep = gkeepapi.Keep()
self.config = configparser.ConfigParser()
self.config.read("config.ini")
self.username = self.config["credentials"]["username"]
self.password = self.config["credentials"]["password"]
self.notes_root = pathlib.Path(self.config["paths"]["notes_root"])
self.token = keyring.get_password("google-keep-token", self.username)
if not self.token:
self.login(self.username, self.password)
else:
self.resume(self.username, self.token)
def resume(self, username, token):
self.keep.resume(username, token)
def login(self, username, password):
self.keep.login(username, password)
self.token = self.keep.getMasterToken()
keyring.set_password("google-keep-token", username, self.token)
def download(self):
gnodes = self.keep.all()
for gnode in gnodes:
is_list = gnode._TYPE == gkeepapi.node.NodeType.List
self._save_locally(gnode, is_list)
def upload(self):
for fpath in self._get_list_filepaths():
lf = self._get_id_title_by_filepath(fpath)
glist = self.keep.get(lf["id"])
with open(fpath, "r", encoding="utf-8") as file:
for line in file:
if len(line) < 5 or line[1] != "[":
continue
checkmark = line[1:4].strip()
is_checkmark_checked = checkmark == "[x]"
text = line[5:].strip()
remote_matching_items = list(
filter(lambda i: i.text.strip() == text, glist.items)
)
if len(remote_matching_items) > 0:
gitem = remote_matching_items[0]
if gitem.checked != is_checkmark_checked:
print(
f"[sync]: update checkmark found at: {fpath}\n{line.strip()}"
)
gitem.checked = is_checkmark_checked
else:
print(f"[sync]: adding item found at: {fpath}\n{line.strip()}")
glist.add(
text,
is_checkmark_checked,
gkeepapi.node.NewListItemPlacementValue.Top,
)
self.keep.sync()
def _get_list_filepaths(self):
return [f for f in self.notes_root.iterdir() if f.suffix == LIST_FILE_EXTENSION]
def _get_filepath_by_id(self, id):
for file in self.notes_root.iterdir():
nodet = self._get_id_title_by_filepath(file)
if nodet["id"] == id:
return file
return "not found!"
def _get_filepath_by_title(self, title):
for file in self.notes_root.iterdir():
nodet = self._get_id_title_by_filepath(file)
if nodet["title"] == title:
return file
return "not found!"
def _get_id_title_by_filepath(self, fpath):
with open(fpath, "r", encoding="utf-8") as gfile:
lines = gfile.readlines()[-3:]
separator = lines[0].strip()
id = lines[1].strip()[7:]
title = lines[2].strip()[7:]
if separator == "---":
return {"id": id, "title": title}
return {"id": "", "title": ""}
def _save_locally(self, gnode, is_list):
gnode_fpath = self._get_gnode_filepath(gnode, is_list)
with open(gnode_fpath, "w+", encoding="utf-8") as gfile:
if is_list:
if gnode.title:
title = f"{gnode.title}:\n"
gfile.write(title)
for gitem in gnode.unchecked:
item = f" [ ] {gitem.text}\n"
gfile.write(item)
for gitem in gnode.checked:
item = f" [x] {gitem.text}\n"
gfile.write(item)
else:
gfile.write(gnode.text)
gfile.write(self._generate_footer(gnode.title, gnode.id))
def _get_gnode_filepath(self, gnode, is_list):
if not gnode.title:
fpath_wo_suffix = self.notes_root / gnode.id
else:
fpath_wo_suffix = self.notes_root / self._get_trimmed_name(gnode.title)
file_ext = LIST_FILE_EXTENSION if is_list else NOTE_FILE_EXTENSION
fpath = fpath_wo_suffix.with_suffix(file_ext)
fpath = self._ensure_filename_is_unique(fpath, gnode, 1)
return fpath
def _get_trimmed_name(self, title):
name = title
name = name.replace("/", "_")
name = name.replace("?", "_")
name = name.replace(":", "_")
name = name.replace(" ", "_")
name = name.lower()
return name
def _ensure_filename_is_unique(self, fpath, gnode, number):
if fpath.exists():
fnode = self._get_id_title_by_filepath(fpath)
if fnode["id"] == gnode.id and fnode["title"] == gnode.title:
return fpath
fname = fpath.stem
if fname[-2] == "_" and fname[-1].isnumeric():
fname = fname[:-2]
fpath = fpath.with_name(f"{fname}_{number}{fpath.suffix}")
return self._ensure_filename_is_unique(fpath, gnode, number + 1)
return fpath
def _generate_footer(self, title, id):
return f"\n\n---\nid: {id}\ntitle: {title}"
def _rename_existing_filename(self, fpath, number):
fname = fpath.stem
if fname[-2] == "_" and fname[-1].isnumeric():
fname = fname[:-2]
fpath = fpath.with_name(f"{fname}_{number}{fpath.suffix}")
if fpath.exists():
fpath = self._rename_existing_filename(fpath, number + 1)
return fpath