示例#1
0
def random_folder(logged_in_client: IMAPClient):
    folder = "random-122122121"
    if not logged_in_client.folder_exists(folder):
        logged_in_client.create_folder(folder)

    yield folder

    if logged_in_client.folder_exists(folder):
        logged_in_client.delete_folder(folder)

    assert not logged_in_client.folder_exists(folder)
示例#2
0
class ImapDB(BaseDB):
    def __init__(self,
                 username,
                 password='******',
                 host='localhost',
                 port=143,
                 *args,
                 **kwargs):
        super().__init__(username, *args, **kwargs)
        self.imap = IMAPClient(host, port, use_uid=True, ssl=False)
        res = self.imap.login(username, password)
        self.cursor.execute(
            "SELECT lowModSeq,highModSeq,highModSeqMailbox,highModSeqThread,highModSeqEmail FROM account LIMIT 1"
        )
        row = self.cursor.fetchone()
        self.lastfoldersync = 0
        if row:
            self.lowModSeq,
            self.highModSeq,
            self.highModSeqMailbox,
            self.highModSeqThread,
            self.highModSeqEmail = row
        else:
            self.lowModSeq = 0
            self.highModSeq = 1
            self.highModSeqMailbox = 1
            self.highModSeqThread = 1
            self.highModSeqEmail = 1

        # (imapname, readonly)
        self.selected_folder = (None, False)
        self.mailboxes = {}
        self.sync_mailboxes()
        self.messages = {}

    def get_messages_cached(self, properties=(), id__in=()):
        messages = []
        if not self.messages:
            return messages, id__in, properties
        fetch_props = set()
        fetch_ids = set(id__in)
        for id in id__in:
            msg = self.messages.get(id, None)
            if msg:
                found = True
                for prop in properties:
                    try:
                        msg[prop]
                    except (KeyError, AttributeError):
                        found = False
                        fetch_props.add(prop)
                if found:
                    fetch_ids.remove(id)
                    messages.append(msg)
        # if one messages is missing, need to fetch all properties
        if len(messages) < len(id__in):
            fetch_props = properties
        return messages, fetch_ids, fetch_props

    def get_messages(self,
                     properties=(),
                     sort={},
                     inMailbox=None,
                     inMailboxOtherThan=(),
                     id__in=None,
                     threadId__in=None,
                     **criteria):
        # XXX: id == threadId for now
        if id__in is None and threadId__in is not None:
            id__in = [id[1:] for id in threadId__in]
        if id__in is None:
            messages = []
        else:
            # try get everything from cache
            messages, id__in, properties = self.get_messages_cached(
                properties, id__in=id__in)

        fetch_fields = {
            f
            for prop, f in FIELDS_MAP.items() if prop in properties
        }
        if 'RFC822' in fetch_fields:
            # remove redundand fields
            fetch_fields.discard('RFC822.HEADER')
            fetch_fields.discard('RFC822.SIZE')

        if inMailbox:
            mailbox = self.mailboxes.get(inMailbox, None)
            if not mailbox:
                raise errors.notFound(f'Mailbox {inMailbox} not found')
            mailboxes = [mailbox]
        elif inMailboxOtherThan:
            mailboxes = [
                m for m in self.mailboxes.values()
                if m['id'] not in inMailboxOtherThan
            ]
        else:
            mailboxes = self.mailboxes.values()

        search_criteria = as_imap_search(criteria)
        sort_criteria = as_imap_sort(sort) or '' if sort else None

        mailbox_uids = {}
        if id__in is not None:
            if len(id__in) == 0:
                return messages  # no messages matches empty ids
            if not fetch_fields and not sort_criteria:
                # when we don't need anything new from IMAP, create empty messages
                # useful when requested conditions can be calculated from id (threadId)
                messages.extend(
                    self.messages.get(id, 0) or ImapMessage(id=id)
                    for id in id__in)
                return messages

            for id in id__in:
                # TODO: check uidvalidity
                mailboxid, uidvalidity, uid = parse_message_id(id)
                uids = mailbox_uids.get(mailboxid, [])
                if not uids:
                    mailbox_uids[mailboxid] = uids
                uids.append(uid)
            # filter out unnecessary mailboxes
            mailboxes = [m for m in mailboxes if m['id'] in mailbox_uids]

        for mailbox in mailboxes:
            imapname = mailbox['imapname']
            if self.selected_folder[0] != imapname:
                self.imap.select_folder(imapname, readonly=True)
                self.selected_folder = (imapname, True)

            uids = mailbox_uids.get(mailbox['id'], None)
            # uids are now None or not empty
            # fetch all
            if sort_criteria:
                if uids:
                    search = f'{",".join(map(str, uids))} {search_criteria}'
                else:
                    search = search_criteria or 'ALL'
                uids = self.imap.sort(sort_criteria, search)
            elif search_criteria:
                if uids:
                    search = f'{",".join(map(str, uids))} {search_criteria}'
                uids = self.imap.search(search)
            if uids is None:
                uids = '1:*'
            fetch_fields.add('UID')
            fetches = self.imap.fetch(uids, fetch_fields)

            for uid, data in fetches.items():
                id = format_message_id(mailbox['id'], mailbox['uidvalidity'],
                                       uid)
                msg = self.messages.get(id, None)
                if not msg:
                    msg = ImapMessage(id=id, mailboxIds=[mailbox['id']])
                    self.messages[id] = msg
                for k, v in data.items():
                    msg[k.decode()] = v
                messages.append(msg)
        return messages

    def changed_record(self, ifolderid, uid, flags=(), labels=()):
        res = self.dmaybeupdate(
            'imessages', {
                'flags': json.dumps(sorted(flags)),
                'labels': json.dumps(sorted(labels)),
            }, {
                'ifolderid': ifolderid,
                'uid': uid
            })
        if res:
            msgid = self.dgefield('imessages', {
                'ifolderid': ifolderid,
                'uid': uid
            }, 'msgid')
            self.mark_sync(msgid)

    def import_message(self, rfc822, mailboxIds, keywords):
        folderdata = self.dget('ifolders')
        foldermap = {f['ifolderid']: f for f in folderdata}
        jmailmap = {
            f['jmailboxid']: f
            for f in folderdata if f.get('jmailboxid', False)
        }
        # store to the first named folder - we can use labels on gmail to add to other folders later.
        id, others = mailboxIds
        imapname = jmailmap[id][imapname]
        flags = set(keywords)
        for kw in flags:
            if kw in KEYWORD2FLAG:
                flags.remove(kw)
                flags.add(KEYWORD2FLAG[kw])
        appendres = self.imap.append('imapname', '(' + ' '.join(flags) + ')',
                                     datetime.now(), rfc822)
        # TODO: compare appendres[2] with uidvalidity
        uid = appendres[3]
        fdata = jmailmap[mailboxIds[0]]
        self.do_folder(fdata['ifolderid'], fdata['label'])
        ifolderid = fdata['ifolderid']
        msgdata = self.dgetone('imessages', {
            'ifolderid': ifolderid,
            'uid': uid,
        }, 'msgid,thrid,size')

        # XXX - did we fail to sync this back?  Annoying
        if not msgdata:
            raise Exception(
                'Failed to get back stored message from imap server')
        # save us having to download it again - drop out of transaction so we don't wait on the parse
        message = parse.parse(rfc822, msgdata['msgid'])
        self.begin()
        self.dinsert(
            'jrawmessage', {
                'msgid': msgdata['msgid'],
                'parsed': json.dumps('message'),
                'hasAttachment': message['hasattachment'],
            })
        self.commit()
        return msgdata

    def update_messages(self, changes, idmap):
        if not changes:
            return {}, {}

        changed = {}
        notchanged = {}
        map = {}
        msgids = set(changes.keys())
        sql = 'SELECT msgid,ifolderid,uid FROM imessages WHERE msgid IN (' + (
            ('?,' * len(msgids))[:-1]) + ')'
        self.cursor.execute(sql, list(msgids))
        for msgid, ifolderid, uid in self.cursor:
            if not msgid in map:
                map[msgid] = {ifolderid: {uid}}
            elif not ifolderid in map[msgid]:
                map[msgid][ifolderid] = {uid}
            else:
                map[msgid][ifolderid].add(uid)
            msgids.discard(msgid)

        for msgid in msgids:
            notchanged[msgid] = {
                'type': 'notFound',
                'description': 'No such message on server',
            }

        folderdata = self.dget('ifolders')
        foldermap = {f['ifolderid']: f for f in folderdata}
        jmailmap = {
            f['jmailboxid']: f
            for f in folderdata if 'jmailboxid' in f
        }
        jmapdata = self.dget('jmailboxes')
        jidmap = {d['jmailboxid']: (d['role'] or '') for d in jmapdata}
        jrolemap = {
            d['role']: d['jmailboxid']
            for d in jmapdata if 'role' in d
        }

        for msgid in map.keys():
            action = changes[msgid]
            try:
                for ifolderid, uids in map[msgid].items():
                    # TODO: merge similar actions?
                    imapname = foldermap[ifolderid]['imapname']
                    uidvalidity = foldermap[ifolderid]['uidvalidity']
                    if self.selected_folder != (imapname, False):
                        self.imap.select_folder(imapname)
                        self.selected_folder = (imapname, False)
                    if imapname and uidvalidity and 'keywords' in action:
                        flags = set(action['keywords'])
                        for kw in flags:
                            if kw in KEYWORD2FLAG:
                                flags.remove(kw)
                                flags.add(KEYWORD2FLAG[kw])
                        self.imap.set_flags(uids, flags, silent=True)

                if 'mailboxIds' in action:
                    mboxes = [idmap(k) for k in action['mailboxIds'].keys()]
                    # existing ifolderids containing this message
                    # identify a source message to work from
                    ifolderid = sorted(map[msgid])[0]
                    uid = sorted(map[msgid][ifolderid])[0]
                    imapname = foldermap[ifolderid]['imapname']
                    uidvalidity = foldermap[ifolderid]['uidvalidity']

                    # existing ifolderids with this message
                    current = set(map[msgid].keys())
                    # new ifolderids that should contain this message
                    new = set(jmailmap[x]['ifolderid'] for x in mboxes)
                    for ifolderid in new:
                        # unless there's already a matching message in it
                        if current.pop(ifolderid):
                            continue
                        # copy from the existing message
                        newfolder = foldermap[ifolderid]['imapname']
                        self.imap.copy(imapname, uidvalidity, uid, newfolder)
                    for ifolderid in current:
                        # these ifolderids didn't exist in new, so delete all matching UIDs from these folders
                        self.imap.move(
                            foldermap[ifolderid]['imapname'],
                            foldermap[ifolderid]['uidvalidity'],
                            map[msgid][ifolderid],  # uids
                        )
            except Exception as e:
                notchanged[msgid] = {'type': 'error', 'description': str(e)}
                raise e
            else:
                changed[msgid] = None

        return changed, notchanged

    def destroy_messages(self, ids):
        if not ids:
            return [], {}
        destroymap = defaultdict(dict)
        notdestroyed = {}
        idset = set(ids)
        rows = self.dget('imessages', {'msgid': ('IN', idset)},
                         'msgid,ifolderid,uid')
        for msgid, ifolderid, uid in rows:
            idset.discard(msgid)
            destroymap[ifolderid][uid] = msgid
        for msgid in idset:
            notdestroyed[msgid] = {
                'type': 'notFound',
                'description': "No such message on server",
            }

        folderdata = self.dget('ifolders')
        foldermap = {d['ifolderid']: d for d in folderdata}
        jmailmap = {
            d['jmailboxid']: d
            for d in folderdata if 'jmailboxid' in d
        }
        destroyed = []
        for ifolderid, ifolder in destroymap.items():
            #TODO: merge similar actions?
            if not ifolder['imapname']:
                for msgid in destroymap[ifolderid]:
                    notdestroyed[msgid] = \
                        {'type': 'notFound', 'description': "No folder"}
            self.imap.move(ifolder['imapname'], ifolder['uidvalidity'],
                           destroymap[ifolderid].keys(), None)
            destroyed.extend(destroymap[ifolderid].values())

        return destroyed, notdestroyed

    def deleted_record(self, ifolderid, uid):
        msgid = self.dgetfield('imessages', {
            'ifolderid': ifolderid,
            'uid': uid
        }, 'msgid')
        if msgid:
            self.ddelete('imessages', {'ifolderid': ifolderid, 'uid': uid})
            self.mark_sync(msgid)

    def get_raw_message(self, msgid, part=None):
        self.cursor.execute(
            'SELECT imapname,uidvalidity,uid FROM ifolders JOIN imessages USING (ifolderid) WHERE msgid=?',
            [msgid])
        imapname, uidvalidity, uid = self.cursor.fetchone()
        if not imapname:
            return None
        typ = 'message/rfc822'
        if part:
            parsed = self.fill_messages([msgid])
            typ = find_type(parsed[msgid], part)

        res = self.imap.getpart(imapname, uidvalidity, uid, part)
        return typ, res['data']

    def get_mailboxes(self, fields=None, **criteria):
        byimapname = {}
        # TODO: LIST "" % RETURN (STATUS (UNSEEN MESSAGES HIGHESTMODSEQ MAILBOXID))
        for flags, sep, imapname in self.imap.list_folders():
            status = self.imap.folder_status(imapname, ([
                'MESSAGES', 'UIDVALIDITY', 'UIDNEXT', 'HIGHESTMODSEQ', 'X-GUID'
            ]))
            flags = [f.lower() for f in flags]
            roles = [f for f in flags if f not in KNOWN_SPECIALS]
            label = roles[0].decode() if roles else imapname
            role = ROLE_MAP.get(label.lower(), None)
            can_select = b'\\noselect' not in flags
            byimapname[imapname] = {
                # Dovecot can fetch X-GUID
                'id': status[b'X-GUID'].decode(),
                'parentId': None,
                'name': imapname,
                'role': role,
                'sortOrder': 2 if role else (1 if role == 'inbox' else 3),
                'isSubscribed': True,  # TODO: use LSUB
                'totalEmails': status[b'MESSAGES'],
                'unreadEmails': 0,
                'totalThreads': 0,
                'unreadThreads': 0,
                'myRights': {
                    'mayReadItems': can_select,
                    'mayAddItems': can_select,
                    'mayRemoveItems': can_select,
                    'maySetSeen': can_select,
                    'maySetKeywords': can_select,
                    'mayCreateChild': True,
                    'mayRename': False if role else True,
                    'mayDelete': False if role else True,
                    'maySubmit': can_select,
                },
                'imapname': imapname,
                'sep': sep.decode(),
                'uidvalidity': status[b'UIDVALIDITY'],
                'uidnext': status[b'UIDNEXT'],
                # Data sync properties
                'createdModSeq': status[b'UIDVALIDITY'],  # TODO: persist
                'updatedModSeq':
                status[b'UIDVALIDITY'],  # TODO: from persistent storage
                'updatedNotCountsModSeq':
                status[b'UIDVALIDITY'],  # TODO: from persistent storage
                'emailHighestModSeq': status[b'HIGHESTMODSEQ'],
                'deleted': 0,
            }

        # set name and parentId for child folders
        for imapname, mailbox in byimapname.items():
            names = imapname.rsplit(mailbox['sep'], maxsplit=1)
            if len(names) == 2:
                mailbox['parentId'] = byimapname[names[0]]['id']
                mailbox['name'] = names[1]

        # update cache
        self.mailboxes = {mbox['id']: mbox for mbox in byimapname.values()}
        return byimapname.values()

    def sync_mailboxes(self):
        self.get_mailboxes()

    def mailbox_imapname(self, parentId, name):
        parent = self.mailboxes.get(parentId, None)
        if not parent:
            raise errors.notFound('parent folder not found')
        return parent['imapname'] + parent['sep'] + name

    def create_mailbox(self,
                       name=None,
                       parentId=None,
                       isSubscribed=True,
                       **kwargs):
        if not name:
            raise errors.invalidProperties('name is required')
        imapname = self.mailbox_imapname(parentId, name)
        # TODO: parse returned MAILBOXID
        try:
            res = self.imap.create_folder(imapname)
        except IMAPClientError as e:
            desc = str(e)
            if '[ALREADYEXISTS]' in desc:
                raise errors.invalidArguments(desc)
        except Exception:
            raise errors.serverFail(res.decode())

        if not isSubscribed:
            self.imap.unsubscribe_folder(imapname)

        status = self.imap.folder_status(imapname, ['UIDVALIDITY'])
        self.sync_mailboxes()
        return f"f{status[b'UIDVALIDITY']}"

    def update_mailbox(self,
                       id,
                       name=None,
                       parentId=None,
                       isSubscribed=None,
                       sortOrder=None,
                       **update):
        mailbox = self.mailboxes.get(id, None)
        if not mailbox:
            raise errors.notFound('mailbox not found')
        imapname = mailbox['imapname']

        if (name is not None and name != mailbox['name']) or \
           (parentId is not None and parentId != mailbox['parentId']):
            if not name:
                raise errors.invalidProperties('name is required')
            newimapname = self.mailbox_imapname(parentId, name)
            res = self.imap.rename_folder(imapname, newimapname)
            if b'NO' in res or b'BAD' in res:
                raise errors.serverFail(res.encode())

        if isSubscribed is not None and isSubscribed != mailbox['isSubscribed']:
            if isSubscribed:
                res = self.imap.subscribe_folder(imapname)
            else:
                res = self.imap.unsubscribe_folder(imapname)
            if b'NO' in res or b'BAD' in res:
                raise errors.serverFail(res.encode())

        if sortOrder is not None and sortOrder != mailbox['sortOrder']:
            # TODO: update in persistent storage
            mailbox['sortOrder'] = sortOrder
        self.sync_mailboxes()

    def destroy_mailbox(self, id):
        mailbox = self.mailboxes.get(id, None)
        if not mailbox:
            raise errors.notFound('mailbox not found')
        res = self.imap.delete_folder(mailbox['imapname'])
        if b'NO' in res or b'BAD' in res:
            raise errors.serverFail(res.encode())
        mailbox['deleted'] = datetime.now().timestamp()
        self.sync_mailboxes()

    def create_submission(self, new, idmap):
        if not new:
            return {}, {}

        todo = {}
        createmap = {}
        notcreated = {}
        for cid, sub in new.items():
            msgid = idmap.get(sub['emailId'], sub['emailId'])
            if not msgid:
                notcreated[cid] = {'error': 'nos msgid provided'}
                continue
            thrid = self.dgetfield('jmessages', {
                'msgid': msgid,
                'deleted': 0
            }, 'thrid')
            if not thrid:
                notcreated[cid] = {'error': 'message does not exist'}
                continue
            id = self.dmake(
                'jsubmission', {
                    'sendat':
                    datetime.fromisoformat()(sub['sendAt']).isoformat()
                    if sub['sendAt'] else datetime.now().isoformat(),
                    'msgid':
                    msgid,
                    'thrid':
                    thrid,
                    'envelope':
                    json.dumps(sub['envelope']) if 'envelope' in sub else None,
                })
            createmap[cid] = {'id': id}
            todo[cid] = msgid
        self.commit()

        for cid, sub in todo.items():
            type, rfc822 = self.get_raw_message(todo[cid])
            self.imap.send_mail(rfc822, sub['envelope'])

        return createmap, notcreated

    def update_submission(self, changed, idmap):
        return {}, {x: 'change not supported' for x in changed.keys()}

    def destroy_submission(self, destroy):
        if not destroy:
            return [], {}
        destroyed = []
        notdestroyed = {}
        namemap = {}
        for subid in destroy:
            deleted = self.dgetfield('jsubmission', {'jsubid': subid},
                                     'deleted')
            if deleted:
                destroy.append(subid)
                self.ddelete('jsubmission', {'jsubid': subid})
            else:
                notdestroyed[subid] = {
                    'type': 'notFound',
                    'description': 'submission not found'
                }
        self.commit()
        return destroyed, notdestroyed

    def _initdb(self):
        super()._initdb()

        self.dbh.execute("""
            CREATE TABLE IF NOT EXISTS ifolders (
            ifolderid INTEGER PRIMARY KEY NOT NULL,
            jmailboxid INTEGER,
            sep TEXT NOT NULL,
            imapname TEXT NOT NULL,
            label TEXT,
            uidvalidity INTEGER,
            uidfirst INTEGER,
            uidnext INTEGER,
            highestmodseq INTEGER,
            uniqueid TEXT,
            mtime DATE NOT NULL
            )""")
        self.dbh.execute(
            "CREATE INDEX IF NOT EXISTS ifolderj ON ifolders (jmailboxid)")
        self.dbh.execute(
            "CREATE INDEX IF NOT EXISTS ifolderlabel ON ifolders (label)")

        self.dbh.execute("""
            CREATE TABLE IF NOT EXISTS imessages (
            imessageid INTEGER PRIMARY KEY NOT NULL,
            ifolderid INTEGER,
            uid INTEGER,
            internaldate DATE,
            modseq INTEGER,
            flags TEXT,
            labels TEXT,
            thrid TEXT,
            msgid TEXT,
            envelope TEXT,
            bodystructure TEXT,
            size INTEGER,
            mtime DATE NOT NULL
            )""")
        self.dbh.execute(
            "CREATE UNIQUE INDEX IF NOT EXISTS imsgfrom ON imessages (ifolderid, uid)"
        )
        self.dbh.execute(
            "CREATE INDEX IF NOT EXISTS imessageid ON imessages (msgid)")
        self.dbh.execute(
            "CREATE INDEX IF NOT EXISTS imessagethrid ON imessages (thrid)")

        self.dbh.execute("""
            CREATE TABLE IF NOT EXISTS ithread (
            messageid TEXT PRIMARY KEY,
            sortsubject TEXT,
            thrid TEXT
            )""")
        self.dbh.execute(
            "CREATE INDEX IF NOT EXISTS ithrid ON ithread (thrid)")
