Skip to content

Instantly share code, notes, and snippets.

@mcsf
Created September 4, 2012 16:43
Show Gist options
  • Save mcsf/3623303 to your computer and use it in GitHub Desktop.
Save mcsf/3623303 to your computer and use it in GitHub Desktop.
Simple e-mail notifications for Gmail. Requires Desktop Notifications and wget/curl.
#!/usr/bin/env python
# Limitations:
# - only the last sender will be shown for a given conversation with
# multiple new messages
# TODO
# - chmod for config file
# - config for every default value
# DEFAULTS #############################################################
PERIOD = 30
FETCHER = 'Wget' # 'Wget' or 'Curl'
ALERT_CMD = 'notify-send \'Incoming e-mail\' -- \'%s\''
########################################################################
import ConfigParser
import os.path
import subprocess
import time
import xml.etree.ElementTree as et
FEED_URL = 'https://mail.google.com/mail/feed/atom'
NAMESPACE = '{http://purl.org/atom/ns#}'
SECTION = 'General'
CONFIG_FILE = '~/.gmail-notifier'
USER_MSG = 'Please edit the configuration file at %s.' % CONFIG_FILE
# EXCEPTIONS ###########################################################
class FormatError(Exception):
def __str__(self):
return 'Command format must be specified.'
class FetchError(Exception):
def __init__(self, err):
self.err = err
def __str__(self):
return 'Couldn\'t fetch Gmail feed.\n' + str(self.err)
# FETCHERS #############################################################
class FeedFetcher:
cmd_format = None
def __init__(self, user, passwd, url=None):
if not self.cmd_format:
raise FormatError
self.user = user
self.passwd = passwd
self.url = url or FEED_URL
self.cmd = self.cmd_format % self.__dict__
def fetch(self):
try:
return subprocess.check_output(self.cmd, shell=True)
except subprocess.CalledProcessError, e:
raise FetchError(e)
class Wget(FeedFetcher):
cmd_format = ( 'wget '
+ '--user %(user)s '
+ '--password %(passwd)s '
+ '-O- '
+ FEED_URL )
class Curl(FeedFetcher):
cmd_format = ( 'curl '
+ '--user %(user)s:%(passwd)s '
+ FEED_URL )
# HELPERS ##############################################################
def wrap(s): return NAMESPACE + s
def show(entry):
author = entry.find(wrap('author')).find(wrap('name')).text
subject = entry.find(wrap('title')).text
return '- %s: %s' % (author, subject)
# PARSE CONFIG FILE ####################################################
def get_credentials():
config = ConfigParser.RawConfigParser()
filepath = os.path.expanduser(CONFIG_FILE)
config.read([filepath])
if config.has_option(SECTION, 'username') \
and config.has_option(SECTION, 'password'):
u = config.get(SECTION, 'username')
p = config.get(SECTION, 'password')
if '' in [u, p]:
return False
return (u, p)
else:
if not config.has_section(SECTION):
config.add_section(SECTION)
config.set(SECTION, 'username', '')
config.set(SECTION, 'password', '')
with open(filepath, 'w') as cf:
config.write(cf)
return False
# MAIN #################################################################
def main():
try:
user, passwd = get_credentials()
except TypeError:
print USER_MSG
return 1
inbox = {}
fetcher = eval(FETCHER)(user, passwd)
while True:
print '---'
feed = fetcher.fetch()
try:
root = et.fromstring(feed)
except et.ParseError, e:
print e
time.sleep(PERIOD)
continue
oldbox = inbox
inbox = {}
new = []
for e in root.iter(wrap('entry')):
tag = e.find(wrap('id')).text
inbox[tag] = e
if not oldbox.get(tag):
new.insert(0, e)
if new:
body = '\n'.join(map(show, new))
subprocess.call(ALERT_CMD % body, shell=True)
time.sleep(PERIOD)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment