#!/usr/bin/env python
# -*- coding: UTF8 -*-
#***************************************************************************
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU General Public License (GPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
'''
Fluxtwitter 0.6 - 19.08.2011
author: Yorik van Have
url: http://yorik.uncreated.net
A very simple app to connect to your twitter account, fetch your friends
timeline and display it in a pidgin-like window. Supports fluxbox
pseudo-transparency.
History:
0.1 - 12.08.2009 - First version
0.2 - 19.11.2009 - Added fluxbox pseudo-transparency support
0.3 - 10.04.2010 - Tweets now accumulate until you open the window
0.4 - 24.04.2010 - Using monsterID in case twitter avatar doesn't work
0.5 - 03.10.2010 - Implemented OAuth authentication
0.6 - 19.08.2011 - Bugfixes and added text output option
Usage:
fluxtwitter can be invoked with no arguments, in which case it will run in
normal GUI mode. If it is the first time you use it, you'll be asked to
connect it to your account on the twitter website. After that, your settings
will be stored for next times. The application then connects to your twitter
account and retrieves your timeline.
By default tweets will stack until you read them. The trat icon changes to
green to tell you there are new tweets to be read. You can enable fluxbox
pseudo-transparency in the options, but it will work well only if your
background image is exactly the pixel size of your screen.
Fluxtwitter can also be run in text mode, in which case it will simply
display the last tweets on the standard output. Use this for example
to add tweets in a conky setup, with a line like:
${execi 120 fluxtwitter.py -t}
Options:
-t or --text: displays a list of tweets without opening the GUI,
-h or --help: displays this help text
'''
import sys, os, gtk, gobject, urllib, re, subprocess, time, string, hashlib, simplejson, getopt
from twitter import Api, User
try:
import oauth.oauth as oauth
except ImportError:
import oauth
# defaults
CONSUMERKEY = "G2EgGMNLFAJxvebsuv9D8A"
CONSUMERSECRET = "zstJsJxBAaND3ria36Mo6khdgyBEgc9gyz9tDdgg"
ACCESSTOKEN = None
DISPLAYTWEETS = 8 # minimum number of tweets displayed
INTERVAL = 120 # update interval in seconds
BROWSER = "x-www-browser" # default browser
TOOLBARHEIGHT = 19 # height of your dektop toolbar, for calculating bg offset
TRANSPARENCY = 50 # transparency level
COMPOSITE = True # if we apply pseudo-transparency or not
COMPOSITECOLOR = 0xffffff00 # color to composite the bg image to
STACKMODE = True # if true, tweets will stack up until you read them
LOGGING = False # if true, a logfile will be created
REQUEST_TOKEN_URL = 'https://twitter.com/oauth/request_token'
ACCESS_TOKEN_URL = 'https://twitter.com/oauth/access_token'
AUTHORIZATION_URL = 'http://twitter.com/oauth/authorize'
SIGNIN_URL = 'http://twitter.com/oauth/authenticate'
fluxicon=[
"16 16 17 1",
" c None",
". c #060911",
"+ c #011136",
"@ c #022274",
"# c #002CA7",
"$ c #1E2F58",
"% c #313434",
"& c #3D360B",
"* c #0045DE",
"= c #505461",
"- c #7A6700",
"; c #8B8B8B",
"> c #B89F00",
", c #B6B8B5",
"' c #F9D900",
") c #DCDCD7",
"! c #FBFDFA",
"..++++.. ",
" @#######@+ ",
" @#@$$$@##@$. ",
" @@=)!);@=));. ",
" @=!!!!!,;)!!=. ",
" @,!!!!%%%%!!;+ ",
" @,!!!)....,!;@ ",
"+#;!!!!....)!$#+",
"+#$!!!!;.+$;%@@.",
" ##=)!!!=$&->>>&",
" @*#$;;&>''''''&",
" +**#.-''''''''&",
" @**#=->''''''&",
" @*#@#@&>'''> ",
" .@#***$-'>& ",
" +++ && "
]
fluxiconalt=[
"16 16 17 1",
" c None",
". c #753F00",
"+ c #804300",
"@ c #884900",
"# c #914B00",
"$ c #9D5300",
"% c #AC5C01",
"& c #B15F00",
"* c #BB6201",
"= c #C06500",
"- c #D17000",
"; c #DA7500",
"> c #DE7800",
", c #E67903",
"' c #EE7E00",
") c #F68500",
"! c #FE8600",
" %>,,,>=@ ",
" ! +*'> ",
" &@ .-% %!@ ",
" > >- #'.)$&!@ ",
" '#& ! @! ",
" ,, ,-!> '; ",
" >, !)') ,, ",
" ,' !=!) .-, ",
" ,%% *!)'>!>! ",
" ' ' ')'-$.*#",
" ' >>,)* +=",
" #= !% $%",
" ' ,)# ; ",
" '+>'$-)@ ' ",
" >'+ !' ' ",
" =,,>+ )@ "
]
iconnew = [
"16 16 17 1",
" c None",
". c #080A02",
"+ c #102E06",
"@ c #174A00",
"# c #444A3A",
"$ c #545200",
"% c #2C7200",
"& c #72756B",
"* c #7C7B7D",
"= c #62A200",
"- c #8A8C89",
"; c #B69B00",
"> c #AFAFB0",
", c #93E100",
"' c #C4C3C5",
") c #FCDB00",
"! c #F7F9F6",
" .++++++. ",
" @%%%%%%%@. ",
" +%@++@%%@+. ",
" @@>!!!#@'!'. ",
" %*!!!!!*!!!*. ",
" .@'!!!'.*.!!'+ ",
" .@!!!!-...-!'% ",
" .%>!!!>...>!#,.",
" .=#!!!!#.%@@.",
" .==*!!!'+$$;))$",
" ===+$))))))$",
" +,,%$))))))))$",
" =,,%$;)))))).",
" =,%$=$;))); ",
" .=,,,=$)). ",
" .$+ .+ "
]
iconnewalt = [
"16 16 17 1",
" c None",
". c #130D07",
"+ c #251300",
"@ c #412500",
"# c #6F4203",
"$ c #946405",
"% c #8C6B41",
"& c #BE7400",
"* c #A17F48",
"= c #B78A00",
"- c #9D9489",
"; c #F89400",
"> c #E7BF00",
", c #FDBF00",
"' c #D8CFC7",
") c #FEDC00",
"! c #FCFEFA",
" +++@++. ",
" @;;;;;;&$+ ",
" .;;;&&;;; ",
" +;&-!'%;$''# ",
" @;'!!!!%!!!'@ ",
" #$!!!!--.-!!$+ ",
" $*!!!!....!!$# ",
" &$!!!!....!!=$ ",
" &&'!!!-.$*-#=$ ",
" $,$!!!!-$==>)= ",
" @,,&*-$>)))))>.",
" =,,$>)))))))>.",
" @,,,=$>)))))= ",
" @>=$==$>)))@ ",
" +$>)))$=)$ ",
" .+@+ @ "
]
# debug logging
if LOGGING:
fsock = open('/var/log/fluxtwitter.log', 'w')
sys.stdout = fsock
sys.stderr = fsock
class OAuthApi(Api):
"OAuthApi code from http://oauth-python-twitter.googlecode.com"
def __init__(self, consumer_key, consumer_secret, access_token=None):
if access_token:
Api.__init__(self,access_token.key, access_token.secret)
else:
Api.__init__(self)
self._Consumer = oauth.OAuthConsumer(consumer_key, consumer_secret)
self._signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
self._access_token = access_token
def _GetOpener(self):
opener = self._urllib.build_opener()
return opener
def _FetchUrl(self,
url,
post_data=None,
parameters=None,
no_cache=None):
'''Fetch a URL, optionally caching for a specified time.
Args:
url: The URL to retrieve
post_data:
A dict of (str, unicode) key/value pairs. If set, POST will be used.
parameters:
A dict whose key/value pairs should encoded and added
to the query string. [OPTIONAL]
no_cache: If true, overrides the cache on the current request
Returns:
A string containing the body of the response.
'''
# Build the extra parameters dict
extra_params = {}
if self._default_params:
extra_params.update(self._default_params)
if parameters:
extra_params.update(parameters)
# Add key/value parameters to the query string of the url
#url = self._BuildUrl(url, extra_params=extra_params)
if post_data:
http_method = "POST"
extra_params.update(post_data)
else:
http_method = "GET"
req = self._makeOAuthRequest(url, parameters=extra_params,
http_method=http_method)
self._signRequest(req, self._signature_method)
# Get a url opener that can handle Oauth basic auth
opener = self._GetOpener()
#encoded_post_data = self._EncodePostData(post_data)
if post_data:
encoded_post_data = req.to_postdata()
url = req.get_normalized_http_url()
else:
url = req.to_url()
encoded_post_data = ""
no_cache=True
# Open and return the URL immediately if we're not going to cache
# OR we are posting data
if encoded_post_data or no_cache:
if encoded_post_data:
url_data = opener.open(url, encoded_post_data).read()
else:
url_data = opener.open(url).read()
opener.close()
else:
# Unique keys are a combination of the url and the username
if self._username:
key = self._username + ':' + url
else:
key = url
# See if it has been cached before
last_cached = self._cache.GetCachedTime(key)
# If the cached version is outdated then fetch another and store it
if not last_cached or time.time() >= last_cached + self._cache_timeout:
url_data = opener.open(url).read()
opener.close()
self._cache.Set(key, url_data)
else:
url_data = self._cache.Get(key)
# Always return the latest version
return url_data
def _makeOAuthRequest(self, url, token=None,
parameters=None, http_method="GET"):
'''Make a OAuth request from url and parameters
Args:
url: The Url to use for creating OAuth Request
parameters:
The URL parameters
http_method:
The HTTP method to use
Returns:
A OAauthRequest object
'''
if not token:
token = self._access_token
request = oauth.OAuthRequest.from_consumer_and_token(
self._Consumer, token=token,
http_url=url, parameters=parameters,
http_method=http_method)
return request
def _signRequest(self, req, signature_method=oauth.OAuthSignatureMethod_HMAC_SHA1()):
'''Sign a request
Reminder: Created this function so incase
if I need to add anything to request before signing
Args:
req: The OAuth request created via _makeOAuthRequest
signate_method:
The oauth signature method to use
'''
req.sign_request(signature_method, self._Consumer, self._access_token)
def getAuthorizationURL(self, token, url=AUTHORIZATION_URL):
'''Create a signed authorization URL
Returns:
A signed OAuthRequest authorization URL
'''
req = self._makeOAuthRequest(url, token=token)
self._signRequest(req)
return req.to_url()
def getSigninURL(self, token, url=SIGNIN_URL):
'''Create a signed Sign-in URL
Returns:
A signed OAuthRequest Sign-in URL
'''
signin_url = self.getAuthorizationURL(token, url)
return signin_url
def getAccessToken(self, pin, url=ACCESS_TOKEN_URL):
token = self._FetchUrl(url, parameters={'oauth_verifier':pin},no_cache=True)
return oauth.OAuthToken.from_string(token)
def getRequestToken(self, url=REQUEST_TOKEN_URL):
'''Get a Request Token from Twitter
Returns:
A OAuthToken object containing a request token
'''
resp = self._FetchUrl(url, no_cache=True)
token = oauth.OAuthToken.from_string(resp)
return token
def GetUserInfo(self, url='https://twitter.com/account/verify_credentials.json'):
'''Get user information from twitter
Returns:
Returns the twitter.User object
'''
json = self._FetchUrl(url)
data = simplejson.loads(json)
self._CheckForTwitterError(data)
return User.NewFromJsonDict(data)
class TwitterStatusIcon(gtk.StatusIcon):
def __init__(self):
gtk.StatusIcon.__init__(self)
# creating the status icon with its menu
menu = '''
'''
actions = [
('Menu', None, 'Menu'),
('Update', gtk.STOCK_REFRESH, '_Update now', None, 'Update', self.update),
('Settings', gtk.STOCK_PREFERENCES, '_Settings...', None, 'Settings', self.config),
('About', gtk.STOCK_ABOUT, '_About...', None, 'About Fluxtwitter', self.about),
('Close', gtk.STOCK_CLOSE, '_Close', None, 'Close', self.close)]
ag = gtk.ActionGroup('Actions')
ag.add_actions(actions)
self.manager = gtk.UIManager()
self.manager.insert_action_group(ag, 0)
self.manager.add_ui_from_string(menu)
self.menu = self.manager.get_widget('/Twitter/Menu/About').props.parent
self.icon = gtk.gdk.pixbuf_new_from_xpm_data(fluxicon)
self.iconnew = gtk.gdk.pixbuf_new_from_xpm_data(iconnew)
self.set_from_pixbuf(self.icon)
self.getconfig()
self.set_visible(True)
self.isTweet = False
self.connect('popup-menu', self.popup_menu)
self.connect('activate', self.showtimeline)
self.api = OAuthApi(CONSUMERKEY, CONSUMERSECRET)
self.request_token = self.api.getRequestToken()
if not self.accesstoken:
self.getpin()
else:
self.launch()
def launch(self):
# registrating
self.api = OAuthApi(CONSUMERKEY, CONSUMERSECRET, self.accesstoken)
self.username = self.api.GetUserInfo().name
# creating the main dialog
self.set_tooltip(self.username + "'s timeline")
self.tweetdialog = gtk.Window()
self.tweetdialog.connect("destroy",self.showtimeline)
self.tweetdialog.connect("delete-event",self.showtimeline)
self.tweetdialog.connect('configure-event', self.updateBackground)
self.tweetdialog.set_title('twitter - '+self.username)
self.tweetdialog.set_icon(self.icon)
self.tweetdialog.set_border_width(5)
self.tweetdialog.set_size_request(280, 500)
self.layout = gtk.ScrolledWindow()
self.layout.set_policy(gtk.POLICY_NEVER,gtk.POLICY_AUTOMATIC)
self.vbox = gtk.VBox()
self.layout.add_with_viewport(self.vbox)
self.tweetdialog.add(self.layout)
self.layout.get_child().set_shadow_type(gtk.SHADOW_NONE)
self.layout.connect('scroll-child', self.updateBackground)
self.tweets = []
self.table = None
self.iteration = 1
self.updateBackground()
self.timeout = gobject.timeout_add(self.interval*1000,self.update)
self.update()
def update(self, data=None):
# updating from twitter
print "iteration",self.iteration,": fetching",self.timeout,"tweets on",time.strftime('%X %x %Z')
try:
statuses = self.api.GetFriendsTimeline(count=self.displaytweets)
except:
print "Error: Couldn't connect to Twitter server."
return True # even if we cannot connect, we continue trying next time
self.iteration += 1
extras = 0
if (not self.tweets):
extras = len(statuses)
self.set_from_pixbuf(self.iconnew)
elif (statuses[0].id != self.tweets[0]['id']):
self.set_from_pixbuf(self.iconnew)
for i in range(len(statuses)):
if statuses[i].id == self.tweets[0]['id']:
extras = i
break
if not extras:
print "no new tweet to display"
return True
print 'list currently has',len(self.tweets),' - adding',extras
for i in range(extras-1,-1,-1):
# retrieving all we need from the tweet
iconurl = statuses[i].user.GetProfileImageUrl()
iconfile=urllib.urlopen(iconurl)
print 'extra tweet',i,"from ",statuses[i].user.name," : ",statuses[i].text," ",iconurl
tweetpb = None
try:
pbl = gtk.gdk.PixbufLoader()
pbl.write(iconfile.read())
tweetpb = pbl.get_pixbuf()
pbl.close()
except:
print "error reading avatar file"
if not tweetpb:
# if icon is invalid, try to get a monsterid
h = hashlib.md5()
h.update(statuses[i].user.screen_name)
v = h.hexdigest()
url = "http://friedcellcollective.net/monsterid/"+v+"/48"
iconfile=urllib.urlopen(url)
pbl = gtk.gdk.PixbufLoader()
pbl.write(iconfile.read())
tweetpb = pbl.get_pixbuf()
pbl.close()
if not tweetpb:
# if everything fails, use default icon
tweetpb = self.icon
tweetpb = tweetpb.scale_simple(48,48,gtk.gdk.INTERP_BILINEAR)
tweettext = statuses[i].text.replace('ç','c')
tweettext = tweettext.replace('ã','a')
tweettext = tweettext.replace("'","")
tweettext = tweettext.replace("&","&")
pat1 = re.compile(r"(^|[\n ])(([\w]+?://[\w\#$%&~.\-;:=,?@\[\]+]*)(/[\w\#$%&~/.\-;:=,?@\[\]+]*)?)", re.IGNORECASE | re.DOTALL)
pat2 = re.compile(r"(^|[\n ])(@([\w\#$%&~.\-;:=,?@\[\]+]*)(/[\w\#$%&~/.\-;:=,?@\[\]+]*)?)", re.IGNORECASE | re.DOTALL)
pat3 = re.compile(r"(^|[\n ])(#([\w\#$%&~.\-;:=,?@\[\]+]*)(/[\w\#$%&~/.\-;:=,?@\[\]+]*)?)", re.IGNORECASE | re.DOTALL)
tweettext = pat1.sub(r'\1\3', tweettext)
tweettext = pat2.sub(r'\1\2', tweettext)
tweettext = pat3.sub(r'\1\2', tweettext)
# adding to our tweet list
thistweet = {'id':statuses[i].id,
'user':statuses[i].user.screen_name,
'tweet':tweettext,
'icon':tweetpb}
self.tweets.insert(0,thistweet) #adding our new tweet to the top of the list
# if list window is visible, don't stack
if self.isTweet: self.tweets = self.tweets[:self.displaytweets]
# dont rebuild if window is open (buggy)
# if not self.isTweet: self.rebuildTable()
self.rebuildTable()
return True
def rebuildTable(self):
print "building table with",len(self.tweets),"items"
newtable = gtk.Table(len(self.tweets),2)
newtable.set_row_spacings(10)
for i in range(len(self.tweets)):
label = gtk.Label()
label.set_line_wrap(True)
label.set_width_chars(25)
label.set_markup(self.tweets[i]['tweet'])
label.set_selectable(True)
# label.connect("activate-current-link",self.clicked) # not really working
newtable.attach(label,1,2,i,i+1)
icon = gtk.Image()
icon.set_from_pixbuf(self.tweets[i]['icon'])
icon.set_tooltip_text(self.tweets[i]['user'])
button = gtk.Button()
button.set_relief(gtk.RELIEF_NONE)
button.set_image(icon)
button.set_name(str(i))
button.connect("clicked",self.clicked)
button.set_focus_on_click(False)
newtable.attach(button,0,1,i,i+1)
if self.table: self.vbox.remove(self.table)
self.vbox.pack_start(newtable)
self.table = newtable
def getconfig(self):
self.displaytweets = DISPLAYTWEETS
self.browser = BROWSER
self.interval = INTERVAL
self.composite = COMPOSITE
self.transparency = TRANSPARENCY
self.toolbarheight = TOOLBARHEIGHT
self.compositecolor = COMPOSITECOLOR
self.stackmode = STACKMODE
self.accesstoken = ACCESSTOKEN
configfile = os.path.expanduser('~') + os.sep + '.fluxtwitterrc'
if os.path.isfile(configfile):
print "reading config file"
file = open(configfile)
for line in file:
if not("#" in line):
key, value = line.split("=", 1)
key = key.strip()
value = value.strip()
if key == "displaytweets": self.displaytweets = int(value)
elif key == "browser": self.browser = value
elif key == "interval": self.interval = int(value)
elif key == "composite": self.composite = bool(int(value))
elif key == "transparency": self.transparency = int(value)
elif key == "toolbarheight": self.toolbarheight = int(value)
elif key == "compositecolor": self.compositecolor = string.atoi(value,0)
elif key == "stackmode": self.stackmode = bool(int(value))
elif key == "accesstoken": self.accesstoken = oauth.OAuthToken.from_string(value)
file.close()
else:
print "Creating config file..."
self.writeconfig()
def writeconfig(self):
configfile = os.path.expanduser('~') + os.sep + '.fluxtwitterrc'
file = open(configfile,'wb')
file.write('# Fluxtwitter configuration file\n')
file.write('# Number of tweets displayed (default 8)\n')
file.write('displaytweets = ' + str(self.displaytweets) + '\n')
file.write('# Browser command to open links (default x-www-browser) \n')
file.write('browser = ' + self.browser + '\n')
file.write('# Interval in seconds between twitter updates (default 120)\n')
file.write('interval = ' + str(self.interval) + '\n')
file.write('# Do we use pseudo-transparency?\n')
file.write('composite = '+ str(int(self.composite)) + '\n')
file.write('# Amount of image fading in percent\n')
file.write('transparency = '+ str(self.transparency) + '\n')
file.write('# Color to composite background with (0x00000000)\n')
file.write('compositecolor = '+str(self.compositecolor) + '\n')
file.write('# Window titlebar height correction in pixels\n')
file.write('toolbarheight = ' + str(self.toolbarheight) + '\n')
file.write('# Stack mode (if tweets will stack until you read them\n')
file.write('stackmode = ' + str(int(self.stackmode)) + '\n')
if self.accesstoken:
file.write('# Access token (automatically generated once you allow it on twitter\n')
file.write('accesstoken = ' + self.accesstoken.to_string() + '\n')
file.close()
def config(self,data):
dialog = gtk.Dialog()
dialog.set_title('Fluxtwitter settings')
table = gtk.Table(8,2)
c1 = gtk.Entry()
c1.set_text(str(self.displaytweets))
c1.set_tooltip_text('Number of tweets to display')
c2 = gtk.Entry()
c2.set_text(self.browser)
c2.set_tooltip_text('Web browser to open links in')
c3 = gtk.Entry()
c3.set_text(str(self.interval))
c3.set_tooltip_text('Interval in seconds between updates')
c4 = gtk.ToggleButton()
c4.set_active(self.composite)
c4.set_tooltip_text('Check this to use fluxbox pseudo-transparency')
c5 = gtk.Entry()
c5.set_text(str(self.transparency))
c5.set_tooltip_text('Fading level in percents')
c6 = gtk.Entry()
c6.set_text(str(self.toolbarheight))
c6.set_tooltip_text('Vertical correction in pixels')
c7 = gtk.Entry()
c7.set_text(str(self.compositecolor))
c7.set_tooltip_text('Composite color for transparency')
c8 = gtk.ToggleButton()
c8.set_active(self.stackmode)
c8.set_tooltip_text('Check this for tweets to stack until you read them')
table.attach(gtk.Label('Nr of tweets '),0,1,0,1)
table.attach(c1,1,2,0,1)
table.attach(gtk.Label('Web browser '),0,1,1,2)
table.attach(c2,1,2,1,2)
table.attach(gtk.Label('Interval '),0,1,2,3)
table.attach(c3,1,2,2,3)
table.attach(gtk.Label('Pseudo-transparency '),0,1,3,4)
table.attach(c4,1,2,3,4)
table.attach(gtk.Label('Fading '),0,1,4,5)
table.attach(c5,1,2,4,5)
table.attach(gtk.Label('Vertical correction '),0,1,5,6)
table.attach(c6,1,2,5,6)
table.attach(gtk.Label('Composite Color '),0,1,6,7)
table.attach(c7,1,2,6,7)
table.attach(gtk.Label('Stack mode '),0,1,7,8)
table.attach(c8,1,2,7,8)
dialog.vbox.pack_start(table)
dialog.show_all()
cancel_button = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
ok_button = dialog.add_button(gtk.STOCK_OK,gtk.RESPONSE_OK)
ok_button.grab_default()
resp = dialog.run()
if resp == gtk.RESPONSE_OK:
self.displaytweets = int(c1.get_text())
self.browser = c2.get_text()
self.interval = int(c3.get_text())
self.composite = c4.get_active()
self.transparency = int(c5.get_text())
self.toolbarheight = int(c6.get_text())
self.compositecolor = string.atoi(c7.get_text(),0)
self.stackmode = c8.get_active()
self.writeconfig()
dialog.destroy()
def getpin(self):
auth_url = self.api.getAuthorizationURL(self.request_token)
subprocess.Popen([self.browser,auth_url],shell=False)
dialog = gtk.Dialog()
dialog.set_title('Inform Twitter authorization PIN')
table = gtk.Table(2,1)
c1 = gtk.Entry()
c1.set_tooltip_text('The PIN number you received from twitter')
table.attach(gtk.Label('Twitter PIN '),0,1,0,1)
table.attach(c1,1,2,0,1)
dialog.vbox.pack_start(table)
dialog.show_all()
cancel_button = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
ok_button = dialog.add_button(gtk.STOCK_OK,gtk.RESPONSE_OK)
ok_button.grab_default()
resp = dialog.run()
if resp == gtk.RESPONSE_OK:
pin = c1.get_text()
dialog.destroy()
self.api = OAuthApi(CONSUMERKEY, CONSUMERSECRET, self.request_token)
self.accesstoken = self.api.getAccessToken(pin)
self.writeconfig()
self.launch()
def updateBackground(self,args=None,stuff=None):
if self.composite:
print "rebuilding background"
x,y = self.tweetdialog.get_position()
w,h = self.tweetdialog.get_size()
bgfile = os.path.expanduser('~') + os.sep + '.fluxbox/lastwallpaper'
if os.path.isfile(bgfile):
wpfile = open(bgfile)
pb=gtk.gdk.pixbuf_new_from_file(wpfile.read().split('|')[1])
wpfile.close()
crop = gtk.gdk.Pixbuf( gtk.gdk.COLORSPACE_RGB, False, 8, w, h )
pb.copy_area(x, y+self.toolbarheight, w, h, crop, 0, 0)
mask = crop.copy()
mask.fill(self.compositecolor)
opacity = int((self.transparency/100.0)*255)
mask.composite(crop, 0, 0, w, h, 0, 0, 1, 1, gtk.gdk.INTERP_BILINEAR, opacity)
pm,m = crop.render_pixmap_and_mask(255)
style = self.tweetdialog.get_style().copy()
style.bg_pixmap[gtk.STATE_NORMAL] = pm
self.tweetdialog.set_style(style)
self.layout.get_child().set_style(style)
def clicked(self,data,url=None):
if data.name:
url="http://www.twitter.com/"+self.tweets[int(data.name)]['user']
print "clicked link:",url
self.tweetdialog.hide()
self.isTweet = False
self.tweets = self.tweets[:self.displaytweets]
self.rebuildTable()
subprocess.Popen([self.browser,url],shell=False)
def close(self, data):
gobject.source_remove(self.timeout)
gtk.main_quit()
def popup_menu(self, status, button, time):
self.menu.popup(None, None, None, button, time)
def about(self, data):
dialog = gtk.AboutDialog()
dialog.set_name('Fluxtwitter')
dialog.set_version('0.2')
dialog.set_comments('A system tray icon displaying twitter feed')
dialog.set_website('http://yorik.uncreated.net')
dialog.run()
dialog.destroy()
def showtimeline(self,data,event=None):
if self.isTweet:
self.tweetdialog.hide()
self.isTweet = False
self.tweets = self.tweets[:self.displaytweets]
self.rebuildTable()
else:
self.isTweet = True
self.tweetdialog.show_all()
self.set_from_pixbuf(self.icon)
return True
if __name__ == '__main__':
if len(sys.argv) == 1:
# no argument? GUI mode
TwitterStatusIcon()
gtk.main()
else:
try:
opts, args = getopt.getopt(sys.argv[1:], "th", ["text","help"])
except getopt.GetoptError:
print "Unrecognized option."
print __doc__
sys.exit()
else:
for o, a in opts:
if o in ("-t", "--text"):
# with -t or --text? we output the last 5 tweets
accesstoken = ACCESSTOKEN
configfile = os.path.expanduser('~') + os.sep + '.fluxtwitterrc'
if os.path.isfile(configfile):
file = open(configfile)
for line in file:
if not("#" in line):
key, value = line.split("=", 1)
key = key.strip()
value = value.strip()
if key == "accesstoken":
accesstoken = oauth.OAuthToken.from_string(value)
file.close()
api = OAuthApi(CONSUMERKEY, CONSUMERSECRET)
request_token = api.getRequestToken()
api = OAuthApi(CONSUMERKEY, CONSUMERSECRET, accesstoken)
statuses = api.GetFriendsTimeline(count=5)
for s in statuses:
print s.user.name + " : " + s.text
if o in ("-h", "--help"):
print __doc__
sys.exit()