# Path: imap_tools/utils.py
# def decode_value(value: AnyStr, encoding: Optional[str] = None) -> str:
#     """Converts value to utf-8 encoding"""
#     if isinstance(encoding, str):
#         encoding = encoding.lower()
#     if isinstance(value, bytes):
#         try:
#             return value.decode(encoding or 'utf-8', 'ignore')
#         except LookupError:  # unknown encoding
#             return value.decode('utf-8', 'ignore')
#     return value
# 
# def parse_email_addresses(raw_header: Union[str, Header]) -> Tuple[EmailAddress, ...]:
#     """
#     Parse email addresses from header
#     :param raw_header: example: '=?UTF-8?B?0J7Qu9C1=?= <name@company.ru>,\r\n "\'\\"z, z\\"\'" <imap.tools@ya.ru>'
#     :return: (EmailAddress, ...)
#     """
#     result = []
#     if type(raw_header) is Header:
#         raw_header = decode_value(*decode_header(raw_header)[0])
#     for raw_name, email in getaddresses([raw_header.replace('\r\n', '').replace('\n', '')]):
#         name = decode_value(*decode_header(raw_name)[0]).strip()
#         email = email.strip()
#         if not (name or email):
#             continue
#         result.append(EmailAddress(
#             name=name,
#             email=email if '@' in email else '',
#             full='{} <{}>'.format(name, email) if name and email else name or email
#         ))
#     return tuple(result)
# 
# def parse_email_date(value: str) -> datetime.datetime:
#     """
#     Parsing the date described in rfc2822
#     Result datetime may be naive or with tzinfo
#     1900-1-1 for unparsed
#     """
#     try:
#         return parsedate_to_datetime(value)
#     except Exception:  # noqa
#         pass
#     match = re.search(
#         r'(?P<date>\d{1,2}\s+(' + '|'.join(SHORT_MONTH_NAMES) + r')\s+\d{4})\s+' +
#         r'(?P<time>\d{1,2}:\d{1,2}(:\d{1,2})?)\s*' +
#         r'(?P<zone_sign>[+-])?(?P<zone>\d{4})?',
#         value
#     )
#     if match:
#         group = match.groupdict()
#         day, month, year = group['date'].split()
#         time_values = group['time'].split(':')
#         zone_sign = int('{}1'.format(group.get('zone_sign') or '+'))
#         zone = group['zone']
#         try:
#             return datetime.datetime(
#                 year=int(year),
#                 month=SHORT_MONTH_NAMES.index(month) + 1,
#                 day=int(day),
#                 hour=int(time_values[0]),
#                 minute=int(time_values[1]),
#                 second=int(time_values[2]) if len(time_values) > 2 else 0,
#                 tzinfo=datetime.timezone(datetime.timedelta(
#                     hours=int(zone[:2]) * zone_sign,
#                     minutes=int(zone[2:]) * zone_sign
#                 )) if zone else None,
#             )
#         except ValueError:
#             pass
#     return datetime.datetime(1900, 1, 1)
# 
# class EmailAddress:
#     """Parsed email address info"""
#     __slots__ = 'name', 'email', 'full'
# 
#     def __init__(self, name: str, email: str, full: str):
#         self.name = name
#         self.email = email
#         self.full = full
# 
#     def __repr__(self):
#         return "{}(name={}, email={}, full={})".format(
#             self.__class__.__name__, repr(self.name), repr(self.email), repr(self.full))
# 
#     def __eq__(self, other):
#         return all(getattr(self, i) == getattr(other, i) for i in self.__slots__)
# 
# Path: imap_tools/consts.py
# UID_PATTERN = re.compile(r'(^|\s+|\W)UID\s+(?P<uid>\d+)')


