これでMac, Windowsでのgithub growlerがある事になるのですが、Linuxにありません。Linuxにはアイコンが表示できてGNTPプロトコルを喋るGrowlシステムがありません。
そこで以前から使っていた、Growlネットワークプロトコル(Growlネットワークプロトコルはアイコンが出せません)をサポートしているmumblesというGrowlシステムを調べて見たところ、内部ではpythonでDBusによるプロセス間通信を行っている事が分かりました。
mumbles-project.orgさらにそのDBusインタフェース上ではアイコン表示をサポートしていた為、これは!と思いGitHubのGrowlアプリケーションを作ってみました。
a plugin driven, modern notification system for Gnome
http://www.mumbles-project.org/
まず、DBusで通信する為のプラグインを作成します。
DBusでメソッドが呼ばれると、MumblesPluginクラスに渡されるMumblesNotifyオブジェクトのalertメソッドを呼び出します。
全体のソースは以下の様になります。
from MumblesPlugin import *
import dbus
import gnomevfs
import os
import urllib
class GithubMumbles(MumblesPlugin):
plugin_name = 'GithubMumbles'
dbus_interface = 'com.github.DBus'
dbus_path = '/com/github/DBus'
icons = {'github' : 'github.png'}
__url = None
def __init__(self, mumbles_notify, session_bus):
self.signal_config = {
'Notify': self.Notify,
'NotifyNum': self.NotifyNum
}
MumblesPlugin.__init__(self, mumbles_notify, session_bus)
self.add_click_handler(self.onClick)
def NotifyNum(self, num):
self.__url = 'http://github.com/'
icon = self.get_icon('github')
title = 'Github'
msg = str(num)+' new messages!'
self.mumbles_notify.alert(self.plugin_name, title, msg, icon)
def Notify(self, link, author, text):
self.__url = link
path = os.path.join(PLUGIN_DIR_USER, 'icons', 'github-%s' % author)
if os.path.exists(path):
self.icons[author] = 'github-%s' % author
icon = self.get_icon(author)
else:
icon = self.get_icon('github')
self.mumbles_notify.alert(self.plugin_name, author, text, icon)
def onClick(self, widget, event, plugin_name):
if event.button == 3:
self.mumbles_notify.close(widget.window)
else:
self.open_url(self.__url)
def open_url(self, url):
mime_type = gnomevfs.get_mime_type(url)
application = gnomevfs.mime_get_default_application(mime_type)
os.system(application[2] + ' "' + url + '" &')
インタフェースはリンク、作者、本文のみとしました。これをegg形式にビルドしてmumblesのpluginフォルダに置くと、上記のインタフェース呼び出しによりGrowlが表示されます。MumblesPluginにはget_iconメソッドが用意されており、これにはアイコン名称を渡す事になります。実際にはplugin/iconsというフォルダにある名称のファイルが使用されるので、今回の仕組としてはgithubフィードのチェッカースクリプトでアイコンをplugin/iconsフォルダに格納させ、それを使用してプラグイン側が使用するという形になっています。プラグイン側でアイコンを取って来ても良いのですがアイコンをダウンロードしている最中はGrowlが固まってしまう為、今回の様な作りとなっています。
次にチェッカースクリプトですが以下の様なコードになります。
#!/usr/bin/env python
UPDATE_INTERVAL=1000 # 10 minutes
MAX_NOTIFICATIONS = 40
DEBUG = True
##################################################
import os
import sys
import time
import getopt
import rfc822
import calendar
import urllib
import feedparser
import dbus
import dbus.service
from dbus.mainloop.glib import DBusGMainLoop
import gobject
from BeautifulSoup import BeautifulSoup
from pit import Pit
GITHUB_DBUS_INTERFACE = 'com.github.DBus'
GITHUB_DBUS_PATH = '/com/github/DBus'
config = Pit.get('github.com', {'require': {
'user' : 'user id on github.com',
'token' : 'user token on github.com'
}})
class Usage(Exception):
def __init__(self, msg=None):
app = sys.argv[0]
if msg != 'help':
self.msg = app+': Invalid options. Try --help for usage details.'
else:
self.msg = app+": DBus notifications on new github messages.\n"
class GithubCheck(dbus.service.Object):
def __init__(self):
session_bus = dbus.SessionBus()
bus_name = dbus.service.BusName(GITHUB_DBUS_INTERFACE, bus=session_bus)
dbus.service.Object.__init__(self, bus_name, GITHUB_DBUS_PATH)
self.interval = UPDATE_INTERVAL
self.notifyLimit = MAX_NOTIFICATIONS
self.debug = DEBUG
self.lastCheck = None
self.minInterval = 60000 # 1 minute min refresh interval
if self.interval < self.minInterval:
print "Warning: Cannot check github more often than once a minute! Using default of 1 minute."
self.interval = self.minInterval
self._check()
@dbus.service.signal(dbus_interface=GITHUB_DBUS_INTERFACE, signature='sss')
def Notify(self, link, author, text):
pass
@dbus.service.signal(dbus_interface=GITHUB_DBUS_INTERFACE, signature='i')
def NotifyNum(self, num):
pass
def _check(self):
if self.debug:
if self.lastCheck:
print "checking feed (newer than %s):" %(self.lastCheck)
else:
print "checking feed:"
try:
items = feedparser.parse("http://github.com/%s.private.atom/?token=%s" % (config['user'], config['token']))['entries']
except Exception, e:
items = []
if self.lastCheck:
lastCheck = calendar.timegm(time.localtime(calendar.timegm(rfc822.parsedate(self.lastCheck))))
for item in items:
if calendar.timegm(item.published_parsed) < lastCheck:
items.remove(item)
self.lastCheck = rfc822.formatdate()
num_notifications = len(items)
if num_notifications > MAX_NOTIFICATIONS:
if self.debug:
print "%s new entries\n" %(num_notifications)
self.NotifyNum(num_notifications)
elif num_notifications < 0:
if self.debug:
print "no new entries\n"
else:
for item in items:
path = os.path.join(os.path.expanduser('~'), '.mumbles', 'plugins', 'icons', 'github-%s' % item['author'])
if not os.path.exists(path):
html = urllib.urlopen('http://github.com/%s' % item['author']).read()
soup = BeautifulSoup(html)
img = soup.findAll('div', {'class':'identity'})[0].find('img')['src']
img = img.replace("?s=50&", "?s=30&");
urllib.urlretrieve(img, path)
self.Notify(item['link'], item['author'], item['title'])
time.sleep(6)
gobject.timeout_add(self.interval,self._check)
if __name__ == '__main__':
DBusGMainLoop(set_as_default=True)
try:
try:
opts, args = getopt.getopt(
sys.argv[1:], "hp", ["help"])
except getopt.GetoptError:
raise Usage()
for o, a in opts:
if o in ("-h", "--help"):
raise Usage('help')
else:
raise Usage()
except Usage, err:
print >> sys.stderr, err.msg
sys.exit(2)
t = GithubCheck()
try:
loop = gobject.MainLoop()
loop.run()
except KeyboardInterrupt:
print "githubcheck shut down..."
except Exception, ex:
print "Exception in githubcheck: %s" %(ex)
だらだらとしたコードですが、大体分かってもらえるかと思います。pitを使っているので初回起動のみユーザとトークンをエディタで入力する必要があります。トークンはGitHubのダッシュボードにあるRSSアイコンのリンク先URLに含まれています。実行してしばらくすると以下の様な画面が表示されます。
これで快適になりました。
github上で全てのソースを公開しています。
mattn's mumbles-github-growler at master - GitHubよろしければどうぞ。
github growler using mumbles plugin and checker script.
http://github.com/mattn/mumbles-github-growler/tree/master