Commits

Josh VanderLinden committed 8e65bed

Updated the error handling a little bit to be able to better see where problems are coming from in the log file. Began the "info" command, to display information about a tweet, such as when it was posted and from what program. Fixed a few show-stopping typos. Tried to make it possible for the user to "reply" to a direct message that shows up in their chat. Added methods to be able to check for mentions and direct messages periodically, and refactored the code that sends updates to the user. Moved the line that replaces ampersands to a later point in the HTML formatting method. Added a few more settings to accomodate the additional features of being able to check for mentions and direct messages.

Comments (0)

Files changed (3)

 dist
 simplejson
 xmpp
-twitter.py
 scripts/Output
 twim.wpr
 nbproject
 twibber.tmproj
+*~
+*.swp
 import simplejson
 import sys
 import time
+import traceback
 import twitter
 import ui
 import unicodedata
             'friends': self.OnFriendsCommand,
             'flush': self.OnFlushCommand,
             'prune': self.OnPruneCommand,
-            'interval': self.OnIntervalCommand
+            'interval': self.OnIntervalCommand,
+            'info': self.OnInfoCommand,
         }
 
         # enable the "as" command if we have multiple users setup
         """
 
         try:
-            if force and self.has_connected:
+            if force and self._has_connected:
                 self.client.reconnectAndReauth()
             else:
                 params = dict(server=None, proxy=None, 
                     )
 
                 if self.client.connect(**params) == '':
-                    raise ConnectionError()
+                    raise ConnectionError('Failed to connect')
         
                 from twibber import APP_TITLE, __version__
                 auth = self.client.auth(self.jid.getNode(),
-                                        self.login_as_pass,
+                                        config.login_as_pass,
                                         '%s %s' % (APP_TITLE, __version__))
+                if not auth:
+                    raise ConnectionError('Failed to authenticate')
 
                 # handle messages sent by the user
                 self.client.RegisterHandler('message', self.OnMessage)
             self.presence = xmpp.Presence(show='chat',
                                           status='Doing thy bidding, master.')
             self.client.send(self.presence)
-            self.is_connected = True
-            self.has_connected = True
+            self._is_connected = True
+            self._has_connected = True
         except Exception, ex:
             log.error(ex)
 
         log.info('Terminating the application')
         self.checker.set()
         self.poster.set()
-        self.client.disconnected()
+        #self.client.disconnected()
 
         config.persist()
 
         if args:
             username = args.group(1)
             try:
+                log.info('Attempting to follow %s' % username)
                 user.api.CreateFriendship(username)
                 self.TellUser('You are now following %s' % username)
             except urllib2.HTTPError, ex:
             reply = args.group(2)
             tweet = cache.by_id(tid)
             if tweet:
-                text = '@%s %s' % (tweet.user.screen_name, reply)
-                self.PostUpdate(user, text, in_reply_to_status_id=tweet.id)
+                if isinstance(tweet, twitter.DirectMessage):
+                    log.info('Sending direct message reply to %s: %s' % (
+                        tweet.sender_screen_name, reply))
+                    user.api.PostDirectMessage(tweet.sender_screen_name, reply)
+                    self.TellUser('You have replied to the direct message from'
+                            ' user %s' % tweet.sender_screen_name)
+                else:
+                    text = '@%s %s' % (tweet.user.screen_name, reply)
+                    self.PostUpdate(user, text, in_reply_to_status_id=tweet.id)
             else:
                 self.TellUser('Invalid tweet!  Please try again.')
         else:
         self.TellUser("Friends whose screen name begins with: %s\n%s" % (
                             remaining, ', '.join(friends)))
 
+    def OnInfoCommand(self, user, body, remaining):
+        """
+        The info command allows you to learn more information about a 
+        particular tweet.  For example, if you wanted to learn what time an 
+        update was posted, you could do something like this:
+
+        [ 50 ] ev: I love Twibber!
+        ./info 50
+        """
+
+        args = re.search('(\d+)')
+        if args:
+            tid = int(args.group(1))
+            tweet = cache.by_id(tid)
+            if tweet:
+                log.info('Printing information about tweet %i' % tweet.id)
+                self.TellUser('Tweet Information: %s' % tweet.AsDict())
+            else:
+                self.TellUser('Invalid ID')
+        else:
+            self.TellUser('Please specify the tweet you want information about')
+
     def FindCommand(self, text):
         """
         Attempts to find a Twibber command based on the specified text.  The
             try:
                 event.wait(interval)
                 action()
