def __init__(self, token, secret): self.session = Twython(base64.b64decode(APP_KEY), base64.b64decode(APP_SECRET), token, secret) try: self.authenticated_user = self.session.verify_credentials() except TwythonError: print(TwythonError.msg) self.tweet_count = Settings().get('tweet_count')
def do_activate(self, _): # birdie is not activated if not self.window: # widgets self.home_tweet_list = TweetList() self.activity_list = TweetList() self.dm_inbox_list = TweetList() self.dm_outbox_list = TweetList() self.tweets_list = TweetList() self.favorites_list = TweetList() self.search_list = TweetList() self.users_list = TweetList() # start twitter.com availability thread self.network = Network() self.network.connect_signal("twitter-down", self.twitter_down) self.network.connect_signal("twitter-up", self.twitter_up) self.network.start() # start downloader thread self.downloader = Download() self.downloader.start() # Initialize notification manager self.notification = NotificationManager() # initialize settings self.settings = Settings() self.tweet_count = self.settings.get('tweet_count') # start datetime updater thread self.update_tl_thread = Thread( target=self.update_datetimes, args=(60,)) self.update_tl_thread.daemon = True # initialize the top level window self.window = MainWindow(self.settings) self.window.welcome.connect_signal("account-added", lambda x, y: ( self.save_new_account(x, y), self.on_initialized(x, y) )) self.add_window(self.window) # connect exit signal self.window.connect_signal("exit", self.on_exit) # status icon object if self.settings.get("use_status_icon") == "true": self.tray = StatusIcon() self.tray.connect_signal( "new-tweet-compose", self.on_new_tweet_composer) self.tray.connect_signal( "toggle-window-visibility", self.window.toggle_visibility) self.tray.connect_signal("on-exit", self.window.on_exit_event) # hide, if settings indicate that birdie should start minimized if self.settings.get("start_minimized") == "true": self.window.hide() # add lists to scrolled views self.window.home_scrolled.add(self.home_tweet_list) self.window.activity_scrolled.add(self.activity_list) self.window.dm_inbox_scrolled.add(self.dm_inbox_list) self.window.dm_outbox_scrolled.add(self.dm_outbox_list) self.window.tweets_scrolled.add(self.tweets_list) self.window.favorites_scrolled.add(self.favorites_list) self.window.search_scrolled.add(self.search_list) self.window.users_scrolled.add(self.users_list) self.window.users_box.connect_signal("follow", self.on_follow_th) self.window.users_box.connect_signal("unfollow", self.on_unfollow_th) # load accounts info self.accounts = load_pickle(BIRDIE_LOCAL_SHARE_PATH + "accounts.obj") self.window.add_account_menu( self.accounts, self.set_active_account) for account in self.accounts: if account.active: self.active_account = account self.on_initialized( self.active_account.token, self.active_account.secret) # load cached users self.users = load_users(BIRDIE_LOCAL_SHARE_PATH + "users.obj") # start thread to update timedates as daemon self.update_tl_thread.start() # if birdie is already activated self.window.present() for argl in self.args: if not hasattr(argl, 'url'): continue for arg in argl.url: if arg: if "birdie://user/" in arg: user = arg.replace("birdie://user/", "") if "/" in user: user = user.replace("/", "") if "@" in user: user = user.replace("@", "") self.show_profile(user) elif "birdie://hashtag/" in arg: hashtag = arg.replace("birdie://hashtag/", "") if "/" in hashtag: hashtag = hashtag.replace("/", "") if "" in hashtag: hashtag = hashtag.replace("%23", "") self.on_search(None, "#" + hashtag)
class Application(Gtk.Application): def __init__(self): Gtk.Application.__init__(self, application_id="org.birdieapp", flags=Gio.ApplicationFlags. HANDLES_COMMAND_LINE) GLib.set_application_name("Birdie") self.args = None self.window = None self.accounts = None self.active_account = None self.twitter = None self.home_stream_thread = None self.tweet_dialog = None self.last_search_txt = None self.users = None # create required directories if they do not exist check_required_dirs() def do_startup(self): Gtk.Application.do_startup(self) def do_command_line(self, args): ''' Gtk.Application command line handler called if Gio.ApplicationFlags.HANDLES_COMMAND_LINE is set. must call the self.do_activate() to get the application up and running ''' Gtk.Application.do_command_line(self, args) parser = argparse.ArgumentParser(prog='birdie') # add an option #parser.add_argument('-c', '--color', action='store_true') parser.add_argument('url', default=[], nargs='*') self.args = parser.parse_known_args(args.get_arguments()[1:]) self.do_activate(None) def do_activate(self, _): # birdie is not activated if not self.window: # widgets self.home_tweet_list = TweetList() self.activity_list = TweetList() self.dm_inbox_list = TweetList() self.dm_outbox_list = TweetList() self.tweets_list = TweetList() self.favorites_list = TweetList() self.search_list = TweetList() self.users_list = TweetList() # start twitter.com availability thread self.network = Network() self.network.connect_signal("twitter-down", self.twitter_down) self.network.connect_signal("twitter-up", self.twitter_up) self.network.start() # start downloader thread self.downloader = Download() self.downloader.start() # Initialize notification manager self.notification = NotificationManager() # initialize settings self.settings = Settings() self.tweet_count = self.settings.get('tweet_count') # start datetime updater thread self.update_tl_thread = Thread( target=self.update_datetimes, args=(60,)) self.update_tl_thread.daemon = True # initialize the top level window self.window = MainWindow(self.settings) self.window.welcome.connect_signal("account-added", lambda x, y: ( self.save_new_account(x, y), self.on_initialized(x, y) )) self.add_window(self.window) # connect exit signal self.window.connect_signal("exit", self.on_exit) # status icon object if self.settings.get("use_status_icon") == "true": self.tray = StatusIcon() self.tray.connect_signal( "new-tweet-compose", self.on_new_tweet_composer) self.tray.connect_signal( "toggle-window-visibility", self.window.toggle_visibility) self.tray.connect_signal("on-exit", self.window.on_exit_event) # hide, if settings indicate that birdie should start minimized if self.settings.get("start_minimized") == "true": self.window.hide() # add lists to scrolled views self.window.home_scrolled.add(self.home_tweet_list) self.window.activity_scrolled.add(self.activity_list) self.window.dm_inbox_scrolled.add(self.dm_inbox_list) self.window.dm_outbox_scrolled.add(self.dm_outbox_list) self.window.tweets_scrolled.add(self.tweets_list) self.window.favorites_scrolled.add(self.favorites_list) self.window.search_scrolled.add(self.search_list) self.window.users_scrolled.add(self.users_list) self.window.users_box.connect_signal("follow", self.on_follow_th) self.window.users_box.connect_signal("unfollow", self.on_unfollow_th) # load accounts info self.accounts = load_pickle(BIRDIE_LOCAL_SHARE_PATH + "accounts.obj") self.window.add_account_menu( self.accounts, self.set_active_account) for account in self.accounts: if account.active: self.active_account = account self.on_initialized( self.active_account.token, self.active_account.secret) # load cached users self.users = load_users(BIRDIE_LOCAL_SHARE_PATH + "users.obj") # start thread to update timedates as daemon self.update_tl_thread.start() # if birdie is already activated self.window.present() for argl in self.args: if not hasattr(argl, 'url'): continue for arg in argl.url: if arg: if "birdie://user/" in arg: user = arg.replace("birdie://user/", "") if "/" in user: user = user.replace("/", "") if "@" in user: user = user.replace("@", "") self.show_profile(user) elif "birdie://hashtag/" in arg: hashtag = arg.replace("birdie://hashtag/", "") if "/" in hashtag: hashtag = hashtag.replace("/", "") if "" in hashtag: hashtag = hashtag.replace("%23", "") self.on_search(None, "#" + hashtag) def on_initialized(self, oauth_token, oauth_token_secret, connect_signals=True): """ Get threaded timelines and initialize the main streamer :param oauth_token: str :param oauth_token_secret: str :return: """ try: self.twitter = Twitter(oauth_token, oauth_token_secret) except TwythonError as e: error_dialog(self.window, "Twitter error", str(e)) self.twitter = None self.home_stream_thread = None return self.update_account_in_file() if connect_signals: # timelines self.home_tweet_list.more.connect( "clicked", self.get_home_timeline_th) self.activity_list.more.connect( "clicked", self.get_mentions_th) self.dm_inbox_list.more.connect("clicked", self.get_dm_th) self.dm_outbox_list.more.connect("clicked", self.get_dm_th) self.tweets_list.more.connect( "clicked", self.get_tweets_th, self.active_account.screen_name) self.favorites_list.more.connect( "clicked", self.get_favorites_th, self.active_account.screen_name) self.search_list.more.connect("clicked", self.on_search) self.users_more_tweets = self.users_list.more.connect( "clicked", self.get_tweets_th, None, self.users_list) # actions self.window.new_tweet.connect( "clicked", self.on_new_tweet_composer) self.window.connect_signal("search", self.on_search) if self.twitter.authenticated_user and self.active_account: # set profile GLib.idle_add(lambda: self.window.user_box.set(self.twitter.authenticated_user, self.active_account)) # get a fresh home timeline, before firing up the streamer self.get_home_timeline_th(None) self.home_stream_thread = self.init_streamer( self.active_account.token, self.active_account.secret) # get mentions self.get_mentions_th(None) # get dm self.get_dm_th(None) self.get_dm_outbox_th(None) # get profile self.get_tweets_th(None, self.active_account.screen_name) self.get_favorites_th(None, self.active_account.screen_name) self.window.stack.set_visible_child_name('home') # ACCOUNTS def save_new_account(self, oauth_token, oauth_token_secret): """ save a new twitter account :param oauth_token: string :param oauth_token_secret: string """ for account in self.accounts: account.active = False self.active_account = Account( "", "", "", True, oauth_token, oauth_token_secret) self.accounts.append(self.active_account) write_pickle(BIRDIE_LOCAL_SHARE_PATH + "accounts.obj", self.accounts) self.clean_all_lists() def update_account_in_file(self): write_to_file = False # get a fresh avatar self.downloader.add( {'url': self.twitter.authenticated_user['profile_image_url_https'], 'box': self.window.menu_btn_img, 'type': 'own'}) if (self.twitter.authenticated_user['screen_name'] != self.active_account.screen_name or self.twitter.authenticated_user['name'] != self.active_account.name or os.path.basename(self.twitter.authenticated_user[ 'profile_image_url_https']) != self.active_account.avatar): self.active_account.screen_name = self.twitter.authenticated_user[ 'screen_name'] self.active_account.name = self.twitter.authenticated_user['name'] self.active_account.avatar = os.path.basename( self.twitter.authenticated_user['profile_image_url_https']) write_to_file = True if write_to_file: write_pickle( BIRDIE_LOCAL_SHARE_PATH + "accounts.obj", self.accounts) self.window.add_account_menu({self.active_account}, self.set_active_account) def set_active_account(self, screen_name): """Change the active account""" for account in self.accounts: if account.screen_name == screen_name: account.active = True self.active_account = account else: account.active = False self.clean_all_lists() write_pickle(BIRDIE_LOCAL_SHARE_PATH + "accounts.obj", self.accounts) # disconnect streaming self.streamer.disconnect() self.on_initialized( self.active_account.token, self.active_account.secret, connect_signals=False) def clean_all_lists(self): self.home_tweet_list.empty() self.activity_list.empty() self.dm_inbox_list.empty() self.dm_outbox_list.empty() self.tweets_list.empty() self.favorites_list.empty() self.search_list.empty() self.users_list.empty() # STREAMER def init_streamer(self, oauth_token, oauth_token_secret, stream='user'): """ Initializes a user streamer object :param oauth_token: string :param oauth_token_secret: string :param stream: string - 'user' or 'site' :return: """ self.streamer = BirdieStreamer(base64.b64decode(APP_KEY), base64.b64decode(APP_SECRET), oauth_token, oauth_token_secret) self.streamer.connect_signal("tweet-received", self.on_new_tweet_received) self.streamer.connect_signal("event-received", self.on_event_received) self.streamer.connect_signal("dm-received", self.on_new_dm_received) try: stream_thread = Thread(target=getattr(self.streamer, stream)) stream_thread.daemon = True stream_thread.start() return stream_thread except Thread: print("Error: unable to start thread") # HOME TIMELINE def get_home_timeline_th(self, _): try: th = Thread( target=self.get_home_timeline, args=(self.get_home_timeline_cb,)) th.start() except Thread: print("Error: unable to start thread") def get_home_timeline(self, cb): try: if self.home_tweet_list.oldest_id > 0: data = self.twitter.session.get_home_timeline( max_id=self.home_tweet_list.oldest_id - 1, count=self.tweet_count) else: data = self.twitter.session.get_home_timeline( count=self.tweet_count) except TwythonError as e: self.twitter_error(e) return if len(data) > 0: index = len(data) - 1 self.home_tweet_list.oldest_id = data[index]['id'] cb(data) def get_home_timeline_cb(self, data): for x in range(len(data)): self.on_new_tweet_received(data[x], False) # MENTIONS def get_mentions_th(self, _): try: mentions_th = Thread( target=self.get_mentions, args=(self.get_mentions_cb,)) mentions_th.start() except Thread: print("Error: unable to start thread") def get_mentions(self, cb): try: if self.activity_list.oldest_id > 0: data = self.twitter.session.get_mentions_timeline( max_id=self.activity_list.oldest_id - 1, count=self.tweet_count) else: data = self.twitter.session.get_mentions_timeline( count=self.tweet_count) except TwythonError as e: self.twitter_error(e) return if len(data) > 0: index = len(data) - 1 self.activity_list.oldest_id = data[index]['id'] cb(data) def get_mentions_cb(self, data): for x in range(len(data)): self.add_to_list(data[x], self.activity_list, False) # INBOX DIRECT MESSAGES def get_dm_th(self, _): try: dm_th = Thread( target=self.get_dm, args=(self.get_dm_cb,)) dm_th.start() except Thread: print("Error: unable to start thread") def get_dm(self, cb): try: if self.dm_inbox_list.oldest_id > 0: data = self.twitter.session.get_direct_messages( max_id=self.dm_inbox_list.oldest_id - 1, count=self.tweet_count) else: data = self.twitter.session.get_direct_messages( count=self.tweet_count) except TwythonError as e: self.twitter_error(e) return if len(data) > 0: index = len(data) - 1 self.dm_inbox_list.oldest_id = data[index]['id'] cb(data) def get_dm_cb(self, data): for x in range(len(data)): self.add_to_list(data[x], self.dm_inbox_list, False) # OUTBOX DIRECT MESSAGES def get_dm_outbox_th(self, _): try: dm_outbox_th = Thread( target=self.get_dm_outbox, args=(self.get_dm_outbox_cb,)) dm_outbox_th.start() except Thread: print("Error: unable to start thread") def get_dm_outbox(self, cb): try: if self.dm_outbox_list.oldest_id > 0: data = self.twitter.session.get_sent_messages( max_id=self.dm_inbox_list.oldest_id - 1, count=self.tweet_count) else: data = self.twitter.session.get_sent_messages( count=self.tweet_count) except TwythonError as e: self.twitter_error(e) return if len(data) > 0: index = len(data) - 1 self.dm_outbox_list.oldest_id = data[index]['id'] cb(data) def get_dm_outbox_cb(self, data): for x in range(len(data)): self.add_to_list(data[x], self.dm_outbox_list, False) # OWN TWEETS def get_tweets_th(self, _, screen_name, list=None): try: tweets_th = Thread( target=self.get_tweets, args=(self.get_tweets_cb, screen_name, list)) tweets_th.start() except Thread: print("Error: unable to start thread") def get_tweets(self, cb, screen_name, list=None): if list is None: list = self.tweets_list try: if list.oldest_id > 0: data = self.twitter.session.get_user_timeline( screen_name=screen_name, max_id=list.oldest_id - 1, count=self.tweet_count) else: data = self.twitter.session.get_user_timeline( screen_name=screen_name, count=self.tweet_count) except TwythonError as e: self.twitter_error(e) return if len(data) > 0: index = len(data) - 1 list.oldest_id = data[index]['id'] cb(data, list) def get_tweets_cb(self, data, list=None): if list is None: list = self.tweets_list for x in range(len(data)): self.add_to_list(data[x], list, False) # FAVORITES def get_favorites_th(self, _, screen_name): try: favorites = Thread( target=self.get_favorites, args=(self.get_favorites_cb, screen_name,)) favorites.start() except Thread: print("Error: unable to start thread") return None def get_favorites(self, cb, screen_name): try: if self.favorites_list.oldest_id > 0: data = self.twitter.session.get_favorites( screen_name=screen_name, max_id=self.favorites_list.oldest_id - 1, count=self.tweet_count) else: data = self.twitter.session.get_favorites( screen_name=screen_name, count=self.tweet_count) except TwythonError as e: self.twitter_error(e) return if len(data) > 0: index = len(data) - 1 self.favorites_list.oldest_id = data[index]['id'] cb(data) def get_favorites_cb(self, data): for x in range(len(data)): self.add_to_list(data[x], self.favorites_list, False) # ACTIONS CALLBACKS def on_new_tweet_composer(self, _): if self.window.dm.get_active(): dm = True else: dm = False self.tweet_dialog = TweetDialog( '', [], self.window, self.active_account.avatar, dm, None, False, self.users) self.tweet_dialog.connect_signal( "new-tweet-dispatcher", lambda x: self.on_new_tweet_dispatcher(x)) self.tweet_dialog.connect_signal( "new-dm-dispatcher", lambda x: self.on_new_dm_dispatcher(x)) def on_new_tweet_dispatcher(self, data): self.tweet_dialog.destroy() self.twitter.update_status(data) def on_new_dm_dispatcher(self, data): self.tweet_dialog.destroy() self.twitter.send_dm_status(data) def on_event_received(self, data, stream=True): if 'event' in data and data['event'] == 'favorite'\ or data['event'] == 'unfavorite': if data['event'] == 'favorite': fav = _("favorited") else: fav = _("unfavorited") screen_name = data['source']['screen_name'] # if own favorite, add to list and return if screen_name == self.active_account.screen_name: #print data if data['event'] == 'favorite': data = data['target_object'] data['favorited'] = True self.add_to_list(data, self.favorites_list, stream) return name = data['source']['name'] profile = data['source']['profile_image_url'] data = data['target_object'] data['user']['name'] = name + " " + fav + " " + _("your tweet") data['user']['profile_image_url'] = profile data['user']['screen_name'] = screen_name self.add_to_list(data, self.activity_list, stream) if stream and self.settings.get("notify_events") == 'true': self.notification.notify(name + " " + fav + " " + _("a Tweet"), strip_html(data['text']), BIRDIE_CACHE_PATH + os.path.basename(profile)) if 'event' in data and data['event'] == 'follow' \ and data['source']['screen_name'] != self.active_account.screen_name: data['name'] = data['source']['name'] data['screen_name'] = data['source']['screen_name'] data['profile_image_url'] = data['source']['profile_image_url'] self.add_to_event(data, self.activity_list, stream) if stream and self.settings.get("notify_events") == 'true': self.notification.notify(_("You've got a new follower!'"), data['source']['name'] + " " + _("is now following you on Twitter"), BIRDIE_CACHE_PATH + os.path.basename(data['source']['profile_image_url'])) def on_new_tweet_received(self, data, stream=True): # add user to cache if self.users: self.users.add(data['user']['screen_name']) # we've got a mention if self.active_account.screen_name in data['text']: self.on_new_mention_received(data, stream) return self.add_to_list(data, self.home_tweet_list, stream) if stream and self.settings.get("notify_tweets") == 'true': self.notification.notify(_("New tweet from ") + data['user']['name'], strip_html(data['text']), BIRDIE_CACHE_PATH + os.path.basename(data['user']['profile_image_url'])) def on_new_mention_received(self, data, stream=True): if stream: self.add_to_list(data, self.activity_list, stream) self.window.mentions_img.set_from_icon_name('twitter-mentions-urgent', Gtk.IconSize.LARGE_TOOLBAR) if stream and self.settings.get("notify_mentions") == 'true': self.notification.notify(_("New mention from ") + data['user']['name'], strip_html(data['text']), BIRDIE_CACHE_PATH + os.path.basename(data['user']['profile_image_url']), urgency=2) def on_new_search_received(self, data, stream=True): self.add_to_list(data, self.search_list, stream) def on_new_dm_received(self, data, stream=True): box = TweetBox(data, self.active_account) box.connect_signal( "tweet-favorited", lambda x, y, z: self.on_tweet_favorited(x, y, z)) box.connect_signal( "dm-destroy", lambda x, y: self.on_dm_destroy(x, y)) box.connect_signal("reply", lambda x: self.on_reply(x)) if data['sender']['screen_name'] == self.active_account.screen_name: GLib.idle_add(lambda: self.dm_outbox_list.append(box, stream)) else: GLib.idle_add(lambda: self.dm_inbox_list.append(box, stream)) self.downloader.add( {'url': data['sender']['profile_image_url'], 'box': box, 'type': 'avatar'}) try: for media in data['entities']['media']: self.downloader.add( {'url': media['media_url_https'], 'box': box, 'type': 'media'}) except: pass self.window.dm_img.set_from_icon_name('twitter-dm-urgent', Gtk.IconSize.LARGE_TOOLBAR) if stream and self.settings.get("notify_dm") == 'true': if data['sender']['screen_name'] != \ self.active_account.screen_name: self.notification.notify(_("New Direct Message from ") + data['user']['name'], strip_html(data['text']), BIRDIE_CACHE_PATH + os.path.basename(data['user']['profile_image_url']), urgency=2) def on_tweet_favorited(self, tweetbox, tweet_id, favorite): try: tweet_favorited_th = Thread( target=self.twitter.create_favorite(tweet_id, favorite)) tweet_favorited_th.callback = tweetbox.on_favorite_cb() tweet_favorited_th.start() except Thread: print("Error: unable to start thread") def on_tweet_destroy(self, tweetbox, tweet_id): try: tweet_destroy_th = Thread( target=self.twitter.destroy_tweet(tweet_id)) tweet_destroy_th.callback = tweetbox.on_tweet_destroy_cb() tweet_destroy_th.start() except Thread: print("Error: unable to start thread") def on_dm_destroy(self, tweetbox, dm_id): try: dm_destroy_th = Thread( target=self.twitter.destroy_dm(dm_id)) dm_destroy_th.callback = tweetbox.on_dm_destroy_cb() dm_destroy_th.start() except Thread: print("Error: unable to start thread") def on_retweet(self, tweetbox, tweet_id): try: retweet_th = Thread(target=self.twitter.retweet(tweet_id)) retweet_th.callback = tweetbox.on_retweet_cb() retweet_th.start() except Thread: print("Error: unable to start thread") def on_retweet_quote(self, tweetbox, data): self.tweet_dialog = TweetDialog(data['user']['screen_name'], data['text'], self.window, self.active_account.avatar, False, data['id'], quote=True) self.tweet_dialog.connect_signal( "retweet-quote-dispatcher", lambda x: self.on_retweet_quote_dispatcher(x)) def on_retweet_quote_dispatcher(self, data): self.tweet_dialog.destroy() self.twitter.update_status(data) def on_reply(self, data): self.tweet_dialog = TweetDialog(data['screen_name'], data['user_mentions'], self.window, self.active_account.avatar, False, data['in_reply_to_status_id']) self.tweet_dialog.connect_signal( "new-tweet-dispatcher", lambda x: self.on_new_tweet_dispatcher(x)) def on_dm_reply(self, data): self.tweet_dialog = TweetDialog(data['screen_name'], None, self.window, self.active_account.avatar, True, data['in_reply_to_status_id']) self.tweet_dialog.connect_signal( "new-dm-dispatcher", lambda x: self.on_new_dm_dispatcher(x)) def on_search(self, _, txt=None): self.window.deselect_all_buttons() self.window.stack.set_visible_child(self.window.search_scrolled) self.window.searchbar.set_search_mode(False) if txt is None: txt = self.last_search_txt else: self.search_list.oldest_id = None self.search_list.empty() try: search_th = Thread(target=self.twitter.search, args=(txt, self.search_list.oldest_id, self.on_search_cb,)) search_th.start() except Thread: print("Error: unable to start thread") return None def on_search_cb(self, data, txt): if len(data['statuses']): data = data['statuses'] for x in range(len(data)): self.on_new_search_received(data[x], False) index = len(data) - 1 if index >= 0: self.search_list.oldest_id = data[index]['id'] self.last_search_txt = txt else: GLib.idle_add(lambda: error_dialog(self.window, _("Searching"), _("No more results."))) # FOLLOW def on_follow_th(self, screen_name): try: th = Thread(target=self.on_follow, args=(screen_name,)) th.callback = self.on_follow_cb() th.start() except Thread: print("Error: unable to start follow thread") def on_follow(self, screen_name): try: self.twitter.session.create_friendship(screen_name=screen_name) except TwythonError as e: self.twitter_error(e) def on_follow_cb(self): GLib.idle_add(lambda: self.window.users_box.toggle_follow(True)) # UNFOLLOW def on_unfollow_th(self, screen_name): try: th = Thread(target=self.on_unfollow, args=(screen_name,)) th.callback = self.on_unfollow_cb() th.start() except Thread: print("Error: unable to start unfollow thread") def on_unfollow(self, screen_name): try: self.twitter.session.destroy_friendship(screen_name=screen_name) except TwythonError as e: self.twitter_error(e) def on_unfollow_cb(self): GLib.idle_add(lambda: self.window.users_box.toggle_follow(False)) # SHOW PROFILE def show_profile(self, screen_name): self.users_list.empty() self.window.deselect_all_buttons() self.users_list.more.disconnect(self.users_more_tweets) self.users_more_tweets = self.users_list.more.connect( "clicked", self.get_tweets_th, screen_name, self.users_list) try: th = Thread(target=self.twitter.get_user, args=(screen_name, self.show_profile_cb,)) th.start() except Thread: print("Error: unable to start thread") def show_profile_cb(self, data): self.lookup_friendships_th(data) # FRIENDSHIPS def lookup_friendships_th(self, data): try: th = Thread(target=self.lookup_friendships, args=(data, self.lookup_friendships_cb,)) th.start() except Thread: print("Error: unable to start lookup friendship thread") def lookup_friendships(self, data, cb): try: fs_data = self.twitter.session.lookup_friendships( screen_name=data['screen_name']) except TwythonError as e: self.twitter_error(e) return self.lookup_friendships_cb(data, fs_data) def lookup_friendships_cb(self, data, fs_data): GLib.idle_add(lambda: self.window.stack.set_visible_child( self.window.users_profile_box)) self.window.users_box.screen_name = data['screen_name'] GLib.idle_add(lambda: self.window.users_box.set(data, self.active_account, fs_data)) self.get_tweets(self.get_tweets_cb, data['screen_name'], self.users_list) # HELPERS def add_to_event(self, data, tweet_list, stream): box = ActivityBox(data, self.active_account) GLib.idle_add(lambda: tweet_list.append(box, stream)) def add_to_list(self, data, tweet_list, stream): """ Create a TweetBox and add it to the TweetList :param data: BirdieStreamer data obj :param tweet_list: TweetList obj :param stream: bool """ # retweets if 'retweeted_status' in data: data['retweeted_status']['retweeted_by'] = data['user']['name'] data['retweeted_status'][ 'retweeted_by_screen_name'] = data['user']['screen_name'] data = data['retweeted_status'] box = TweetBox(data, self.active_account) box.connect_signal( "tweet-favorited", lambda x, y, z: self.on_tweet_favorited(x, y, z)) box.connect_signal( "tweet-destroy", lambda x, y: self.on_tweet_destroy(x, y)) box.connect_signal( "dm-destroy", lambda x, y: self.on_dm_destroy(x, y)) box.connect_signal("retweet", lambda x, y: self.on_retweet(x, y)) box.connect_signal("retweet-quote", lambda x, y: self.on_retweet_quote(x, y)) box.connect_signal("reply", lambda x: self.on_reply(x)) box.connect_signal("dm-reply", lambda x: self.on_dm_reply(x)) box.connect_signal("update-favorites", self.update_favorites) # dms if tweet_list == self.dm_inbox_list or \ tweet_list == self.dm_outbox_list: profile_image_url = data['sender']['profile_image_url_https'] else: profile_image_url = data['user']['profile_image_url'] GLib.idle_add(lambda: tweet_list.append(box, stream)) self.downloader.add( {'url': profile_image_url, 'box': box, 'type': 'avatar'}) if tweet_list != self.dm_inbox_list and \ tweet_list != self.dm_outbox_list: try: for media in data['entities']['media']: self.downloader.add( {'url': media['media_url_https'], 'box': box, 'type': 'media'}) except: pass # trying to catch imgur images try: for media in data['entities']['urls']: if "imgur.com" in media['expanded_url']: media['expanded_url'] = media['expanded_url'].replace( "http://imgur.com/" + os.path.basename(media['expanded_url']), "http://i.imgur.com/" + os.path.basename(media['expanded_url']) + '.jpg') self.downloader.add( {'url': media['expanded_url'], 'box': box, 'type': 'media'}) except: pass # trying to catch youtube video try: for media in data['entities']['urls']: if "youtube.com" in media['expanded_url'] or \ "youtu.be" in media['expanded_url']: if "youtu.be" in media['expanded_url']: media['expanded_url'] = \ media['expanded_url'].replace("youtu.be/", "youtube.com/watch?v=") youtube_id = get_youtube_id(media['expanded_url']) youtube_thumb = "http://i3.ytimg.com/vi/" + \ youtube_id + "/mqdefault.jpg" self.downloader.add( {'url': youtube_thumb, 'box': box, 'type': 'youtube', 'id': youtube_id}) except: pass def update_datetimes(self, n): while True: GLib.idle_add(lambda: self.home_tweet_list.update_datetimes()) GLib.idle_add(lambda: self.activity_list.update_datetimes()) GLib.idle_add(lambda: self.dm_inbox_list.update_datetimes()) GLib.idle_add(lambda: self.dm_outbox_list.update_datetimes()) GLib.idle_add(lambda: self.search_list.update_datetimes()) time.sleep(n) def update_favorites(self, box, tweet_id): self.home_tweet_list.update_favorites(box, tweet_id) self.activity_list.update_favorites(box, tweet_id) self.tweets_list.update_favorites(box, tweet_id) self.search_list.update_favorites(box, tweet_id) self.users_list.update_favorites(box, tweet_id) self.favorites_list.remove_favorite(box, tweet_id) def twitter_down(self): self.window.twitter_down() def twitter_up(self): self.window.twitter_up() def twitter_error(self, e): GLib.idle_add(lambda: error_dialog(self.window, "Twitter error", e.message)) def on_exit(self): self.window.store_geometry() self.settings.save() write_pickle(BIRDIE_LOCAL_SHARE_PATH + "users.obj", self.users) self.window.destroy()
def __init__(self, settings): super(MainWindow, self).__init__() super(MainWindow, self).init_signals() self.settings = Settings() self.connect("delete-event", self.on_delete_event) self.set_size_request(580, 600) self.set_wmclass (APP_NAME, APP_NAME) self.set_title (APP_NAME) self.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK) # header_bar self.header_bar = Gtk.HeaderBar() self.header_bar.set_title(APP_NAME) self.set_for_desktop_environment() # widgets self.home_scrolled = Gtk.ScrolledWindow(None, None) self.activity_scrolled = Gtk.ScrolledWindow(None, None) self.dm_inbox_scrolled = Gtk.ScrolledWindow(None, None) self.dm_outbox_scrolled = Gtk.ScrolledWindow(None, None) self.tweets_scrolled = Gtk.ScrolledWindow(None, None) self.favorites_scrolled = Gtk.ScrolledWindow(None, None) self.lists_scrolled = Gtk.ScrolledWindow(None, None) self.search_scrolled = Gtk.ScrolledWindow(None, None) self.users_scrolled = Gtk.ScrolledWindow(None, None) self.home = Gtk.RadioToolButton.new(group=None) self.home_img = Gtk.Image() self.mentions = Gtk.RadioToolButton.new_from_widget(group=self.home) self.mentions_img = Gtk.Image() self.mentions_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.stack = Gtk.Stack() self.dm_stack = Gtk.Stack() self.activity_stack = Gtk.Stack() self.profile_stack = Gtk.Stack() self.users_profile_stack = Gtk.Stack() self.profile = Gtk.RadioToolButton.new_from_widget(group=self.home) self.profile_img = Gtk.Image() self.users_profile_img = Gtk.Image() self.menu = Gtk.Menu() self.menu_btn = Gtk.MenuButton() self.menu_btn_img = Gtk.Image() self.menu_btn.set_image(self.menu_btn_img) self.search_box = Gtk.Box() self.searchbar = Gtk.SearchBar() self.search_entry = Gtk.SearchEntry() self.search = Gtk.ToggleToolButton.new() self.search_img = Gtk.Image() self.dm = Gtk.RadioToolButton.new_from_widget(group=self.home) self.dm_img = Gtk.Image() self.inactive_menu = Gtk.RadioToolButton.new_from_widget(group=self.home) self.centered_toolbar = Gtk.Box() self.new_tweet = Gtk.ToolButton() self.new_tweet_img = Gtk.Image() self.profile_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.welcome = Welcome() # settings self.settings = settings self.restore_geometry() # prefer dark theme if key is set if self.settings.get("dark_theme") == 'true': Gtk.Settings.get_default().set_property( "gtk-application-prefer-dark-theme", True) self.fill_headerbar() self.show_all()
class MainWindow(Gtk.Window, SignalObject): """Build the main window""" __gtype_name__ = "MainWindow" vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) def __init__(self, settings): super(MainWindow, self).__init__() super(MainWindow, self).init_signals() self.settings = Settings() self.connect("delete-event", self.on_delete_event) self.set_size_request(580, 600) self.set_wmclass (APP_NAME, APP_NAME) self.set_title (APP_NAME) self.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK) # header_bar self.header_bar = Gtk.HeaderBar() self.header_bar.set_title(APP_NAME) self.set_for_desktop_environment() # widgets self.home_scrolled = Gtk.ScrolledWindow(None, None) self.activity_scrolled = Gtk.ScrolledWindow(None, None) self.dm_inbox_scrolled = Gtk.ScrolledWindow(None, None) self.dm_outbox_scrolled = Gtk.ScrolledWindow(None, None) self.tweets_scrolled = Gtk.ScrolledWindow(None, None) self.favorites_scrolled = Gtk.ScrolledWindow(None, None) self.lists_scrolled = Gtk.ScrolledWindow(None, None) self.search_scrolled = Gtk.ScrolledWindow(None, None) self.users_scrolled = Gtk.ScrolledWindow(None, None) self.home = Gtk.RadioToolButton.new(group=None) self.home_img = Gtk.Image() self.mentions = Gtk.RadioToolButton.new_from_widget(group=self.home) self.mentions_img = Gtk.Image() self.mentions_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.stack = Gtk.Stack() self.dm_stack = Gtk.Stack() self.activity_stack = Gtk.Stack() self.profile_stack = Gtk.Stack() self.users_profile_stack = Gtk.Stack() self.profile = Gtk.RadioToolButton.new_from_widget(group=self.home) self.profile_img = Gtk.Image() self.users_profile_img = Gtk.Image() self.menu = Gtk.Menu() self.menu_btn = Gtk.MenuButton() self.menu_btn_img = Gtk.Image() self.menu_btn.set_image(self.menu_btn_img) self.search_box = Gtk.Box() self.searchbar = Gtk.SearchBar() self.search_entry = Gtk.SearchEntry() self.search = Gtk.ToggleToolButton.new() self.search_img = Gtk.Image() self.dm = Gtk.RadioToolButton.new_from_widget(group=self.home) self.dm_img = Gtk.Image() self.inactive_menu = Gtk.RadioToolButton.new_from_widget(group=self.home) self.centered_toolbar = Gtk.Box() self.new_tweet = Gtk.ToolButton() self.new_tweet_img = Gtk.Image() self.profile_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.welcome = Welcome() # settings self.settings = settings self.restore_geometry() # prefer dark theme if key is set if self.settings.get("dark_theme") == 'true': Gtk.Settings.get_default().set_property( "gtk-application-prefer-dark-theme", True) self.fill_headerbar() self.show_all() def add_widget(self, widget, expanded=True): self.vbox.pack_start(widget, expanded, expanded, 0) def set_for_desktop_environment(self): """Setting titlebar for different environments""" #TODO: should we detect and act accordingly or always use the header bar? #if (detect_desktop_environment() == 'gnome' # or detect_desktop_environment() == 'cinnamon' # or self.settings.get("window_titlebar") == 'false'): # self.header_bar.set_show_close_button(True) # self.set_titlebar(self.header_bar) #else: # self.header_bar.set_show_close_button(False) # self.vbox.pack_start(self.header_bar, False, False, 0) self.header_bar.set_show_close_button(True) self.set_titlebar(self.header_bar) self.add(self.vbox) def fill_headerbar(self): """Build all the header_bar buttons""" # statusbar self.statusbar = Gtk.Statusbar.new() self.status_bar_context_id = self.statusbar.get_context_id("birdie") # new tweet button self.new_tweet_img.set_from_icon_name( NEW_TWEET_ICON_NAME, Gtk.IconSize.LARGE_TOOLBAR) self.new_tweet.set_icon_widget(self.new_tweet_img) self.new_tweet.set_tooltip_text('New Tweet') self.header_bar.pack_start(self.new_tweet) # Stack widget self.stack.set_transition_type( Gtk.StackTransitionType.SLIDE_LEFT_RIGHT) self.stack.set_transition_duration(500) # home self.home_img.set_from_icon_name(HOME_ICON_NAME, Gtk.IconSize.LARGE_TOOLBAR) self.home.set_tooltip_text(_('Home')) self.home.set_icon_widget(self.home_img) self.centered_toolbar.add(self.home) self.stack.add_named(self.home_scrolled, "home") self.home.connect("clicked", self.on_home) # mentions self.mentions_img.set_from_icon_name( MENTIONS_ICON_NAME, Gtk.IconSize.LARGE_TOOLBAR) self.mentions.set_tooltip_text(_('Activity')) self.mentions.set_icon_widget(self.mentions_img) self.centered_toolbar.add(self.mentions) self.stack.add_named(self.activity_scrolled, "activity") self.mentions.connect("clicked", self.on_mentions) # dm self.dm_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.dm_stack.add_titled( self.dm_inbox_scrolled, "inbox", _("Received")) self.dm_stack.add_titled(self.dm_outbox_scrolled, "outbox", _("Sent")) self.dm_stack_switcher = Gtk.StackSwitcher(True) self.dm_stack_switcher.set_stack(self.dm_stack) self.dm_stack_switcher.set_margin_left(12) self.dm_stack_switcher.set_margin_right(12) self.dm_box.pack_start(self.dm_stack_switcher, False, False, 12) self.dm_box.pack_start(self.dm_stack, True, True, 0) self.dm_img.set_from_icon_name(DM_ICON_NAME, Gtk.IconSize.LARGE_TOOLBAR) self.dm.set_tooltip_text(_('Direct Messages')) self.dm.set_icon_widget(self.dm_img) self.centered_toolbar.add(self.dm) self.dm.connect("clicked", self.on_dm) self.stack.add_named(self.dm_box, "dm") # own profile self.profile_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.user_box = UserBox() self.profile_box.pack_start(self.user_box, False, False, 0) self.profile_stack.add_titled(self.tweets_scrolled, "tweets", _("Tweets")) self.profile_stack.add_titled(self.favorites_scrolled, "favorites", _("Favorites")) self.profile_stack.add_titled(self.lists_scrolled, "lists", _("Lists")) self.profile_stack_switcher = Gtk.StackSwitcher(True) self.profile_stack_switcher.set_stack(self.profile_stack) self.profile_stack_switcher.set_margin_left(12) self.profile_stack_switcher.set_margin_right(12) self.profile_box.pack_start(self.profile_stack_switcher, False, False, 12) self.profile_box.pack_start(self.profile_stack, True, True, 0) self.profile_img.set_from_icon_name( PROFILE_ICON_NAME, Gtk.IconSize.LARGE_TOOLBAR) self.profile.set_tooltip_text(_('Profile')) self.profile.set_icon_widget(self.profile_img) self.centered_toolbar.add(self.profile) self.stack.add_named(self.profile_box, "profile") self.profile.connect("clicked", self.on_profile) # users profile self.users_profile_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.users_box = UserBox() self.users_profile_box.pack_start(self.users_box, False, False, 0) self.users_profile_box.pack_start(self.users_scrolled, True, True, 0) self.users_profile_img.set_from_icon_name(PROFILE_ICON_NAME, Gtk.IconSize.LARGE_TOOLBAR) self.stack.add_named(self.users_profile_box, "users_profile") # searchbar self.search_entry.connect("activate", self.on_search) self.searchbar.add(self.search_entry) self.vbox.pack_start(self.searchbar, False, False, 0) self.search_img.set_from_icon_name( SEARCH_ICON_NAME, Gtk.IconSize.LARGE_TOOLBAR) self.search.set_tooltip_text(_('Search')) self.search.set_icon_widget(self.search_img) self.search.bind_property("active", self.searchbar, "search-mode-enabled") self.centered_toolbar.add(self.search) # search box self.stack.add_named(self.search_scrolled, "search") self.header_bar.set_custom_title(self.centered_toolbar) # menu self.header_bar.pack_end(self.menu_btn) # menu items self.add_separator() self.append_menu_item( lambda x: self.on_add_account(), _("Add account")) self.append_menu_item( lambda x: self.on_remove_account(), _("Remove account")) self.add_separator() self.append_menu_item(lambda x: AboutDialog(self), _("About")) self.append_menu_item(lambda x: self.on_donations(), _("Donations")) self.append_menu_item( lambda x: self.on_exit_event(None, None), _("Exit")) self.menu_btn.set_popup(self.menu) self.menu_btn.set_relief(Gtk.ReliefStyle.NONE) self.add_widget(self.stack) self.add_widget(self.statusbar, False) self.statusbar.set_no_show_all(True) self.statusbar.hide() # welcome self.stack.add_named(self.welcome, "welcome") def add_account_menu(self, accounts, cb): for account in accounts: self.prepend_menu_item(cb, account.name, account.screen_name) def append_menu_item(self, command, title): menu_item = Gtk.MenuItem() menu_item.set_label(title) menu_item.connect("activate", command) self.menu.append(menu_item) self.menu.show_all() def prepend_menu_item(self, command, title, identifier=None): menu_item = Gtk.MenuItem() menu_item.set_label(title) menu_item.connect("activate", lambda w: command(identifier)) self.menu.prepend(menu_item) self.menu.show_all() def add_separator(self): menu_item = Gtk.SeparatorMenuItem() self.menu.append(menu_item) self.menu.show_all() def restore_geometry(self): """Restore main window geometry from dconf""" self.move(self.settings.getint("x"), self.settings.getint("y")) self.set_default_size( self.settings.getint("width"), self.settings.getint("height")) pass def store_geometry(self): """Store main window geometry in dconf""" x, y = self.get_position() width, height = self.get_size() self.settings.write("x", x) self.settings.write("y", y) self.settings.write("width", width) self.settings.write("height", height) def toggle_visibility(self): self.hide() if self.get_visible() else self.present() def toggle_sensitivity(self): self.header_bar.set_sensitive(False) \ if self.header_bar.get_sensitive() \ else self.header_bar.set_sensitive(True) def deselect_all_buttons(self): self.inactive_menu.set_active(True) # for w in self.home.get_group(): # print w #GLib.idle_add(lambda: w.set_active(False)) def twitter_down(self): self.statusbar.push(self.status_bar_context_id, _("Connectivity lost. Will keep retrying.")) self.statusbar.set_no_show_all(False) self.statusbar.show() def twitter_up(self): self.statusbar.remove_all(self.status_bar_context_id) self.statusbar.set_no_show_all(True) self.statusbar.hide() # EVENTS HANDLING def on_delete_event(self, widget, event, data=None): """Delete event signal callback""" if self.settings.get("hide_on_close") == "true": self.store_geometry() self.hide_on_delete() else: self.on_exit_event(None, None) return True def on_exit_event(self, widget, event, data=None): """Exit event signal callback""" self.emit_signal("exit") def on_add_account(self): self.welcome.on_signin(self.welcome) def on_remove_account(self): print("remove account triggered") # TODO def on_search(self, search_entry): self.emit_signal_with_args("search", (None, search_entry.get_text())) def on_home(self, _): self.stack.set_visible_child(self.home_scrolled) adj = self.home_scrolled.get_vadjustment() adj.set_value(0) def on_mentions(self, _): self.stack.set_visible_child(self.activity_scrolled) adj = self.activity_scrolled.get_vadjustment() adj.set_value(0) self.mentions_img.set_from_icon_name('twitter-mentions', Gtk.IconSize.LARGE_TOOLBAR) def on_dm(self, _): self.stack.set_visible_child(self.dm_box) adj = self.dm_inbox_scrolled.get_vadjustment() adj.set_value(0) self.dm_img.set_from_icon_name('twitter-dm', Gtk.IconSize.LARGE_TOOLBAR) def on_profile(self, _): self.stack.set_visible_child(self.profile_box) adj = self.tweets_scrolled.get_vadjustment() adj.set_value(0) def connect_add_account(self, cb): self.welcome.connect_signin(cb) @staticmethod def on_donations(): webbrowser.open('http://birdieapp.github.io/donate', new=2)
def do_activate(self, _): # birdie is not activated if not self.window: # widgets self.home_tweet_list = TweetList() self.activity_list = TweetList() self.dm_inbox_list = TweetList() self.dm_outbox_list = TweetList() self.tweets_list = TweetList() self.favorites_list = TweetList() self.search_list = TweetList() self.users_list = TweetList() # start twitter.com availability thread self.network = Network() self.network.connect_signal("twitter-down", self.twitter_down) self.network.connect_signal("twitter-up", self.twitter_up) self.network.start() # start downloader thread self.downloader = Download() self.downloader.start() # Initialize notification manager self.notification = NotificationManager() # initialize settings self.settings = Settings() self.tweet_count = self.settings.get('tweet_count') # start datetime updater thread self.update_tl_thread = Thread(target=self.update_datetimes, args=(60, )) self.update_tl_thread.daemon = True # initialize the top level window self.window = MainWindow(self.settings) self.window.welcome.connect_signal( "account-added", lambda x, y: (self.save_new_account(x, y), self.on_initialized(x, y))) self.add_window(self.window) # connect exit signal self.window.connect_signal("exit", self.on_exit) # status icon object if self.settings.get("use_status_icon") == "true": self.tray = StatusIcon() self.tray.connect_signal("new-tweet-compose", self.on_new_tweet_composer) self.tray.connect_signal("toggle-window-visibility", self.window.toggle_visibility) self.tray.connect_signal("on-exit", self.window.on_exit_event) # hide, if settings indicate that birdie should start minimized if self.settings.get("start_minimized") == "true": self.window.hide() # add lists to scrolled views self.window.home_scrolled.add(self.home_tweet_list) self.window.activity_scrolled.add(self.activity_list) self.window.dm_inbox_scrolled.add(self.dm_inbox_list) self.window.dm_outbox_scrolled.add(self.dm_outbox_list) self.window.tweets_scrolled.add(self.tweets_list) self.window.favorites_scrolled.add(self.favorites_list) self.window.search_scrolled.add(self.search_list) self.window.users_scrolled.add(self.users_list) self.window.users_box.connect_signal("follow", self.on_follow_th) self.window.users_box.connect_signal("unfollow", self.on_unfollow_th) # load accounts info self.accounts = load_pickle(BIRDIE_LOCAL_SHARE_PATH + "accounts.obj") self.window.add_account_menu(self.accounts, self.set_active_account) for account in self.accounts: if account.active: self.active_account = account self.on_initialized(self.active_account.token, self.active_account.secret) # load cached users self.users = load_users(BIRDIE_LOCAL_SHARE_PATH + "users.obj") # start thread to update timedates as daemon self.update_tl_thread.start() # if birdie is already activated self.window.present() for argl in self.args: if not hasattr(argl, 'url'): continue for arg in argl.url: if arg: if "birdie://user/" in arg: user = arg.replace("birdie://user/", "") if "/" in user: user = user.replace("/", "") if "@" in user: user = user.replace("@", "") self.show_profile(user) elif "birdie://hashtag/" in arg: hashtag = arg.replace("birdie://hashtag/", "") if "/" in hashtag: hashtag = hashtag.replace("/", "") if "" in hashtag: hashtag = hashtag.replace("%23", "") self.on_search(None, "#" + hashtag)
class Application(Gtk.Application): def __init__(self): Gtk.Application.__init__( self, application_id="org.birdieapp", flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE) GLib.set_application_name("Birdie") self.args = None self.window = None self.accounts = None self.active_account = None self.twitter = None self.home_stream_thread = None self.tweet_dialog = None self.last_search_txt = None self.users = None # create required directories if they do not exist check_required_dirs() def do_startup(self): Gtk.Application.do_startup(self) def do_command_line(self, args): ''' Gtk.Application command line handler called if Gio.ApplicationFlags.HANDLES_COMMAND_LINE is set. must call the self.do_activate() to get the application up and running ''' Gtk.Application.do_command_line(self, args) parser = argparse.ArgumentParser(prog='birdie') # add an option #parser.add_argument('-c', '--color', action='store_true') parser.add_argument('url', default=[], nargs='*') self.args = parser.parse_known_args(args.get_arguments()[1:]) self.do_activate(None) def do_activate(self, _): # birdie is not activated if not self.window: # widgets self.home_tweet_list = TweetList() self.activity_list = TweetList() self.dm_inbox_list = TweetList() self.dm_outbox_list = TweetList() self.tweets_list = TweetList() self.favorites_list = TweetList() self.search_list = TweetList() self.users_list = TweetList() # start twitter.com availability thread self.network = Network() self.network.connect_signal("twitter-down", self.twitter_down) self.network.connect_signal("twitter-up", self.twitter_up) self.network.start() # start downloader thread self.downloader = Download() self.downloader.start() # Initialize notification manager self.notification = NotificationManager() # initialize settings self.settings = Settings() self.tweet_count = self.settings.get('tweet_count') # start datetime updater thread self.update_tl_thread = Thread(target=self.update_datetimes, args=(60, )) self.update_tl_thread.daemon = True # initialize the top level window self.window = MainWindow(self.settings) self.window.welcome.connect_signal( "account-added", lambda x, y: (self.save_new_account(x, y), self.on_initialized(x, y))) self.add_window(self.window) # connect exit signal self.window.connect_signal("exit", self.on_exit) # status icon object if self.settings.get("use_status_icon") == "true": self.tray = StatusIcon() self.tray.connect_signal("new-tweet-compose", self.on_new_tweet_composer) self.tray.connect_signal("toggle-window-visibility", self.window.toggle_visibility) self.tray.connect_signal("on-exit", self.window.on_exit_event) # hide, if settings indicate that birdie should start minimized if self.settings.get("start_minimized") == "true": self.window.hide() # add lists to scrolled views self.window.home_scrolled.add(self.home_tweet_list) self.window.activity_scrolled.add(self.activity_list) self.window.dm_inbox_scrolled.add(self.dm_inbox_list) self.window.dm_outbox_scrolled.add(self.dm_outbox_list) self.window.tweets_scrolled.add(self.tweets_list) self.window.favorites_scrolled.add(self.favorites_list) self.window.search_scrolled.add(self.search_list) self.window.users_scrolled.add(self.users_list) self.window.users_box.connect_signal("follow", self.on_follow_th) self.window.users_box.connect_signal("unfollow", self.on_unfollow_th) # load accounts info self.accounts = load_pickle(BIRDIE_LOCAL_SHARE_PATH + "accounts.obj") self.window.add_account_menu(self.accounts, self.set_active_account) for account in self.accounts: if account.active: self.active_account = account self.on_initialized(self.active_account.token, self.active_account.secret) # load cached users self.users = load_users(BIRDIE_LOCAL_SHARE_PATH + "users.obj") # start thread to update timedates as daemon self.update_tl_thread.start() # if birdie is already activated self.window.present() for argl in self.args: if not hasattr(argl, 'url'): continue for arg in argl.url: if arg: if "birdie://user/" in arg: user = arg.replace("birdie://user/", "") if "/" in user: user = user.replace("/", "") if "@" in user: user = user.replace("@", "") self.show_profile(user) elif "birdie://hashtag/" in arg: hashtag = arg.replace("birdie://hashtag/", "") if "/" in hashtag: hashtag = hashtag.replace("/", "") if "" in hashtag: hashtag = hashtag.replace("%23", "") self.on_search(None, "#" + hashtag) def on_initialized(self, oauth_token, oauth_token_secret, connect_signals=True): """ Get threaded timelines and initialize the main streamer :param oauth_token: str :param oauth_token_secret: str :return: """ try: self.twitter = Twitter(oauth_token, oauth_token_secret) except TwythonError as e: error_dialog(self.window, "Twitter error", str(e)) self.twitter = None self.home_stream_thread = None return self.update_account_in_file() if connect_signals: # timelines self.home_tweet_list.more.connect("clicked", self.get_home_timeline_th) self.activity_list.more.connect("clicked", self.get_mentions_th) self.dm_inbox_list.more.connect("clicked", self.get_dm_th) self.dm_outbox_list.more.connect("clicked", self.get_dm_th) self.tweets_list.more.connect("clicked", self.get_tweets_th, self.active_account.screen_name) self.favorites_list.more.connect("clicked", self.get_favorites_th, self.active_account.screen_name) self.search_list.more.connect("clicked", self.on_search) self.users_more_tweets = self.users_list.more.connect( "clicked", self.get_tweets_th, None, self.users_list) # actions self.window.new_tweet.connect("clicked", self.on_new_tweet_composer) self.window.connect_signal("search", self.on_search) if self.twitter.authenticated_user and self.active_account: # set profile GLib.idle_add(lambda: self.window.user_box.set( self.twitter.authenticated_user, self.active_account)) # get a fresh home timeline, before firing up the streamer self.get_home_timeline_th(None) self.home_stream_thread = self.init_streamer( self.active_account.token, self.active_account.secret) # get mentions self.get_mentions_th(None) # get dm self.get_dm_th(None) self.get_dm_outbox_th(None) # get profile self.get_tweets_th(None, self.active_account.screen_name) self.get_favorites_th(None, self.active_account.screen_name) self.window.stack.set_visible_child_name('home') # ACCOUNTS def save_new_account(self, oauth_token, oauth_token_secret): """ save a new twitter account :param oauth_token: string :param oauth_token_secret: string """ for account in self.accounts: account.active = False self.active_account = Account("", "", "", True, oauth_token, oauth_token_secret) self.accounts.append(self.active_account) write_pickle(BIRDIE_LOCAL_SHARE_PATH + "accounts.obj", self.accounts) self.clean_all_lists() def update_account_in_file(self): write_to_file = False # get a fresh avatar self.downloader.add({ 'url': self.twitter.authenticated_user['profile_image_url_https'], 'box': self.window.menu_btn_img, 'type': 'own' }) if (self.twitter.authenticated_user['screen_name'] != self.active_account.screen_name or self.twitter.authenticated_user['name'] != self.active_account.name or os.path.basename( self.twitter.authenticated_user['profile_image_url_https']) != self.active_account.avatar): self.active_account.screen_name = self.twitter.authenticated_user[ 'screen_name'] self.active_account.name = self.twitter.authenticated_user['name'] self.active_account.avatar = os.path.basename( self.twitter.authenticated_user['profile_image_url_https']) write_to_file = True if write_to_file: write_pickle(BIRDIE_LOCAL_SHARE_PATH + "accounts.obj", self.accounts) self.window.add_account_menu({self.active_account}, self.set_active_account) def set_active_account(self, screen_name): """Change the active account""" for account in self.accounts: if account.screen_name == screen_name: account.active = True self.active_account = account else: account.active = False self.clean_all_lists() write_pickle(BIRDIE_LOCAL_SHARE_PATH + "accounts.obj", self.accounts) # disconnect streaming self.streamer.disconnect() self.on_initialized(self.active_account.token, self.active_account.secret, connect_signals=False) def clean_all_lists(self): self.home_tweet_list.empty() self.activity_list.empty() self.dm_inbox_list.empty() self.dm_outbox_list.empty() self.tweets_list.empty() self.favorites_list.empty() self.search_list.empty() self.users_list.empty() # STREAMER def init_streamer(self, oauth_token, oauth_token_secret, stream='user'): """ Initializes a user streamer object :param oauth_token: string :param oauth_token_secret: string :param stream: string - 'user' or 'site' :return: """ self.streamer = BirdieStreamer(base64.b64decode(APP_KEY), base64.b64decode(APP_SECRET), oauth_token, oauth_token_secret) self.streamer.connect_signal("tweet-received", self.on_new_tweet_received) self.streamer.connect_signal("event-received", self.on_event_received) self.streamer.connect_signal("dm-received", self.on_new_dm_received) try: stream_thread = Thread(target=getattr(self.streamer, stream)) stream_thread.daemon = True stream_thread.start() return stream_thread except Thread: print("Error: unable to start thread") # HOME TIMELINE def get_home_timeline_th(self, _): try: th = Thread(target=self.get_home_timeline, args=(self.get_home_timeline_cb, )) th.start() except Thread: print("Error: unable to start thread") def get_home_timeline(self, cb): try: if self.home_tweet_list.oldest_id > 0: data = self.twitter.session.get_home_timeline( max_id=self.home_tweet_list.oldest_id - 1, count=self.tweet_count) else: data = self.twitter.session.get_home_timeline( count=self.tweet_count) except TwythonError as e: self.twitter_error(e) return if len(data) > 0: index = len(data) - 1 self.home_tweet_list.oldest_id = data[index]['id'] cb(data) def get_home_timeline_cb(self, data): for x in range(len(data)): self.on_new_tweet_received(data[x], False) # MENTIONS def get_mentions_th(self, _): try: mentions_th = Thread(target=self.get_mentions, args=(self.get_mentions_cb, )) mentions_th.start() except Thread: print("Error: unable to start thread") def get_mentions(self, cb): try: if self.activity_list.oldest_id > 0: data = self.twitter.session.get_mentions_timeline( max_id=self.activity_list.oldest_id - 1, count=self.tweet_count) else: data = self.twitter.session.get_mentions_timeline( count=self.tweet_count) except TwythonError as e: self.twitter_error(e) return if len(data) > 0: index = len(data) - 1 self.activity_list.oldest_id = data[index]['id'] cb(data) def get_mentions_cb(self, data): for x in range(len(data)): self.add_to_list(data[x], self.activity_list, False) # INBOX DIRECT MESSAGES def get_dm_th(self, _): try: dm_th = Thread(target=self.get_dm, args=(self.get_dm_cb, )) dm_th.start() except Thread: print("Error: unable to start thread") def get_dm(self, cb): try: if self.dm_inbox_list.oldest_id > 0: data = self.twitter.session.get_direct_messages( max_id=self.dm_inbox_list.oldest_id - 1, count=self.tweet_count) else: data = self.twitter.session.get_direct_messages( count=self.tweet_count) except TwythonError as e: self.twitter_error(e) return if len(data) > 0: index = len(data) - 1 self.dm_inbox_list.oldest_id = data[index]['id'] cb(data) def get_dm_cb(self, data): for x in range(len(data)): self.add_to_list(data[x], self.dm_inbox_list, False) # OUTBOX DIRECT MESSAGES def get_dm_outbox_th(self, _): try: dm_outbox_th = Thread(target=self.get_dm_outbox, args=(self.get_dm_outbox_cb, )) dm_outbox_th.start() except Thread: print("Error: unable to start thread") def get_dm_outbox(self, cb): try: if self.dm_outbox_list.oldest_id > 0: data = self.twitter.session.get_sent_messages( max_id=self.dm_inbox_list.oldest_id - 1, count=self.tweet_count) else: data = self.twitter.session.get_sent_messages( count=self.tweet_count) except TwythonError as e: self.twitter_error(e) return if len(data) > 0: index = len(data) - 1 self.dm_outbox_list.oldest_id = data[index]['id'] cb(data) def get_dm_outbox_cb(self, data): for x in range(len(data)): self.add_to_list(data[x], self.dm_outbox_list, False) # OWN TWEETS def get_tweets_th(self, _, screen_name, list=None): try: tweets_th = Thread(target=self.get_tweets, args=(self.get_tweets_cb, screen_name, list)) tweets_th.start() except Thread: print("Error: unable to start thread") def get_tweets(self, cb, screen_name, list=None): if list is None: list = self.tweets_list try: if list.oldest_id > 0: data = self.twitter.session.get_user_timeline( screen_name=screen_name, max_id=list.oldest_id - 1, count=self.tweet_count) else: data = self.twitter.session.get_user_timeline( screen_name=screen_name, count=self.tweet_count) except TwythonError as e: self.twitter_error(e) return if len(data) > 0: index = len(data) - 1 list.oldest_id = data[index]['id'] cb(data, list) def get_tweets_cb(self, data, list=None): if list is None: list = self.tweets_list for x in range(len(data)): self.add_to_list(data[x], list, False) # FAVORITES def get_favorites_th(self, _, screen_name): try: favorites = Thread(target=self.get_favorites, args=( self.get_favorites_cb, screen_name, )) favorites.start() except Thread: print("Error: unable to start thread") return None def get_favorites(self, cb, screen_name): try: if self.favorites_list.oldest_id > 0: data = self.twitter.session.get_favorites( screen_name=screen_name, max_id=self.favorites_list.oldest_id - 1, count=self.tweet_count) else: data = self.twitter.session.get_favorites( screen_name=screen_name, count=self.tweet_count) except TwythonError as e: self.twitter_error(e) return if len(data) > 0: index = len(data) - 1 self.favorites_list.oldest_id = data[index]['id'] cb(data) def get_favorites_cb(self, data): for x in range(len(data)): self.add_to_list(data[x], self.favorites_list, False) # ACTIONS CALLBACKS def on_new_tweet_composer(self, _): if self.window.dm.get_active(): dm = True else: dm = False self.tweet_dialog = TweetDialog('', [], self.window, self.active_account.avatar, dm, None, False, self.users) self.tweet_dialog.connect_signal( "new-tweet-dispatcher", lambda x: self.on_new_tweet_dispatcher(x)) self.tweet_dialog.connect_signal( "new-dm-dispatcher", lambda x: self.on_new_dm_dispatcher(x)) def on_new_tweet_dispatcher(self, data): self.tweet_dialog.destroy() self.twitter.update_status(data) def on_new_dm_dispatcher(self, data): self.tweet_dialog.destroy() self.twitter.send_dm_status(data) def on_event_received(self, data, stream=True): if 'event' in data and data['event'] == 'favorite'\ or data['event'] == 'unfavorite': if data['event'] == 'favorite': fav = _("favorited") else: fav = _("unfavorited") screen_name = data['source']['screen_name'] # if own favorite, add to list and return if screen_name == self.active_account.screen_name: #print data if data['event'] == 'favorite': data = data['target_object'] data['favorited'] = True self.add_to_list(data, self.favorites_list, stream) return name = data['source']['name'] profile = data['source']['profile_image_url'] data = data['target_object'] data['user']['name'] = name + " " + fav + " " + _("your tweet") data['user']['profile_image_url'] = profile data['user']['screen_name'] = screen_name self.add_to_list(data, self.activity_list, stream) if stream and self.settings.get("notify_events") == 'true': self.notification.notify( name + " " + fav + " " + _("a Tweet"), strip_html(data['text']), BIRDIE_CACHE_PATH + os.path.basename(profile)) if 'event' in data and data['event'] == 'follow' \ and data['source']['screen_name'] != self.active_account.screen_name: data['name'] = data['source']['name'] data['screen_name'] = data['source']['screen_name'] data['profile_image_url'] = data['source']['profile_image_url'] self.add_to_event(data, self.activity_list, stream) if stream and self.settings.get("notify_events") == 'true': self.notification.notify( _("You've got a new follower!'"), data['source']['name'] + " " + _("is now following you on Twitter"), BIRDIE_CACHE_PATH + os.path.basename(data['source']['profile_image_url'])) def on_new_tweet_received(self, data, stream=True): # add user to cache if self.users: self.users.add(data['user']['screen_name']) # we've got a mention if self.active_account.screen_name in data['text']: self.on_new_mention_received(data, stream) return self.add_to_list(data, self.home_tweet_list, stream) if stream and self.settings.get("notify_tweets") == 'true': self.notification.notify( _("New tweet from ") + data['user']['name'], strip_html(data['text']), BIRDIE_CACHE_PATH + os.path.basename(data['user']['profile_image_url'])) def on_new_mention_received(self, data, stream=True): if stream: self.add_to_list(data, self.activity_list, stream) self.window.mentions_img.set_from_icon_name( 'twitter-mentions-urgent', Gtk.IconSize.LARGE_TOOLBAR) if stream and self.settings.get("notify_mentions") == 'true': self.notification.notify( _("New mention from ") + data['user']['name'], strip_html(data['text']), BIRDIE_CACHE_PATH + os.path.basename(data['user']['profile_image_url']), urgency=2) def on_new_search_received(self, data, stream=True): self.add_to_list(data, self.search_list, stream) def on_new_dm_received(self, data, stream=True): box = TweetBox(data, self.active_account) box.connect_signal("tweet-favorited", lambda x, y, z: self.on_tweet_favorited(x, y, z)) box.connect_signal("dm-destroy", lambda x, y: self.on_dm_destroy(x, y)) box.connect_signal("reply", lambda x: self.on_reply(x)) if data['sender']['screen_name'] == self.active_account.screen_name: GLib.idle_add(lambda: self.dm_outbox_list.append(box, stream)) else: GLib.idle_add(lambda: self.dm_inbox_list.append(box, stream)) self.downloader.add({ 'url': data['sender']['profile_image_url'], 'box': box, 'type': 'avatar' }) try: for media in data['entities']['media']: self.downloader.add({ 'url': media['media_url_https'], 'box': box, 'type': 'media' }) except: pass self.window.dm_img.set_from_icon_name('twitter-dm-urgent', Gtk.IconSize.LARGE_TOOLBAR) if stream and self.settings.get("notify_dm") == 'true': if data['sender']['screen_name'] != \ self.active_account.screen_name: self.notification.notify( _("New Direct Message from ") + data['user']['name'], strip_html(data['text']), BIRDIE_CACHE_PATH + os.path.basename(data['user']['profile_image_url']), urgency=2) def on_tweet_favorited(self, tweetbox, tweet_id, favorite): try: tweet_favorited_th = Thread( target=self.twitter.create_favorite(tweet_id, favorite)) tweet_favorited_th.callback = tweetbox.on_favorite_cb() tweet_favorited_th.start() except Thread: print("Error: unable to start thread") def on_tweet_destroy(self, tweetbox, tweet_id): try: tweet_destroy_th = Thread( target=self.twitter.destroy_tweet(tweet_id)) tweet_destroy_th.callback = tweetbox.on_tweet_destroy_cb() tweet_destroy_th.start() except Thread: print("Error: unable to start thread") def on_dm_destroy(self, tweetbox, dm_id): try: dm_destroy_th = Thread(target=self.twitter.destroy_dm(dm_id)) dm_destroy_th.callback = tweetbox.on_dm_destroy_cb() dm_destroy_th.start() except Thread: print("Error: unable to start thread") def on_retweet(self, tweetbox, tweet_id): try: retweet_th = Thread(target=self.twitter.retweet(tweet_id)) retweet_th.callback = tweetbox.on_retweet_cb() retweet_th.start() except Thread: print("Error: unable to start thread") def on_retweet_quote(self, tweetbox, data): self.tweet_dialog = TweetDialog(data['user']['screen_name'], data['text'], self.window, self.active_account.avatar, False, data['id'], quote=True) self.tweet_dialog.connect_signal( "retweet-quote-dispatcher", lambda x: self.on_retweet_quote_dispatcher(x)) def on_retweet_quote_dispatcher(self, data): self.tweet_dialog.destroy() self.twitter.update_status(data) def on_reply(self, data): self.tweet_dialog = TweetDialog(data['screen_name'], data['user_mentions'], self.window, self.active_account.avatar, False, data['in_reply_to_status_id']) self.tweet_dialog.connect_signal( "new-tweet-dispatcher", lambda x: self.on_new_tweet_dispatcher(x)) def on_dm_reply(self, data): self.tweet_dialog = TweetDialog(data['screen_name'], None, self.window, self.active_account.avatar, True, data['in_reply_to_status_id']) self.tweet_dialog.connect_signal( "new-dm-dispatcher", lambda x: self.on_new_dm_dispatcher(x)) def on_search(self, _, txt=None): self.window.deselect_all_buttons() self.window.stack.set_visible_child(self.window.search_scrolled) self.window.searchbar.set_search_mode(False) if txt is None: txt = self.last_search_txt else: self.search_list.oldest_id = None self.search_list.empty() try: search_th = Thread(target=self.twitter.search, args=( txt, self.search_list.oldest_id, self.on_search_cb, )) search_th.start() except Thread: print("Error: unable to start thread") return None def on_search_cb(self, data, txt): if len(data['statuses']): data = data['statuses'] for x in range(len(data)): self.on_new_search_received(data[x], False) index = len(data) - 1 if index >= 0: self.search_list.oldest_id = data[index]['id'] self.last_search_txt = txt else: GLib.idle_add(lambda: error_dialog(self.window, _("Searching"), _("No more results."))) # FOLLOW def on_follow_th(self, screen_name): try: th = Thread(target=self.on_follow, args=(screen_name, )) th.callback = self.on_follow_cb() th.start() except Thread: print("Error: unable to start follow thread") def on_follow(self, screen_name): try: self.twitter.session.create_friendship(screen_name=screen_name) except TwythonError as e: self.twitter_error(e) def on_follow_cb(self): GLib.idle_add(lambda: self.window.users_box.toggle_follow(True)) # UNFOLLOW def on_unfollow_th(self, screen_name): try: th = Thread(target=self.on_unfollow, args=(screen_name, )) th.callback = self.on_unfollow_cb() th.start() except Thread: print("Error: unable to start unfollow thread") def on_unfollow(self, screen_name): try: self.twitter.session.destroy_friendship(screen_name=screen_name) except TwythonError as e: self.twitter_error(e) def on_unfollow_cb(self): GLib.idle_add(lambda: self.window.users_box.toggle_follow(False)) # SHOW PROFILE def show_profile(self, screen_name): self.users_list.empty() self.window.deselect_all_buttons() self.users_list.more.disconnect(self.users_more_tweets) self.users_more_tweets = self.users_list.more.connect( "clicked", self.get_tweets_th, screen_name, self.users_list) try: th = Thread(target=self.twitter.get_user, args=( screen_name, self.show_profile_cb, )) th.start() except Thread: print("Error: unable to start thread") def show_profile_cb(self, data): self.lookup_friendships_th(data) # FRIENDSHIPS def lookup_friendships_th(self, data): try: th = Thread(target=self.lookup_friendships, args=( data, self.lookup_friendships_cb, )) th.start() except Thread: print("Error: unable to start lookup friendship thread") def lookup_friendships(self, data, cb): try: fs_data = self.twitter.session.lookup_friendships( screen_name=data['screen_name']) except TwythonError as e: self.twitter_error(e) return self.lookup_friendships_cb(data, fs_data) def lookup_friendships_cb(self, data, fs_data): GLib.idle_add(lambda: self.window.stack.set_visible_child( self.window.users_profile_box)) self.window.users_box.screen_name = data['screen_name'] GLib.idle_add(lambda: self.window.users_box.set( data, self.active_account, fs_data)) self.get_tweets(self.get_tweets_cb, data['screen_name'], self.users_list) # HELPERS def add_to_event(self, data, tweet_list, stream): box = ActivityBox(data, self.active_account) GLib.idle_add(lambda: tweet_list.append(box, stream)) def add_to_list(self, data, tweet_list, stream): """ Create a TweetBox and add it to the TweetList :param data: BirdieStreamer data obj :param tweet_list: TweetList obj :param stream: bool """ # retweets if 'retweeted_status' in data: data['retweeted_status']['retweeted_by'] = data['user']['name'] data['retweeted_status']['retweeted_by_screen_name'] = data[ 'user']['screen_name'] data = data['retweeted_status'] box = TweetBox(data, self.active_account) box.connect_signal("tweet-favorited", lambda x, y, z: self.on_tweet_favorited(x, y, z)) box.connect_signal("tweet-destroy", lambda x, y: self.on_tweet_destroy(x, y)) box.connect_signal("dm-destroy", lambda x, y: self.on_dm_destroy(x, y)) box.connect_signal("retweet", lambda x, y: self.on_retweet(x, y)) box.connect_signal("retweet-quote", lambda x, y: self.on_retweet_quote(x, y)) box.connect_signal("reply", lambda x: self.on_reply(x)) box.connect_signal("dm-reply", lambda x: self.on_dm_reply(x)) box.connect_signal("update-favorites", self.update_favorites) # dms if tweet_list == self.dm_inbox_list or \ tweet_list == self.dm_outbox_list: profile_image_url = data['sender']['profile_image_url_https'] else: profile_image_url = data['user']['profile_image_url'] GLib.idle_add(lambda: tweet_list.append(box, stream)) self.downloader.add({ 'url': profile_image_url, 'box': box, 'type': 'avatar' }) if tweet_list != self.dm_inbox_list and \ tweet_list != self.dm_outbox_list: try: for media in data['entities']['media']: self.downloader.add({ 'url': media['media_url_https'], 'box': box, 'type': 'media' }) except: pass # trying to catch imgur images try: for media in data['entities']['urls']: if "imgur.com" in media['expanded_url']: media['expanded_url'] = media['expanded_url'].replace( "http://imgur.com/" + os.path.basename(media['expanded_url']), "http://i.imgur.com/" + os.path.basename(media['expanded_url']) + '.jpg') self.downloader.add({ 'url': media['expanded_url'], 'box': box, 'type': 'media' }) except: pass # trying to catch youtube video try: for media in data['entities']['urls']: if "youtube.com" in media['expanded_url'] or \ "youtu.be" in media['expanded_url']: if "youtu.be" in media['expanded_url']: media['expanded_url'] = \ media['expanded_url'].replace("youtu.be/", "youtube.com/watch?v=") youtube_id = get_youtube_id(media['expanded_url']) youtube_thumb = "http://i3.ytimg.com/vi/" + \ youtube_id + "/mqdefault.jpg" self.downloader.add({ 'url': youtube_thumb, 'box': box, 'type': 'youtube', 'id': youtube_id }) except: pass def update_datetimes(self, n): while True: GLib.idle_add(lambda: self.home_tweet_list.update_datetimes()) GLib.idle_add(lambda: self.activity_list.update_datetimes()) GLib.idle_add(lambda: self.dm_inbox_list.update_datetimes()) GLib.idle_add(lambda: self.dm_outbox_list.update_datetimes()) GLib.idle_add(lambda: self.search_list.update_datetimes()) time.sleep(n) def update_favorites(self, box, tweet_id): self.home_tweet_list.update_favorites(box, tweet_id) self.activity_list.update_favorites(box, tweet_id) self.tweets_list.update_favorites(box, tweet_id) self.search_list.update_favorites(box, tweet_id) self.users_list.update_favorites(box, tweet_id) self.favorites_list.remove_favorite(box, tweet_id) def twitter_down(self): self.window.twitter_down() def twitter_up(self): self.window.twitter_up() def twitter_error(self, e): GLib.idle_add( lambda: error_dialog(self.window, "Twitter error", e.message)) def on_exit(self): self.window.store_geometry() self.settings.save() write_pickle(BIRDIE_LOCAL_SHARE_PATH + "users.obj", self.users) self.window.destroy()