Skip to content

Instantly share code, notes, and snippets.

@jhonasn
Last active January 5, 2024 08:22
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jhonasn/479f28360041834a064163352d06e9fc to your computer and use it in GitHub Desktop.
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
#!/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)).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')
f = open(path.join(nextcloud_dir, path_name, fname), 'w', encoding='utf-8')
# 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)
f.close()
print('note created inside "{}" folder\n'.format(path_name or 'nextcloud'))
print('\nAll notes converted and placed into {} folder'.format(nextcloud_dir))
@dmonder
Copy link

dmonder commented Feb 14, 2022

The utime call needs to follow the f.close() statement. Otherwise, the close call will update the modification time to the current time. :)

@jhonasn
Copy link
Author

jhonasn commented Mar 26, 2022

The utime call needs to follow the f.close() statement. Otherwise, the close call will update the modification time to the current time. :)

man, there's a lot of time that I don't put my hands on this, can you send me an example for me to update the script?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment