Esempio n. 1
0
    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')
Esempio n. 2
0
    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)
Esempio n. 3
0
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()
Esempio n. 4
0
    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()
Esempio n. 5
0
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)
Esempio n. 6
0
    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)
Esempio n. 7
0
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()