· 10 years ago · Aug 06, 2016, 08:27 AM
1#!/usr/bin/python -tt
2#
3# Copyright 2011-2014 David Steele <dsteele@gmail.com>
4# This file is part of gnome-gmail
5# Available under the terms of the GNU General Public License version 2
6# or later
7#
8""" gnome-gmail
9This script accepts an argument of a mailto url, and calls up an appropriate
10GMail web page to handle the directive. It is intended to support GMail as a
11GNOME Preferred Email application """
12
13import sys
14import webbrowser
15import os
16import os.path
17import re
18import textwrap
19import locale
20import gettext
21import string
22import json
23import mimetypes
24import random
25import time
26import subprocess
27import shlex
28from contextlib import contextmanager
29
30from email import encoders
31from email.mime.audio import MIMEAudio
32from email.mime.base import MIMEBase
33from email.mime.image import MIMEImage
34from email.mime.multipart import MIMEMultipart
35from email.mime.text import MIMEText
36
37from six.moves import urllib
38from six.moves.configparser import SafeConfigParser
39
40import gi
41from gi.repository import Gio # noqa
42
43gi.require_version('Gtk', '3.0')
44from gi.repository import Gtk # noqa
45
46gi.require_version('Secret', '1')
47from gi.repository import Secret # noqa
48
49gi.require_version('Notify', '0.7')
50from gi.repository import Notify # noqa
51
52gi.require_version('Wnck', '3.0')
53from gi.repository import Wnck # noqa
54
55locale.setlocale(locale.LC_ALL, '')
56kwargs = {}
57if sys.version_info[0] > 3:
58 kwargs['unicode'] = True
59gettext.install("gnome-gmail", **kwargs)
60_ = gettext.gettext
61
62try:
63 environ = os.environ['XDG_CURRENT_DESKTOP']
64except:
65 environ = 'GNOME'
66
67config = None
68
69class GGError(Exception):
70 """ Gnome Gmail exception """
71 def __init__(self, value):
72 self.value = value
73 super(GGError, self).__init__()
74
75 def __str__(self):
76 return repr(self.value)
77
78
79@contextmanager
80def nullfd(fd):
81 saveout = os.dup(fd)
82 os.close(fd)
83 os.open(os.devnull, os.O_RDWR)
84 try:
85 yield
86 finally:
87 os.dup2(saveout, fd)
88
89
90def set_as_default_mailer():
91 if environ == 'GNOME':
92 for app in Gio.app_info_get_all_for_type("x-scheme-handler/mailto"):
93 if app.get_id() == "gnome-gmail.desktop":
94 app.set_as_default_for_type("x-scheme-handler/mailto")
95 elif environ == 'KDE':
96 cfgpath = os.path.expanduser('~/.kde/share/config/emaildefaults')
97 with open(cfgpath, 'r') as cfp:
98 cfglines = cfp.readlines()
99
100 cfglines = [x for x in cfglines if 'EmailClient' not in x]
101
102 outlines = []
103 for line in cfglines:
104 outlines.append(line)
105 if 'PROFILE_Default' in line:
106 outlines.append("EmailClient[$e]=/usr/bin/gnome-gmail %u\n")
107
108 with open(cfgpath, 'w') as cfp:
109 cfp.writelines(outlines)
110
111
112def is_default_mailer():
113 returnvalue = True
114
115 if environ == 'GNOME':
116 mailer = Gio.app_info_get_default_for_type(
117 "x-scheme-handler/mailto",
118 True
119 )
120 try:
121 returnvalue = mailer.get_id() == "gnome-gmail.desktop"
122 except AttributeError:
123 pass
124 elif environ == 'KDE':
125 cfgpath = os.path.expanduser('~/.kde/share/config/emaildefaults')
126 with open(cfgpath, 'r') as cfp:
127 returnvalue = 'gnome-gmail' in cfp.read()
128
129 return returnvalue
130
131
132def browser():
133 cmd = "xdg-settings get default-web-browser"
134 brsr_name = subprocess.check_output(
135 cmd.split(), universal_newlines=True).strip()
136
137 browser = webbrowser.get()
138
139 for candidate in webbrowser._tryorder:
140 if candidate in brsr_name:
141 browser = webbrowser.get(using=candidate)
142
143 return customize_browser(browser)
144
145
146def customize_browser(browser):
147
148 argmap = {
149 'Chrome': '--app=%s',
150 'Konqueror': '',
151 'Mozilla': '',
152 'Galeon': '',
153 'Opera': '',
154 'Grail': '',
155 }
156
157 replace_args = config.get_str('browser_options')
158
159 if replace_args:
160 browser.remote_args = shlex.split(replace_args)
161 else:
162 try:
163 std_args = argmap[type(browser).__name__]
164
165 if std_args:
166 browser.remote_args = shlex.split(std_args)
167
168 except KeyError:
169 pass
170
171 return browser
172
173
174class GMOauth():
175 """oauth mechanism per
176 https://developers.google.com/accounts/docs/OAuth2InstalledApp
177 example at
178 https://github.com/google/gmail-oauth2-tools/blob/master/python/oauth2.py
179 Eg:
180 (access, refresh) = GMOauth().generate_tokens( "user@gmail.com" )
181 """
182
183 def __init__(self):
184 self.auth_endpoint = "https://accounts.google.com/o/oauth2/auth"
185 self.token_endpoint = "https://accounts.google.com/o/oauth2/token"
186 self.scope = "https://www.googleapis.com/auth/gmail.compose"
187 self.client_id = "284739582412.apps.googleusercontent.com"
188 self.client_secret = "EVt3cQrYlI_hZIt2McsPeqSp"
189
190 def get_code(self, login_hint):
191 s = string.ascii_letters + string.digits
192 state = ''.join(random.sample(s, 10))
193
194 args = {
195 "response_type": "code",
196 "client_id": self.client_id,
197 "redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
198 "prompt": "consent",
199 "scope": self.scope,
200 "state": state,
201 "login_hint": login_hint,
202 }
203
204 code_url = "%s?%s" % (self.auth_endpoint, urllib.parse.urlencode(args))
205
206 with nullfd(1), nullfd(2):
207 browser().open(code_url, 1, True)
208
209 now = time.time()
210
211 while time.time() - now < 120:
212 Gtk.main_iteration()
213 screen = Wnck.Screen.get_default()
214 screen.force_update()
215
216 for win in screen.get_windows():
217 m = re.search("state=%s.code=([^ ]+)" % state, win.get_name())
218 if m:
219 win.close(time.time())
220
221 return m.group(1)
222
223 raise GGError(_("Timeout getting OAuth authentication"))
224
225 def get_token_dict(self, code):
226
227 args = {
228 "code": code,
229 "client_id": self.client_id,
230 "client_secret": self.client_secret,
231 "redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
232 "grant_type": "authorization_code",
233 }
234
235 try:
236 token_page = urllib.request.urlopen(
237 self.token_endpoint,
238 urllib.parse.urlencode(args).encode("utf-8"))
239 except urllib.error.HTTPError as e:
240 token_page = e
241
242 return(json.loads(token_page.read().decode("utf-8")))
243
244 def get_access_from_refresh(self, refresh_token):
245
246 args = {
247 "refresh_token": refresh_token,
248 "client_id": self.client_id,
249 "client_secret": self.client_secret,
250 "grant_type": "refresh_token",
251 }
252
253 try:
254 token_page = urllib.request.urlopen(
255 self.token_endpoint,
256 urllib.parse.urlencode(args).encode("utf-8"))
257 except urllib.error.HTTPError as e:
258 token_page = e
259 token_dict = json.loads(token_page.read().decode("utf-8"))
260
261 if "access_token" in token_dict:
262 return(token_dict["access_token"])
263 else:
264 return(None)
265
266 def generate_tokens(self, login, refresh_token=None):
267 """Generate an access token/refresh token pair for 'login' email
268 account, using an optional refresh token.
269 If refresh is not possible, the caller will be prompted for
270 authentication via an internal browser window."""
271
272 if refresh_token:
273 access_token = self.get_access_from_refresh(refresh_token)
274 if access_token:
275 return((access_token, refresh_token))
276
277 code = self.get_code(login)
278
279 token_dict = self.get_token_dict(code)
280
281 try:
282 return((token_dict["access_token"], token_dict["refresh_token"]))
283 except:
284 # todo - replace with a GG exception
285 return((None, None))
286
287 def access_iter(self, access, refresh, login):
288 if access:
289 yield (access, refresh)
290
291 if refresh:
292 yield (self.get_access_from_refresh(refresh), refresh)
293
294 yield self.generate_tokens(login)
295
296
297class GMailAPI():
298 """ Handle mailto URLs that include 'attach' fields by uploading the
299 messages using the GMail API """
300
301 def __init__(self, mail_dict):
302 self.mail_dict = mail_dict
303
304 def form_message(self):
305 """ Form an RFC822 message, with an appropriate MIME attachment """
306
307 msg = MIMEMultipart()
308
309 for header in ("To", "Cc", "Bcc", "Subject",
310 "References", "In-Reply-To"):
311 if header.lower() in self.mail_dict:
312 msg[header] = self.mail_dict[header.lower()][0]
313
314 try:
315 fname = os.path.split(self.mail_dict["attach"][0])[1]
316
317 if "subject" not in self.mail_dict:
318 msg["Subject"] = _("Sending %s") % fname
319 except KeyError:
320 pass
321
322 msg.preamble = _("Mime message attached")
323
324 try:
325 body = self.mail_dict['body'][0]
326
327 mimebody = MIMEMultipart('alternative')
328 mimebody.attach(MIMEText(body))
329 mimebody.attach(MIMEText(self.body2html(), 'html'))
330
331 msg.attach(mimebody)
332
333 except KeyError:
334 pass
335
336 try:
337 for filename in self.mail_dict['attach']:
338 attachment = self.file2mime(filename)
339 msg.attach(attachment)
340 except KeyError:
341 pass
342
343 try:
344 self.message_text = msg.as_bytes()
345 except AttributeError:
346 self.message_text = msg.as_string()
347
348 def file2mime(self, filename):
349 if(filename.find("file://") == 0):
350 filename = filename[7:]
351
352 filepath = urllib.parse.urlsplit(filename).path
353
354 if not os.path.isfile(filepath):
355 raise GGError(_("File not found - %s") % filepath)
356
357 ctype, encoding = mimetypes.guess_type(filepath)
358
359 if ctype is None or encoding is not None:
360 ctype = 'application/octet-stream'
361
362 maintype, subtype = ctype.split('/', 1)
363
364 with open(filepath, 'r' if maintype == 'text' else 'rb') as fp:
365 attach_data = fp.read()
366
367 if maintype == 'text':
368 attachment = MIMEText(attach_data, _subtype=subtype)
369 elif maintype == 'image':
370 attachment = MIMEImage(attach_data, _subtype=subtype)
371 elif maintype == 'audio':
372 attachment = MIMEAudio(attach_data, _subtype=subtype)
373 else:
374 attachment = MIMEBase(maintype, subtype)
375 attachment.set_payload(attach_data)
376 encoders.encode_base64(attachment)
377
378 attachment.add_header(
379 'Content-Disposition', 'attachment',
380 filename=os.path.split(filename)[1]
381 )
382
383 return(attachment)
384
385 def _convert_links(self, text):
386 schemes = [
387 'http', 'https', 'ftp', 'mailto',
388 'about', 'chrome', 'bitcoin', 'callto', 'file', 'geo', 'git',
389 'gtalk', 'irc', 'magnet', 'market', 'skype', 'ssh', 'webcal',
390 'xmpp',
391 ]
392 rgx = "(?P<url>(%s):[^ \),]+[^ \)\],\.'\"])" % '|'.join(schemes)
393 substr = "<a href=\"\g<url>\">\g<url></a>"
394
395 text = re.sub(rgx, substr, text)
396
397 return text
398
399 def body2html(self):
400
401 htmlbody = self.mail_dict['body'][0]
402
403# htmlbody = htmlbody.replace('&', '&')
404 htmlbody = re.sub('>', '>', htmlbody)
405 htmlbody = re.sub('<', '<', htmlbody)
406 htmlbody = re.sub('\t', ' ', htmlbody)
407
408 htmlbody = self._convert_links(htmlbody)
409
410 while " " in htmlbody:
411 htmlbody = re.sub(" ", " ", htmlbody)
412
413 while " " in htmlbody:
414 htmlbody = re.sub(" ", " ", htmlbody)
415
416 htmlbody = re.sub("\n", "<br>\n", htmlbody)
417
418 htmlhdr = "<html>\n<head>\n</head>\n<body>\n"
419 htmltail = "\n</body>\n</html>"
420 htmltext = htmlhdr + htmlbody + htmltail
421
422 return htmltext
423
424 def _has_attachment(self):
425
426 return('attach' in self.mail_dict)
427
428 def _has_body(self):
429 return('body' in self.mail_dict)
430
431 def needs_api(self):
432
433 return self._has_attachment() or self._has_body()
434
435 def send_mail(self, user, access_token):
436 """ transfer the message to GMail Drafts folder, using the GMail API.
437 Return a message ID that can be used to reference the mail via URL"""
438
439 if access_token is None:
440 raise GGError(_("Unable to authenticate with GMail"))
441
442 url = ("https://www.googleapis.com/upload/gmail/v1/users/%s/drafts" +
443 "?uploadType=media") % urllib.parse.quote(user)
444
445 opener = urllib.request.build_opener(urllib.request.HTTPSHandler)
446 request = urllib.request.Request(url, data=self.message_text)
447 request.add_header('Content-Type', 'message/rfc822')
448 request.add_header('Content-Length', str(len(self.message_text)))
449 request.add_header('Authorization', "Bearer " + access_token)
450 request.get_method = lambda: 'POST'
451
452 try:
453 urlfp = opener.open(request)
454 except urllib.error.HTTPError as e:
455 raise GGError(_("Error returned from the GMail API - %s - %s") %
456 (e.code, e.msg))
457
458 result = urlfp.fp.read().decode('utf-8')
459 json_result = json.loads(result)
460 id = json_result['message']['id']
461
462 return id
463
464
465class GMailURL():
466 """ Logic to convert a mailto link to an appropriate GMail URL, by
467 any means necessary, including API uploads."""
468
469 def __init__(self, mailto_url, from_address):
470 self.mailto_url = mailto_url
471 self.from_address = from_address
472
473 self.mail_dict = self.mailto2dict()
474
475 def mailto2dict(self):
476 """ Convert a mailto: reference to a dictionary containing the
477 message parts """
478 # get the path string from the 'possible' mailto url
479 usplit = urllib.parse.urlsplit(self.mailto_url, "mailto")
480
481 path = usplit.path
482
483 try:
484 # for some reason, urlsplit is not splitting off the
485 # query string.
486 # do it here
487 # ( address, qs ) = string.split( path, "?", 1 )
488 (address, query_string) = path.split("?", 1)
489 except ValueError:
490 address = path
491
492 query_string = usplit.query
493
494 # For whatever reason, xdg-open 1.0.2 on Ubuntu 15 passes
495 # "mailto:///email@address" so the path has a leading slash when
496 # parsed by urlsplit. Just trim off leading slashes; they're not
497 # valid in email addresses anyway.
498 address = re.sub("^/+", "", address)
499
500 qsdict = urllib.parse.parse_qs(query_string)
501
502 qsdict['to'] = [address]
503
504 if 'attachment' in qsdict:
505 qsdict['attach'] = qsdict['attachment']
506
507 outdict = {}
508 for (key, value) in qsdict.items():
509 for i in range(0, len(value)):
510 if key.lower() in ['to', 'cc', 'bcc', 'body']:
511 value[i] = urllib.parse.unquote(value[i])
512 else:
513 value[i] = urllib.parse.unquote_plus(value[i])
514
515 outdict[key.lower()] = value
516
517 return(outdict)
518
519 def simple_gmail_url(self):
520 """ url to use if there is no mailto url """
521
522 return("https://mail.google.com/mail/b/%s" % self.from_address)
523
524 def api_gmail_url(self):
525 """ if the mailto refers to an attachment,
526 use the GMail API to upload the file """
527
528 api_url = "https://mail.google.com/mail/b/%s#drafts/" % \
529 self.from_address
530
531 try:
532 gm_api = GMailAPI(self.mail_dict)
533 gm_api.form_message()
534 except OSError:
535 GGError(_("Error creating message with attachment"))
536
537 msg_id = None
538 auth = GMOauth()
539 keys = Oauth2Keyring(auth.scope)
540 old_access, old_refresh = keys.getTokens(self.from_address)
541
542 error_str = ""
543 for access, refresh in auth.access_iter(old_access, old_refresh,
544 self.from_address):
545 try:
546 msg_id = gm_api.send_mail(self.from_address,
547 access)
548 break
549 except GGError as e:
550 error_str = e.value
551
552 if msg_id:
553 if (old_access, old_refresh) != (access, refresh):
554 keys.setTokens(self.from_address, access, refresh)
555 else:
556 raise GGError(error_str)
557
558 return(api_url + msg_id)
559
560 def gmail_url(self):
561 """ Return a GMail URL appropriate for the mailto handled
562 by this instance """
563 if(len(self.mailto_url) == 0):
564 gmailurl = self.simple_gmail_url()
565 else:
566 gmailurl = self.api_gmail_url()
567
568 return(gmailurl)
569
570
571def getFromAddress(last_address, config, gladefile):
572 class Handler:
573 def __init__(self, fromInit, dlg):
574 self.txtbox = builder.get_object("entryFrom")
575 self.txtbox.set_activates_default(True)
576
577 self.txtbox.set_property("text", fromInit)
578
579 self.txt = None
580
581 self.dlg = dlg
582
583 def onOkClicked(self, button):
584 self.txt = self.txtbox.get_property("text")
585 self.dlg.hide()
586 Gtk.main_quit()
587
588 def onCancelClicked(self, button):
589 self.txt = None
590 self.dlg.hide()
591 Gtk.main_quit()
592
593 def onUserSelClose(self, foo):
594 self.onCancelClicked(foo)
595
596 def onDestroy(self, foo):
597 self.onCancelClicked(foo)
598
599 suppress_account_selection = config.get_bool('suppress_account_selection')
600 if last_address and suppress_account_selection:
601 return last_address
602
603 builder = Gtk.Builder()
604 builder.add_from_file(gladefile)
605
606 dlg = builder.get_object("user_select_dialog")
607
608 hdlr = Handler(last_address, dlg)
609 builder.connect_signals(hdlr)
610
611 dlg.show_all()
612
613 Gtk.main()
614
615 sup_acc_sel = builder.get_object(
616 "check_account_dont_ask_again").get_active()
617 config.set_bool('suppress_account_selection', sup_acc_sel)
618
619 return hdlr.txt
620
621
622def getGoogleFromAddress(last_address, config, gladefile):
623 retval = getFromAddress(last_address, config, gladefile)
624
625 if retval and not re.search('@', retval):
626 retval += "@gmail.com"
627
628 return retval
629
630
631class GgConfig(SafeConfigParser):
632 def __init__(self, *args, **kwargs):
633
634 self.fpath = os.path.expanduser(self.strip_kwarg(kwargs, 'fpath'))
635 self.section = self.strip_kwarg(kwargs, 'section')
636 initvals = self.strip_kwarg(kwargs, 'initvals')
637 self.header = self.strip_kwarg(kwargs, 'header')
638
639 SafeConfigParser.__init__(self, *args, **kwargs)
640
641 self.add_section(self.section)
642
643 for option in initvals:
644 self.set(self.section, option, initvals[option])
645
646 self.read(self.fpath)
647 self.save()
648
649 def strip_kwarg(self, kwargs, option):
650 val = kwargs[option]
651 kwargs.pop(option, None)
652 return val
653
654 def save(self):
655 dir = os.path.dirname(self.fpath)
656
657 if not os.path.exists(dir):
658 os.makedirs(dir)
659
660 with open(self.fpath, 'w') as fp:
661 fp.write(self.header)
662 fp.write("# Automatically updated file - comments stripped\n")
663 self.write(fp)
664
665 def _saveit(fp):
666 def wrapper(inst, *args, **kwargs):
667 retval = fp(inst, *args, **kwargs)
668 inst.save()
669 return retval
670 return wrapper
671
672 def get_str(self, option):
673 return self.get(self.section, option)
674
675 @_saveit
676 def set_str(self, option, value):
677 return self.set(self.section, option, value)
678
679 def get_bool(self, option):
680 return self.getboolean(self.section, option)
681
682 @_saveit
683 def set_bool(self, param, val):
684 if isinstance(val, bool):
685 val = '1' if val else '0'
686 return self.set(self.section, param, val)
687
688
689class Oauth2Keyring():
690 # per
691 # https://people.gnome.org/~stefw/libsecret-docs/py-examples.html#py-schema-example
692 TOKEN_SCHEMA = Secret.Schema.new(
693 'com.github.davesteele.oauth2',
694 Secret.SchemaFlags.NONE,
695 {
696 "user": Secret.SchemaAttributeType.STRING,
697 "scope": Secret.SchemaAttributeType.STRING,
698 }
699 )
700
701 def __init__(self, scope):
702 self.scope = scope
703
704 def encodeTokens(self, access_token, refresh_token):
705 return "access:%s;refresh:%s" % (access_token, refresh_token)
706
707 def decodeTokens(self, encode_str):
708 match = re.search("^access:(.+);refresh:(.+)$", encode_str)
709
710 if match:
711 return match.group(1, 2)
712 else:
713 return (None, None)
714
715 def getTokens(self, user):
716 attributes = {
717 "user": user,
718 "scope": self.scope,
719 }
720
721 password = Secret.password_lookup_sync(self.TOKEN_SCHEMA,
722 attributes, None)
723
724 if password:
725 return self.decodeTokens(password)
726 else:
727 return (None, None)
728
729 def setTokens(self, user, access_token, refresh_token):
730 attributes = {
731 "user": user,
732 "scope": self.scope,
733 }
734
735 Secret.password_store_sync(
736 self.TOKEN_SCHEMA, attributes,
737 Secret.COLLECTION_DEFAULT,
738 "Mail access to %s for %s" % (self.scope, user),
739 self.encodeTokens(access_token, refresh_token),
740 None
741 )
742
743
744def do_preferred(glade_file, config):
745
746 class Handler:
747 def onCancelClicked(self, button):
748 Gtk.main_quit()
749
750 builder = Gtk.Builder()
751 builder.add_from_file(glade_file)
752
753 hdlr = Handler()
754 builder.connect_signals(hdlr)
755
756 response = builder.get_object("preferred_app_dialog").run()
757
758 preferred_setting = builder.get_object("check_dont_ask_again").get_active()
759 config.set_bool('suppress_preferred', preferred_setting)
760
761 if response == 1:
762 set_as_default_mailer()
763
764
765def main():
766 """ given an optional parameter of a valid mailto url, open an appropriate
767 gmail web page """
768
769 global config
770
771 if(len(sys.argv) > 1):
772 mailto = sys.argv[1]
773 else:
774 mailto = ""
775
776 header = textwrap.dedent("""\
777 # GNOME Gmail Configuration
778 #
779 # suppress_preferred
780 # If True ('1', 'yes'...) don't ask if GNOME Gmail should be made
781 # the default mail program.
782 # suppress_account_selection
783 # If True ('1', 'yes'...) don't ask account to use, if you have
784 # only one.
785 # new_browser
786 # If True ('1', 'yes'...) forcedly open Gmail in a new browser
787 # window.
788 # last_email
789 # The email account used for the last run. It is used to populate
790 # the account selection dialog. This is updated automatically.
791 #
792 # browser_options
793 # Replace the command line arguments used to call the browser. Note
794 # that these options are not portable acrosss browsers. '%s' is
795 # replaced with the url. '%action' is replaced with an option that
796 # that implements the 'new_browser' functionality. Default options
797 # are:
798 # Chrome - "%action %s"
799 # Mozilla - "-remote openurl(%s%action)"
800 # ...
801 #
802 """)
803 config = GgConfig(
804 fpath="~/.config/gnome-gmail/gnome-gmail.conf",
805 section='gnome-gmail',
806 initvals={
807 'suppress_preferred': '0',
808 'suppress_account_selection': '0',
809 'new_browser': '1',
810 'last_email': '',
811 'browser_options': '',
812 },
813 header=header,
814 )
815
816 # anyone know how to do this right?
817 glade_suffix = "share/gnome-gmail/gnomegmail.glade"
818 glade_file = os.path.join('/usr', glade_suffix)
819 for gpath in [os.path.join(x, glade_suffix) for x in ['/usr/local']]:
820 if os.path.isfile(gpath):
821 glade_file = gpath
822
823 if not is_default_mailer() \
824 and not config.get_bool('suppress_preferred'):
825 do_preferred(glade_file, config)
826
827 # quiet mode, to set preferred app in postinstall
828 if(len(sys.argv) > 1 and sys.argv[1] == "-q"):
829 sys.exit(0)
830
831 Notify.init("GNOME Gmail")
832
833 last_from = config.get_str('last_email')
834 from_address = getGoogleFromAddress(last_from, config, glade_file)
835 if from_address:
836 config.set_str('last_email', from_address)
837
838 try:
839 gm_url = GMailURL(mailto, from_address)
840 gmailurl = gm_url.gmail_url()
841 except GGError as gerr:
842 notice = Notify.Notification.new(
843 "GNOME GMail",
844 gerr.value,
845 "dialog-information"
846 )
847
848 notice.show()
849 time.sleep(5)
850 else:
851 new_browser = config.get_bool('new_browser')
852 browser().open(gmailurl, new_browser, True)
853
854if __name__ == "__main__":
855 main()