Example #1
0
class TmBot(object):
   '''
      The class that actually runs the bot.
   '''

   def __init__(self, argDict=None):
      if not argDict:
         argDict = { 'debug' : False, "force": False, 'stream': False, 'botPath' : "."}
      # update this object's internal dict with the dict of args that was passed
      # in so we can access those values as attributes.
      self.__dict__.update(argDict)

      # we build a list of dicts containing status (and whatever other args
      # we may need to pass to the update_status function as we exit, most
      # probably 'in_reply-to_status_id' when we're replying to someone.)
      self.tweets = []

      self.settings = Settings(self.GetPath("ajjthebot.json"))
      self.history = Settings(self.GetPath("ajjthebot_history.json"))
      s = self.settings
      if self.stream:
         self.twitter = BotStreamer(s.appKey, s.appSecret, s.accessToken, s.accessTokenSecret)
         self.twitter.SetOutputPath(self.botPath)
      else:
         self.twitter = Twython(s.appKey, s.appSecret, s.accessToken, s.accessTokenSecret)

   def GetPath(self, path):
      '''
         Put all the relative path calculations in one place. If we're given a path
         that has a leading slash, we treat it as absolute and do nothing. Otherwise,
         we treat it as a relative path based on the botPath setting in our config file.
      '''
      if not path.startswith(os.sep):
         path = os.path.join(self.botPath, path)
      return path

   def Log(self, eventType, dataList):
      '''
         Create an entry in the log file. Each entry will look like:
         timestamp\tevent\tdata1\tdata2 <etc>\n
         where:
         timestamp = integer seconds since the UNIX epoch
         event = string identifying the event
         data1..n = individual data fields, as appropriate for each event type.
         To avoid maintenance issues w/r/t enormous log files, the log filename
         that's stored in the settings file is passed through datetime.strftime()
         so we can expand any format codes found there against the current date/time
         and create e.g. a monthly log file.
      '''
      now = int(time())
      today = datetime.fromtimestamp(now)
      fileName = self.settings.logFilePath
      if not fileName:
         fileName = "%Y-%m.txt"
         self.settings.logFilePath = fileName
      path = self.GetPath(fileName)
      path = today.strftime(path)
      with open(path, "a+t") as f:
         f.write("{0}\t{1}\t".format(now, eventType))
         f.write("\t".join(dataList))
         f.write("\n")

   def SendTweets(self):
      ''' send each of the status updates that are collected in self.tweets
      '''
      for msg in self.tweets:
         if self.debug:
            print msg['status'].encode("UTF-8")
         else:
            self.twitter.update_status(**msg)


   def CheckDaySpacing(self, album, title):
      ''' There are a few tunes that seem to come up *way* too often. We'll maintain 
         a new history file that just tracks the last time that any given (album, track) tuple
         is used (and maybe this should just be title?). If we're okay to use this song, return 
         true. 
      '''
      key = "{0}_{1}".format(album, title)
      retval = True
      lastUsed = self.history[key]
      if lastUsed:
         minimumSpace = self.settings.minimumDaySpacing
         if not minimumSpace:
            minimumSpace = 30
            self.settings.minimumDaySpacing = minimumSpace
         today = date.today()
         last = date.fromordinal(lastUsed)
         # how many days has it been since we last tweeted this album/track?
         daysAgo = (today - last).days
         retval =  daysAgo > minimumSpace
         if not retval:
            self.Log("TooSoon", [album, title, "used {0} days ago".format(daysAgo)])
      return retval


   def LogHistory(self, album, title):
      today = date.today()
      key = "{0}_{1}".format(album, title)
      self.history[key] = today.toordinal()

   def CreateUpdate(self):
      '''
         Called everytime the bot is Run().
         If a random number is less than the probability that we should generate
         a tweet (or if we're told to force one), we look into the lyrics database
         and (we hope) append a status update to the list of tweets.

         1/11/14: Added a configurable 'minimumSpacing' variable to prevent us from
         posting an update too frequently. Starting at an hour ()

      '''
      doUpdate = False
      last = self.settings.lastUpdate or 0
      now = int(time())
      lastTweetAge = now - last

      maxSpace = self.settings.maximumSpacing
      if not maxSpace:
         # default to creating a tweet at *least* every 4 hours.
         maxSpace = 4 * 60 * 60
         self.settings.maximumSpacing = maxSpace

      if lastTweetAge > maxSpace:
         # been too long since the last tweet. Make a new one for our fans!
         doUpdate = True

      elif random() < self.settings.tweetProbability:
         # Make sure that we're not tweeting too frequently. Default is to enforce
         # a 1-hour gap between tweets (configurable using the 'minimumSpacing' key
         # in the config file, providing a number of minutes we must remain silent.)
         requiredSpace = self.settings.minimumSpacing
         if not requiredSpace:
            # no entry in the file -- let's create one. Default = 1 hour.
            requiredSpace = 60*60
            self.settings.minimumSpacing = requiredSpace

         if lastTweetAge > requiredSpace:
            # Our last tweet was a while ago, let's make another one.
            doUpdate = True

      if doUpdate or self.force:
         try:
            # Occasionally force some short(er) updates so they're not all
            # paragraph-length.. (these values arbitrarily chosen)
            maxLen = choice([120, 120, 120, 120, 100, 100, 100, 80, 80, 40])
            album, track, msg = self.GetLyric(maxLen)
            self.tweets.append({'status' : msg})
            self.settings.lastUpdate = int(time())
            # we'll log album name, track name, number of lines, number of characters
            self.Log("Tweet", [album, track, str(1 + msg.count("\n")), str(len(msg))])
         except NoLyricError:
            self.Log("NoLyric", [])
            pass

   def HandleMentions(self):
      '''
         Get all the tweets that mention us since the last time we ran and process each
         one.
         Any time we're mentioned in someone's tweet, we favorite it. If they ask
         us a question, we reply to them.
      '''
      mentions = self.twitter.get_mentions_timeline(since_id=self.settings.lastMentionId)
      if mentions:
         # Remember the most recent tweet id, which will be the one at index zero.
         self.settings.lastMentionId = mentions[0]['id_str']
         for mention in mentions:
            who = mention['user']['screen_name']
            text = mention['text']
            theId = mention['id_str']

            # we favorite every mention that we see
            if self.debug:
               print "Faving tweet {0} by {1}:\n {2}".format(theId, who, text.encode("utf-8"))
            else:
               self.twitter.create_favorite(id=theId)

            eventType = 'Mention'
            # if they asked us a question, reply to them.
            if "?" in text:
               # create a reply to them.
               maxReplyLen = 120 - len(who)
               album, track, msg = self.GetLyric(maxReplyLen)
               # get just the first line
               msg = msg.split('\n')[0]
               # In order to post a reply, you need to be sure to include their username
               # in the body of the tweet.
               replyMsg = "@{0} {1}".format(who, msg)
               self.tweets.append({'status': replyMsg, "in_reply_to_status_id" : theId})
               eventType = "Reply"

            self.Log(eventType, [who])



   def HandleQuotes(self):
      ''' The streaming version of the bot may have detected some quoted tweets
         that we want to respond to. Look for files with the .fav extension, and 
         if we find any, handle them. 
      '''
      faves = glob(self.GetPath("*.fav"))
      for fileName in faves:
         with open(fileName, "rt") as f:
            tweetId = f.readline().strip()
            if self.debug:
               print "Faving quoted tweet {0}".format(tweetId)
            else:
               try:
                  self.twitter.create_favorite(id=tweetId)
               except TwythonError as e:
                  self.Log("EXCEPTION", str(e))
         os.remove(fileName)


   def Run(self):
      if self.stream:
         if self.debug:
            print "About to stream from user account."
         try:
            # The call to user() will sit forever waiting for events on 
            # our user account to stream down. Those events will be handled 
            # for us by the BotStreamer object that we created ab
            self.twitter.user()
         except KeyboardInterrupt:
            # disconnect cleanly from the server.
            self.twitter.disconnect()
      else:
         self.CreateUpdate()
         self.HandleMentions()
         self.HandleQuotes()
         self.SendTweets()

         # if anything we dpsid changed the settings, make sure those changes get written out.
         self.settings.lastExecuted = str(datetime.now())
         self.settings.Write()
         self.history.Write()


   def GetLyric(self, maxLen, count=10):
      ''' open a random lyric file, then grab a random stanza of lyrics from it,
         then (if needed) trim it down into lines <= maxLen
         returns a tuple (album, track, stanza) (we may want to log the album/tracks
            that are being used...)

         If we don't immediately find a random chunk of text that meets the maxLen
         criteria, we call ourself recursively, decrementing the count parameter until
         it hits zero, at which point we give up and raise an exception. Obviously, we
         could look for a longer time, or come up with a search algorithm to find text
         meets the current length criteria, or, or, or..., but I actually like the idea
         that it's possible to occasionally just throw up our hands and give up. We'll
         try again in a bit.
      '''
      if 0 == count:
         raise NoLyricError()

      files = glob(self.GetPath(self.settings.lyricFilePath))
      if not files:
         # there aren't any lyrics files to use -- tell them to  GetLyrics
         raise LyricsFileError("Please run GetLyrics.py to fetch lyric data first.")

      fName = choice(files)
      album, track = ParseFilename(fName)
      # Check to see if it's been long enough since we tweeted from this song:
      if not self.CheckDaySpacing(album, track):
         return self.GetLyric(maxLen, count-1)
      stanza = ""
      with open(fName, "rt") as f:
         data = f.read().decode("utf-8")
         stanzas = data.split("\n\n")
         stanza = choice(stanzas).strip().split('\n')
         stanza = TrimTweetToFit(stanza, maxLen)

      if stanza:
         self.LogHistory(album, track)
         return (album, track, stanza)
      else:
         return self.GetLyric(maxLen, count-1)
