RhetTbull / macnotesapp

Work with Apple MacOS Notes.app from the command line. Also includes python interface for scripting Notes.app from your own python code.
MIT License
139 stars 6 forks source link

Add support for attachments #15

Open RhetTbull opened 1 year ago

RhetTbull commented 1 year ago

Attachments are not currently handled.

SKaplanOfficial commented 1 year ago

You could use an AppleScript bridge such as appscript or my own PyXA for this, or interface with ScriptingBridge via pyobjc:

import ScriptingBridge
notes = ScriptingBridge.SBApplication.alloc().initWithBundleIdentifier_("com.apple.notes")
attachments = notes.attachments()
for attachment in attachments:
    print(attachment.URL())

I think the way you're handling AppleScripts now works well for one-off actions but is inefficient for bulk actions (e.g. getting the name of 100s of notes at a time). For reference, in PyXA you can do Application("Notes").notes().name() to list the name of every note almost instantaneously as it requests all note names in one Apple Event request instead of for each note successively.

Granted, PyXA is a larger library than you need here, so I'd recommend either using pyobjc-ScriptingBridge or implementing some logic to merge bulk requests into singular AppleScript calls.

RhetTbull commented 1 year ago

@SKaplanOfficial I wasn't aware of PyXa. Had played around with ScriptingBridge a bit on another project but got frustrated by lack of documentation. I just toyed around with PyXA -- amazing work! I may just rewrite macnotesapp to use PyXA as it is so much faster. I have a similar project photoscript that uses the same AppleScript techniques that might benefit from PyXA (though photoscript currently supports creating folders/albums, moving photos from one album to another, etc. that don't appear to be supported in PyXA yet).

SKaplanOfficial commented 1 year ago

@RhetTbull Yeah, ScriptingBridge is a bit of a pain to work with, but I've found it to be robust enough for my needs. I wanted PyXA to serve as sort of an extended reference for ScriptingBridge/pyobjc as a whole since the existing documentation was... lacking. PyXA currently supports creating folders and albums, but moving photos is something I'm working on (moving AS objects in general, really, since the implementation varies slightly across different apps).

If you do use PyXA, let me know if you run into any problems -- I'd be happy to help!

RhetTbull commented 1 year ago

PyXA currently supports creating folders and albums

Great! I'll dive into the docs and source over the holiday break. I quickly skimmed the docs and thought I saw those as still "todo". Quick question: is it possible to access an existing album by path (e.g. Folder1/SubFolder1/AlbumName and get back an album object?) That's something I do a lot for another project and the current AppleScript implementation is awfully slow so I'm in the middle of a big refactor.

SKaplanOfficial commented 1 year ago

Quick question: is it possible to access an existing album by path (e.g. Folder1/SubFolder1/AlbumName and get back an album object?)

Yes and no; you can't access it by path in the form you specified, but you can use by_name() to get an album/folder object quickly. For example:

import PyXA
app = PyXA.Application("Photos")
print(app.folders().by_name("Folder1").folders().by_name("Folder2").albums().by_name("Album1"))
>>> <<class 'PyXA.apps.PhotosApp.XAPhotosAlbum'>Album1, id=F0DA93AB-CC92-23B2-9E16-973F6A062468/L0/040>

This should work near-instantly in most cases, unless your by_ call is to something that has to be queried per-object, e.g. for the body text (not plaintext) of notes.

RhetTbull commented 1 year ago

Perfect -- thanks!

RhetTbull commented 1 year ago

I'm able to add attachments (on Ventura at least, does not work on Catalina, not sure about Big Sur/Monterey) using the following AppleScript:

tell application "Notes"
    set theNote to note id "x-coredata://B36F4BF3-D2F0-4997-BD9B-96A608B6D5E0/IMAPNote/p40"
    set theFile to POSIX file "/Users/rhet/Downloads/debug.txt"
    make new attachment at theNote with data theFile
end tell

Can't figure out a way to do this via ScriptingBridge though.

SKaplanOfficial commented 1 year ago

This works for me to add an attachment on Ventura:

import AppKit
import ScriptingBridge

app = ScriptingBridge.SBApplication.alloc().initWithBundleIdentifier_("com.apple.notes")
note = app.notes()[0]
url = AppKit.NSURL.alloc().initFileURLWithPath_("/Users/exampleUser/Documents/Example.jpg")
new_attachment = app.classForScriptingClass_("attachment").alloc().initWithData_andProperties_(url, {})
note.attachments().addObject_(new_attachment)

But the AppleScript approach is probably good enough (and maybe better on other macOS versions).

RhetTbull commented 1 year ago

Thanks! Will give that a try! I've been digging through your PyXA source to figure out ScriptingBridge and its been immensely helpful but it's still a lot of trial and error. I did get most of macnotesapp rewritten to use ScriptingBridge though and it's much faster now. I'd like to contribute back to PyXA once I get through my current round of projects. (for example, I've got a partial interface to Photos via PhotoKit that's much faster than scripting)

RhetTbull commented 1 year ago

Out of curiosity I benchmarked both the ScriptingBridge implementation and the AppleScript version:

M1 Mac Mini running Ventura:

AppleScript: ~300ms to add an attachment ScriptingBridge: ~80ms to add attachment

ScriptingBridge is the clear winner however, I stuck with AppleScript as the ScriptingBridge sometimes ended up in duplicate attachments being added. The formatting of the note (how the breaks were inserted between attachments) was also wonky with ScriptingBridge when inserting multiple attachments.

Also, calling .attachments() on the Note object resulted in duplicates regardless of which method was used to add the attachment. That is, the resulting SBElementArray always contained the same attachment twice (verified by looking at total number of attachments which was always 2x what it should be and also that each id was in the SBElementArray twice. No idea why but I'm filtering for those now to eliminate the dupes. This appears to happen only with attachments added via the ScriptingBridge or AppleScript, not with those added natively in the GUI.