def add_fake_message( db_session, namespace_id, thread=None, from_addr=None, to_addr=None, cc_addr=None, bcc_addr=None, received_date=None, subject="", body="", snippet="", g_msgid=None, add_sent_category=False, ): from inbox.contacts.processing import update_contacts_from_message from inbox.models import Category, Message m = Message() m.namespace_id = namespace_id m.from_addr = from_addr or [] m.to_addr = to_addr or [] m.cc_addr = cc_addr or [] m.bcc_addr = bcc_addr or [] m.received_date = received_date or datetime.utcnow() m.size = 0 m.is_read = False m.is_starred = False m.body = body m.snippet = snippet m.subject = subject m.g_msgid = g_msgid if thread: thread.messages.append(m) update_contacts_from_message(db_session, m, thread.namespace.id) db_session.add(m) db_session.commit() if add_sent_category: category = Category.find_or_create(db_session, namespace_id, "sent", "sent", type_="folder") if category not in m.categories: m.categories.add(category) db_session.commit() return m
def create_imap_message(db_session, account, folder, msg): """ IMAP-specific message creation logic. Returns ------- imapuid : inbox.models.backends.imap.ImapUid New db object, which links to new Message and Block objects through relationships. All new objects are uncommitted. """ log.debug("creating message", account_id=account.id, folder_name=folder.name, mid=msg.uid) new_message = Message.create_from_synced( account=account, mid=msg.uid, folder_name=folder.name, received_date=msg.internaldate, body_string=msg.body, ) # Check to see if this is a copy of a message that was first created # by the Nylas API. If so, don't create a new object; just use the old one. existing_copy = reconcile_message(new_message, db_session) if existing_copy is not None: new_message = existing_copy imapuid = ImapUid(account=account, folder=folder, msg_uid=msg.uid, message=new_message) imapuid.update_flags(msg.flags) if msg.g_labels is not None: imapuid.update_labels(msg.g_labels) # Update the message's metadata with db_session.no_autoflush: is_draft = imapuid.is_draft and (folder.canonical_name == "drafts" or folder.canonical_name == "all") update_message_metadata(db_session, account, new_message, is_draft) update_contacts_from_message(db_session, new_message, account.namespace.id) return imapuid
def update_draft( db_session, account, draft, to_addr=None, subject=None, body=None, blocks=None, cc_addr=None, bcc_addr=None, from_addr=None, reply_to=None, ): """ Update draft with new attributes. """ def update(attr, value=None): if value is not None: setattr(draft, attr, value) if attr == "body": # Update size, snippet too draft.size = len(value) draft.snippet = draft.calculate_html_snippet(value) update("to_addr", to_addr) update("cc_addr", cc_addr) update("bcc_addr", bcc_addr) update("reply_to", reply_to) update("from_addr", from_addr) update("subject", subject if subject else None) update("body", body if body else None) update("received_date", datetime.utcnow()) # Remove any attachments that aren't specified new_block_ids = [b.id for b in blocks] for part in [x for x in draft.parts if x.block_id not in new_block_ids]: draft.parts.remove(part) db_session.delete(part) # Parts require special handling for block in blocks: # Don't re-add attachments that are already attached if block.id in [p.block_id for p in draft.parts]: continue draft.parts.append(block_to_part(block, draft, account.namespace)) thread = draft.thread if len(thread.messages) == 1: # If there are no prior messages on the thread, update its subject and # dates to match the draft. thread.subject = draft.subject thread.subjectdate = draft.received_date thread.recentdate = draft.received_date # Remove previous message-contact associations, and create new ones. draft.contacts = [] update_contacts_from_message(db_session, draft, account.namespace.id) # The draft we're updating may or may not be one authored through the API: # - Ours: is_created = True, Message-Id = public_id+version # - Not Ours: is_created = False, Message-Id = ??? # Mark that the draft is now created by us draft.is_created = True # Save the current Message-Id so we know which draft to delete in syncback old_message_id_header = draft.message_id_header # Increment version and rebuild the message ID header. draft.version += 1 draft.regenerate_nylas_uid() # Sync to remote schedule_action( "update_draft", draft, draft.namespace.id, db_session, version=draft.version, old_message_id_header=old_message_id_header, ) db_session.commit() return draft
def create_message_from_json(data, namespace, db_session, is_draft): """ Construct a Message instance from `data`, a dictionary representing the POST body of an API request. All new objects are added to the session, but not committed.""" # Validate the input and get referenced objects (thread, attachments) # as necessary. to_addr = get_recipients(data.get("to"), "to") cc_addr = get_recipients(data.get("cc"), "cc") bcc_addr = get_recipients(data.get("bcc"), "bcc") from_addr = get_recipients(data.get("from"), "from") reply_to = get_recipients(data.get("reply_to"), "reply_to") if from_addr and len(from_addr) > 1: raise InputError("from_addr field can have at most one item") if reply_to and len(reply_to) > 1: raise InputError("reply_to field can have at most one item") subject = data.get("subject") if subject is not None and not isinstance(subject, basestring): raise InputError('"subject" should be a string') body = data.get("body", "") if not isinstance(body, basestring): raise InputError('"body" should be a string') blocks = get_attachments(data.get("file_ids"), namespace.id, db_session) reply_to_thread = get_thread(data.get("thread_id"), namespace.id, db_session) reply_to_message = get_message(data.get("reply_to_message_id"), namespace.id, db_session) if (reply_to_message is not None and reply_to_thread is not None and reply_to_message not in reply_to_thread.messages): raise InputError("Message {} is not in thread {}".format( reply_to_message.public_id, reply_to_thread.public_id)) with db_session.no_autoflush: account = namespace.account dt = datetime.utcnow() uid = generate_public_id() to_addr = to_addr or [] cc_addr = cc_addr or [] bcc_addr = bcc_addr or [] blocks = blocks or [] if subject is None: # If this is a reply with no explicitly specified subject, set the # subject from the prior message/thread by default. # TODO(emfree): Do we want to allow changing the subject on a reply # at all? if reply_to_message is not None: subject = reply_to_message.subject elif reply_to_thread is not None: subject = reply_to_thread.subject subject = subject or "" message = Message() message.namespace = namespace message.is_created = True message.is_draft = is_draft message.from_addr = (from_addr if from_addr else [(account.name, account.email_address)]) # TODO(emfree): we should maybe make received_date nullable, so its # value doesn't change in the case of a drafted-and-later-reconciled # message. message.received_date = dt message.subject = subject message.body = body message.to_addr = to_addr message.cc_addr = cc_addr message.bcc_addr = bcc_addr message.reply_to = reply_to # TODO(emfree): this is different from the normal 'size' value of a # message, which is the size of the entire MIME message. message.size = len(body) message.is_read = True message.is_sent = False message.public_id = uid message.version = 0 message.regenerate_nylas_uid() # Set the snippet message.snippet = message.calculate_html_snippet(body) # Associate attachments to the draft message for block in blocks: message.parts.append(block_to_part(block, message, namespace)) update_contacts_from_message(db_session, message, namespace.id) if reply_to_message is not None: message.is_reply = True _set_reply_headers(message, reply_to_message) thread = reply_to_message.thread message.reply_to_message = reply_to_message elif reply_to_thread is not None: message.is_reply = True thread = reply_to_thread # Construct the in-reply-to and references headers from the last # message currently in the thread. previous_messages = [m for m in thread.messages if not m.is_draft] if previous_messages: last_message = previous_messages[-1] message.reply_to_message = last_message _set_reply_headers(message, last_message) else: # If this isn't a reply to anything, create a new thread object for # the draft. We specialize the thread class so that we can, for # example, add the g_thrid for Gmail later if we reconcile a synced # message with this one. This is a huge hack, but works. message.is_reply = False thread_cls = account.thread_cls thread = thread_cls( subject=message.subject, recentdate=message.received_date, namespace=namespace, subjectdate=message.received_date, ) message.thread = thread db_session.add(message) if is_draft: schedule_action("save_draft", message, namespace.id, db_session, version=message.version) db_session.flush() return message