Example #2
0
def TwitterSearch(query, count):
    logger.info("starting search")
    t = Twython(app_key=TWITTER_APP_KEY,
                app_secret=TWITTER_APP_KEY_SECRET,
                oauth_token=TWITTER_ACCESS_TOKEN,
                oauth_token_secret=TWITTER_ACCESS_TOKEN_SECRET)

    search = t.cursor(t.search, q=query,
                      count=count, lang='en')

    # count = 3
    graph = Graph("http://localhost:7474/db/data/")
    p.set_options(p.OPT.URL, p.OPT.EMOJI, p.OPT.SMILEY,
                  p.OPT.MENTION, p.OPT.NUMBER)

    i = 0
    for tweet in search:
        try:
            i += 1
            print i
            logger.info(i)
            t1 = Tweet()
            u = User()
            t1.id_str = tweet['id_str']
            t1.text = tweet['text']
            t1.retweet_count = tweet['retweet_count']
            t1.favorite_count = tweet['favorite_count']
            t1.created_at = tweet['created_at']

            t1.lemm = pre.preprocess_tweet(tweet['text'])
            # print t1.lemm
            sentiment_tweet(t1.lemm)

            for source in tweet['source']:
                s = Source()
                s.name = tweet['source']
                graph.push(s)
                t1.USING.add(s)

            for url in tweet['entities']['urls']:
                ul = Url()
                ul.tweet_url = url['url']
                graph.push(ul)
                t1.CONTAINS.add(ul)

            t1.RETWEETS.add(t1)
            t1.REPLY_TO.add(t1)
            graph.push(t1)

            for hashtag in tweet['entities']['hashtags']:
                h = Hashtag()
                t1 = Tweet()
                h.hashtag = hashtag['text']
                h.TAGS.add(t1)
                graph.push(h)

            for user in tweet['user']:
                u = User()
                t1 = Tweet()
                u.id = tweet['user']['id']
                u.id_str = tweet['user']['id_str']
                u.name = tweet['user']['name']
                u.screen_name = tweet['user']['screen_name']
                u.location = tweet['user']['location']
                u.favourites_count = tweet['user']['favourites_count']
                u.followers_count = tweet['user']['followers_count']
                u.friends_count = tweet['user']['friends_count']
                u.lang = tweet['user']['lang']
                u.geo_enabled = tweet['user']['geo_enabled']
                u.description = tweet['user']['description']
                u.POSTS.add(t1)

                graph.push(u)

            mentions = []
            for user_t in tweet['entities']['user_mentions']:
                if tweet['user']['screen_name'] != user_t['screen_name']:
                    user = t.show_user(screen_name=user_t['screen_name'])
                    u1 = User()
                    u1.id = user['id']
                    u1.id_str = user['id_str']
                    u1.name = user['name']
                    u1.screen_name = user['screen_name']
                    u1.location = user['location']
                    u1.favourites_count = user['favourites_count']
                    u1.followers_count = user['followers_count']
                    u1.friends_count = user['friends_count']
                    u1.lang = user['lang']
                    u1.geo_enabled = user['geo_enabled']
                    u1.description = user['description']
                    t1.MENTIONS.add(u1)
                    graph.push(u1)

            # # is reply
            if tweet['in_reply_to_status_id']:
                status = t.show_status(id=tweet['in_reply_to_status_id'])
                t2 = Tweet()
                u1 = User()
                t2.text = status['text']

                t2.retweet_count = status['retweet_count']
                t2.favorite_count = status['favorite_count']
                t2.created_at = status['created_at']
                t2.lemm = pre.preprocess_tweet(tweet['text'])
                # t2.RETWEETS.add(t1)
                t1.REPLY_TO.add(t2)
                graph.push(t2)
                # t12.MENTIONS.add(u1)
                user = t.show_user(screen_name=tweet[
                                   'in_reply_to_screen_name'])
                u1.id = user['id']
                u1.id_str = user['id_str']
                u1.name = user['name']
                u1.screen_name = user['screen_name']
                u1.location = user['location']
                u1.favourites_count = user['favourites_count']
                u1.followers_count = user['followers_count']
                u1.friends_count = user['friends_count']
                u1.lang = user['lang']
                u1.geo_enabled = user['geo_enabled']
                u1.description = user['description']
                u1.POSTS.add(t2)
                graph.push(u1)

            # if i==count:
            #     pass
        except TwythonRateLimitError:
            remainder = float(t.get_lastfunction_header(
                header='x-rate-limit-reset')) - time.time()
            t.disconnect()
            time.sleep(remainder)
            t = Twython(app_key=TWITTER_APP_KEY,
                        app_secret=TWITTER_APP_KEY_SECRET,
                        oauth_token=TWITTER_ACCESS_TOKEN,
                        oauth_token_secret=TWITTER_ACCESS_TOKEN_SECRET)
            continue
