Last active
February 23, 2025 19:08
-
-
Save mbarrben/c6d205dcb1631b29e070308daf5ace46 to your computer and use it in GitHub Desktop.
Script to parse a Markdown file with notes exported from Xiaomi Cloud following and then upload them to Google Keep. Notes exported using https://github.com/nogiszd/xiaomi-note-exporter
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
import gkeepapi # https://github.com/kiwiz/gkeepapi | |
import gpsoauth # https://github.com/simon-weber/gpsoauth | |
import re | |
import os | |
import readline | |
import threading | |
import time | |
import sys | |
# INSTRUCTIONS | |
# 1. First export your Xiaomi Cloud notes to a Markdown file using https://github.com/nogiszd/xiaomi-note-exporter?tab=readme-ov-file | |
# 2. Get and ANDROID_ID from your old Android device (ANDROID_ID is sort of deprecated, I was able to get one from a Xiaomi Redmi Note 5 with Android 9) | |
# 3. Use that Android device to log in to your Google Account where you want to import the notes to | |
# 4. Get an OAuth token following https://github.com/simon-weber/gpsoauth?tab=readme-ov-file#alternative-flow | |
# 5. Run this script entering your email, ANDROID_ID, token and file path and hope for the best | |
# Enable tab-completion for file paths | |
def complete_path(text, state): | |
"""Autocompletes file and folder paths dynamically.""" | |
dirname = os.path.dirname(text) | |
prefix = os.path.basename(text) | |
# If no directory is specified, search in the current directory | |
search_path = os.path.expanduser(dirname) if dirname else "." | |
try: | |
# List matching files and directories | |
matches = [os.path.join(dirname, f) for f in os.listdir(search_path) if f.startswith(prefix)] | |
except FileNotFoundError: | |
matches = [] | |
return matches[state] if state < len(matches) else None | |
readline.set_completer_delims(" \t\n") # Avoids breaking on spaces | |
readline.parse_and_bind("tab: complete") # Enable tab completion | |
readline.set_completer(complete_path) # Set our custom completer | |
# Your Google account credentials | |
EMAIL = input("Enter your Google email: ").strip() | |
ANDROID_ID = input("Enter your Android ID: ").strip() | |
OAUTH_TOKEN = input("Enter your OAuth token: ").strip() | |
# Path to your Markdown file | |
MD_FILE = input("Enter the path to your Markdown file with notes: ").strip() | |
def get_master_token(): | |
master_response = gpsoauth.exchange_token(EMAIL, OAUTH_TOKEN, ANDROID_ID) | |
master_token = master_response['Token'] | |
return master_token | |
# Extract notes from the MD file taking into account they are formatted as the output from https://github.com/nogiszd/xiaomi-note-exporter | |
def extract_notes(filename): | |
with open(filename, "r", encoding="utf-8") as f: | |
content = f.read() | |
notes = [] | |
current_title = None | |
current_body = "" | |
for line in content.split("\n"): | |
if line.startswith("****"): # Separator between notes | |
if current_title or current_body.strip(): | |
notes.append((current_title, current_body.strip())) | |
current_title = None | |
current_body = "" | |
elif re.match(r"\*\*(.+?)\*\*", line): # Title in bold **Title** | |
current_title = re.sub(r"\*\*(.+?)\*\*", r"\1", line).strip() | |
else: | |
current_body += line + "\n" | |
if current_title or current_body.strip(): | |
notes.append((current_title, current_body.strip())) | |
return notes | |
# Login to Google Keep | |
try: | |
token = get_master_token() | |
keep = gkeepapi.Keep() | |
keep.authenticate(EMAIL, token) | |
print("Authentication successful!") | |
except Exception as e: | |
print(f"Authentication failed: {e}") | |
exit(1) | |
# Extract notes | |
notes = extract_notes(md_file) | |
# Add notes to Google Keep | |
for title, body in notes: | |
note = keep.createNote(title if title else "", body) | |
print(f"Added note: {title if title else body[:30]}...") | |
# Blinking animation while syncing | |
def show_upload_animation(): | |
"""Shows a blinking 'Uploading notes...' message.""" | |
animation = ["Uploading your notes to Google Keep ", | |
"Uploading your notes to Google Keep. ", | |
"Uploading your notes to Google Keep.. ", | |
"Uploading your notes to Google Keep..."] | |
i = 0 | |
while not sync_done: | |
print(animation[i % len(animation)], end="\r", flush=True) | |
i += 1 | |
time.sleep(0.5) | |
# Start the animation in a separate thread | |
sync_done = False | |
animation_thread = threading.Thread(target=show_upload_animation) | |
animation_thread.start() | |
# Sync with Google Keep | |
keep.sync() | |
# Stop the animation and clear the line | |
sync_done = True | |
animation_thread.join() | |
print("Upload complete! ") | |
print("All notes have been imported!") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment