#!/usr/bin/python # # Custom twitter client... mostly for learning Python import sys, twitter, ConfigParser, os, datetime, dateutil.tz, gtk, gtk.glade, gobject, re, subprocess class MyTwitter(): """ Display Tweets, post to twitter """ # Precompile a regex for searching for @ at the beginning of a string at_check = re.compile('^@') def __init__(self): config = ConfigParser.ConfigParser() config.read(os.path.expanduser("~/.mytwitter")) self.username = config.get('global', 'username') self.password = config.get('global', 'password') self.num_entries = int(config.get('global', 'entries')) if self.num_entries < 20: self.num_entries = 20 self.refresh_time = int(config.get('global', 'refreshtime')) if self.refresh_time < 10: self.refresh_time = 10 self.tweets = [] self.list = None self.reply_id = None # Authenticate with twitter, set up the API object self.api = twitter.Api(username=self.username, password=self.password) # Load up all the GUI stuff self.init_user_interface('./default.glade') self.init_widgets() def init_user_interface(self, path_to_skin): self.widget_tree=gtk.glade.XML(path_to_skin, "window") self.widget_tree.signal_autoconnect(self) def init_widgets(self): self.list_select = self.widget_tree.get_widget('list_select') 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') # Build us some labels... for i in range(0, self.num_entries): self.tweets.append(TweetBox()) self.tweet_box.pack_start(self.tweets[i]) self.tweets[i].connect('reply', self.on_reply) self.tweets[i].connect('retweet', self.on_retweet) self.tweet_box.show_all() # 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) # Timer to update periodically self.update_window() gobject.timeout_add(self.refresh_time * 1000, self.update_window) def update_window(self): 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) for i in range(0, self.num_entries): if i < len(statuses): self.tweets[i].set_status(statuses[i]) else: self.tweets[i].clear_status() def update_window_callback(self, widget): self.update_window() def update_status(self): text = self.update_entry.get_text() self.update_entry.set_text("") self.api.PostUpdate(text, in_reply_to_status_id=self.reply_id) self.reply_id = None def update_status_callback(self, widget): self.update_status() 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" self.update_count.set_label(new_count) # 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 def gtk_main_quit(self, widget): gtk.main_quit() def on_about(self, widget): print "STUB: help->about not yet implemented" def on_list_select_changed(self, widget): self.list = widget.get_active_text() self.update_window() def on_reply(self, widget): self.update_entry.set_text('@' + widget.screen_name + ' ') self.reply_id = widget.id self.update_entry.grab_focus() def on_retweet(self, widget): self.api.PostRetweet(widget.id) ### 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) self.text.connect('activate-link', self.on_url_clicked) button_box = gtk.HBox() self.pack_start(button_box) 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) 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 new_text = status.text new_text = re.sub('& ', '& ', new_text) new_text = re.sub(r"(http://.*?)( |$)", r'\1\2', new_text) self.text.set_markup(new_text) def clear_status(self): self.header.set_markup('') self.text.set_markup('') self.screen_name = None self.id = None def on_reply_clicked(self, widget): self.emit('reply') def on_retweet_clicked(self, widget): self.emit('retweet') def on_url_clicked(self, widget): # fixme: for now, hard code firefox, since that's what I use # Eventually make this configgable subprocess.call('firefox ' + self.text.get_current_uri()) # end class TweetBox # main # 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, ()) my_twitter = MyTwitter() gtk.main()