Skip to content

Instantly share code, notes, and snippets.

@jpmens
Last active April 23, 2020 20:44
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jpmens/959ea1bdcb7f671a423b to your computer and use it in GitHub Desktop.
Save jpmens/959ea1bdcb7f671a423b to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
# easyIMAP2Notes (C)2015 by Jan-Piet Mens
# Connect to an IMAP mailbox, read messages, and convert them into
# a format suitable for iOS/OSX Notes, then store them in Notes/
#
# This uses two connections (consider that a feature b/c you can
# slurp from one IMAP account into another). The real reason is I
# couldn't be bothered to implement message decoding/attachment
# extraction with imaplib, so I chose easyimap (pip install easyimap)
# to do that.
# It reads all messages in Inbox, and creates Notes/, correctly
# handling attachments, for each of the messages, which are then
# moved into Archive/
import easyimap
import sys
import imaplib
import ConfigParser
import os
import time
import email.message
from email import encoders
import email.message
import email.utils
from email.header import Header
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
import uuid
def buildmessage(subj, message_id, body, attachments):
guid = str(uuid.uuid4()).upper()
mtime = time.time()
date_string = email.utils.formatdate(int(mtime), localtime=True)
msg = MIMEMultipart('related', 'Apple-Mail-%s' % uuid.uuid4(), content_type='text/html')
msg['Subject'] = subj
msg['From'] = 'jp@example.com'
msg['To'] = 'jp@example.com'
msg['X-Uniform-Type-Identifier'] = 'com.apple.mail-note'
msg['Content-Transfer-Encoding'] = '7bit'
msg['Mime-Version'] = '1.0'
msg['Date'] = date_string # Last modif time
msg['X-Mail-Created-Date'] = date_string # Creation time
msg['X-Universally-Unique-Identifier'] = guid
msg['Message-Id'] = message_id
# We have to know the CID of all attachments for the first HTML MIME part
# so build a list of those now.
objects = {}
for (filename, content, datatype) in mail.attachments:
objects[filename] = "%s@example.org" % str(uuid.uuid4())
# Notes have the subject in the first line of the body (yuck).
# Furthermore, if this line is later edited in the Note, then
# the title of the note changes accordingly.
html_body = "%s\n<br/><br/>\n<div>%s\n<br/><br/>" % (subj, body)
if len(objects):
html_body = html_body
# OSX will show the attachment without the <object>, but
# iOS won't. Of course. Add the list of attachments, using
# the CIDs obtained previously.
for k in objects:
id = objects[k]
o = '<object type="application/x-apple-msg-attachment" data="cid:%s"></object>' % id
html_body = html_body + o
html_body = html_body + "</div>"
body_part = MIMEText(html_body, 'html') # , 'utf-8')
msg.attach(body_part)
# Now for the attachments, if we have any.
for (filename, content, ctype) in mail.attachments:
cid = objects[filename]
if ctype is None:
ctype = 'application/octet-stream'
maintype, subtype = ctype.split('/', 1)
if maintype == 'text':
attachment = MIMEText(content, _subtype=subtype)
attachment.add_header('Content-ID', "<%s>" % cid)
attachment.add_header('Content-Disposition', 'attachment', filename=os.path.basename(filename))
elif maintype == 'image':
img_t = {
'_subtype' : subtype,
'name' : os.path.basename(filename),
'x-apple-part-url' : cid,
}
attachment = MIMEImage(content, **img_t)
attachment.add_header('Content-ID', "<%s>" % cid)
attachment.add_header('Content-Disposition', 'inline', filename=os.path.basename(filename))
else:
attachment = MIMEBase(maintype, subtype)
attachment.set_payload(content)
encoders.encode_base64(attachment)
attachment.add_header('Content-ID', "<%s>" % cid)
attachment.add_header('Content-Disposition', 'attachment', filename=os.path.basename(filename))
msg.attach(attachment)
return msg
if __name__ == '__main__':
config = ConfigParser.ConfigParser()
try:
config.read([os.path.expanduser('creds')])
except:
print "Cannot read configuration"
sys.exit(1)
hostname = config.get('imap', 'hostname')
username = config.get('imap', 'username')
password = config.get('imap', 'password')
# First connection with easyimap to "read" INBOX (KISS)
imapper = easyimap.connect(hostname, username, password, 'INBOX', ssl=True, port=993)
# Second connection to actually add Notes to IMAP
connection = imaplib.IMAP4_SSL(hostname)
connection.login(username, password)
connection.select('Notes')
criterion = 'ALL'
criterion = '(SUBJECT "Notes test 1")'
criterion = '(SUBJECT "Notes")'
criterion = None
for id in imapper.listids(limit=10, criterion=criterion):
mail = imapper.mail(id)
print "Doing id=",id, mail.title
# print mail.from_addr
# print mail.to
# print mail.date
# print mail.message_id
# print "-- BODY\n%s\nBODY --" % mail.body
#for (filename, content, datatype) in mail.attachments:
# print "Got file: ", filename, datatype
# # f = open('xx-%d' % n, 'wb')
# # f.write(content)
# # f.close()
msg = buildmessage(mail.title.encode('utf-8'), mail.message_id, mail.body.encode('utf-8'), mail.attachments)
folder = 'Notes/tmp'
typ, resp = connection.append(folder, '', imaplib.Time2Internaldate(time.time()), str(msg))
if typ != 'OK':
print "Cannot create Notes/: ", resp
# Move the processed message from Inbox/ to Archive/
connection.select('INBOX')
try:
typ, resp = connection.create('Archive')
# print "CREATE ", typ, resp
except:
pass
typ, resp = connection.uid('COPY', id, 'Archive')
if typ == 'OK':
typ, resp = connection.uid('STORE', id, '+FLAGS', '(\Deleted)')
if typ != "OK":
print "STORE ", typ, resp
else:
print "Cannot copy message: ", resp
imapper.quit()
connection.close()
connection.logout()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment