All pastes #576617 Raw Edit

Miscellany

public text v1 · immutable
·published 2007-06-19 15:33 UTC
rendered paste body
        REMEMBER: ID IS A STRING

        Returns False if no contact
        could be found
        """
        idList = self.getContactListById(myId)
        if len(idList) > 0:
            return idList[0]
        else:
            return False
    def getContactListByName(self, name):
        """
        This function returns a LIST
        of GmailContacts whose name is
        'name'. 

        Returns an empty list if no contacts
        were found
        """
        nameList = []
        for entry in self.contactList:
            if entry.getName() == name:
                nameList.append(entry)
        return nameList
    def getContactListByEmail(self, email):
        """
        This function returns a LIST
        of GmailContacts whose email is
        'email'. As of this writing, two contacts
        cannot share an email address, so this
        should only return just one item.
        But it doesn't hurt to be prepared?

        Returns an empty list if no contacts
        were found
        """
        emailList = []
        for entry in self.contactList:
            if entry.getEmail() == email:
                emailList.append(entry)
        return emailList
    def getContactListById(self, myId):
        """
        This function returns a LIST
        of GmailContacts whose id is
        'myId'. We expect there only to
        be one, but just in case!

        Remember: ID IS A STRING

        Returns an empty list if no contacts
        were found
        """
        idList = []
        for entry in self.contactList:
            if entry.getId() == myId:
                idList.append(entry)
        return idList
    
class GmailSearchResult:
    """
    """

    def __init__(self, account, search, threadsInfo):
        """

        `threadsInfo` -- As returned from Gmail but unbunched.
        """
        #print "\nthreadsInfo\n",threadsInfo
        try:
            if not type(threadsInfo[0]) is types.ListType:
                threadsInfo = [threadsInfo]
        except IndexError:
            print "No messages found"
            
        self._account = account
        self.search = search # TODO: Turn into object + format nicely.
        self._threads = []
        
        for thread in threadsInfo:
            self._threads.append(GmailThread(self, thread[0]))


    def __iter__(self):
        """
        """
        return iter(self._threads)

    def __len__(self):
        """
        """
        return len(self._threads)

    def __getitem__(self,key):
        """
        """
        return self._threads.__getitem__(key)


class GmailSessionState:
    """
    """

    def __init__(self, account = None, filename = ""):
        """
        """
        if account:
            self.state = (account.name, account._cookieJar)
        elif filename:
            self.state = load(open(filename, "rb"))
        else:
            raise ValueError("GmailSessionState must be instantiated with " \
                             "either GmailAccount object or filename.")


    def save(self, filename):
        """
        """
        dump(self.state, open(filename, "wb"), -1)


class _LabelHandlerMixin(object):
    """

    Note: Because a message id can be used as a thread id this works for
          messages as well as threads.
    """
    def _makeLabelList(self, labelList):
        self._labels = labelList
    
    def addLabel(self, labelName):
        """
        """
        # Note: It appears this also automatically creates new labels.
        result = self._account._doThreadAction(U_ADDCATEGORY_ACTION+labelName,
                                               self)
        if not self._labels:
            self._makeLabelList([])
        # TODO: Caching this seems a little dangerous; suppress duplicates maybe?
        self._labels.append(labelName)
        return result


    def removeLabel(self, labelName):
        """
        """
        # TODO: Check label is already attached?
        # Note: An error is not generated if the label is not already attached.
        result = \
               self._account._doThreadAction(U_REMOVECATEGORY_ACTION+labelName,
                                             self)
        
        removeLabel = True
        try:
            self._labels.remove(labelName)
        except:
            removeLabel = False
            pass
    
        # If we don't check both, we might end up in some weird inconsistent state
        return result and removeLabel

    def getLabels(self):
        return self._labels
    


class GmailThread(_LabelHandlerMixin):
    """
    Note: As far as I can tell, the "canonical" thread id is always the same
          as the id of the last message in the thread. But it appears that
          the id of any message in the thread can be used to retrieve
          the thread information.
    
    """

    def __init__(self, parent, threadsInfo):
        """
        """
        
        # TODO Handle this better?
        self._parent = parent
        self._account = self._parent._account
        
        self.id = threadsInfo[T_THREADID] # TODO: Change when canonical updated?
        self.subject = threadsInfo[T_SUBJECT_HTML]

        self.snippet = threadsInfo[T_SNIPPET_HTML]
        #self.extraSummary = threadInfo[T_EXTRA_SNIPPET] #TODO: What is this?

        # TODO: Store other info?
        # Extract number of messages in thread/conversation.

        self._authors = threadsInfo[T_AUTHORS_HTML]
        self.info = threadsInfo
    
        try:
            # TODO: Find out if this information can be found another way...
            #       (Without another page request.)
            self._length = int(re.search("\((\d+?)\)\Z",
                                         self._authors).group(1))
        except AttributeError,info:
            # If there's no message count then the thread only has one message.
            self._length = 1

        # TODO: Store information known about the last message  (e.g. id)?
        self._messages = []

        # Populate labels
        self._makeLabelList(threadsInfo[T_CATEGORIES])

    def __getattr__(self, name):
        """
        Dynamically dispatch some interesting thread properties.
        """
        attrs = { 'unread': T_UNREAD,
                  'star': T_STAR,
                  'date': T_DATE_HTML,
                  'authors': T_AUTHORS_HTML,
                  'flags': T_FLAGS,
                  'subject': T_SUBJECT_HTML,
                  'snippet': T_SNIPPET_HTML,
                  'categories': T_CATEGORIES,
                  'attach': T_ATTACH_HTML,
                  'matching_msgid': T_MATCHING_MSGID,
                  'extra_snippet': T_EXTRA_SNIPPET }
        if name in attrs:
            return self.info[ attrs[name] ];

        raise AttributeError("no attribute %s" % name)
        
    def __len__(self):
        """
        """
        return self._length


    def __iter__(self):
        """
        """
        if not self._messages:
            self._messages = self._getMessages(self)
            
        return iter(self._messages)

    def __getitem__(self, key):
        """
        """
        if not self._messages:
            self._messages = self._getMessages(self)
        try:
            result = self._messages.__getitem__(key)
        except IndexError:
            result = []
        return result

    def _getMessages(self, thread):
        """
        """
        # TODO: Do this better.
        # TODO: Specify the query folder using our specific search?
        items = self._account._parseSearchResult(U_QUERY_SEARCH,
                                                 view = U_CONVERSATION_VIEW,
                                                 th = thread.id,
                                                 q = "in:anywhere")
        result = []
        # TODO: Handle this better?
        # Note: This handles both draft & non-draft messages in a thread...
        for key, isDraft in [(D_MSGINFO, False), (D_DRAFTINFO, True)]:
            try:
                msgsInfo = items[key]
            except KeyError:
                # No messages of this type (e.g. draft or non-draft)
                continue
            else:
                # TODO: Handle special case of only 1 message in thread better?
                if type(msgsInfo[0]) != types.ListType:
                    msgsInfo = [msgsInfo]
                for msg in msgsInfo:
                    result += [GmailMessage(thread, msg, isDraft = isDraft)]
                           

        return result

class GmailMessageStub(_LabelHandlerMixin):
    """

    Intended to be used where not all message information is known/required.

    NOTE: This may go away.
    """

    # TODO: Provide way to convert this to a full `GmailMessage` instance
    #       or allow `GmailMessage` to be created without all info?

    def __init__(self, id = None, _account = None):
        """
        """
        self.id = id
        self._account = _account
    

        
class GmailMessage(object):
    """
    """
    
    def __init__(self, parent, msgData, isDraft = False):
        """

        Note: `msgData` can be from either D_MSGINFO or D_DRAFTINFO.
        """
        # TODO: Automatically detect if it's a draft or not?
        # TODO Handle this better?
        self._parent = parent
        self._account = self._parent._account
        
        self.author = msgData[MI_AUTHORFIRSTNAME]
        self.id = msgData[MI_MSGID]
        self.number = msgData[MI_NUM]
        self.subject = msgData[MI_SUBJECT]
        self.to = msgData[MI_TO]
        self.cc = msgData[MI_CC]
        self.bcc = msgData[MI_BCC]
        self.sender = msgData[MI_AUTHOREMAIL]
        
        self.attachments = [GmailAttachment(self, attachmentInfo)
                            for attachmentInfo in msgData[MI_ATTACHINFO]]

        # TODO: Populate additional fields & cache...(?)

        # TODO: Handle body differently if it's from a draft?
        self.isDraft = isDraft
        
        self._source = None


    def _getSource(self):
        """
        """
        if not self._source:
            # TODO: Do this more nicely...?
            # TODO: Strip initial white space & fix up last line ending
            #       to make it legal as per RFC?
            self._source = self._account.getRawMessage(self.id)

        return self._source

    source = property(_getSource, doc = "")
        


class GmailAttachment:
    """
    """

    def __init__(self, parent, attachmentInfo):
        """
        """
        # TODO Handle this better?
        self._parent = parent
        self._account = self._parent._account

        self.id = attachmentInfo[A_ID]
        self.filename = attachmentInfo[A_FILENAME]
        self.mimetype = attachmentInfo[A_MIMETYPE]
        self.filesize = attachmentInfo[A_FILESIZE]

        self._content = None


    def _getContent(self):
        """
        """
        if not self._content:
            # TODO: Do this a more nicely...?
            self._content = self._account._retrievePage(
                _buildURL(view=U_ATTACHMENT_VIEW, disp="attd",
                          attid=self.id, th=self._parent._parent.id))
            
        return self._content

    content = property(_getContent, doc = "")


    def _getFullId(self):
        """

        Returns the "full path"/"full id" of the attachment. (Used
        to refer to the file when forwarding.)

        The id is of the form: "<thread_id>_<msg_id>_<attachment_id>"
        
        """
        return "%s_%s_%s" % (self._parent._parent.id,
                             self._parent.id,
                             self.id)

    _fullId = property(_getFullId, doc = "")



class GmailComposedMessage:
    """
    """

    def __init__(self, to, subject, body, cc = None, bcc = None,
                 filenames = None, files = None):
        """

          `filenames` - list of the file paths of the files to attach.
          `files` - list of objects implementing sub-set of
                    `email.Message.Message` interface (`get_filename`,
                    `get_content_type`, `get_payload`). This is to
                    allow use of payloads from Message instances.
                    TODO: Change this to be simpler class we define ourselves?
        """
        self.to = to
        self.subject = subject
        self.body = body
        self.cc = cc
        self.bcc = bcc
        self.filenames = filenames
        self.files = files



if __name__ == "__main__":
    import sys
    from getpass import getpass

    try:
        name = sys.argv[1]
    except IndexError:
        name = raw_input("Gmail account name: ")
        
    pw = getpass("Password: ")

    ga = GmailAccount(name, pw)

    print "\nPlease wait, logging in..."

    try:
        ga.login()
    except GmailLoginFailure,e:
        print "\nLogin failed. (%s)" % e.message
    else:
        print "Login successful.\n"

        # TODO: Use properties instead?
        quotaInfo = ga.getQuotaInfo()
        quotaMbUsed = quotaInfo[QU_SPACEUSED]
        quotaMbTotal = quotaInfo[QU_QUOTA]
        quotaPercent = quotaInfo[QU_PERCENT]
        print "%s of %s used. (%s)\n" % (quotaMbUsed, quotaMbTotal, quotaPercent)

        searches = STANDARD_FOLDERS + ga.getLabelNames()
        name = None
        while 1:
            try:
                print "Select folder or label to list: (Ctrl-C to exit)"
                for optionId, optionName in enumerate(searches):
                    print "  %d. %s" % (optionId, optionName)
                while not name:
                    try:
                        name = searches[int(raw_input("Choice: "))]
                    except ValueError,info:
                        print info
                        name = None
                if name in STANDARD_FOLDERS:
                    result = ga.getMessagesByFolder(name, True)
                else:
                    result = ga.getMessagesByLabel(name, True)
                    
                if not len(result):
                    print "No threads found in `%s`." % name
                    break
                name = None
                tot = len(result)
                
                i = 0
                for thread in result:
                    print "%s messages in thread" % len(thread)
                    print thread.id, len(thread), thread.subject
                    for msg in thread:
                        print "\n ", msg.id, msg.number, msg.author,msg.subject
                        # Just as an example of other usefull things
                        #print " ", msg.cc, msg.bcc,msg.sender
                        i += 1
                print
                print "number of threads:",tot
                print "number of messages:",i
            except KeyboardInterrupt:
                break
            
    print "\n\nDone."