Hello community, here is the log from the commit of package python-imbox for openSUSE:Factory checked in at 2018-09-03 10:36:32 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-imbox (Old) and /work/SRC/openSUSE:Factory/.python-imbox.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-imbox" Mon Sep 3 10:36:32 2018 rev:4 rq:632744 version:0.9.6 Changes: -------- --- /work/SRC/openSUSE:Factory/python-imbox/python-imbox.changes 2017-12-14 11:01:34.563760118 +0100 +++ /work/SRC/openSUSE:Factory/.python-imbox.new/python-imbox.changes 2018-09-03 10:36:34.884898680 +0200 @@ -1,0 +2,9 @@ +Sun Aug 26 12:04:54 UTC 2018 - [email protected] + +- update to 0.9.6: + * Vendors package, adding provider specific functionality ([#139](https://github.com/martinrusev/imbox/pull/139)) - Contributed by @zevaverbach + * Type hints for every method and function ([#136](https://github.com/martinrusev/imbox/pull/136)) - Contributed by @zevaverbach + * Move all code out of __init__.py and into a separate module ([#130](https://github.com/martinrusev/imbox/pull/130)) - Contributed by @zevaverbach + * Enhance `messages' generator: ([#129](https://github.com/martinrusev/imbox/pull/129)) - Contributed by @zevaverbach + +------------------------------------------------------------------- Old: ---- imbox-0.9.5.tar.gz New: ---- imbox-0.9.6.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-imbox.spec ++++++ --- /var/tmp/diff_new_pack.VM6ODD/_old 2018-09-03 10:36:35.292899736 +0200 +++ /var/tmp/diff_new_pack.VM6ODD/_new 2018-09-03 10:36:35.296899747 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-imbox # -# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %define skip_python2 1 Name: python-imbox -Version: 0.9.5 +Version: 0.9.6 Release: 0 Summary: Python IMAP for Human beings License: MIT @@ -46,7 +46,8 @@ %python_expand %fdupes %{buildroot}%{$python_sitelib} %files %{python_files} -%doc README.rst LICENSE +%doc README.rst +%license LICENSE %{python_sitelib}/* %changelog ++++++ imbox-0.9.5.tar.gz -> imbox-0.9.6.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imbox-0.9.5/CHANGELOG.md new/imbox-0.9.6/CHANGELOG.md --- old/imbox-0.9.5/CHANGELOG.md 2017-12-05 18:55:07.000000000 +0100 +++ new/imbox-0.9.6/CHANGELOG.md 2018-08-14 17:23:46.000000000 +0200 @@ -1,22 +1,32 @@ +## 0.9.6 (16 August 2018) + +IMPROVEMENTS: + + * Vendors package, adding provider specific functionality ([#139](https://github.com/martinrusev/imbox/pull/139)) - Contributed by @zevaverbach + * Type hints for every method and function ([#136](https://github.com/martinrusev/imbox/pull/136)) - Contributed by @zevaverbach + * Move all code out of __init__.py and into a separate module ([#130](https://github.com/martinrusev/imbox/pull/130)) - Contributed by @zevaverbach + * Enhance `messages' generator: ([#129](https://github.com/martinrusev/imbox/pull/129)) - Contributed by @zevaverbach + + ## 0.9.5 (5 December 2017) IMPROVEMENTS: - * `date__on` support: ([#109](https://github.com/martinrusev/imbox/pull/109)) - * Starttls support: ([#108](https://github.com/martinrusev/imbox/pull/108)) - * Mark emails as flagged/starred: ([#107](https://github.com/martinrusev/imbox/pull/107)) - * Messages filter can use date objects instead of stringified dates: ([#104](https://github.com/martinrusev/imbox/pull/104)) - * Fix attachment parsing when a semicolon character ends the Content-Disposition line: ([#100](https://github.com/martinrusev/imbox/pull/100)) - * Parsing - UnicecodeDecodeError() fixes: ([#96](https://github.com/martinrusev/imbox/pull/96)) - * Imbox() `with` support: ([#92](https://github.com/martinrusev/imbox/pull/92)) + * `date__on` support: ([#109](https://github.com/martinrusev/imbox/pull/109)) - Contributed by @balsagoth + * Starttls support: ([#108](https://github.com/martinrusev/imbox/pull/108)) - Contributed by @balsagoth + * Mark emails as flagged/starred: ([#107](https://github.com/martinrusev/imbox/pull/107)) - Contributed by @memanikantan + * Messages filter can use date objects instead of stringified dates: ([#104](https://github.com/martinrusev/imbox/pull/104)) - Contributed by @sblondon + * Fix attachment parsing when a semicolon character ends the Content-Disposition line: ([#100](https://github.com/martinrusev/imbox/pull/100)) - Contributed by @sblondon + * Parsing - UnicecodeDecodeError() fixes: ([#96](https://github.com/martinrusev/imbox/pull/96)) - Contributed by @am0z + * Imbox() `with` support: ([#92](https://github.com/martinrusev/imbox/pull/92)) - Contributed by @sblondon ## 0.9 (18 September 2017) IMPROVEMENTS: - * Permissively Decode Emails: ([#78](https://github.com/martinrusev/imbox/pull/78)) - * "With" statement for automatic cleanup/logout ([#92](https://github.com/martinrusev/imbox/pull/92)) + * Permissively Decode Emails: ([#78](https://github.com/martinrusev/imbox/pull/78)) - Contributed by @AdamNiederer + * "With" statement for automatic cleanup/logout ([#92](https://github.com/martinrusev/imbox/pull/92)) - Contributed by @sblondon @@ -24,7 +34,7 @@ IMPROVEMENTS: - * Add support for Python 3.3+ Parsing policies: ([#75](https://github.com/martinrusev/imbox/pull/75)) + * Add support for Python 3.3+ Parsing policies: ([#75](https://github.com/martinrusev/imbox/pull/75)) - Contributed by @bhtucker BACKWARDS INCOMPATIBILITIES / NOTES: @@ -35,4 +45,4 @@ IMPROVEMENTS: - * ssl_context: Check SSLContext for IMAP4_SSL connections ([#69](https://github.com/martinrusev/imbox/pull/69)) + * ssl_context: Check SSLContext for IMAP4_SSL connections ([#69](https://github.com/martinrusev/imbox/pull/69)) - Contributed by @dmth diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imbox-0.9.5/PKG-INFO new/imbox-0.9.6/PKG-INFO --- old/imbox-0.9.5/PKG-INFO 2017-12-05 19:09:13.000000000 +0100 +++ new/imbox-0.9.6/PKG-INFO 2018-08-14 17:26:01.000000000 +0200 @@ -1,12 +1,11 @@ Metadata-Version: 1.1 Name: imbox -Version: 0.9.5 +Version: 0.9.6 Summary: Python IMAP for Human beings Home-page: https://github.com/martinrusev/imbox Author: Martin Rusev Author-email: [email protected] License: MIT -Description-Content-Type: UNKNOWN Description: Imbox - Python IMAP for Humans ============================== @@ -45,36 +44,50 @@ ssl=True, ssl_context=None, starttls=False) as imbox: + # Get all folders status, folders_with_additional_info = imbox.folders() - # Gets all messages - all_messages = imbox.messages() + # Gets all messages from the inbox + all_inbox_messages = imbox.messages() # Unread messages - unread_messages = imbox.messages(unread=True) + unread_inbox_messages = imbox.messages(unread=True) + + # Flagged messages + inbox_flagged_messages = imbox.messages(flagged=True) + + # Un-flagged messages + inbox_unflagged_messages = imbox.messages(unflagged=True) + + # Flagged messages + flagged_messages = imbox.messages(flagged=True) + + # Un-flagged messages + unflagged_messages = imbox.messages(unflagged=True) # Messages sent FROM - messages_from = imbox.messages(sent_from='[email protected]') + inbox_messages_from = imbox.messages(sent_from='[email protected]') # Messages sent TO - messages_from = imbox.messages(sent_to='[email protected]') + inbox_messages_to = imbox.messages(sent_to='[email protected]') # Messages received before specific date - messages_from = imbox.messages(date__lt=datetime.date(2013, 7, 31)) + inbox_messages_received_before = imbox.messages(date__lt=datetime.date(2018, 7, 31)) # Messages received after specific date - messages_from = imbox.messages(date__gt=datetime.date(2013, 7, 30)) + inbox_messages_received_after = imbox.messages(date__gt=datetime.date(2018, 7, 30)) # Messages received on a specific date - messages_from = imbox.messages(date__on=datetime.date(2013, 7, 30)) - - # Messages from a specific folder - messages_folder = imbox.messages(folder='Social') + inbox_messages_received_on_date = imbox.messages(date__on=datetime.date(2018, 7, 30)) + # Messages whose subjects contain a string + inbox_messages_subject_christmas = imbox.messages(subject='Christmas') + # Messages from a specific folder + messages_in_folder_social = imbox.messages(folder='Social') - for uid, message in all_messages: + for uid, message in all_inbox_messages: # Every message is an object with the following keys message.sent_from @@ -134,7 +147,7 @@ # mark the message as read imbox.mark_seen(uid) - + Changelog @@ -167,3 +180,4 @@ Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imbox-0.9.5/README.rst new/imbox-0.9.6/README.rst --- old/imbox-0.9.5/README.rst 2017-12-05 18:44:32.000000000 +0100 +++ new/imbox-0.9.6/README.rst 2018-08-14 17:22:47.000000000 +0200 @@ -36,36 +36,50 @@ ssl=True, ssl_context=None, starttls=False) as imbox: + # Get all folders status, folders_with_additional_info = imbox.folders() - # Gets all messages - all_messages = imbox.messages() + # Gets all messages from the inbox + all_inbox_messages = imbox.messages() # Unread messages - unread_messages = imbox.messages(unread=True) + unread_inbox_messages = imbox.messages(unread=True) + + # Flagged messages + inbox_flagged_messages = imbox.messages(flagged=True) + + # Un-flagged messages + inbox_unflagged_messages = imbox.messages(unflagged=True) + + # Flagged messages + flagged_messages = imbox.messages(flagged=True) + + # Un-flagged messages + unflagged_messages = imbox.messages(unflagged=True) # Messages sent FROM - messages_from = imbox.messages(sent_from='[email protected]') + inbox_messages_from = imbox.messages(sent_from='[email protected]') # Messages sent TO - messages_from = imbox.messages(sent_to='[email protected]') + inbox_messages_to = imbox.messages(sent_to='[email protected]') # Messages received before specific date - messages_from = imbox.messages(date__lt=datetime.date(2013, 7, 31)) + inbox_messages_received_before = imbox.messages(date__lt=datetime.date(2018, 7, 31)) # Messages received after specific date - messages_from = imbox.messages(date__gt=datetime.date(2013, 7, 30)) + inbox_messages_received_after = imbox.messages(date__gt=datetime.date(2018, 7, 30)) # Messages received on a specific date - messages_from = imbox.messages(date__on=datetime.date(2013, 7, 30)) - - # Messages from a specific folder - messages_folder = imbox.messages(folder='Social') + inbox_messages_received_on_date = imbox.messages(date__on=datetime.date(2018, 7, 30)) + # Messages whose subjects contain a string + inbox_messages_subject_christmas = imbox.messages(subject='Christmas') + # Messages from a specific folder + messages_in_folder_social = imbox.messages(folder='Social') - for uid, message in all_messages: + for uid, message in all_inbox_messages: # Every message is an object with the following keys message.sent_from @@ -125,7 +139,7 @@ # mark the message as read imbox.mark_seen(uid) - + Changelog diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imbox-0.9.5/imbox/__init__.py new/imbox-0.9.6/imbox/__init__.py --- old/imbox-0.9.5/imbox/__init__.py 2017-12-05 18:44:32.000000000 +0100 +++ new/imbox-0.9.6/imbox/__init__.py 2018-08-14 17:24:05.000000000 +0200 @@ -1,93 +1,9 @@ -from imbox.imap import ImapTransport -from imbox.parser import parse_email -from imbox.query import build_search_query +from imbox.imbox import Imbox -import logging -logger = logging.getLogger(__name__) +__version_info__ = (0, 9, 6) +__version__ = '.'.join([str(x) for x in __version_info__]) -class Imbox: +__all__ = ['Imbox'] - def __init__(self, hostname, username=None, password=None, ssl=True, - port=None, ssl_context=None, policy=None, starttls=False): - self.server = ImapTransport(hostname, ssl=ssl, port=port, - ssl_context=ssl_context, starttls=starttls) - self.hostname = hostname - self.username = username - self.password = password - self.parser_policy = policy - self.connection = self.server.connect(username, password) - logger.info("Connected to IMAP Server with user {username} on {hostname}{ssl}".format( - hostname=hostname, username=username, ssl=(" over SSL" if ssl or starttls else ""))) - - def __enter__(self): - return self - - def __exit__(self, type, value, traceback): - self.logout() - - def logout(self): - self.connection.close() - self.connection.logout() - logger.info("Disconnected from IMAP Server {username}@{hostname}".format( - hostname=self.hostname, username=self.username)) - - def query_uids(self, **kwargs): - query = build_search_query(**kwargs) - message, data = self.connection.uid('search', None, query) - if data[0] is None: - return [] - return data[0].split() - - def fetch_by_uid(self, uid): - message, data = self.connection.uid('fetch', uid, '(BODY.PEEK[])') - logger.debug("Fetched message for UID {}".format(int(uid))) - raw_email = data[0][1] - - email_object = parse_email(raw_email, policy=self.parser_policy) - - return email_object - - def fetch_list(self, **kwargs): - uid_list = self.query_uids(**kwargs) - logger.debug("Fetch all messages for UID in {}".format(uid_list)) - - for uid in uid_list: - yield (uid, self.fetch_by_uid(uid)) - - def mark_seen(self, uid): - logger.info("Mark UID {} with \\Seen FLAG".format(int(uid))) - self.connection.uid('STORE', uid, '+FLAGS', '(\\Seen)') - - def mark_flag(self, uid): - logger.info("Mark UID {} with \\Flagged FLAG".format(int(uid))) - self.connection.uid('STORE', uid, '+FLAGS', '(\\Flagged)') - - def delete(self, uid): - logger.info("Mark UID {} with \\Deleted FLAG and expunge.".format(int(uid))) - mov, data = self.connection.uid('STORE', uid, '+FLAGS', '(\\Deleted)') - self.connection.expunge() - - def copy(self, uid, destination_folder): - logger.info("Copy UID {} to {} folder".format(int(uid), str(destination_folder))) - return self.connection.uid('COPY', uid, destination_folder) - - def move(self, uid, destination_folder): - logger.info("Move UID {} to {} folder".format(int(uid), str(destination_folder))) - if self.copy(uid, destination_folder): - self.delete(uid) - - def messages(self, *args, **kwargs): - folder = kwargs.get('folder', False) - msg = "" - - if folder: - self.connection.select(folder) - msg = " from folder '{}'".format(folder) - - logger.info("Fetch list of messages{}".format(msg)) - return self.fetch_list(**kwargs) - - def folders(self): - return self.connection.list() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imbox-0.9.5/imbox/imap.py new/imbox-0.9.6/imbox/imap.py --- old/imbox-0.9.5/imbox/imap.py 2017-12-05 18:44:32.000000000 +0100 +++ new/imbox-0.9.6/imbox/imap.py 2018-08-14 17:22:47.000000000 +0200 @@ -10,22 +10,16 @@ def __init__(self, hostname, port=None, ssl=True, ssl_context=None, starttls=False): self.hostname = hostname - self.port = port - kwargs = {} if ssl: - self.transport = IMAP4_SSL - if not self.port: - self.port = 993 + self.port = port or 993 if ssl_context is None: ssl_context = pythonssllib.create_default_context() - kwargs["ssl_context"] = ssl_context + self.server = IMAP4_SSL(self.hostname, self.port, ssl_context=ssl_context) else: - self.transport = IMAP4 - if not self.port: - self.port = 143 + self.port = port or 143 + self.server = IMAP4(self.hostname, self.port) - self.server = self.transport(self.hostname, self.port, **kwargs) if starttls: self.server.starttls() logger.debug("Created IMAP4 transport for {host}:{port}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imbox-0.9.5/imbox/imbox.py new/imbox-0.9.6/imbox/imbox.py --- old/imbox-0.9.5/imbox/imbox.py 1970-01-01 01:00:00.000000000 +0100 +++ new/imbox-0.9.6/imbox/imbox.py 2018-08-14 17:22:47.000000000 +0200 @@ -0,0 +1,98 @@ +import imaplib + +from imbox.imap import ImapTransport +from imbox.messages import Messages + +import logging + +from imbox.vendors import GmailMessages, hostname_vendorname_dict, name_authentication_string_dict + +logger = logging.getLogger(__name__) + + +class Imbox: + + authentication_error_message = None + + def __init__(self, hostname, username=None, password=None, ssl=True, + port=None, ssl_context=None, policy=None, starttls=False, + vendor=None): + + self.server = ImapTransport(hostname, ssl=ssl, port=port, + ssl_context=ssl_context, starttls=starttls) + + self.hostname = hostname + self.username = username + self.password = password + self.parser_policy = policy + self.vendor = vendor or hostname_vendorname_dict.get(self.hostname) + + if self.vendor is not None: + self.authentication_error_message = name_authentication_string_dict.get(self.vendor) + + try: + self.connection = self.server.connect(username, password) + except imaplib.IMAP4.error as e: + if self.authentication_error_message is None: + raise + raise imaplib.IMAP4.error(self.authentication_error_message + '\n' + str(e)) + + logger.info("Connected to IMAP Server with user {username} on {hostname}{ssl}".format( + hostname=hostname, username=username, ssl=(" over SSL" if ssl or starttls else ""))) + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.logout() + + def logout(self): + self.connection.close() + self.connection.logout() + logger.info("Disconnected from IMAP Server {username}@{hostname}".format( + hostname=self.hostname, username=self.username)) + + def mark_seen(self, uid): + logger.info("Mark UID {} with \\Seen FLAG".format(int(uid))) + self.connection.uid('STORE', uid, '+FLAGS', '(\\Seen)') + + def mark_flag(self, uid): + logger.info("Mark UID {} with \\Flagged FLAG".format(int(uid))) + self.connection.uid('STORE', uid, '+FLAGS', '(\\Flagged)') + + def delete(self, uid): + logger.info("Mark UID {} with \\Deleted FLAG and expunge.".format(int(uid))) + self.connection.uid('STORE', uid, '+FLAGS', '(\\Deleted)') + self.connection.expunge() + + def copy(self, uid, destination_folder): + logger.info("Copy UID {} to {} folder".format(int(uid), str(destination_folder))) + return self.connection.uid('COPY', uid, destination_folder) + + def move(self, uid, destination_folder): + logger.info("Move UID {} to {} folder".format(int(uid), str(destination_folder))) + if self.copy(uid, destination_folder): + self.delete(uid) + + def messages(self, **kwargs): + folder = kwargs.get('folder', False) + + messages_class = Messages + + if self.vendor == 'gmail': + messages_class = GmailMessages + + if folder: + self.connection.select(messages_class.folder_lookup.get((folder.lower())) or folder) + msg = " from folder '{}'".format(folder) + else: + msg = " from inbox" + + logger.info("Fetch list of messages{}".format(msg)) + + return messages_class(connection=self.connection, + parser_policy=self.parser_policy, + **kwargs) + + def folders(self): + return self.connection.list() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imbox-0.9.5/imbox/messages.py new/imbox-0.9.6/imbox/messages.py --- old/imbox-0.9.5/imbox/messages.py 1970-01-01 01:00:00.000000000 +0100 +++ new/imbox-0.9.6/imbox/messages.py 2018-08-14 17:22:47.000000000 +0200 @@ -0,0 +1,64 @@ +from imbox.parser import fetch_email_by_uid +from imbox.query import build_search_query + +import logging + +logger = logging.getLogger(__name__) + + +class Messages: + + folder_lookup = {} + + def __init__(self, + connection, + parser_policy, + **kwargs): + + self.connection = connection + self.parser_policy = parser_policy + self.kwargs = kwargs + self._uid_list = self._query_uids(**kwargs) + + logger.debug("Fetch all messages for UID in {}".format(self._uid_list)) + + def _fetch_email(self, uid): + return fetch_email_by_uid(uid=uid, + connection=self.connection, + parser_policy=self.parser_policy) + + def _query_uids(self, **kwargs): + query_ = build_search_query(**kwargs) + message, data = self.connection.uid('search', None, query_) + if data[0] is None: + return [] + return data[0].split() + + def _fetch_email_list(self): + for uid in self._uid_list: + yield uid, self._fetch_email(uid) + + def __repr__(self): + if len(self.kwargs) > 0: + return 'Messages({})'.format('\n'.join('{}={}'.format(key, value) + for key, value in self.kwargs.items())) + return 'Messages(ALL)' + + def __iter__(self): + return self._fetch_email_list() + + def __next__(self): + return self + + def __len__(self): + return len(self._uid_list) + + def __getitem__(self, index): + uids = self._uid_list[index] + + if not isinstance(uids, list): + uid = uids + return uid, self._fetch_email(uid) + + return [(uid, self._fetch_email(uid)) + for uid in uids] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imbox-0.9.5/imbox/parser.py new/imbox-0.9.6/imbox/parser.py --- old/imbox-0.9.5/imbox/parser.py 2017-12-05 18:44:32.000000000 +0100 +++ new/imbox-0.9.6/imbox/parser.py 2018-08-14 17:22:47.000000000 +0200 @@ -1,14 +1,17 @@ +import imaplib import io import re import email import base64 import quopri +import sys import time from datetime import datetime from email.header import decode_header from imbox.utils import str_encode, str_decode import logging + logger = logging.getLogger(__name__) @@ -33,7 +36,10 @@ return str_decode(str_encode(value, default_charset, 'replace'), default_charset) else: for index, (text, charset) in enumerate(headers): - logger.debug("Mail header no. {}: {} encoding {}".format(index, str_decode(text, charset or 'utf-8'), charset)) + logger.debug("Mail header no. {index}: {data} encoding {charset}".format( + index=index, + data=str_decode(text, charset or 'utf-8', 'replace'), + charset=charset)) try: headers[index] = str_decode(text, charset or default_charset, 'replace') @@ -54,7 +60,7 @@ for index, (address_name, address_email) in enumerate(addresses): addresses[index] = {'name': decode_mail_header(address_name), 'email': address_email} - logger.debug("{} Mail addressees in message: <{}> {}".format(header_name.upper(), address_name, address_email)) + logger.debug("{} Mail address in message: <{}> {}".format(header_name.upper(), address_name, address_email)) return addresses @@ -63,7 +69,7 @@ values = v.split('\n') value_results = [] for value in values: - match = re.search(r'=\?((?:\w|-)+)\?(Q|B)\?(.+)\?=', value) + match = re.search(r'=\?((?:\w|-)+)\?([QB])\?(.+)\?=', value) if match: encoding, type_, code = match.groups() if type_ == 'Q': @@ -120,10 +126,34 @@ charset = message.get_content_charset('utf-8') try: return content.decode(charset, 'ignore') + except LookupError: + return content.decode(charset.replace("-", ""), 'ignore') except AttributeError: return content +def fetch_email_by_uid(uid, connection, parser_policy): + message, data = connection.uid('fetch', uid, '(BODY.PEEK[] FLAGS)') + logger.debug("Fetched message for UID {}".format(int(uid))) + + raw_headers, raw_email = data[0] + + email_object = parse_email(raw_email, policy=parser_policy) + flags = parse_flags(raw_headers.decode()) + email_object.__dict__['flags'] = flags + + return email_object + + +def parse_flags(headers): + """Copied from https://github.com/girishramnani/gmail/blob/master/gmail/message.py""" + if len(headers) == 0: + return [] + if sys.version_info[0] == 3: + headers = bytes(headers, "ascii") + return list(imaplib.ParseFlags(headers)) + + def parse_email(raw_email, policy=None): if isinstance(raw_email, bytes): raw_email = str_encode(raw_email, 'utf-8', errors='ignore') @@ -137,9 +167,7 @@ except UnicodeEncodeError: email_message = email.message_from_string(raw_email.encode('utf-8'), **email_parse_kwargs) maintype = email_message.get_content_maintype() - parsed_email = {} - - parsed_email['raw_email'] = raw_email + parsed_email = {'raw_email': raw_email} body = { "plain": [], @@ -159,7 +187,7 @@ content = decode_content(part) is_inline = content_disposition is None \ - or content_disposition.startswith("inline") + or content_disposition.startswith("inline") if content_type == "text/plain" and is_inline: body['plain'].append(content) elif content_type == "text/html" and is_inline: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imbox-0.9.5/imbox/query.py new/imbox-0.9.6/imbox/query.py --- old/imbox-0.9.5/imbox/query.py 2017-12-05 18:44:32.000000000 +0100 +++ new/imbox-0.9.6/imbox/query.py 2018-08-14 17:22:47.000000000 +0200 @@ -8,8 +8,7 @@ def format_date(date): if isinstance(date, datetime.date): return date.strftime('%d-%b-%Y') - else: - return date + return date def build_search_query(**kwargs): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imbox-0.9.5/imbox/utils.py new/imbox-0.9.6/imbox/utils.py --- old/imbox-0.9.5/imbox/utils.py 2017-09-18 10:12:35.000000000 +0200 +++ new/imbox-0.9.6/imbox/utils.py 2018-08-14 17:22:47.000000000 +0200 @@ -1,14 +1,16 @@ import logging logger = logging.getLogger(__name__) + def str_encode(value='', encoding=None, errors='strict'): logger.debug("Encode str {} with and errors {}".format(value, encoding, errors)) return str(value, encoding, errors) + def str_decode(value='', encoding=None, errors='strict'): if isinstance(value, str): return bytes(value, encoding, errors).decode('utf-8') elif isinstance(value, bytes): return value.decode(encoding or 'utf-8', errors=errors) else: - raise TypeError( "Cannot decode '{}' object".format(value.__class__) ) + raise TypeError("Cannot decode '{}' object".format(value.__class__)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imbox-0.9.5/imbox/vendors/__init__.py new/imbox-0.9.6/imbox/vendors/__init__.py --- old/imbox-0.9.5/imbox/vendors/__init__.py 1970-01-01 01:00:00.000000000 +0100 +++ new/imbox-0.9.6/imbox/vendors/__init__.py 2018-08-14 17:22:47.000000000 +0200 @@ -0,0 +1,11 @@ +from imbox.vendors.gmail import GmailMessages + +vendors = [GmailMessages] + +hostname_vendorname_dict = {vendor.hostname: vendor.name for vendor in vendors} +name_authentication_string_dict = {vendor.name: vendor.authentication_error_message for vendor in vendors} + +__all__ = [v.__name__ for v in vendors] + +__all__ += ['hostname_vendorname_dict', + 'name_authentication_string_dict'] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imbox-0.9.5/imbox/vendors/gmail.py new/imbox-0.9.6/imbox/vendors/gmail.py --- old/imbox-0.9.5/imbox/vendors/gmail.py 1970-01-01 01:00:00.000000000 +0100 +++ new/imbox-0.9.6/imbox/vendors/gmail.py 2018-08-14 17:22:47.000000000 +0200 @@ -0,0 +1,29 @@ +from imbox.messages import Messages + + +class GmailMessages(Messages): + authentication_error_message = ('If you\'re not using an app-specific password, grab one here: ' + 'https://myaccount.google.com/apppasswords') + hostname = 'imap.gmail.com' + name = 'gmail' + folder_lookup = { + + 'all_mail': '"[Gmail]/All Mail"', + 'all': '"[Gmail]/All Mail"', + 'all mail': '"[Gmail]/All Mail"', + 'sent': '"[Gmail]/Sent Mail"', + 'sent mail': '"[Gmail]/Sent Mail"', + 'sent_mail': '"[Gmail]/Sent Mail"', + 'drafts': '"[Gmail]/Drafts"', + 'important': '"[Gmail]/Important"', + 'spam': '"[Gmail]/Spam"', + 'starred': '"[Gmail]/Starred"', + 'trash': '"[Gmail]/Trash"', + + } + + def __init__(self, + connection, + parser_policy, + **kwargs): + super().__init__(connection, parser_policy, **kwargs) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imbox-0.9.5/imbox.egg-info/PKG-INFO new/imbox-0.9.6/imbox.egg-info/PKG-INFO --- old/imbox-0.9.5/imbox.egg-info/PKG-INFO 2017-12-05 19:09:13.000000000 +0100 +++ new/imbox-0.9.6/imbox.egg-info/PKG-INFO 2018-08-14 17:26:01.000000000 +0200 @@ -1,12 +1,11 @@ Metadata-Version: 1.1 Name: imbox -Version: 0.9.5 +Version: 0.9.6 Summary: Python IMAP for Human beings Home-page: https://github.com/martinrusev/imbox Author: Martin Rusev Author-email: [email protected] License: MIT -Description-Content-Type: UNKNOWN Description: Imbox - Python IMAP for Humans ============================== @@ -45,36 +44,50 @@ ssl=True, ssl_context=None, starttls=False) as imbox: + # Get all folders status, folders_with_additional_info = imbox.folders() - # Gets all messages - all_messages = imbox.messages() + # Gets all messages from the inbox + all_inbox_messages = imbox.messages() # Unread messages - unread_messages = imbox.messages(unread=True) + unread_inbox_messages = imbox.messages(unread=True) + + # Flagged messages + inbox_flagged_messages = imbox.messages(flagged=True) + + # Un-flagged messages + inbox_unflagged_messages = imbox.messages(unflagged=True) + + # Flagged messages + flagged_messages = imbox.messages(flagged=True) + + # Un-flagged messages + unflagged_messages = imbox.messages(unflagged=True) # Messages sent FROM - messages_from = imbox.messages(sent_from='[email protected]') + inbox_messages_from = imbox.messages(sent_from='[email protected]') # Messages sent TO - messages_from = imbox.messages(sent_to='[email protected]') + inbox_messages_to = imbox.messages(sent_to='[email protected]') # Messages received before specific date - messages_from = imbox.messages(date__lt=datetime.date(2013, 7, 31)) + inbox_messages_received_before = imbox.messages(date__lt=datetime.date(2018, 7, 31)) # Messages received after specific date - messages_from = imbox.messages(date__gt=datetime.date(2013, 7, 30)) + inbox_messages_received_after = imbox.messages(date__gt=datetime.date(2018, 7, 30)) # Messages received on a specific date - messages_from = imbox.messages(date__on=datetime.date(2013, 7, 30)) - - # Messages from a specific folder - messages_folder = imbox.messages(folder='Social') + inbox_messages_received_on_date = imbox.messages(date__on=datetime.date(2018, 7, 30)) + # Messages whose subjects contain a string + inbox_messages_subject_christmas = imbox.messages(subject='Christmas') + # Messages from a specific folder + messages_in_folder_social = imbox.messages(folder='Social') - for uid, message in all_messages: + for uid, message in all_inbox_messages: # Every message is an object with the following keys message.sent_from @@ -134,7 +147,7 @@ # mark the message as read imbox.mark_seen(uid) - + Changelog @@ -167,3 +180,4 @@ Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imbox-0.9.5/imbox.egg-info/SOURCES.txt new/imbox-0.9.6/imbox.egg-info/SOURCES.txt --- old/imbox-0.9.5/imbox.egg-info/SOURCES.txt 2017-12-05 19:09:13.000000000 +0100 +++ new/imbox-0.9.6/imbox.egg-info/SOURCES.txt 2018-08-14 17:26:01.000000000 +0200 @@ -5,6 +5,8 @@ setup.py imbox/__init__.py imbox/imap.py +imbox/imbox.py +imbox/messages.py imbox/parser.py imbox/query.py imbox/utils.py @@ -12,12 +14,10 @@ imbox.egg-info/SOURCES.txt imbox.egg-info/dependency_links.txt imbox.egg-info/not-zip-safe -imbox.egg-info/pbr.json imbox.egg-info/top_level.txt +imbox/vendors/__init__.py +imbox/vendors/gmail.py tests/8422.msg tests/__init__.py tests/parser_tests.py -tests/query_tests.py -tests/__pycache__/__init__.cpython-35.pyc -tests/__pycache__/parser_tests.cpython-35.pyc -tests/__pycache__/query_tests.cpython-35.pyc \ No newline at end of file +tests/query_tests.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imbox-0.9.5/imbox.egg-info/pbr.json new/imbox-0.9.6/imbox.egg-info/pbr.json --- old/imbox-0.9.5/imbox.egg-info/pbr.json 2016-06-08 08:38:41.000000000 +0200 +++ new/imbox-0.9.6/imbox.egg-info/pbr.json 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ -{"is_release": false, "git_version": "64c06c1"} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imbox-0.9.5/setup.py new/imbox-0.9.6/setup.py --- old/imbox-0.9.5/setup.py 2017-12-05 19:08:52.000000000 +0100 +++ new/imbox-0.9.6/setup.py 2018-08-14 17:22:47.000000000 +0200 @@ -1,7 +1,9 @@ from setuptools import setup import os -version = '0.9.5' +import imbox + +version = imbox.__version__ def read(filename): @@ -17,7 +19,7 @@ author_email='[email protected]', url='https://github.com/martinrusev/imbox', license='MIT', - packages=['imbox'], + packages=['imbox', 'imbox.vendors'], package_dir={'imbox': 'imbox'}, zip_safe=False, classifiers=( @@ -25,7 +27,8 @@ 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6' + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', ), test_suite='tests', ) Binary files old/imbox-0.9.5/tests/__pycache__/__init__.cpython-35.pyc and new/imbox-0.9.6/tests/__pycache__/__init__.cpython-35.pyc differ Binary files old/imbox-0.9.5/tests/__pycache__/parser_tests.cpython-35.pyc and new/imbox-0.9.6/tests/__pycache__/parser_tests.cpython-35.pyc differ Binary files old/imbox-0.9.5/tests/__pycache__/query_tests.cpython-35.pyc and new/imbox-0.9.6/tests/__pycache__/query_tests.cpython-35.pyc differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imbox-0.9.5/tests/parser_tests.py new/imbox-0.9.6/tests/parser_tests.py --- old/imbox-0.9.5/tests/parser_tests.py 2017-12-05 18:44:32.000000000 +0100 +++ new/imbox-0.9.6/tests/parser_tests.py 2018-08-14 17:22:47.000000000 +0200 @@ -274,6 +274,44 @@ --____NOIBTUQXSYRVOOAFLCHY____-- """ +raw_email_encoded_encoding_charset_contains_a_minus = b"""Delivered-To: <[email protected]> +Return-Path: <[email protected]> +Message-ID: <74836CF6FF9B1965927DE7EE8A087483@NXOFGRQFQW2> +From: <[email protected]> +To: <[email protected]> +Subject: Salut, mon cher. +Date: 30 May 2018 22:47:37 +0200 +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="----=_NextPart_000_0038_01D3F85C.02934C4A" + +------=_NextPart_000_0038_01D3F85C.02934C4A +Content-Type: text/plain; + charset="cp-850" +Content-Transfer-Encoding: quoted-printable + +spam here + + +cliquez ici +------=_NextPart_000_0038_01D3F85C.02934C4A +Content-Type: text/html; + charset="cp-850" +Content-Transfer-Encoding: quoted-printable + +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML><HEAD> +<META http-equiv=3DContent-Type content=3D"text/html; charset=3Dcp-850"> +<META content=3D"MSHTML 6.00.2900.2456" name=3DGENERATOR> +<STYLE></STYLE> +</HEAD> +<BODY bgColor=3D#ffffff> +spam here<br> +<br> +<a href=3D"http://spammer-url"><b>cliquez = +ici</b></a></br></BODY></HTML> +------=_NextPart_000_0038_01D3F85C.02934C4A-- +""" class TestParser(unittest.TestCase): @@ -324,6 +362,12 @@ self.assertEqual('abc.xyz', attachment['filename']) self.assertTrue(attachment['content']) + def test_parse_email_accept_if_declared_charset_contains_a_minus_character(self): + parsed_email = parse_email(raw_email_encoded_encoding_charset_contains_a_minus) + self.assertEqual("Salut, mon cher.", parsed_email.subject) + self.assertTrue(parsed_email.body['plain']) + self.assertTrue(parsed_email.body['html']) + # TODO - Complete the test suite def test_decode_mail_header(self): pass @@ -336,6 +380,9 @@ from_message_object = email.message_from_string("From: John Smith <[email protected]>") self.assertEqual([{'email': '[email protected]', 'name': 'John Smith'}], get_mail_addresses(from_message_object, 'from')) + invalid_encoding_in_from_message_object = email.message_from_string("From: =?UTF-8?Q?C=E4cilia?= <[email protected]>") + self.assertEqual([{'email': '[email protected]', 'name': 'C�cilia'}], get_mail_addresses(invalid_encoding_in_from_message_object, 'from')) + def test_parse_email_with_policy(self): if not SMTP: return