Example #3
0
class Nanobot(object):
   '''
      A tiny little twitterbot framework in Python.
   '''

   def __init__(self, argDict=None):
      if not argDict:
         argDict = { 'debug' : False, "force": False, 
                     'stream': False, 'botPath' : "."}
      # update this object's internal dict with the dict of args that was passed
      # in so we can access those values as attributes.   
      self.__dict__.update(argDict)

      # we build a list of dicts containing status (and whatever other args 
      # we may need to pass to the update_status function as we exit, most 
      # probably 'in_reply-to_status_id' when we're replying to someone.)
      self.tweets = []




   ##
   ## Methods That Your Bot Might Wish To Override
   ##
   
   def GetDefaultConfigOptions(self):
      ''' 
         Override this in your derived class if you'd like to ensure that
         there's one or more specific key/value pairs present in the 
         settings file for a user to edit by hand as needed.
      '''
      return {}

   def IsReadyForUpdate(self):
      ''' Check to see if we should be generating a tweet this time.
         Defaults to the built-in logic where we prevent tweets happening
         too closely together or too far apart, and this can be overridden 
         if self.force is True.

         Derived classes are free to create their own version of this method.
      '''
      doUpdate = self.force
      last = self.settings.lastUpdate or 0
      now = int(time())
      lastTweetAge = now - last

      # default to creating a tweet at *least* every 4 hours.
      maxSpace = self.settings.GetOrDefault("maximumSpacing", 4 * 60 * 60)

      if lastTweetAge > maxSpace:
         # been too long since the last tweet. Make a new one for our fans!
         doUpdate = True

      elif random() < self.settings.tweetProbability:
         # Make sure that we're not tweeting too frequently. Default is to enforce 
         # a 1-hour gap between tweets (configurable using the 'minimumSpacing' key
         # in the config file, providing a number of minutes we must remain silent.)
         requiredSpace = self.settings.GetOrDefault("minimumSpacing",  60*60)

         if lastTweetAge > requiredSpace:
            # Our last tweet was a while ago, let's make another one.
            doUpdate = True

      return doUpdate



   def CreateUpdateTweet(self):
      ''' Override this method in your derived bot class. '''
      pass

   def HandleOneMention(self, mention):
      ''' should be overridden by derived classes. Base version 
      likes any tweet that mentions us.
      '''
      who = mention['user']['screen_name']
      text = mention['text']
      theId = mention['id_str']

      # we favorite every mention that we see
      if self.debug:
         print "Faving tweet {0} by {1}:\n {2}".format(theId, who, text.encode("utf-8"))
      else:
         self.twitter.create_favorite(id=theId)

   def PreRun(self):
      ''' 
         override in derived class to perform any actions that need
         to happen before the body of the Run() method.
      '''
      pass

   def PostRun(self):
      ''' 
         override in derived class to perform any actions that need
         to happen after the body of the Run() method.
      '''
      pass



   ##
   ## Methods That Your Bot Probably Won't Want To Override
   ## 

   def GetPath(self, path):
      '''
         Put all the relative path calculations in one place. If we're given a path
         that has a leading slash, we treat it as absolute and do nothing. Otherwise, 
         we treat it as a relative path based on the botPath setting in our config file.
      '''
      if not path.startswith(os.sep):
         path = os.path.join(self.botPath, path)
      return path

   def Log(self, eventType, dataList):
      '''
         Create an entry in the log file. Each entry will look like:
         timestamp\tevent\tdata1\tdata2 <etc>\n
         where:
         timestamp = integer seconds since the UNIX epoch
         event = string identifying the event
         data1..n = individual data fields, as appropriate for each event type.
         To avoid maintenance issues w/r/t enormous log files, the log filename 
         that's stored in the settings file is passed through datetime.strftime()
         so we can expand any format codes found there against the current date/time
         and create e.g. a monthly log file.
      '''
      now = int(time())
      today = datetime.fromtimestamp(now)
      # if there's no explicit log file path/name, we create one
      # that's the current year & month.
      fileName = self.settings.logFilePath
      if not fileName:
         fileName = "%Y-%m.txt"
         self.settings.logFilePath = fileName
      path = self.GetPath(fileName)
      path = today.strftime(path)
      with open(path, "a+t") as f:
         f.write("{0}\t{1}\t".format(now, eventType))
         f.write("\t".join(dataList))
         f.write("\n")

   def SendTweets(self):
      ''' send each of the status updates that are collected in self.tweets 
      '''
      for msg in self.tweets:
         if self.debug:
            print "TWEET: {0}".format(msg['status'].encode("UTF-8"))
         else:
            self.twitter.update_status(**msg)


   def CreateUpdate(self):
      '''
         Called everytime the bot is Run(). 

         Checks to see if the bot thinks that it's ready to generate new output, 
         and if so, calls CreateUpdateTweet to generate it.

      '''

      if self.force or self.IsReadyForUpdate():
         self.CreateUpdateTweet()


   def HandleMentions(self):
      '''
         Get all the tweets that mention us since the last time we ran and 
         process each one.
      '''
      mentions = self.twitter.get_mentions_timeline(since_id=self.settings.lastMentionId)
      if mentions:
         # Remember the most recent tweet id, which will be the one at index zero.
         self.settings.lastMentionId = mentions[0]['id_str']
         for mention in mentions:
            self.HandleOneMention(mention)


   def HandleStreamEvents(self):
      ''' 
         There may be a bot process that's waiting for stream events. When it
         encounters one, it writes the data out into a file with the extension
         ".stream". Handle any of those files that are present and delete them when
         we're done. 

         See https://dev.twitter.com/node/201
         for more information on the events that your bot can be sent.

         The event types that are listed at the time of writing are:
         access_revoked, block, unblock, favorite, unfavorite, follow, 
         unfollow, list_created, list_destroyed, list_updated,
         list_member_added, list_member_removed, list_user_subscribed,
         list_user_unsubscribed, quoted_tweet, user_update. 
      '''
      events = glob(self.GetPath("*{0}".format(kStreamFileExtension)))
      for fileName in events:
         with open(fileName, "rt") as f:
            data = json.loads(f.read().decode("utf-8"))
            eventType = data["event"]
            handlerName = "Handle_{0}".format(eventType)
            handler = getattr(self, handlerName, None)
            if handler:
               handler(data)
            else:
               # log that we got something we didn't know how to handle.
               self.Log("UnknownStreamEvent", [eventType])
         # remove the file so we don't process it again!
         os.remove(self.GetPath(fileName))



   def LoadSettings(self):
      # load the settings file.
      # If one doesn't exist, we create one that's populated with 
      # defaults, print a message to the console telling the user to
      # edit the file with correct values, and  exit.
      defaultSettings = kDefaultConfigDict.copy()
      defaultSettings.update(self.GetDefaultConfigOptions())
      self.settings = Settings(self.GetPath("{}.json".format(self.botName)), 
         defaultSettings)

   def Run(self):
      '''
         All the high-level logic of the bot is driven from here:
         - load settings
         - connect to twitter
         - (let your derived bot class get set up)
         - either:
            - wait for events from the streaming API
            - do bot stuff:
               - maybe create one or more tweets
               - handle any mentions
               - handle any streaming API events that were saved
               - send tweets out
         - (let your derived bot class clean up)
      '''
      self.LoadSettings()


      # create the Twython object that's going to communicate with the
      # twitter API.
      appKey = self.settings.appKey
      appSecret = self.settings.appSecret
      accessToken = self.settings.accessToken
      accessTokenSecret = self.settings.accessTokenSecret
      if self.stream:
         self.twitter = NanobotStreamer(appKey, appSecret, accessToken, accessTokenSecret)
         self.twitter.SetOutputPath(self.botPath)
      else:
         self.twitter = Twython(appKey, appSecret, accessToken, accessTokenSecret)


      # give the derived bot class a chance to do whatever it needs
      # to do before we actually execute. 
      self.PreRun()
      if self.stream:
         if self.debug:
            print "About to stream from user account."
         try:
            # The call to user() will sit forever waiting for events on 
            # our user account to stream down. Those events will be handled 
            # for us by the BotStreamer object that we created above
            self.twitter.user()
         except KeyboardInterrupt:
            # disconnect cleanly from the server.
            self.twitter.disconnect()
      else:
         self.CreateUpdate()
         self.HandleMentions()
         self.HandleStreamEvents()
         self.SendTweets()

         # if anything we did changed the settings, make sure those changes 
         # get written out.
         self.settings.lastExecuted = str(datetime.now())
         self.settings.Write()

      # ...and let the derived bot class clean up as it needs to.
      self.PostRun()



   @classmethod
   def CreateAndRun(cls, argDict):
      '''
         Use this class method together with the below `GetBotArguments()`
         function to create an initialized instance of your derived bot 
         class and start it running. 
      '''
      try:
         bot = cls(argDict)
         bot.Run()
      except Exception as e:
         print str(e)
         bot.Log("ERROR", [str(e)])
