8e156e6b86
For now mblog only contains the mnote-html executable which takes a mime message from a maildir and prints the equivalent HTML fragment to stdout. It is intended to work with the mblaze(7) utilities, i. e. mnote-html resolves all `object` tags to proper `img` inclusions with the correct filename, so mshow(1)'s -x version can supply the needed image files. A note created using Apple's Notes app (tested with the iOS version) can be converted in a viewable HTML file like this: $ mnote-html path/to/msg > fragment.html $ mshow -x path/to/msg $ cat <(echo "<!DOCTYPE html>") fragment.html > document.html $ xdg-open document.html Note that only the limited feature set of Apple Notes when using the IMAP backend is supported. The iCloud-based one has more (quite neat) features, but its notes can only accessed via an internal API as far as I know. This CLI is a bit impractical due to the big startup overhead of loading the lisp image. mblog should be become a fully fletched static site generator in the future, but this is a good starting point and providing the mnote-html tool is certainly useful. Change-Id: Iee6d1558e939b932da1e70ca2d2ae75638d855df Reviewed-on: https://cl.tvl.fyi/c/depot/+/3271 Tested-by: BuildkiteCI Reviewed-by: sterni <sternenseemann@systemli.org>
60 lines
2.5 KiB
Common Lisp
60 lines
2.5 KiB
Common Lisp
(in-package :mblog)
|
|
(declaim (optimize (safety 3)))
|
|
|
|
;;; util
|
|
|
|
(defun html-escape-stream (in out)
|
|
"Escape characters read from stream IN and write them to
|
|
stream OUT escaped using WHO:ESCAPE-CHAR-MINIMAL."
|
|
(loop for char = (read-char in nil nil)
|
|
while char
|
|
do (write-string (who:escape-char-minimal char) out)))
|
|
|
|
(defun cid-header-value (cid)
|
|
"Takes a Content-ID as present in Apple Notes' <object> tags and properly
|
|
surrounds them with angle brackets for a MIME header"
|
|
(concatenate 'string "<" cid ">"))
|
|
|
|
;;; main implementation
|
|
|
|
;; TODO(sterni): make this a “parser” instead of a predicate
|
|
(defun apple-note-p (msg)
|
|
"Checks X-Uniform-Type-Identifier of a MIME:MIME-MESSAGE
|
|
to determine if a given mime message is an Apple Note."
|
|
(when-let (uniform-id (assoc "X-Uniform-Type-Identifier"
|
|
(mime:mime-message-headers msg)
|
|
:test #'string=))
|
|
(string= (cdr uniform-id) "com.apple.mail-note")))
|
|
|
|
(defun apple-note-html-fragment (msg out)
|
|
"Takes a MIME:MIME-MESSAGE and writes its text content as HTML to
|
|
the OUT stream. The <object> tags are resolved to <img> which
|
|
refer to the respective attachment's filename as a relative path,
|
|
but extraction of the attachments must be done separately. The
|
|
surrounding <html> and <body> tags are stripped and <head>
|
|
discarded completely, so only a fragment which can be included
|
|
in custom templates remains."
|
|
(let ((text (find-mime-text-part msg)))
|
|
(cond
|
|
;; Sanity checking of the note
|
|
((not (apple-note-p msg))
|
|
(error "Unsupported or missing X-Uniform-Type-Identifier"))
|
|
((not text) (error "Malformed Apple Note: no text part"))
|
|
;; notemap creates text/plain notes we need to handle properly.
|
|
;; Additionally we *could* check X-Mailer which notemap sets
|
|
((string= (mime:mime-subtype text) "plain")
|
|
(html-escape-stream (mime:mime-body-stream text :binary nil) out))
|
|
;; Notes.app creates text/html parts
|
|
((string= (mime:mime-subtype text) "html")
|
|
(closure-html:parse
|
|
(mime:mime-body-stream text)
|
|
(make-instance
|
|
'apple-note-transformer
|
|
:cid-lookup
|
|
(lambda (cid)
|
|
(when-let* ((part (mime:find-mime-part-by-id msg (cid-header-value cid)))
|
|
(file (mime:mime-part-file-name part)))
|
|
file))
|
|
:next-handler
|
|
(closure-html:make-character-stream-sink out))))
|
|
(t (error "Malformed Apple Note: unknown mime type")))))
|