From a96118006548596ef34c0a1045e25580966d7560 Mon Sep 17 00:00:00 2001 From: "Brian S. Stephan" Date: Wed, 19 Jan 2011 22:56:49 -0600 Subject: [PATCH] Twitter: support for polling the bot's timeline and mentions feeds needs authentication. this adds a sqlite database, to track a couple settings. one, since_id, tracks the last successful time this poll happened, so it's pretty important you don't muck around with it. default value is 0, so the first time this poll occurs, it may be a bit spammy. --- modules/Twitter.py | 137 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 136 insertions(+), 1 deletion(-) diff --git a/modules/Twitter.py b/modules/Twitter.py index db497ee..8ba2be9 100644 --- a/modules/Twitter.py +++ b/modules/Twitter.py @@ -16,8 +16,11 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ -import re +from ConfigParser import NoSectionError, NoOptionError import oauth2 as oauth +import re +import sqlite3 +from threading import Timer import urlparse from extlib import irclib @@ -62,6 +65,34 @@ class Twitter(Module): self.twit = twitter.Api() self.authed = False + def db_init(self): + """Set up the settings table.""" + + # init the table if it doesn't exist + version = self.db_module_registered(self.__class__.__name__) + if version == None: + # create tables + db = self.get_db() + try: + db.execute(''' + CREATE TABLE twitter_settings ( + since_id INTEGER NOT NULL, + output_channel TEXT NOT NULL + )''') + db.execute('''INSERT INTO twitter_settings (since_id, output_channel) VALUES (0, '#drbotzo')''') + db.execute('INSERT INTO drbotzo_modules VALUES (?,?)', (self.__class__.__name__, 1)) + db.commit() + except sqlite3.Error as e: + db.rollback() + print("sqlite error: " + str(e)) + raise + + def shutdown(self): + """Deauth, and make the twitter API item inoperable.""" + + self.twit.ClearCredentials() + self.authed = False + def do(self, connection, event, nick, userhost, what, admin_unlocked): """Attempt to do twitter things.""" @@ -165,8 +196,46 @@ class Twitter(Module): # finally, create the twitter API object self.twit = twitter.Api(self.consumer_key, self.consumer_secret, self.access_token['oauth_token'], self.access_token['oauth_token_secret']) self.authed = True + + # ugly, but the best place to track it. store the connection for later use + self.connection = connection + + # print timeline stuff. this will set up the appropriate timer + self._check_self_timeline() + return 'The bot is now logged in.' + def _check_self_timeline(self): + """Check my timeline, and if there are entries, print them to the channel.""" + + if self.authed: + # get the id of the last check we made + since_id = self._get_last_since_id() + output_channel = self._get_output_channel() + + if since_id is not None and output_channel != '': + tweets = self.twit.GetFriendsTimeline(since_id=since_id) + for tweet in tweets: + tweet_text = self._return_tweet_or_retweet_text(tweet=tweet, print_source=True) + self.irc.reply(self.connection, output_channel.encode('utf-8', 'ignore'), tweet_text) + + # friends timeline printed, find the latest id + new_since_id = self._get_latest_tweet_id(tweets, since_id) + + tweets = self.twit.GetMentions(since_id=since_id) + for tweet in tweets: + tweet_text = self._return_tweet_or_retweet_text(tweet=tweet, print_source=True) + self.irc.reply(self.connection, output_channel.encode('utf-8', 'ignore'), tweet_text) + + # mentions printed, find the latest id + new_since_id = self._get_latest_tweet_id(tweets, new_since_id) + + # set since_id + self._set_last_since_id(new_since_id) + + # re-register this check + Timer(300, self._check_self_timeline, ()).start() + def _return_tweet_or_retweet_text(self, tweet, print_source=False): """ Return a string of the author and text body of a status, @@ -186,5 +255,71 @@ class Twitter(Module): else: return '%s [%s]' % (tweet.text.encode('utf-8', 'ignore'), tweet.id) + def _get_last_since_id(self): + """Get the since_id out of the database.""" + + try: + # need to create our own db object, since this is likely going + # to be called in a new thread + dbfile = self.config.get('dr.botzo', 'database') + self.conn = sqlite3.connect(dbfile) + self.conn.row_factory = sqlite3.Row + db = self.conn + query = 'SELECT since_id FROM twitter_settings' + cursor = db.execute(query) + result = cursor.fetchone() + if result: + return result['since_id'] + except sqlite3.Error as e: + print("sqlite error: " + str(e)) + raise + + def _get_output_channel(self): + """Get the output_channel out of the database.""" + + try: + # need to create our own db object, since this is likely going + # to be called in a new thread + dbfile = self.config.get('dr.botzo', 'database') + self.conn = sqlite3.connect(dbfile) + self.conn.row_factory = sqlite3.Row + db = self.conn + query = 'SELECT output_channel FROM twitter_settings' + cursor = db.execute(query) + result = cursor.fetchone() + if result: + return result['output_channel'] + except sqlite3.Error as e: + print("sqlite error: " + str(e)) + raise + + def _set_last_since_id(self, since_id): + """Set the since_id.""" + + try: + # need to create our own db object, since this is likely going + # to be called in a new thread + dbfile = self.config.get('dr.botzo', 'database') + self.conn = sqlite3.connect(dbfile) + self.conn.row_factory = sqlite3.Row + db = self.conn + cur = db.cursor() + statement = 'UPDATE twitter_settings SET since_id = ?' + cur.execute(statement, (since_id,)) + db.commit() + except sqlite3.Error as e: + print("sqlite error: " + str(e)) + raise + + def _get_latest_tweet_id(self, tweets, since_id): + """Find the latest tweet id in the provided list, or the given since_id.""" + + latest = since_id + for tweet in tweets: + if tweet.id > latest: + latest = tweet.id + + return latest + # vi:tabstop=4:expandtab:autoindent # kate: indent-mode python;indent-width 4;replace-tabs on;