-            except Exception, ex:
-                log.error(ex)
+            except:
+                klass, exception, trace = sys.exc_info()
+                log.error('Type: %s\tException: %s\n%s' % (klass, exception,
+                    ''.join(traceback.format_tb(trace))))
                 #self.TellUser('We are experiencing problems... please be patient.')
 
     def CheckForUpdates(self):
             now = datetime.now()
             for user in config.users.values():
                 last_update = user.last_update or now - self.update_interval
+                last_mention_update = user.last_mention_update or now - \
+                        self.mention_interval
+                last_direct_update = user.last_direct_update or now - \
+                        self.direct_interval
+
                 next_run = last_update + self.update_interval
                 if next_run <= now:
                     log.info('Getting updates for %s' % user.username)
-                    self.GetUpdatesFor(user)
+                    self.GetUpdates(user)
+
+                if last_mention_update + self.mention_interval <= now:
+                    log.info('Getting user mentions for %s' % user.username)
+                    self.GetMentions(user)
+
+                if last_direct_update + self.direct_interval <= now:
+                    log.info('Getting direct messages for %s' % user.username)
+                    self.GetDirectMessages(user)
         except urllib2.HTTPError, ex:
             log.error(ex)
             time.sleep(5)
 
         self.client.Process(1)
 
-    def GetUpdatesFor(self, user):
+    def GetUpdates(self, user):
         """
         Connects to Twitter to find updates for a particular user.  If updates
         are found, they're IM'ed out.
 
         user.last_update = datetime.now()
 
+    def GetMentions(self, user):
+        """
+        Retrieves the newest mentions for the given user
+        """
+
+        try:
+            url = 'http://twitter.com/statuses/mentions.json'
+            last = user.last_mention_id or config.last_mention_id
+            params = {'since_id': last}
+            json = user.api._FetchUrl(url, params)
+            data = simplejson.loads(json)
+            mentions = [twitter.Status.NewFromJsonDict(x) for x in data]
+        except Exception, ex:
+            log.error(ex)
+            mentions = []
+
+        if mentions and len(mentions):
+            mentions.reverse()
+            for mention in mentions:
+                # display only the mentions that haven't been displayed yet
+                if mention.id not in cache._tweets.keys():
+                    self.SendTweet(user, mention)
+
+            config.last_mention_id = user.last_mention_id = mentions[-1].id
+
+        user.last_mention_update = datetime.now()
+
+    def GetDirectMessages(self, user):
+        """
+        Retrieves the newest direct messages for the given user
+        """
+
+        try:
+            last = user.last_direct_id or config.last_direct_id
+            directs = user.api.GetDirectMessages(since_id=last)
+        except Exception, ex:
+            log.error(ex)
+            directs = []
+
+        if directs and len(directs):
+            directs.reverse()
+            for direct in directs:
+                # display only the messages that haven't been displayed yet
+                if direct.id not in cache._tweets.keys():
+                    self.SendTweet(user, direct)
+
+            config.last_direct_id = user.last_direct_id = directs[-1].id
+
+        user.last_direct_update = datetime.now()
+
     def SendTweets(self, user, tweets, do_not_update=False):
         """
         Sends a collection of tweets to the user's IM
         """
-        prefix = len(config.users) > 1 and '(%s) ' % user.username or ''
-
         for tweet in tweets:
-            name = tweet.user and tweet.user.screen_name or ''
-
-            # make sure this tweet is in the cache
-            cache.set(tweet)
-
-            # make sure the tweet doesn't have any filtered tags in it
-            good_tags = True
-            for tag in config.filtered_tags:
-                if '#' + tag.lower() in tweet.text.lower():
-                    good_tags = False
-                    break
-            if not good_tags: continue
-
-            text = self.HTMLizeTweet(name, tweet)
-            log.debug('HTML Text: ' + text)
-
-            pre = '[ %i ] %s' % (cache.get_id(tweet.id), prefix)
-            params = {
-                'body': pre + name + ': ' + self.ShowLinkDestinations(tweet.text),
-                'html': pre + text
-            }
-            output = clean(TWEET % params)
-            output = unicode(output).translate(TRANSLATIONS)
-
-            log.debug(output)
-
-            try:
-                node = xmpp.simplexml.BadXML2Node(output)
-                kw = {'node': node}
-            except Exception, ex:
-                # sometimes we get invalid XML--from HTML entities
-                log.error(ex)
-                kw = {'body': params['body']}
-
-            # send the update
-            message = xmpp.Message(to=config.send_to_user, **kw)
-            self.client.send(message)
+            self.SendTweet(user, tweet)
 
             # sleep a bit so we don't get flagged as a spammer
             time.sleep(1)
         if not do_not_update:
             user.has_shown_tweets = True
 
+    def SendTweet(self, user, tweet):
+        """
+        Sends a single tweet to the user's IM
+        """
+        prefix = len(config.users) > 1 and '(%s) ' % user.username or ''
+        if isinstance(tweet, twitter.DirectMessage):
+            name = tweet.sender_screen_name
+            t_type = '[ DM ] '
+        else:
+            name = tweet.user and tweet.user.screen_name or ''
+            t_type = ''
+
+        # make sure this tweet is in the cache
+        cache.set(tweet)
+
+        # make sure the tweet doesn't have any filtered tags in it
+        good_tags = True
+        for tag in config.filtered_tags:
+            if '#' + tag.lower() in tweet.text.lower():
+                good_tags = False
+                break
+        if not good_tags: return
+
+        text = self.HTMLizeTweet(name, tweet)
+        log.debug('HTML Text: ' + text)
+
+        pre = '[ %i ] %s%s' % (cache.get_id(tweet.id), t_type, prefix)
+        params = {
+            'body': pre + name + ': ' + self.ShowLinkDestinations(tweet.text),
+            'html': pre + text
+        }
+        output = clean(TWEET % params)
+        output = unicode(output).translate(TRANSLATIONS)
+
+        log.debug(output)
+
+        try:
+            node = xmpp.simplexml.BadXML2Node(output)
+            kw = {'node': node}
+        except Exception, ex:
+            # sometimes we get invalid XML--from HTML entities
+            log.error(ex)
+            kw = {'body': params['body']}
+
+        # send the update
+        message = xmpp.Message(to=config.send_to_user, **kw)
+        self.client.send(message)
+
     def ShowLinkDestinations(self, text):
         """
         This will search through the specified text, looking for anything that
         else:
             uhref = ''
 