Example #4
0
APP_KEY = "h0rZYPs6iDq4orLJzBLJ0iFYl"
APP_SECRET = "y6A4U5QsfdIRzbbUNY493d696BmSVZdb4NPHRvPNp8jFpzduI4"

twitter = Twython(APP_KEY, APP_SECRET, oauth_version=2)
ACCESS_TOKEN = twitter.obtain_access_token()

twitter = Twython(APP_KEY, access_token = ACCESS_TOKEN)
tag = raw_input("Enter the term to search for-->")
results = twitter.cursor(twitter.search, q=tag)

target = open("TwittrSolrData.txt", 'a')
#target = open("TwittrSolrData.txt", 'a') For appending to the file

#number_of_tweets = raw_input("Enter the number of tweets that you want to collect-->")
i = 0

key = raw_input("Enter the key-->")
for result in results:
    #target.write(result)
    #target.write('\n')
    print result
    target.write(str(result[key]).encode("utf-8") + '\n')
    #target.write(str(result) + '\n')
    i = i+1
    if (i > 4):
		break

#print "File has been written to"
twitter.disconnect()
target.close()
while loop:
    for keyword in keywords:
        try:
            t = twitter.search(q='keywords', count=MAX_COUNT, lang='en')
            tweet_object = t['statuses']
            for tweet in tweet_object:
                if len(tweet) > 0:
                    tweet['sentiment'] = sentiment_analyzer(tweet['text'])
                    tw_id = tweet['id_str']
                    print("Works\n\n")

                    if tw_id in db:
                        print('Duplicate tweet')
                    else:
                        db[tw_id] = tweet
                        #print('Written to database')
                else:
                    break

        except:
            remainder = float(
                twitter.get_lastfunction_header(
                    header='x-rate-limit-reset')) - time.time()
            loop = False
            twitter.disconnect()
            time.sleep(remainder)
            twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN,
                              OAUTH_TOKEN_SECRET)
            loop = True
            continue