示例#3
0
class imap:
    def __init__(self):
        try:
            dbfile = open('.credentials', 'rb')
            db = pickle.load(dbfile)
            temp_server = db["server"]
            if (temp_server == ""):
                self.server_fail_flag = 1  # declaring that server object of imap is not initialised
                pass

            else:
                try:
                    self.server = IMAPClient(temp_server, use_uid=True)
                    self.server_fail_flag = 0
                except:
                    self.server_fail_flag = 1  # declaring that server object of imap is not initialised
                    print(
                        "Couldn't able to sign in. Please try again with valied credentials"
                    )

        except:
            self.server_fail_flag = 1  # declaring that server object of imap is not initialised
            file = open(".credentials", "w+")
            file.close()

    def login(self, id, passwd, server):
        self.server_name = server
        self.email = id
        self.passwd = passwd
        try:
            if (self.server_fail_flag == 1):
                self.server = IMAPClient(self.server_name, use_uid=True)
            self.server.login(self.email, self.passwd)
            return 1
            print("Login successful")
        except:
            return 0

    def logout(self):
        print("Logged out")
        self.server.logout()

    def list_folders(self):
        print("LIST OF FOLDERS")
        folders = self.server.list_folders()
        for i in folders:
            print(i[-1])

        return folders

        print("")
        print(server.get_gmail_labels(messages))

    def createFolder(self, folderName):
        self.server.create_folder(folderName)
        print("Folder named - %s - is created successfully..." % folderName)

    def deleteFolder(self, folderName):
        self.server.delete_folder(folderName)

    def deleteEmail(self, messages):
        self.server.delete_messages(messages)

    def list_mails(self, folder_name):
        select_info = self.server.select_folder(folder_name)
        print('%d messages in %s' % (select_info[b'EXISTS'], folder_name))

        # messages = server.search(['FROM', '*****@*****.**'])
        messages = self.server.search(['ALL'])
        # messages = self.server.sort(['ARRIVAL'])
        print("%s messages from %s" % (len(messages), folder_name))

        fetched_msgs = self.server.fetch(messages, ['ENVELOPE']).items()

        return fetched_msgs

    def search_mails(self, catagory, parameter):
        if (catagory in ["FROM", "TEXT", "SINCE", "SUBJECT"]):
            print([catagory, parameter])
            messages = self.server.search([catagory, parameter])
            print("%s messages from Search catagory -> %s" %
                  (len(messages), catagory))

            fetched_msgs = self.server.fetch(messages, ['ENVELOPE']).items()
            print("fetched Messages are ", fetched_msgs)
            return fetched_msgs

        else:
            print([catagory])
            messages = self.server.search([catagory])
            print(messages)
            print("%s messages from Search catagory -> %s" %
                  (len(messages), catagory))

            fetched_msgs = self.server.fetch(messages, ['ENVELOPE']).items()
            print("fetched Messages are ", fetched_msgs)
            return fetched_msgs

    def fetch_email_content(self, msg_id):
        for msgid, data in self.server.fetch(msg_id, 'RFC822').items():
            # print("\nMESSAGE", msgid)
            # print("\nDATA IS => \n", data)
            # envelope = data[b'ENVELOPE']
            msg = email.message_from_bytes(data[b'RFC822'])
            # print(msgid, email_message.get('From'), email_message.get('Subject'))
            # print(email_message.get('To'), email_message.get('body'))
            subject = msg.get('Subject')
            if msg.is_multipart():
                # iterate over email parts
                for part in msg.walk():
                    # extract content type of email
                    content_type = part.get_content_type()
                    content_disposition = str(part.get("Content-Disposition"))
                    try:
                        # get the email body
                        body = part.get_payload(decode=True).decode()
                    except:
                        pass
                    if content_type == "text/plain" and "attachment" not in content_disposition:
                        # print text/plain emails and skip attachments
                        print(body)
                    elif "attachment" in content_disposition:
                        # download attachment
                        filename = part.get_filename()
                        if filename:
                            if not os.path.isdir(subject):
                                # make a folder for this email (named after the subject)
                                os.mkdir(subject)
                            filepath = os.path.join(subject, filename)
                            # download attachment and save it
                            open(filepath,
                                 "wb").write(part.get_payload(decode=True))
            else:
                # extract content type of email
                content_type = msg.get_content_type()
                # get the email body
                body = msg.get_payload(decode=True).decode()
                if content_type == "text/plain":
                    # print only text email parts
                    print(body)

            if content_type == "text/html":
                # if it's HTML, create a new HTML file and open it in browser
                if not os.path.isdir(subject):
                    # make a folder for this email (named after the subject)
                    os.mkdir(subject)
                filename = "{}.html".format(subject[:50])
                filepath = os.path.join(subject, filename)
                # write the file
                open(filepath, "w").write(body)

            # some data about mail
            to = msg.get("To")
            mail_from = msg.get("From")
            mail_date = msg.get("Date")
            mail_subject = msg.get("Subject")
            print("To   =>", to)
            print("From =>", mail_from)
            print("Date =>", mail_date)
            print("=" * 100)

            # returning the useful values
            return (filepath, body, to, mail_from, mail_date, mail_subject)
