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."