📄 libgmail.py
字号:
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 __init__(self): self._labels = None 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): """ """ _LabelHandlerMixin.__init__(self) # 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 resultclass 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): """ """ _LabelHandlerMixin.__init__(self) 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].decode('utf-8') self.author_fullname = msgData[MI_AUTHORNAME].decode('utf-8') self.id = msgData[MI_MSGID] self.number = msgData[MI_NUM] self.subject = msgData[MI_SUBJECT].decode('utf-8') self.to = [x.decode('utf-8') for x in msgData[MI_TO]] self.cc = [x.decode('utf-8') for x in msgData[MI_CC]] self.bcc = [x.decode('utf-8') for x in msgData[MI_BCC]] self.sender = msgData[MI_AUTHOREMAIL].decode('utf-8') # Messages created by google chat (from reply with chat, etc.) # don't have any attachments, so we need this check not to choke # on them try: self.attachments = [GmailAttachment(self, attachmentInfo) for attachmentInfo in msgData[MI_ATTACHINFO]] except TypeError: self.attachments = [] # 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.decode('utf-8') 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 = filesif __name__ == "__main__": import sys from getpass import getpass try: name = sys.argv[1] except IndexError: name = raw_input("Gmail account name: ") pw = getpass("Password: ") domain = raw_input("Domain? [leave blank for Gmail]: ") ga = GmailAccount(name, pw, domain=domain) 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."
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -