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)
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
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)])
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
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)])