now link to Google Keep exported to-do items

This commit is contained in:
2025-09-18 18:01:41 +03:00
parent d4ceb4f01c
commit 9435752f11
2 changed files with 556 additions and 0 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,302 @@
import os
import json
""" This was written almost entirely by Google Gemini 2.5 (apps and code) on 18 Sept. 2025
Google Takeout is accessible from the browser when logged in to your Google account.
It can be set up to export dozens of different types of data. Here we only export "Keep"
data.
After running this, move the generated file to expoweb/handbook/troggle
and git add/commit/push
Philip Sargent
"""
# --- CONFIGURATION ---
# 📌 IMPORTANT: Replace this with the actual path to your Google Keep Takeout folder.
TAKEOUT_DIRECTORY = 'Takeout/Keep' # Example for Linux/macOS
#
def process_keep_files_to_dict(directory_path):
"""
Scans a directory for Google Keep JSON files, filters for a specific
label, prints the content, and returns the data as a list of dictionaries.
Args:
directory_path (str): The path to the folder containing the JSON files.
Returns:
list: A list of dictionaries, where each dictionary represents a note.
"""
print(f"📁 Processing files in: {directory_path}\n")
# This list will store the dictionary for each matching note
extracted_notes = []
if not os.path.isdir(directory_path):
print(f"❌ Error: Directory not found at '{directory_path}'.")
print("Please update the TAKEOUT_DIRECTORY variable with the correct path.")
return extracted_notes
# Iterate over every file in the specified directory
for filename in os.listdir(directory_path):
if filename.endswith('.json'):
file_path = os.path.join(directory_path, filename)
try:
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
# --- Label Check ---
has_expo_label = False
if 'labels' in data:
if any(label.get('name', '').lower() == 'expo' for label in data['labels']):
has_expo_label = True
if not has_expo_label:
continue # Skip this file
# --- Data Extraction & Storage ---
# Create a dictionary to hold the current note's data
current_note = {
'title': data.get('title') or 'Untitled Note',
'source_file': filename,
'content_type': None,
'color': data.get('color', 'DEFAULT'),
'content': None
}
# As before, print to the console for immediate feedback
print("-" * 40)
print(f"✅ Title: {current_note['title']}")
# Handle text notes
if 'textContent' in data:
content = data['textContent']
current_note['content_type'] = 'text'
current_note['content'] = content
print(content)
# Handle list notes
elif 'listContent' in data:
current_note['content_type'] = 'list'
list_items = []
checked_items = []
# print("Tasks:")
for item in data['listContent']:
# Create a clean dictionary for the list item
item_data = {
'text': item.get('text', ''),
'is_checked': item.get('isChecked', False)
}
list_items.append(item_data)
if item_data['is_checked']:
checked_items.append(item_data)
# Print the list item to the console
status = 'x' if item_data['is_checked'] else ' '
# print(f" [{status}] {item_data['text']}")
print(f"Tasks: {len(list_items)} of which {len(checked_items)} are done.")
current_note['content'] = list_items
# Add the completed dictionary for this note to our main list
extracted_notes.append(current_note)
# print("-" * 40 + "\n")
except Exception as e:
print(f"❗️ An unexpected error occurred with file {filename}: {e}")
if not extracted_notes:
print("No notes with the 'EXPO' label were found in the directory.")
return extracted_notes
import html
from datetime import datetime
def write_html_output(notes_data, filename="keep_export.html"):
"""
Generates an HTML file from the extracted Keep notes data.
Args:
notes_data (list): A list of note dictionaries.
filename (str): The name of the output HTML file.
"""
# These are the standard Google Keep colors.
color_map = {
"DEFAULT": "#ffffff",
"RED": "#f28b82",
"ORANGE": "#fbbc04",
"YELLOW": "#fff475",
"GREEN": "#ccff90",
"TEAL": "#a7ffeb",
"BLUE": "#cbf0f8",
"DARK_BLUE": "#aecbfa",
"PURPLE": "#d7aefb",
"PINK": "#fdcfe8",
"BROWN": "#e6c9a8",
"GRAY": "#e8eaed",
}
# --- HTML Head and CSS Styling ---
# The CSS is embedded directly in the HTML file for simplicity.
html_head = f"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Google Keep Export</title>
<style>
body {{
font-family: 'Google Sans', Roboto, Arial, sans-serif;
background-color: #f1f3f4;
margin: 0;
padding: 24px;
}}
.container {{
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 16px;
}}
.note-card {{
background-color: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 16px;
box-shadow: 0 1px 3px rgba(0,0,0,0.12);
word-wrap: break-word; /* Prevents long text from overflowing */
}}
.note-title {{
font-size: 1.1em;
font-weight: 500;
margin: 0 0 12px 0;
}}
.note-content p {{
margin: 0;
white-space: pre-wrap; /* Respects newlines in text notes */
}}
.task-list {{
list-style: none;
padding: 0;
margin: 0;
}}
.task-item {{
display: flex;
align-items: center;
margin-bottom: 8px;
}}
.task-item input[type="checkbox"] {{
margin-right: 10px;
}}
.completed-item-text {{
text-decoration: line-through;
color: #5f6368;
}}
.completed-items {{
margin-top: 16px;
border-top: 1px solid #e0e0e0;
padding-top: 12px;
}}
.completed-items summary {{
cursor: pointer;
font-weight: bold;
color: #3c4043;
}}
</style>
</head>
"""
# --- HTML Body Generation ---
html_body_content = ""
for note in notes_data:
# Sanitize title to prevent HTML injection issues
title = html.escape(note['title'])
# Start building the content for this specific note
note_content_html = ""
# MODIFICATION 2: Get the color and apply it as an inline style
note_color_name = note.get('color', 'DEFAULT')
# Safely get the color from the map, falling back to the default white
bg_color = color_map.get(note_color_name, color_map['DEFAULT'])
# Handle text notes
if note['content_type'] == 'text':
# Sanitize text content and replace newlines with <br> tags
text = html.escape(note['content'])
note_content_html = f'<div class="note-content"><p>{text}</p></div>'
# Handle list notes
elif note['content_type'] == 'list':
unchecked_items = []
checked_items = []
for item in note['content']:
# Sanitize list item text
item_text = html.escape(item['text'])
if item['is_checked']:
checked_items.append(
f'<li class="task-item"><input type="checkbox" disabled checked><span class="completed-item-text">{item_text}</span></li>'
)
else:
unchecked_items.append(
f'<li class="task-item"><input type="checkbox" disabled><span>{item_text}</span></li>'
)
# Combine unchecked items
note_content_html += '<ul class="task-list">' + "".join(unchecked_items) + '</ul>'
# Add checked items inside a collapsible <details> element
if checked_items:
item_count = len(checked_items)
plural_s = 's' if item_count > 1 else ''
note_content_html += f"""
<details class="completed-items">
<summary>{item_count} completed item{plural_s}</summary>
<ul class="task-list">{"".join(checked_items)}</ul>
</details>
"""
# Combine title and content into a single note card
html_body_content += f"""
<div class="note-card" style="background-color: {bg_color};">
<h2 class="note-title">{title}</h2>
{note_content_html}
</div>
"""
# --- Final HTML Assembly ---
full_html = f"""
{html_head}
<body>
<h1>Troggle coding and design to-do lists</h1>
<p>(Exported from Philip Sargent's Google Keep notes on {datetime.now().strftime('%Y-%m-%d %H:%M')})
<p>Contact Philip if you wish to share these lists on your Google Keep account.
<div class="container">
{html_body_content}
</div>
</body>
</html>
"""
# --- Writing to File ---
try:
with open(filename, 'w', encoding='utf-8') as f:
f.write(full_html)
print(f"\n🎉 Successfully wrote {len(notes_data)} notes to '{filename}'")
except Exception as e:
print(f"❗️ An error occurred while writing the file: {e}")
# --- Run the program ---
if __name__ == "__main__":
all_notes_data = process_keep_files_to_dict(TAKEOUT_DIRECTORY)
if all_notes_data:
print("\n" + "="*50)
print("🎉 Successfully processed all matching files.")
print(f"Total notes extracted: {len(all_notes_data)}")
# print("Data has been stored in the 'all_notes_data' variable.")
print("="*50)
write_html_output(all_notes_data)