import re
import email
import base64
import imaplib
import datetime
from itertools import chain
from functools import lru_cache
from email.header import decode_header
from typing import Tuple, Dict, Optional, List
from .utils import decode_value, parse_email_addresses, parse_email_date, EmailAddress
from .consts import UID_PATTERN
        :return: [MailAttachment]
        """
        results = []
        for part in self.obj.walk():
            if part.get_content_maintype() == 'multipart':  # multipart/* are containers
                continue
            if part.get('Content-ID') is None and part.get_filename() is None \
                    and part.get_content_type() != 'message/rfc822':
                continue
            results.append(MailAttachment(part))
        return results


class MailAttachment:
    """An attachment for a MailMessage"""

    def __init__(self, part):
        self.part = part

    @property
    @lru_cache()
    def filename(self) -> str:
        """
        Attachment filename
        nameless cases:
            inline file (Content-Disposition = inline)
            forwarded message (Content-Type = message/rfc822)
        :return: filename
        """
        raw = self.part.get_filename() or ''

# Path: imap_tools/utils.py
# def decode_value(value: AnyStr, encoding: Optional[str] = None) -> str:
#     """Converts value to utf-8 encoding"""
#     if isinstance(encoding, str):
#         encoding = encoding.lower()
#     if isinstance(value, bytes):
#         try:
#             return value.decode(encoding or 'utf-8', 'ignore')
#         except LookupError:  # unknown encoding
#             return value.decode('utf-8', 'ignore')
#     return value
# 
# def parse_email_addresses(raw_header: Union[str, Header]) -> Tuple[EmailAddress, ...]:
#     """
#     Parse email addresses from header
#     :param raw_header: example: '=?UTF-8?B?0J7Qu9C1=?= <name@company.ru>,\r\n "\'\\"z, z\\"\'" <imap.tools@ya.ru>'
#     :return: (EmailAddress, ...)
#     """
#     result = []
#     if type(raw_header) is Header:
#         raw_header = decode_value(*decode_header(raw_header)[0])
#     for raw_name, email in getaddresses([raw_header.replace('\r\n', '').replace('\n', '')]):
#         name = decode_value(*decode_header(raw_name)[0]).strip()
#         email = email.strip()
#         if not (name or email):
#             continue
#         result.append(EmailAddress(
#             name=name,
#             email=email if '@' in email else '',
#             full='{} <{}>'.format(name, email) if name and email else name or email
#         ))
#     return tuple(result)
# 
# def parse_email_date(value: str) -> datetime.datetime:
#     """
#     Parsing the date described in rfc2822
#     Result datetime may be naive or with tzinfo
#     1900-1-1 for unparsed
#     """
#     try:
#         return parsedate_to_datetime(value)
#     except Exception:  # noqa
#         pass
#     match = re.search(
#         r'(?P<date>\d{1,2}\s+(' + '|'.join(SHORT_MONTH_NAMES) + r')\s+\d{4})\s+' +
#         r'(?P<time>\d{1,2}:\d{1,2}(:\d{1,2})?)\s*' +
#         r'(?P<zone_sign>[+-])?(?P<zone>\d{4})?',
#         value
#     )
#     if match:
#         group = match.groupdict()
#         day, month, year = group['date'].split()
#         time_values = group['time'].split(':')
#         zone_sign = int('{}1'.format(group.get('zone_sign') or '+'))
#         zone = group['zone']
#         try:
#             return datetime.datetime(
#                 year=int(year),
#                 month=SHORT_MONTH_NAMES.index(month) + 1,
#                 day=int(day),
#                 hour=int(time_values[0]),
#                 minute=int(time_values[1]),
#                 second=int(time_values[2]) if len(time_values) > 2 else 0,
#                 tzinfo=datetime.timezone(datetime.timedelta(
#                     hours=int(zone[:2]) * zone_sign,
#                     minutes=int(zone[2:]) * zone_sign
#                 )) if zone else None,
#             )
#         except ValueError:
#             pass
#     return datetime.datetime(1900, 1, 1)
# 
# class EmailAddress:
#     """Parsed email address info"""
#     __slots__ = 'name', 'email', 'full'
# 
#     def __init__(self, name: str, email: str, full: str):
#         self.name = name
#         self.email = email
#         self.full = full
# 
#     def __repr__(self):
#         return "{}(name={}, email={}, full={})".format(
#             self.__class__.__name__, repr(self.name), repr(self.email), repr(self.full))
# 
#     def __eq__(self, other):
#         return all(getattr(self, i) == getattr(other, i) for i in self.__slots__)
# 
# Path: imap_tools/consts.py
# UID_PATTERN = re.compile(r'(^|\s+|\W)UID\s+(?P<uid>\d+)')


# Path: imap_tools/message.py
import re
import email
import base64
import imaplib
import datetime
from itertools import chain
from functools import lru_cache
from email.header import decode_header
from typing import Tuple, Dict, Optional, List
from .utils import decode_value, parse_email_addresses, parse_email_date, EmailAddress
from .consts import UID_PATTERN

        :return: [MailAttachment]
        """
        results = []
        for part in self.obj.walk():
            if part.get_content_maintype() == 'multipart':  # multipart/* are containers
                continue
            if part.get('Content-ID') is None and part.get_filename() is None \
                    and part.get_content_type() != 'message/rfc822':
                continue
            results.append(MailAttachment(part))
        return results


class MailAttachment:
    """An attachment for a MailMessage"""

    def __init__(self, part):
        self.part = part

    @property
    @lru_cache()
    def filename(self) -> str:
        """
        Attachment filename
        nameless cases:
            inline file (Content-Disposition = inline)
            forwarded message (Content-Type = message/rfc822)
        :return: filename
        """
        raw = self.part.get_filename() or ''

        return ''.join(decode_value(*head_part) for head_part in decode_header(raw))