Skip to content

Instantly share code, notes, and snippets.

@lcherone
Created September 5, 2025 08:54
Show Gist options
  • Select an option

  • Save lcherone/392ec0cff05bb2bc6d6f5ff65441cfb4 to your computer and use it in GitHub Desktop.

Select an option

Save lcherone/392ec0cff05bb2bc6d6f5ff65441cfb4 to your computer and use it in GitHub Desktop.

Revisions

  1. lcherone created this gist Sep 5, 2025.
    231 changes: 231 additions & 0 deletions export_apple_notes.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,231 @@
    #!/bin/bash

    # export Apple Notes to HTML files in current directory
    # Usage: ./export_apple_notes.sh

    set -e

    EXPORT_DIR="$(pwd)"
    echo "Exporting Apple Notes to: $EXPORT_DIR"

    # Clean up existing exported files and folders
    echo "Cleaning up existing exports..."
    rm -rf "export" *.md *.html 2>/dev/null || true

    # Create export directory
    mkdir -p "export"

    # Create the AppleScript to export notes
    osascript <<'EOF'
    tell application "Notes"
    set exportDir to POSIX path of (path to desktop folder) & "Notes/"
    -- Get the current directory from shell
    set exportDir to do shell script "pwd"
    set exportDir to exportDir & "/export/"
    -- Create export directory if it doesn't exist
    do shell script "mkdir -p " & quoted form of exportDir
    set noteCount to 0
    repeat with currentAccount in accounts
    set accountName to name of currentAccount
    log "Processing account: " & accountName
    repeat with currentFolder in folders of currentAccount
    set folderName to name of currentFolder
    if folderName is not "Recently Deleted" then
    log "Processing folder: " & folderName
    repeat with currentNote in notes of currentFolder
    set noteTitle to name of currentNote
    set noteBody to body of currentNote
    set noteDate to creation date of currentNote
    set modDate to modification date of currentNote
    -- Get additional note properties (with error handling)
    set noteId to ""
    set noteShared to ""
    set noteLocked to ""
    set noteAttachments to ""
    try
    set noteId to id of currentNote
    end try
    try
    set noteShared to shared of currentNote
    end try
    try
    set noteLocked to password protected of currentNote
    end try
    try
    set attachmentCount to count of attachments of currentNote
    if attachmentCount > 0 then
    set noteAttachments to attachmentCount as string & " attachment(s)"
    else
    set noteAttachments to "No attachments"
    end if
    end try
    -- Sanitize filename (no account/folder prefix)
    set fileName to my sanitizeFileName(noteTitle)
    set filePath to my getUniqueFilePath(exportDir, fileName, ".md")
    -- Create Markdown content with comprehensive metadata
    set markdownContent to "# " & noteTitle & "
    **Created:** " & noteDate & "
    **Modified:** " & modDate & "
    **Account:** " & accountName & "
    **Folder:** " & folderName & "
    **Note ID:** " & noteId & "
    **Shared:** " & noteShared & "
    **Password Protected:** " & noteLocked & "
    **Attachments:** " & noteAttachments & "
    ---
    " & my convertHtmlToMarkdown(noteBody)
    -- Write to file
    try
    do shell script "echo " & quoted form of markdownContent & " > " & quoted form of filePath
    set noteCount to noteCount + 1
    log "Exported: " & noteTitle
    on error errMsg
    log "Failed to export " & noteTitle & ": " & errMsg
    end try
    end repeat
    end if
    end repeat
    end repeat
    log "Export complete. " & noteCount & " notes exported."
    return noteCount
    end tell
    -- Function to sanitize filename
    on sanitizeFileName(fileName)
    set fileName to my replaceText(fileName, "/", "-")
    set fileName to my replaceText(fileName, ":", "-")
    set fileName to my replaceText(fileName, "?", "")
    set fileName to my replaceText(fileName, "*", "")
    set fileName to my replaceText(fileName, "\"", "")
    set fileName to my replaceText(fileName, "<", "")
    set fileName to my replaceText(fileName, ">", "")
    set fileName to my replaceText(fileName, "|", "")
    return fileName
    end sanitizeFileName
    -- Function to get unique file path with incremental numbering
    on getUniqueFilePath(dirPath, baseName, extension)
    set testPath to dirPath & baseName & extension
    -- Check if file exists using shell command
    try
    do shell script "test -f " & quoted form of testPath
    -- File exists, need to find unique name
    set counter to 1
    repeat
    set uniquePath to dirPath & baseName & " (" & counter & ")" & extension
    try
    do shell script "test -f " & quoted form of uniquePath
    set counter to counter + 1
    on error
    -- File doesn't exist, use this path
    return uniquePath
    end try
    end repeat
    on error
    -- File doesn't exist, use original path
    return testPath
    end try
    end getUniqueFilePath
    -- Function to convert basic HTML to Markdown
    on convertHtmlToMarkdown(htmlText)
    set markdownText to htmlText
    -- Handle heading tags (convert to bold since they're often used for emphasis in Notes)
    set markdownText to my replaceText(markdownText, "<h1>", "**")
    set markdownText to my replaceText(markdownText, "</h1>", "**")
    set markdownText to my replaceText(markdownText, "<h2>", "**")
    set markdownText to my replaceText(markdownText, "</h2>", "**")
    set markdownText to my replaceText(markdownText, "<h3>", "**")
    set markdownText to my replaceText(markdownText, "</h3>", "**")
    set markdownText to my replaceText(markdownText, "<h4>", "**")
    set markdownText to my replaceText(markdownText, "</h4>", "**")
    set markdownText to my replaceText(markdownText, "<h5>", "**")
    set markdownText to my replaceText(markdownText, "</h5>", "**")
    set markdownText to my replaceText(markdownText, "<h6>", "**")
    set markdownText to my replaceText(markdownText, "</h6>", "**")
    -- Remove Apple emoji font tags (keep the emoji content)
    set markdownText to my replaceText(markdownText, "<font face=\".AppleColorEmojiUI\">", "")
    set markdownText to my replaceText(markdownText, "</font>", "")
    -- Handle lists
    set markdownText to my replaceText(markdownText, "<ul>", return)
    set markdownText to my replaceText(markdownText, "</ul>", return)
    set markdownText to my replaceText(markdownText, "<ol>", return)
    set markdownText to my replaceText(markdownText, "</ol>", return)
    set markdownText to my replaceText(markdownText, "<li>", "- ")
    set markdownText to my replaceText(markdownText, "</li>", return)
    -- Convert common HTML tags to Markdown
    set markdownText to my replaceText(markdownText, "<b>", "**")
    set markdownText to my replaceText(markdownText, "</b>", "**")
    set markdownText to my replaceText(markdownText, "<strong>", "**")
    set markdownText to my replaceText(markdownText, "</strong>", "**")
    set markdownText to my replaceText(markdownText, "<i>", "*")
    set markdownText to my replaceText(markdownText, "</i>", "*")
    set markdownText to my replaceText(markdownText, "<em>", "*")
    set markdownText to my replaceText(markdownText, "</em>", "*")
    set markdownText to my replaceText(markdownText, "<u>", "*")
    set markdownText to my replaceText(markdownText, "</u>", "*")
    set markdownText to my replaceText(markdownText, "<code>", "`")
    set markdownText to my replaceText(markdownText, "</code>", "`")
    -- Handle line breaks
    set markdownText to my replaceText(markdownText, "<br>", " " & return)
    set markdownText to my replaceText(markdownText, "<br/>", " " & return)
    set markdownText to my replaceText(markdownText, "<br />", " " & return)
    -- Handle paragraphs
    set markdownText to my replaceText(markdownText, "<p>", "")
    set markdownText to my replaceText(markdownText, "</p>", return & return)
    -- Handle divs (common in Notes)
    set markdownText to my replaceText(markdownText, "<div>", "")
    set markdownText to my replaceText(markdownText, "</div>", return)
    -- Clean up consecutive bold markers (like *****)
    set markdownText to my replaceText(markdownText, "******", "**")
    set markdownText to my replaceText(markdownText, "****", "**")
    -- Clean up empty lines with just spaces
    set markdownText to my replaceText(markdownText, " " & return & return, return & return)
    set markdownText to my replaceText(markdownText, " " & return, return)
    -- Clean up extra line breaks
    set markdownText to my replaceText(markdownText, return & return & return, return & return)
    return markdownText
    end convertHtmlToMarkdown
    -- Function to replace text
    on replaceText(theText, searchString, replacementString)
    set AppleScript's text item delimiters to searchString
    set textItems to text items of theText
    set AppleScript's text item delimiters to replacementString
    set newText to textItems as string
    set AppleScript's text item delimiters to ""
    return newText
    end replaceText
    EOF

    echo "Export completed!"