-        text = clean(tweet.text.replace('& ', '&amp; '))
-        text = self.ShowLinkDestinations(text)
+        #text = clean(tweet.text.replace('& ', '&amp; '))
+        text = self.ShowLinkDestinations(tweet.text)
         at_users = AT_REPLY_RE.findall(text)
         template = '<span%(c)s>@<a href="http://twitter.com/%(u)s"%(c)s>%(u)s</a></span>'
 
             r'\1<em><a href="http://twitter.com/#search?q=%23\2">#\2</a></em>\3',
             text)
 
+        text = clean(text.replace('& ', '&amp; '))
+
         return uhref + ': ' + text
 
     def TellUser(self, message):
     """
     __slots__ = ('username', '_password', 'last_tweet_id', 'last_update',
                  '_api', '_request_token', '_auth_url', '_access_token',
-                 'has_shown_tweets')
+                 'has_shown_tweets', 'last_mention_update', 'last_mention_id',
+                 'last_direct_update', 'last_direct_id')
 
     def __init__(self, username=None, password='', last_tweet_id=0,
-                 last_update=None):
+                 last_update=None, last_mention_update=None,
+                 last_direct_update=None):
         self.username = username
         self._set_password(password)
         self.last_tweet_id = last_tweet_id
         self.last_update = last_update
+        self.last_mention_update = last_mention_update
+        self.last_direct_update = last_direct_update
+        self.last_mention_id = None
+        self.last_direct_id = None
         self._api = None
         self._request_token = None
         self._auth_url = None
     Defaults = {
         'update_interval': (90, int),
         'last_tweet_id': (0, int),
+        'last_mention_id': (0, int),
+        'last_direct_id': (0, int),
         'display_destination': (True, bool),
         'login_as_user': '',
         'login_as_pass': '',