Example #6
0
class Nanobot(object):
    '''
      A tiny little twitterbot framework in Python.
   '''
    def __init__(self, argDict=None):
        if not argDict:
            argDict = {
                'debug': False,
                "force": False,
                'stream': False,
                'botPath': "."
            }
        # update this object's internal dict with the dict of args that was passed
        # in so we can access those values as attributes.
        self.__dict__.update(argDict)

        # we build a list of dicts containing status (and whatever other args
        # we may need to pass to the update_status function as we exit, most
        # probably 'in_reply-to_status_id' when we're replying to someone.)
        self.tweets = []

    ##
    ## Methods That Your Bot Might Wish To Override
    ##

    def GetDefaultConfigOptions(self):
        ''' 
         Override this in your derived class if you'd like to ensure that
         there's one or more specific key/value pairs present in the 
         settings file for a user to edit by hand as needed.
      '''
        return {}

    def IsReadyForUpdate(self):
        ''' Check to see if we should be generating a tweet this time.
         Defaults to the built-in logic where we prevent tweets happening
         too closely together or too far apart, and this can be overridden 
         if self.force is True.

         Derived classes are free to create their own version of this method.
      '''
        doUpdate = self.force
        last = self.settings.lastUpdate or 0
        now = int(time())
        lastTweetAge = now - last

        # default to creating a tweet at *least* every 4 hours.
        maxSpace = self.settings.GetOrDefault("maximumSpacing", 4 * 60 * 60)

        if lastTweetAge > maxSpace:
            # been too long since the last tweet. Make a new one for our fans!
            doUpdate = True

        elif random() < self.settings.tweetProbability:
            # Make sure that we're not tweeting too frequently. Default is to enforce
            # a 1-hour gap between tweets (configurable using the 'minimumSpacing' key
            # in the config file, providing a number of minutes we must remain silent.)
            requiredSpace = self.settings.GetOrDefault("minimumSpacing",
                                                       60 * 60)

            if lastTweetAge > requiredSpace:
                # Our last tweet was a while ago, let's make another one.
                doUpdate = True

        return doUpdate

    def CreateUpdateTweet(self):
        ''' Override this method in your derived bot class. '''
        pass

    def HandleOneMention(self, mention):
        ''' should be overridden by derived classes. Base version 
      likes any tweet that mentions us.
      '''
        who = mention['user']['screen_name']
        text = mention['text']
        theId = mention['id_str']

        # we favorite every mention that we see
        if self.debug:
            print "Faving tweet {0} by {1}:\n {2}".format(
                theId, who, text.encode("utf-8"))
        else:
            self.twitter.create_favorite(id=theId)

    def PreRun(self):
        ''' 
         override in derived class to perform any actions that need
         to happen before the body of the Run() method.
      '''
        pass

    def PostRun(self):
        ''' 
         override in derived class to perform any actions that need
         to happen after the body of the Run() method.
      '''
        pass

    ##
    ## Methods That Your Bot Probably Won't Want To Override
    ##

    def GetPath(self, path):
        '''
         Put all the relative path calculations in one place. If we're given a path
         that has a leading slash, we treat it as absolute and do nothing. Otherwise, 
         we treat it as a relative path based on the botPath setting in our config file.
      '''
        if not path.startswith(os.sep):
            path = os.path.join(self.botPath, path)
        return path

    def Log(self, eventType, dataList):
        '''
         Create an entry in the log file. Each entry will look like:
         timestamp\tevent\tdata1\tdata2 <etc>\n
         where:
         timestamp = integer seconds since the UNIX epoch
         event = string identifying the event
         data1..n = individual data fields, as appropriate for each event type.
         To avoid maintenance issues w/r/t enormous log files, the log filename 
         that's stored in the settings file is passed through datetime.strftime()
         so we can expand any format codes found there against the current date/time
         and create e.g. a monthly log file.
      '''
        now = int(time())
        today = datetime.fromtimestamp(now)
        # if there's no explicit log file path/name, we create one
        # that's the current year & month.
        fileName = self.settings.logFilePath
        if not fileName:
            fileName = "%Y-%m.txt"
            self.settings.logFilePath = fileName
        path = self.GetPath(fileName)
        path = today.strftime(path)
        with open(path, "a+t") as f:
            f.write("{0}\t{1}\t".format(now, eventType))
            f.write("\t".join(dataList))
            f.write("\n")

    def SendTweets(self):
        ''' send each of the status updates that are collected in self.tweets 
      '''
        for msg in self.tweets:
            if self.debug:
                print "TWEET: {0}".format(msg['status'].encode("UTF-8"))
            else:
                self.twitter.update_status(**msg)

    def CreateUpdate(self):
        '''
         Called everytime the bot is Run(). 

         Checks to see if the bot thinks that it's ready to generate new output, 
         and if so, calls CreateUpdateTweet to generate it.

      '''

        if self.force or self.IsReadyForUpdate():
            self.CreateUpdateTweet()

    def HandleMentions(self):
        '''
         Get all the tweets that mention us since the last time we ran and 
         process each one.
      '''
        mentions = self.twitter.get_mentions_timeline(
            since_id=self.settings.lastMentionId)
        if mentions:
            # Remember the most recent tweet id, which will be the one at index zero.
            self.settings.lastMentionId = mentions[0]['id_str']
            for mention in mentions:
                self.HandleOneMention(mention)

    def HandleStreamEvents(self):
        ''' 
         There may be a bot process that's waiting for stream events. When it
         encounters one, it writes the data out into a file with the extension
         ".stream". Handle any of those files that are present and delete them when
         we're done. 

         See https://dev.twitter.com/node/201
         for more information on the events that your bot can be sent.

         The event types that are listed at the time of writing are:
         access_revoked, block, unblock, favorite, unfavorite, follow, 
         unfollow, list_created, list_destroyed, list_updated,
         list_member_added, list_member_removed, list_user_subscribed,
         list_user_unsubscribed, quoted_tweet, user_update. 
      '''
        events = glob(self.GetPath("*{0}".format(kStreamFileExtension)))
        for fileName in events:
            with open(fileName, "rt") as f:
                data = json.loads(f.read().decode("utf-8"))
                eventType = data["event"]
                handlerName = "Handle_{0}".format(eventType)
                handler = getattr(self, handlerName, None)
                if handler:
                    handler(data)
                else:
                    # log that we got something we didn't know how to handle.
                    self.Log("UnknownStreamEvent", [eventType])
            # remove the file so we don't process it again!
            os.remove(self.GetPath(fileName))

    def LoadSettings(self):
        # load the settings file.
        # If one doesn't exist, we create one that's populated with
        # defaults, print a message to the console telling the user to
        # edit the file with correct values, and  exit.
        defaultSettings = kDefaultConfigDict.copy()
        defaultSettings.update(self.GetDefaultConfigOptions())
        self.settings = Settings(self.GetPath("{}.json".format(self.botName)),
                                 defaultSettings)

    def Run(self):
        '''
         All the high-level logic of the bot is driven from here:
         - load settings
         - connect to twitter
         - (let your derived bot class get set up)
         - either:
            - wait for events from the streaming API
            - do bot stuff:
               - maybe create one or more tweets
               - handle any mentions
               - handle any streaming API events that were saved
               - send tweets out
         - (let your derived bot class clean up)
      '''
        self.LoadSettings()

        # create the Twython object that's going to communicate with the
        # twitter API.
        appKey = self.settings.appKey
        appSecret = self.settings.appSecret
        accessToken = self.settings.accessToken
        accessTokenSecret = self.settings.accessTokenSecret
        if self.stream:
            self.twitter = NanobotStreamer(appKey, appSecret, accessToken,
                                           accessTokenSecret)
            self.twitter.SetOutputPath(self.botPath)
        else:
            self.twitter = Twython(appKey, appSecret, accessToken,
                                   accessTokenSecret)

        # give the derived bot class a chance to do whatever it needs
        # to do before we actually execute.
        self.PreRun()
        if self.stream:
            if self.debug:
                print "About to stream from user account."
            try:
                # The call to user() will sit forever waiting for events on
                # our user account to stream down. Those events will be handled
                # for us by the BotStreamer object that we created above
                self.twitter.user()
            except KeyboardInterrupt:
                # disconnect cleanly from the server.
                self.twitter.disconnect()
        else:
            self.CreateUpdate()
            self.HandleMentions()
            self.HandleStreamEvents()
            self.SendTweets()

            # if anything we did changed the settings, make sure those changes
            # get written out.
            self.settings.lastExecuted = str(datetime.now())
            self.settings.Write()

        # ...and let the derived bot class clean up as it needs to.
        self.PostRun()

    @classmethod
    def CreateAndRun(cls, argDict):
        '''
         Use this class method together with the below `GetBotArguments()`
         function to create an initialized instance of your derived bot 
         class and start it running. 
      '''
        try:
            bot = cls(argDict)
            bot.Run()
        except Exception as e:
            print str(e)
            bot.Log("ERROR", [str(e)])