示例#4
0
class gmailPy(object):
    def __init__(self):
        self.IMAP_SERVER = 'imap.gmail.com'
        self.ssl = True
        self.myIMAPc = None
        self.response = None
        self.folders = []

    def login(self, username, password):
        self.myIMAPc = IMAPClient(self.IMAP_SERVER, ssl=self.ssl)
        self.myIMAPc.login(username, password)

    # Returns a list of all the folders for a particular account
    def get_folders(self):
        self.response = self.myIMAPc.list_folders()
        for item in self.response:
            self.folders.append(item[2].strip('u'))
        return self.folders

    # Returns the total number of messages in a folder
    def get_mail_count(self, folder='Inbox'):
        self.response = self.myIMAPc.select_folder(folder, True)
        return self.response['EXISTS']

    # Method to delete messages based on their size
    def delete_bigmail(self, folder='Inbox'):
        self.myIMAPc.select_folder(folder, False)
        # Gets all the message ids of the messages which are not deleted in the folder
        messages = self.myIMAPc.search(['NOT DELETED'])
        print "%d messages that aren't deleted" % len(messages)
        if len(messages) > 0:
            print "You can exit by entering 0 or pressing CTRL+C \n"
        else: print "There are no messages in the folder"
        # Gets the message sizes for all the message ids returned in previous step
        # Note: Just sends one request for all message ids with a return time < 10 ms
        self.response = self.myIMAPc.fetch(messages, ['RFC822.SIZE'])
        # Sorts the dictionary returned by fetch by size in descending order
        sorted_response = sorted(self.response.iteritems(), key=operator.itemgetter(1), reverse=True)
        count = 1
        try:
            for item in sorted_response:
                # Gets the biggest message including headers, body, etc.
                big_message = self.myIMAPc.fetch(item[0], ['RFC822'])
                for msgid, data in big_message.iteritems():
                    msg_string = data['RFC822']
                    # Parses the message string using email library
                    msg = email.message_from_string(msg_string)
                    val = dict(self.response[msgid])['RFC822.SIZE']
                    print 'ID %d: From: %s Date: %s' % (msgid, msg['From'], msg['date'])
                    print 'To: %s' % (msg['To'])
                    print 'Subject: %s' % (msg['Subject'])
                    print 'Size: %d bytes \n' % (val)
                    user_del = raw_input("Do you want to delete this message?(Y/N): ")
                    if user_del == 'Y':
                        self.delete_message(msgid)
                        if count == len(sorted_response):
                            print "There are no more messages"
                        else:
                            print "\nMoving on to the next biggest message >>> \n"
                    elif user_del == '0':
                        print "Program exiting"
                        sys.exit()
                    else:
                        if count == len(sorted_response):
                            print "There are no more messages"
                        else:
                            print "\nMoving on to the next biggest message >>> \n"
                    count += 1
        except KeyboardInterrupt:
            print "Program exiting"
            sys.exit()

    # Method to delete messages based on their size with a search criteria
    def delete_bigmail_search(self, folder='Inbox', command='', criteria=''):
        self.myIMAPc.select_folder(folder, False)
        # Gets all the message ids from the server based on the search criteria
        messages = self.myIMAPc.search('%s "%s"' % (command, criteria))
        print "%d messages that match --> %s: %s" % (len(messages), command, criteria)
        if len(messages) > 0:
            print "You can exit by entering 0 or pressing CTRL+C \n"
        else: print "There are no messages in that matched your search criteria"
        # Gets the message sizes for all the message ids returned in previous step
        # Note: Just sends one request for all message ids with a return time < 10 ms
        self.response = self.myIMAPc.fetch(messages, ['RFC822.SIZE'])
        # Sorts the messages in decending order of their sizes
        sorted_response = sorted(self.response.iteritems(), key=operator.itemgetter(1), reverse=True)
        count = 1
        try:
            for item in sorted_response:
                # Gets the entire content for the biggest message identified
                big_message = self.myIMAPc.fetch(item[0], ['RFC822'])
                for msgid, data in big_message.iteritems():
                    msg_string = data['RFC822']
                    msg = email.message_from_string(msg_string)
                    val = dict(self.response[msgid])['RFC822.SIZE']
                    print 'ID %d: From: %s Date: %s' % (msgid, msg['From'], msg['date'])
                    print 'To: %s' % (msg['To'])
                    print 'Subject: %s' % (msg['Subject'])
                    print 'Size: %d bytes \n' % (val)
                    user_del = raw_input("Do you want to delete this message?(Y/N): ")
                    if user_del == 'Y':
                        self.delete_message(msgid)
                        if count == len(sorted_response):
                            print "There are no more messages"
                        else:
                            print "\nMoving on to the next biggest message >>> \n"
                    elif user_del == '0':
                        print "Program exiting"
                        sys.exit()
                    else:
                        if count == len(sorted_response):
                            print "There are no more messages"
                        else:
                            print "\nMoving on to the next biggest message >>> \n"
                    count += 1

        except KeyboardInterrupt:
            print "Program exiting"
            sys.exit()

    # Deletes a message in the current folder based on msg id
    def delete_message(self, id):
        try:
            self.myIMAPc.delete_messages([id])
            self.myIMAPc.expunge()
            print "Message deleted"
        except IMAPClient.Error as err:
            print "Message deletion failed"
            print err

    # Renames a folder
    def rename_folder(self, oldfolder, newfolder):
        try:
            self.myIMAPc.rename_folder(oldfolder, newfolder)
            print "Folder %s renamed to %s" % (oldfolder, newfolder)
        except IMAPClient.Error as err:
            print "Folder renaming failed"
            print err

    # Creates a new folder
    def create_folder(self, folder):
        try:
            self.myIMAPc.create_folder(folder)
            print "New folder %s created" % folder
        except IMAPClient.Error as err:
            print "Folder creation failed"
            print err

    # Deletes a folder
    def delete_folder(self, folder):
        try:
            self.myIMAPc.delete_folder(folder)
            print "Folder %s deleted" % folder
        except IMAPClient.Error as err:
            print "Folder deletion failed"
            print err

    # Creates a new folder and copies the content from the two folders that need to be merged
    # Then deletes the old folders
    def merge_folders(self, merged_folder, folder_1, folder_2):
        try:
            self.create_folder(merged_folder)
            # Selects the folder with read/write permission
            self.myIMAPc.select_folder(folder_1, True)
            messages = self.myIMAPc.search(['NOT DELETED'])
            print "Moving %d messages from %s to %s" % (len(messages), folder_1, merged_folder)
            self.myIMAPc.copy(messages, merged_folder)
            self.myIMAPc.select_folder(folder_2, True)
            messages = self.myIMAPc.search(['NOT DELETED'])
            print "Moving %d messages from %s to %s" % (len(messages), folder_2, merged_folder)
            self.myIMAPc.copy(messages, merged_folder)
            print "Deleting %s and %s..." % (folder_1, folder_2)
            self.delete_folder(folder_1)
            self.delete_folder(folder_2)
            print "Merge folder operation succeeded"
        except IMAPClient.Error as err:
            print "Merge operation failed"
            print err

    def logout(self):
        self.myIMAPc.logout()