6 from urllib
import FancyURLopener
7 from optparse
import OptionParser
9 from xml
.etree
import ElementTree
as ET
11 class TooManyPasswordsError(BaseException
):
12 def __init__(self
, message
=None):
13 BaseException
.__init
__(self
, message
)
15 class UnauthorizedAccessError(BaseException
):
16 def __init__(self
, message
=None):
17 BaseException
.__init
__(self
, message
)
19 class FancyURLopenerMod(FancyURLopener
):
21 This overrides the default prompt_user_password, allowing for username and
22 password to be passed from the command line.
24 def __init__(self
, *args
, **kwargs
):
25 try: self
.username
= kwargs
['username']
26 except KeyError: self
.username
= None
27 try: self
.password
= kwargs
['password']
28 except KeyError: self
.password
= None
30 # once urllib uses new style classes, or in python 3.0+, use:
31 # super(FancyURLopenerMod, self).__init__(*args, **kwargs)
32 # till then this will work, but not in python 3.0+:
33 FancyURLopener
.__init
__(self
, *args
, **kwargs
)
35 # only try opening the account once
37 #self.maxauthtries = 3
41 def prompt_user_passwd(self
,host
,realm
):
43 overridden method of FancyURLopener, allowing for command line arguments
45 if self
.flag
: return None, None
49 #if self.maxauthtries and self.authtries > self.maxauthtries:
50 # #TODO print a final 'giving up on %username' message
51 # return TooManyPasswordsError
55 if self
.username
is not None: user
= self
.username
56 else: user
= raw_input("Enter username for %s at %s: " % (realm
, host
))
57 if self
.password
is not None: passwd
= self
.password
58 else: passwd
= getpass
.getpass("Enter password for %s in %s at %s: " %
61 except KeyboardInterrupt:
67 def __init__(self
, timezone
=(0,0)):
68 self
.timezone
= timezone
70 'entry' : "{http://purl.org/atom/ns#}entry",
71 'fullcount' : "{http://purl.org/atom/ns#}fullcount",
73 'title' : "{http://purl.org/atom/ns#}title",
74 'summary' : "{http://purl.org/atom/ns#}summary",
75 'link' : "{http://purl.org/atom/ns#}link",
76 'modified' : "{http://purl.org/atom/ns#}modified",
77 'issued' : "{http://purl.org/atom/ns#}issued",
78 'id' : "{http://purl.org/atom/ns#}id",
79 'name' : "{http://purl.org/atom/ns#}author/{http://purl.org/atom/ns#}name",
80 'email' : "{http://purl.org/atom/ns#}author/{http://purl.org/atom/ns#}email"
83 def open_feed(self
, username
=None, password
=None):
85 uses gmail's atom feed to retrieve a summary of the inbox, returns an XML
88 FancyURLopenerMod is simply urllib.FancyURLopener with a modified
89 prompt_user_password in order to accept command line arguments
91 url
= 'https://mail.google.com/mail/feed/atom/'
92 opener
= FancyURLopenerMod(username
= username
, password
= password
)
96 self
.tree
= ET
.fromstring(feed
)
97 if self
.tree
.tag
== 'HTML':
98 #TODO make sure that unauthorized is in the page title
99 raise UnauthorizedAccessError
103 calculates the timezone offsetin seconds, and the value of 1 day in seconds
105 # negative_day is used later in order to check whether the email came
106 # 'Today','Yesterday', or 'The day before'
107 timezone_tuple
= (int(self
.timezone
[:-2]), int(self
.timezone
[-2:]))
108 self
.TZ
= (timezone_tuple
[0]*60 + timezone_tuple
[1])*60
109 self
.negative_day
= (-24)*60*60
110 assert self
.negative_day
< 0
112 def _sanitize_datetime(self
, datestring
):
114 accepts the datestring as obtained from the atom feed and returns the time
115 in your timezone and the date as one of 'Today','Yesterday', 'The day
116 before' or the date itself (eg. January 1, 2011)
120 # the date in the feed is in the format 2011-11-20T05:13:59Z
121 emaildate
= datestring
.split('T')[0]
122 emailtime
= datestring
.split('T')[1].split('Z')[0]
125 datetime_str
.extend([int(x
) for x
in emaildate
.split('-')])
126 datetime_str
.extend([int(x
) for x
in emailtime
.split(':')])
128 email_datetime
= datetime
.datetime(*datetime_str
)
129 timezone_correction
= datetime
.timedelta(0,self
.TZ
)
130 email_datetime
+= timezone_correction
132 # calculate today, yesterday and the day before using the seconds value of 1 day
133 today
= datetime
.datetime
.today()
134 yesterday
= datetime
.datetime
.today() \
135 + datetime
.timedelta(0,self
.negative_day
)
136 daybefore
= datetime
.datetime
.today() \
137 + datetime
.timedelta(0,self
.negative_day
) \
138 + datetime
.timedelta(0,self
.negative_day
)
140 # %F displays the full date as %Y-%m-%d
141 email_datetime_date
= email_datetime
.strftime('%F')
142 today_date
= today
.strftime('%F')
143 yesterday_date
= yesterday
.strftime('%F')
144 daybefore_date
= daybefore
.strftime('%F')
146 if email_datetime_date
== today_date
: sanitized_date
= 'Today'
147 elif email_datetime_date
== yesterday_date
: sanitized_date
= 'Yesterday'
148 elif email_datetime_date
== daybefore_date
: sanitized_date
= 'Day before yesterday'
149 else: sanitized_date
= email_datetime
.strftime('%B %d, %Y')
151 sanitized_time
= email_datetime
.strftime('%l:%M %P')
152 return sanitized_date
, sanitized_time
154 def printmail(self
, summary
=False, printcount
=10, printall
=False):
156 Returns the number of emails retrieved
158 print '\n', self
.tree
.find(self
.xml_tags
['title']).text
, '\n'
160 count
= int(self
.tree
.find(self
.xml_tags
['fullcount']).text
)
162 print 'No new messages!'
165 printcount
= int(printcount
)
166 if printall
: printcount
= -1
168 for n
, k
in enumerate(self
.tree
.findall(self
.tree
[-1].tag
)):
169 if printcount
== 0: break
171 sdate
, stime
= self
._sanitize
_datetime
(k
.find(self
.xml_tags
['issued']).text
)
172 print '%s. %s at %s: %s <%s> wrote "%s"' % (n
+1, sdate
,stime
,
173 k
.find(self
.xml_tags
['name']).text
,
174 k
.find(self
.xml_tags
['email']).text
,
175 k
.find(self
.xml_tags
['title']).text
)
176 if summary
: print '\tText: %s' % (k
.find(self
.xml_tags
['summary']).text
)
181 def parse_cmd_line():
183 usage
= '%prog [options] [username1] [username2] ...'
184 parser
= OptionParser(usage
=usage
)
185 parser
.add_option("-u", "--username", dest
="username",
186 help="your gmail username. use this if you also want to specify the password on the command line. otherwise simply pass the usernames as arguments")
187 parser
.add_option("-p", "--password", dest
="password", help="your gmail password")
188 parser
.add_option("-t", "--timezone", dest
="timezone", default
="+0530", help="specify your timezone in +/-HHMM format [default: %default]")
189 parser
.add_option("-n", dest
="printcount", default
=10,
190 help="print 'n' messages")
191 parser
.add_option("-s", "--summary", action
="store_true", dest
="summary",
192 default
=False, help="prints the summary text that is generally visible in your gmail inbox [default: %default]")
193 parser
.add_option("-a", "--all", action
="store_true", dest
="printall",
194 default
=False, help="prints all messages in your inbox")
196 (options
,args
) = parser
.parse_args(sys
.argv
[1:])
202 options
, args
= parse_cmd_line()
204 try: int(options
.printcount
)
206 print 'The parameter to -n needs to be a number'
209 # accounts is a list of (username, password) tuples. if username/password
210 # flags are provided they are added to the list before adding username/password
211 # obtained from the args list. all accounts thus obtained are worked on in one go
214 # verifing that username and password flags are both provided, or neither are
215 if options
.username
or options
.password
:
217 assert options
.username
218 assert options
.password
219 except AssertionError:
220 print 'The --username flag and the --password flag MUST be used in \
221 conjunction or avoided altogether'
223 accounts
= [(options
.username
, options
.password
)]
226 for username
in args
:
227 accounts
.append((username
, getpass
.getpass('Enter password for account %s: ' % username
)))
230 for username
, password
in accounts
:
231 mailchecker
= GMail(timezone
=options
.timezone
)
232 try: mailchecker
.open_feed(username
=username
, password
= password
)
233 except UnauthorizedAccessError
:
234 print 'Incorrect password for account %s, ignoring.' % username
236 #TODO implement a retry mechanism for incorrect password
238 #print 'Incorrect password for %s, retrying: ' % username
239 #try: mailchecker.open_feed(username=username, password = getpass.getpass('Incorrect password for %s, retrying: ' % username))
240 #except TooManyPasswordsError:
241 # print 'Invalid password entered too many times, giving up.'
243 # count.append(mailchecker.printmail(options.summary, options.printcount, options.printall))
245 count
.append(mailchecker
.printmail(options
.summary
, options
.printcount
, options
.printall
))
247 print '\n%s%s messages recieved' % (reduce(lambda x
, y
: x
+y
, count
) if count
else 'No',
248 ' ( %s )' % ' + '.join([str(i
) for i
in count
]) if len(count
)>1 else '' )
251 if __name__
== '__main__':