Last active
January 15, 2025 06:55
-
-
Save jhonasn/479f28360041834a064163352d06e9fc to your computer and use it in GitHub Desktop.
A script to convert google keep notes to a bunch markdown files aiming mainly to nextcloud notes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# from pdb import set_trace as bkp | |
from sys import argv, exit | |
from os import listdir, path, mkdir, utime | |
from json import loads as to_dict | |
from datetime import datetime as date | |
if '-h' in argv or '--help' in argv: | |
print(''' | |
The commands will print those informations on notes: | |
-a all | |
-p pinned | |
-c note color | |
-l labels (it will print only when there's more than one) | |
-le last edit date | |
-s people who the note is shared with | |
None will be printed by default | |
''') | |
exit() | |
is_printing = { | |
'pinned': '-a' in argv or '-p' in argv, | |
'color': '-a' in argv or '-c' in argv, | |
'labels': '-a' in argv or '-l' in argv, | |
'last-edit': '-a' in argv or '-c' in argv, | |
'shared': '-a' in argv or '-c' in argv, | |
'none': len(argv) == 1 | |
} | |
keep_dir = './Keep' | |
labels_file = 'Labels.txt' | |
nextcloud_dir = './nextcloud-notes' | |
if not path.exists(keep_dir): | |
print('"Keep" folder not found. Place this file on the same folder as "Keep" backup folder') | |
exit() | |
# get labels to create folders with the labels names | |
with open(path.join(keep_dir, labels_file), 'a+') as f: | |
f.seek(0) | |
labels = f.read().split('\n') | |
if len(labels): labels.pop() | |
labels.append('trash') | |
labels.append('archived') | |
# read files and separate the note jsons from attachments | |
files = listdir(keep_dir) | |
notes = list(filter(lambda f: '.json' in f, files)) | |
attachments = list(filter(lambda f: '.json' not in f and '.html' not in f and f != labels_file, files)) | |
print('{} notes found\n\n'.format(len(notes))) | |
def fix_filename(filename): | |
new_name = filename | |
for char in ['<', '>', ':', '"', '/', '\\', '|', '?', '*']: | |
new_name = new_name.replace(char, '-') | |
return new_name | |
# create nextcloud and label folders | |
if not path.exists(nextcloud_dir): | |
mkdir(nextcloud_dir) | |
print('created nextcloud folder') | |
for label in labels: | |
label_dir = path.join(nextcloud_dir, fix_filename(label)) | |
if not path.exists(label_dir): | |
mkdir(label_dir) | |
print('created "{}" folder inside nextcloud folder'.format(label_dir)) | |
# create notes | |
for note_file in notes: | |
# if note_file == 'file-to-debug.json': bkp() | |
print('processing note "{}"'.format(note_file)) | |
note = to_dict(open(path.join(keep_dir, note_file), encoding='utf-8').read()) | |
text = '' | |
note_labels = [] | |
# simplify labels array | |
if 'labels' in note: | |
for l in note['labels']: | |
note_labels.append(l['name']) | |
# create note body | |
if note['title']: text += '# ' + note['title'] + '\n' | |
if is_printing['pinned'] and note['isPinned']: text += '**PINNED**\n\n' | |
elif note['title']: text += '\n' | |
# | |
# create note content | |
# | |
if 'textContent' in note: text += note['textContent'] | |
elif 'listContent' in note: | |
# print MD list | |
for item in note['listContent']: | |
text += '- [{}] {}\n'.format('x' if item['isChecked'] else ' ', item['text']) | |
# attachments | |
if 'attachments' in note: | |
for a in note['attachments']: | |
text += '\n'.format(a['filePath']) | |
is_space_added = False | |
# add space before last information if it isn't added yet | |
def add_space(): | |
global is_space_added | |
global text | |
if not is_space_added: | |
text += '\n' | |
is_space_added = True | |
# print color | |
if is_printing['color'] and ('color' in note and note['color'] != 'DEFAULT'): | |
add_space() | |
text += '\ncolor: ' + note['color'] | |
# print label when it has more than one | |
if is_printing['labels'] and len(note_labels) > 1: | |
add_space() | |
text += '\nlabels: ' + str(note_labels)[1:len(str(note_labels))-1].replace('\'', '') | |
# print last edit time | |
if is_printing['last-edit']: | |
add_space() | |
timestamp = int(int(note['userEditedTimestampUsec']) / 1000 / 1000) | |
date_str = date.utcfromtimestamp(timestamp).strftime('%d %B %Y %H:%M:%S') | |
text += '\nlast edit: {}'.format(date_str) | |
# print if is shared | |
if is_printing['shared'] and 'sharees' in note: | |
add_space() | |
text += 'shared with: ' | |
for u in note['sharees']: | |
text += '{} {}'.format(u['email'], 'is the owner\n' if u['isOwner'] else '') | |
# | |
# end note content creation | |
# | |
# decide path | |
path_name = '' | |
if len(note_labels): path_name = fix_filename(note_labels[0]) | |
if note['isArchived']: path_name = 'archived' | |
elif note['isTrashed']: path_name = 'trash' | |
# create note | |
fname = '{}.{}'.format(note_file[0:len(note_file)-5], 'md') | |
with open(path.join(nextcloud_dir, path_name, fname), 'w', encoding='utf-8') as f: | |
# use the original modification time for the new file | |
original_mod_time = path.getmtime(path.join(keep_dir, note_file)) | |
utime(path.join(nextcloud_dir, path_name, fname), (original_mod_time, original_mod_time)) | |
f.write(text) | |
print('note created inside "{}" folder\n'.format(path_name or 'nextcloud')) | |
print('\nAll notes converted and placed into {} folder'.format(nextcloud_dir)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
does anyone have an idea how to extend this ingenious script so that the german umlauts (ä, ö, ü, ß) are converted correctly?
EDIT: ok it was easier than I thought
Line 77:
From:
note = to_dict(open(path.join(keep_dir, note_file)).read())
to this:
note = to_dict(open(path.join(keep_dir, note_file), encoding='utf-8').read())