2010-04-07 03:05:51 +00:00
|
|
|
#!/usr/bin/python
|
|
|
|
#
|
|
|
|
# Custom twitter client... mostly for learning Python
|
|
|
|
|
2010-04-09 04:56:51 +00:00
|
|
|
import sys, twitter, ConfigParser, os, datetime, dateutil.tz, gtk, gtk.glade, gobject, re
|
2010-04-07 03:05:51 +00:00
|
|
|
|
|
|
|
|
2010-04-08 20:39:56 +00:00
|
|
|
class MyTwitter():
|
2010-04-07 03:05:51 +00:00
|
|
|
|
|
|
|
""" Display Tweets, post to twitter """
|
|
|
|
|
2010-04-09 21:45:21 +00:00
|
|
|
# Precompile a regex for searching for @ at the beginning of a string
|
|
|
|
at_check = re.compile('^@')
|
|
|
|
|
|
|
|
|
2010-04-08 20:39:56 +00:00
|
|
|
def __init__(self):
|
2010-04-07 17:17:21 +00:00
|
|
|
config = ConfigParser.ConfigParser()
|
|
|
|
config.read(os.path.expanduser("~/.mytwitter"))
|
|
|
|
self.username = config.get('global', 'username')
|
|
|
|
self.password = config.get('global', 'password')
|
2010-04-08 21:07:27 +00:00
|
|
|
|
2010-04-07 18:47:16 +00:00
|
|
|
self.num_entries = int(config.get('global', 'entries'))
|
2010-04-08 21:07:27 +00:00
|
|
|
if self.num_entries < 20:
|
|
|
|
self.num_entries = 20
|
|
|
|
|
2010-04-07 18:47:16 +00:00
|
|
|
self.refresh_time = int(config.get('global', 'refreshtime'))
|
2010-04-08 21:07:27 +00:00
|
|
|
if self.refresh_time < 10:
|
|
|
|
self.refresh_time = 10
|
2010-04-07 03:05:51 +00:00
|
|
|
|
2010-04-09 04:56:51 +00:00
|
|
|
self.tweets = []
|
2010-04-08 21:07:27 +00:00
|
|
|
self.list = None
|
2010-04-09 04:56:51 +00:00
|
|
|
self.reply_id = None
|
2010-04-07 03:05:51 +00:00
|
|
|
|
2010-04-08 20:39:56 +00:00
|
|
|
# Authenticate with twitter, set up the API object
|
|
|
|
self.api = twitter.Api(username=self.username, password=self.password)
|
2010-04-07 03:05:51 +00:00
|
|
|
|
2010-04-08 20:39:56 +00:00
|
|
|
# Load up all the GUI stuff
|
|
|
|
self.init_user_interface('./default.glade')
|
|
|
|
self.init_widgets()
|
2010-04-08 19:12:57 +00:00
|
|
|
|
|
|
|
|
2010-04-08 20:39:56 +00:00
|
|
|
def init_user_interface(self, path_to_skin):
|
|
|
|
self.widget_tree=gtk.glade.XML(path_to_skin, "window")
|
|
|
|
self.widget_tree.signal_autoconnect(self)
|
2010-04-08 19:12:57 +00:00
|
|
|
|
|
|
|
|
2010-04-08 20:39:56 +00:00
|
|
|
def init_widgets(self):
|
2010-04-08 21:07:27 +00:00
|
|
|
self.list_select = self.widget_tree.get_widget('list_select')
|
2010-04-08 20:39:56 +00:00
|
|
|
self.tweet_box = self.widget_tree.get_widget('tweet_box')
|
|
|
|
self.update_entry = self.widget_tree.get_widget('update_entry')
|
|
|
|
self.update_count = self.widget_tree.get_widget('update_count')
|
2010-04-07 17:21:55 +00:00
|
|
|
|
2010-04-08 20:39:56 +00:00
|
|
|
# Build us some labels...
|
2010-04-07 03:05:51 +00:00
|
|
|
for i in range(0, self.num_entries):
|
2010-04-09 04:56:51 +00:00
|
|
|
self.tweets.append(TweetBox())
|
|
|
|
self.tweet_box.pack_start(self.tweets[i])
|
2010-04-09 21:45:21 +00:00
|
|
|
self.tweets[i].connect('reply', self.on_reply)
|
|
|
|
self.tweets[i].connect('retweet', self.on_retweet)
|
2010-04-08 20:39:56 +00:00
|
|
|
self.tweet_box.show_all()
|
|
|
|
|
2010-04-08 21:07:27 +00:00
|
|
|
# Fill the ComboBox with entries
|
|
|
|
self.list_select.append_text('@' + self.username)
|
|
|
|
lists = self.api.GetUserLists()
|
|
|
|
for l in lists['lists']:
|
|
|
|
self.list_select.append_text(l.name)
|
|
|
|
|
2010-04-08 20:39:56 +00:00
|
|
|
# Timer to update periodically
|
2010-04-07 03:05:51 +00:00
|
|
|
self.update_window()
|
2010-04-08 21:19:45 +00:00
|
|
|
gobject.timeout_add(self.refresh_time * 1000, self.update_window)
|
2010-04-08 19:12:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
def update_window(self):
|
2010-04-08 21:07:27 +00:00
|
|
|
if self.list is None or self.list == 'Home':
|
|
|
|
statuses = self.api.GetFriendsTimeline(self.username, count=self.num_entries)
|
|
|
|
elif self.list == '@' + self.username:
|
|
|
|
statuses = self.api.GetReplies()
|
|
|
|
else:
|
|
|
|
statuses = self.api.GetListStatuses(self.list)
|
|
|
|
|
2010-04-07 03:05:51 +00:00
|
|
|
for i in range(0, self.num_entries):
|
|
|
|
if i < len(statuses):
|
2010-04-09 04:56:51 +00:00
|
|
|
self.tweets[i].set_status(statuses[i])
|
2010-04-07 03:05:51 +00:00
|
|
|
|
2010-04-07 18:17:57 +00:00
|
|
|
|
2010-04-08 21:07:27 +00:00
|
|
|
def update_window_callback(self, widget):
|
|
|
|
self.update_window()
|
|
|
|
|
|
|
|
|
2010-04-07 18:17:57 +00:00
|
|
|
def update_status(self):
|
2010-04-08 20:39:56 +00:00
|
|
|
text = self.update_entry.get_text()
|
2010-04-08 21:07:27 +00:00
|
|
|
self.update_entry.set_text("")
|
2010-04-09 05:08:32 +00:00
|
|
|
self.api.PostUpdate(text, in_reply_to_status_id=self.reply_id)
|
|
|
|
self.reply_id = None
|
2010-04-07 18:17:57 +00:00
|
|
|
|
|
|
|
|
2010-04-08 20:39:56 +00:00
|
|
|
def update_status_callback(self, widget):
|
2010-04-07 18:17:57 +00:00
|
|
|
self.update_status()
|
2010-04-07 18:07:26 +00:00
|
|
|
|
2010-04-07 03:05:51 +00:00
|
|
|
|
2010-04-09 21:45:21 +00:00
|
|
|
def text_watcher(self, widget):
|
|
|
|
''' Watch text entered on the update_entry, update things '''
|
|
|
|
text_len = self.update_entry.get_text_length()
|
|
|
|
new_count = str(text_len) + "/140"
|
2010-04-08 20:39:56 +00:00
|
|
|
self.update_count.set_label(new_count)
|
|
|
|
|
2010-04-09 21:45:21 +00:00
|
|
|
# If reply_id is set, unset it if we have removed the @ symbol
|
|
|
|
if self.reply_id is not None and not MyTwitter.at_check.match(self.update_entry.get_text()):
|
|
|
|
self.reply_id = None
|
|
|
|
|
2010-04-08 20:39:56 +00:00
|
|
|
|
|
|
|
def gtk_main_quit(self, widget):
|
|
|
|
gtk.main_quit()
|
|
|
|
|
|
|
|
|
|
|
|
def on_about(self, widget):
|
2010-04-09 21:45:21 +00:00
|
|
|
print "STUB: help->about not yet implemented"
|
2010-04-08 19:12:57 +00:00
|
|
|
|
2010-04-07 19:54:54 +00:00
|
|
|
|
2010-04-08 21:07:27 +00:00
|
|
|
def on_list_select_changed(self, widget):
|
|
|
|
self.list = widget.get_active_text()
|
|
|
|
self.update_window()
|
2010-04-07 03:05:51 +00:00
|
|
|
|
2010-04-09 04:56:51 +00:00
|
|
|
|
2010-04-09 05:08:32 +00:00
|
|
|
def on_reply(self, widget):
|
|
|
|
self.update_entry.set_text('@' + widget.screen_name + ' ')
|
|
|
|
self.reply_id = widget.id
|
2010-04-09 21:45:21 +00:00
|
|
|
self.update_entry.grab_focus()
|
2010-04-09 04:56:51 +00:00
|
|
|
|
|
|
|
|
2010-04-09 05:08:32 +00:00
|
|
|
def on_retweet(self, widget):
|
2010-04-09 22:18:34 +00:00
|
|
|
self.api.Retweet(widget.id)
|
2010-04-09 04:56:51 +00:00
|
|
|
|
|
|
|
### end class MyTwitter
|
|
|
|
|
|
|
|
|
|
|
|
class TweetBox(gtk.VBox):
|
|
|
|
|
|
|
|
'''
|
|
|
|
GUI for displaying one tweet and associated buttons
|
|
|
|
|
|
|
|
Also stores the data necessary for replying or retweeting (id, screen name)
|
|
|
|
'''
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
gtk.VBox.__init__(self)
|
|
|
|
|
|
|
|
self.screen_name = None
|
|
|
|
self.id = None
|
|
|
|
|
|
|
|
self.init_widgets()
|
|
|
|
|
|
|
|
|
|
|
|
def init_widgets(self):
|
|
|
|
## Build the header
|
|
|
|
self.header = gtk.Label()
|
|
|
|
label_eb = gtk.EventBox()
|
|
|
|
label_eb.add(self.header)
|
|
|
|
self.pack_start(label_eb)
|
|
|
|
|
|
|
|
# Set the header's properties
|
|
|
|
label_eb.modify_text(gtk.STATE_NORMAL,gtk.gdk.color_parse("#ffffff"))
|
|
|
|
label_eb.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse("#8888ff"))
|
|
|
|
self.header.set_alignment(0.0, 0.0)
|
|
|
|
self.header.set_selectable(True)
|
|
|
|
self.header.set_line_wrap(True)
|
|
|
|
|
|
|
|
## Build the text
|
|
|
|
self.text = gtk.Label()
|
|
|
|
text_align = gtk.Alignment()
|
|
|
|
text_align.add(self.text)
|
|
|
|
self.pack_start(text_align)
|
|
|
|
|
|
|
|
# Set the text's properties
|
|
|
|
text_align.set_padding(2, 10, 3, 0)
|
|
|
|
self.text.set_alignment(0.0, 0.0)
|
|
|
|
self.text.set_selectable(True)
|
|
|
|
self.text.set_line_wrap(True)
|
|
|
|
|
|
|
|
button_box = gtk.HBox()
|
|
|
|
self.pack_start(button_box)
|
|
|
|
|
2010-04-09 21:45:21 +00:00
|
|
|
reply_button = gtk.Button("Reply")
|
|
|
|
button_box.pack_start(reply_button, expand=False)
|
|
|
|
reply_button.connect("clicked", self.on_reply_clicked)
|
|
|
|
retweet_button = gtk.Button("Retweet")
|
|
|
|
button_box.pack_start(retweet_button, expand=False)
|
|
|
|
retweet_button.connect("clicked", self.on_retweet_clicked)
|
2010-04-09 04:56:51 +00:00
|
|
|
|
|
|
|
|
|
|
|
def set_status(self, status):
|
|
|
|
timezone = dateutil.tz.gettz()
|
|
|
|
time_format = "%Y.%m.%d %H:%M:%S %Z"
|
|
|
|
|
|
|
|
# Get the user object
|
|
|
|
user = status.user
|
|
|
|
|
|
|
|
# Get user's data for retweeting / replying
|
|
|
|
self.screen_name = user.screen_name
|
|
|
|
self.id = status.id
|
|
|
|
|
|
|
|
# ... and a formatted timestamp
|
|
|
|
timestamp = datetime.datetime.strptime(status.created_at, "%a %b %d %H:%M:%S +0000 %Y")
|
|
|
|
timestamp = timestamp.replace(tzinfo=dateutil.tz.gettz('UTC'))
|
|
|
|
timestring = timestamp.astimezone(timezone).strftime(time_format)
|
|
|
|
|
|
|
|
# Set the header
|
|
|
|
self.header.set_markup(user.name + " (" + user.screen_name + ") " + timestring)
|
|
|
|
|
|
|
|
# and the text
|
|
|
|
self.text.set_markup(status.text)
|
2010-04-09 21:45:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
def on_reply_clicked(self, widget):
|
|
|
|
self.emit('reply')
|
|
|
|
|
|
|
|
|
|
|
|
def on_retweet_clicked(self, widget):
|
|
|
|
self.emit('retweet')
|
2010-04-09 04:56:51 +00:00
|
|
|
|
|
|
|
# end class TweetBox
|
|
|
|
|
2010-04-07 03:05:51 +00:00
|
|
|
|
|
|
|
|
|
|
|
# main
|
2010-04-09 21:45:21 +00:00
|
|
|
|
|
|
|
# Create custom events for TweetBox
|
|
|
|
gobject.signal_new("reply", TweetBox,
|
|
|
|
gobject.SIGNAL_RUN_LAST,
|
|
|
|
gobject.TYPE_NONE, ())
|
|
|
|
gobject.signal_new("retweet", TweetBox,
|
|
|
|
gobject.SIGNAL_RUN_LAST,
|
|
|
|
gobject.TYPE_NONE, ())
|
|
|
|
|
2010-04-08 20:39:56 +00:00
|
|
|
my_twitter = MyTwitter()
|
|
|
|
gtk.main()
|