2010-12-15 23:08:08 -06:00
|
|
|
"""
|
|
|
|
Twitter - access to Twitter through bot commands
|
|
|
|
Copyright (C) 2010 Brian S. Stephan
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import oauth2 as oauth
|
2011-01-19 22:56:49 -06:00
|
|
|
import re
|
2011-06-20 21:18:55 -05:00
|
|
|
import thread
|
|
|
|
import time
|
2011-01-07 20:37:24 -06:00
|
|
|
import urlparse
|
2010-12-15 23:08:08 -06:00
|
|
|
|
2012-07-27 02:18:01 -05:00
|
|
|
import MySQLdb as mdb
|
|
|
|
|
2010-12-15 23:08:08 -06:00
|
|
|
from extlib import twitter
|
|
|
|
|
|
|
|
from Module import Module
|
|
|
|
|
|
|
|
class Twitter(Module):
|
2011-01-19 19:44:04 -06:00
|
|
|
|
2011-01-07 20:37:24 -06:00
|
|
|
"""Access Twitter via the bot as an authenticated client."""
|
2010-12-15 23:08:08 -06:00
|
|
|
|
migrate some code that became pivotal to the bot into DrBotIRC.
this is a big change. DrBotIrc is now in charge of module loading
and unloading, aliases, and recursion. the Alias module is no more,
and a bunch of functionality was moved out of IrcAdmin, including
also config file saving, the sigint handler, and quitting the bot.
additionally, a lot of stuff got caught in the wake. dr.botzo.py
is simpler now, and lets DrBotIRC do the dynamic loading stuff.
Module.__init__ changed, modules no longer get modlist and instead
get a reference to the DrBotIRC object. IrcAdmin still has the same
exposed methods, but now calls out to DrBotIRC to achieve some of
them.
naturally, a recursion/alias rewrite was included with this change.
it is clearer now (i think), but probably brittle somewhere.
additionally, currently any module that has registered a pubmsg
handler can potentially fire more than once on one input (without
recursion). this may be the next thing to fix. do() may need to
be split, or maybe it's time to stop having modules deal with
pubmsg/privmsg entirely. need to decide.
WEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
2011-01-07 17:38:26 -06:00
|
|
|
def __init__(self, irc, config, server):
|
2011-01-19 19:44:04 -06:00
|
|
|
"""Prepare for oauth stuff (but don't execute it yet)."""
|
2011-01-07 20:37:24 -06:00
|
|
|
|
2011-01-19 19:44:04 -06:00
|
|
|
Module.__init__(self, irc, config, server)
|
2010-12-15 23:08:08 -06:00
|
|
|
|
2011-01-19 19:44:04 -06:00
|
|
|
# setup regexes
|
2012-07-14 09:59:45 -05:00
|
|
|
getstatuspattern = "^!twitter\s+getstatus(\s+nosource)?(\s+noid)?\s+(\S+)$"
|
|
|
|
getuserstatuspattern = "^!twitter\s+getuserstatus(\s+nosource)?(\s+noid)?\s+(\S+)(\s+.*|$)"
|
2012-07-14 08:17:21 -05:00
|
|
|
tweetpattern = "^!twitter\s+tweet\s+(.*)"
|
|
|
|
gettokenpattern = "^!twitter\s+gettoken$"
|
|
|
|
authpattern = "^!twitter\s+auth\s+(\S+)$"
|
|
|
|
replytopattern = "^!twitter\s+replyto\s+(\S+)\s+(.*)"
|
2010-12-15 23:08:08 -06:00
|
|
|
|
2011-01-19 19:44:04 -06:00
|
|
|
self.getstatusre = re.compile(getstatuspattern)
|
|
|
|
self.getuserstatusre = re.compile(getuserstatuspattern)
|
|
|
|
self.tweetre = re.compile(tweetpattern)
|
|
|
|
self.gettokenre = re.compile(gettokenpattern)
|
|
|
|
self.authre = re.compile(authpattern)
|
2011-01-25 19:36:24 -06:00
|
|
|
self.replytore = re.compile(replytopattern)
|
2010-12-15 23:08:08 -06:00
|
|
|
|
2011-01-19 19:44:04 -06:00
|
|
|
# prep oauth magic
|
2010-12-16 20:15:49 -06:00
|
|
|
self.consumer_key = 'N2aSGxBP8t3cCgWyF1B2Aw'
|
|
|
|
self.consumer_secret = '0aQPEV4K3MMpicfi2lDtCP5pvjsKaqIpfuWtsPzx8'
|
2010-12-15 23:08:08 -06:00
|
|
|
|
2012-07-14 08:17:21 -05:00
|
|
|
self.request_token_url = "https://api.twitter.com/oauth/request_token"
|
|
|
|
self.access_token_url = "https://api.twitter.com/oauth/access_token"
|
|
|
|
self.authorize_url = "https://api.twitter.com/oauth/authorize"
|
2010-12-15 23:08:08 -06:00
|
|
|
|
2010-12-16 20:15:49 -06:00
|
|
|
self.consumer = oauth.Consumer(self.consumer_key, self.consumer_secret)
|
|
|
|
self.client = oauth.Client(self.consumer)
|
2010-12-15 23:08:08 -06:00
|
|
|
|
2012-07-14 09:29:12 -05:00
|
|
|
# settings
|
2012-07-14 09:54:02 -05:00
|
|
|
# force timeline check to wait 5 minutes (for channel joins and antispam)
|
|
|
|
self.next_timeline_check = time.time() + 300
|
2012-07-14 09:29:12 -05:00
|
|
|
|
|
|
|
# try getting the stored auth tokens and logging in
|
|
|
|
(oauth_token, oauth_token_secret) = self._retrieve_stored_auth_tokens()
|
|
|
|
if oauth_token is not None and oauth_token_secret is not None:
|
|
|
|
self._log_in_to_twitter(oauth_token, oauth_token_secret)
|
|
|
|
if self._verify_credentials():
|
|
|
|
# self.twit is set
|
|
|
|
|
|
|
|
# ugly, but the best place to track it. store the connection for later use
|
2012-07-14 09:54:26 -05:00
|
|
|
self.connection = irc.server
|
2012-07-14 09:29:12 -05:00
|
|
|
|
|
|
|
# print timeline stuff. this will set up the appropriate timer
|
|
|
|
self._check_self_timeline()
|
|
|
|
|
2012-07-15 21:32:12 -05:00
|
|
|
self.log.debug("Logged in to Twitter with saved token.")
|
2012-07-14 09:29:12 -05:00
|
|
|
else:
|
2012-07-15 21:32:12 -05:00
|
|
|
self.log.error("Could not log in to Twitter with saved token.")
|
2012-07-14 09:29:12 -05:00
|
|
|
self.twit = twitter.Api()
|
|
|
|
else:
|
|
|
|
# create a default twitter API account, in case we never auth
|
|
|
|
self.twit = twitter.Api()
|
2011-06-20 21:18:55 -05:00
|
|
|
thread.start_new_thread(self.thread_do, ())
|
|
|
|
|
2011-01-19 22:56:49 -06:00
|
|
|
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__)
|
2012-07-14 09:29:12 -05:00
|
|
|
if version == None or version < 1:
|
2011-01-19 22:56:49 -06:00
|
|
|
db = self.get_db()
|
2012-07-27 02:18:01 -05:00
|
|
|
# create tables
|
2011-01-19 22:56:49 -06:00
|
|
|
try:
|
2012-07-14 09:29:12 -05:00
|
|
|
version = 1
|
2012-07-27 02:18:01 -05:00
|
|
|
cur = db.cursor(mdb.cursors.DictCursor)
|
|
|
|
cur.execute("""
|
2011-01-19 22:56:49 -06:00
|
|
|
CREATE TABLE twitter_settings (
|
2012-07-28 08:37:27 -05:00
|
|
|
since_id BIGINT(20) UNSIGNED NOT NULL,
|
2012-07-27 02:18:01 -05:00
|
|
|
output_channel VARCHAR(64) NOT NULL,
|
|
|
|
oauth_token VARCHAR(256) DEFAULT NULL,
|
|
|
|
oauth_token_secret VARCHAR(256) DEFAULT NULL
|
2012-07-27 14:57:41 -05:00
|
|
|
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin
|
2012-07-27 02:18:01 -05:00
|
|
|
""")
|
2012-07-27 02:23:30 -05:00
|
|
|
cur.execute("""INSERT INTO twitter_settings (since_id, output_channel) VALUES (0, '#dr.botzo')""")
|
2012-07-14 09:29:12 -05:00
|
|
|
db.commit()
|
|
|
|
self.db_register_module_version(self.__class__.__name__, version)
|
2012-07-27 02:18:01 -05:00
|
|
|
except mdb.Error as e:
|
2011-01-19 22:56:49 -06:00
|
|
|
db.rollback()
|
2012-07-27 02:18:01 -05:00
|
|
|
self.log.error("database error trying to create tables")
|
|
|
|
self.log.exception(e)
|
2011-01-19 22:56:49 -06:00
|
|
|
raise
|
2012-07-27 02:18:01 -05:00
|
|
|
finally: cur.close()
|
2011-01-19 22:56:49 -06:00
|
|
|
|
|
|
|
def shutdown(self):
|
|
|
|
"""Deauth, and make the twitter API item inoperable."""
|
|
|
|
|
2011-04-23 12:58:35 -05:00
|
|
|
Module.shutdown(self)
|
2011-01-19 22:56:49 -06:00
|
|
|
self.twit.ClearCredentials()
|
|
|
|
|
2011-01-07 23:09:07 -06:00
|
|
|
def do(self, connection, event, nick, userhost, what, admin_unlocked):
|
2011-01-19 19:44:04 -06:00
|
|
|
"""Attempt to do twitter things."""
|
|
|
|
|
|
|
|
if self.getstatusre.search(what):
|
2012-07-14 08:41:40 -05:00
|
|
|
return self.reply(connection, event, self.twitter_getstatus(connection, event, nick,
|
|
|
|
userhost, what, admin_unlocked))
|
2011-01-19 19:44:04 -06:00
|
|
|
elif self.getuserstatusre.search(what):
|
2012-07-14 08:41:40 -05:00
|
|
|
return self.reply(connection, event, self.twitter_getuserstatus(connection, event, nick,
|
|
|
|
userhost, what, admin_unlocked))
|
2011-01-19 19:44:04 -06:00
|
|
|
elif self.tweetre.search(what):
|
2012-07-14 08:41:40 -05:00
|
|
|
return self.reply(connection, event, self.twitter_tweet(connection, event, nick,
|
|
|
|
userhost, what, admin_unlocked))
|
2011-01-25 19:36:24 -06:00
|
|
|
elif self.replytore.search(what):
|
2012-07-14 08:41:40 -05:00
|
|
|
return self.reply(connection, event, self.twitter_replyto(connection, event, nick,
|
|
|
|
userhost, what, admin_unlocked))
|
2011-01-19 19:44:04 -06:00
|
|
|
elif self.gettokenre.search(what):
|
2012-07-14 08:41:40 -05:00
|
|
|
return self.reply(connection, event, self.twitter_gettoken(connection, event, nick,
|
|
|
|
userhost, what, admin_unlocked))
|
2011-01-19 19:44:04 -06:00
|
|
|
elif self.authre.search(what):
|
2012-07-14 08:41:40 -05:00
|
|
|
return self.reply(connection, event, self.twitter_auth(connection, event, nick,
|
|
|
|
userhost, what, admin_unlocked))
|
2011-01-19 19:44:04 -06:00
|
|
|
|
|
|
|
def twitter_getstatus(self, connection, event, nick, userhost, what, admin_unlocked):
|
|
|
|
"""Get a status by tweet ID."""
|
|
|
|
|
|
|
|
match = self.getstatusre.search(what)
|
2011-01-07 20:37:24 -06:00
|
|
|
if match:
|
2012-07-14 09:59:45 -05:00
|
|
|
print_source = True
|
2012-07-14 09:55:11 -05:00
|
|
|
print_id = True
|
|
|
|
if match.group(1):
|
2012-07-14 09:59:45 -05:00
|
|
|
print_source = False
|
|
|
|
if match.group(2):
|
2012-07-14 09:55:11 -05:00
|
|
|
print_id = False
|
2012-07-14 09:59:45 -05:00
|
|
|
status = match.group(3)
|
2011-01-07 20:37:24 -06:00
|
|
|
try:
|
|
|
|
tweet = self.twit.GetStatus(status)
|
2012-07-14 09:59:45 -05:00
|
|
|
return self._return_tweet_or_retweet_text(tweet=tweet, print_source=print_source, print_id=print_id)
|
2011-01-07 20:37:24 -06:00
|
|
|
except twitter.TwitterError as e:
|
2012-07-14 08:17:21 -05:00
|
|
|
return "Couldn't obtain status: " + str(e)
|
2011-01-07 20:37:24 -06:00
|
|
|
|
2011-01-19 19:44:04 -06:00
|
|
|
def twitter_getuserstatus(self, connection, event, nick, userhost, what, admin_unlocked):
|
|
|
|
"""Get a status for a user. Allows for getting one other than the most recent."""
|
|
|
|
|
|
|
|
match = self.getuserstatusre.search(what)
|
2011-01-07 20:37:24 -06:00
|
|
|
if match:
|
2012-07-14 09:59:45 -05:00
|
|
|
print_source = True
|
2012-07-14 09:55:11 -05:00
|
|
|
print_id = True
|
|
|
|
if match.group(1):
|
2012-07-14 09:59:45 -05:00
|
|
|
print_source = False
|
|
|
|
if match.group(2):
|
2012-07-14 09:55:11 -05:00
|
|
|
print_id = False
|
2012-07-14 09:59:45 -05:00
|
|
|
user = match.group(3)
|
|
|
|
index = match.group(4)
|
2011-01-07 20:37:24 -06:00
|
|
|
|
2011-06-21 17:24:31 -05:00
|
|
|
try:
|
|
|
|
if index:
|
|
|
|
index = int(index)
|
|
|
|
if index > 0:
|
|
|
|
index = 0
|
|
|
|
else:
|
2010-12-16 15:23:35 -06:00
|
|
|
index = 0
|
2011-06-21 17:24:31 -05:00
|
|
|
except ValueError as e:
|
2012-07-15 21:32:12 -05:00
|
|
|
self.log.error("Couldn't convert index: " + str(e))
|
2011-01-07 20:37:24 -06:00
|
|
|
index = 0
|
2011-06-21 17:24:31 -05:00
|
|
|
|
2011-01-07 20:37:24 -06:00
|
|
|
count = (-1*index) + 1
|
|
|
|
|
|
|
|
try:
|
|
|
|
tweets = self.twit.GetUserTimeline(screen_name=user, count=count, include_rts=True)
|
|
|
|
if tweets:
|
|
|
|
tweet = tweets[-1*index]
|
2012-07-14 09:59:45 -05:00
|
|
|
return self._return_tweet_or_retweet_text(tweet=tweet, print_source=print_source, print_id=print_id)
|
2011-01-07 20:37:24 -06:00
|
|
|
except twitter.TwitterError as e:
|
2012-07-14 08:17:21 -05:00
|
|
|
return "Couldn't obtain status: " + str(e)
|
2011-06-27 20:17:30 -05:00
|
|
|
except ValueError as e:
|
2012-07-14 08:17:21 -05:00
|
|
|
return "Couldn't obtain status: " + str(e)
|
2011-01-07 20:37:24 -06:00
|
|
|
|
2011-01-19 19:44:04 -06:00
|
|
|
def twitter_tweet(self, connection, event, nick, userhost, what, admin_unlocked):
|
|
|
|
"""Tweet. Needs authentication."""
|
|
|
|
|
|
|
|
match = self.tweetre.search(what)
|
2011-01-07 20:37:24 -06:00
|
|
|
if match:
|
|
|
|
tweet = match.group(1)
|
2012-07-14 08:41:40 -05:00
|
|
|
if self._verify_credentials() is None:
|
2012-07-14 08:17:21 -05:00
|
|
|
return "You must be authenticated to tweet."
|
2011-01-07 20:37:24 -06:00
|
|
|
if admin_unlocked is False:
|
2012-07-14 08:17:21 -05:00
|
|
|
return "Only admins can tweet."
|
2011-01-07 20:37:24 -06:00
|
|
|
|
|
|
|
try:
|
|
|
|
if self.twit.PostUpdates(tweet, continuation='\xe2\x80\xa6') is not None:
|
2012-07-14 08:17:21 -05:00
|
|
|
return "'{0:s}' tweeted.".format(tweet)
|
2011-01-07 20:37:24 -06:00
|
|
|
else:
|
2012-07-14 08:17:21 -05:00
|
|
|
return "Unknown error sending tweet(s)."
|
2011-01-07 20:37:24 -06:00
|
|
|
except twitter.TwitterError as e:
|
2012-07-14 08:17:21 -05:00
|
|
|
return "Couldn't tweet: " + str(e)
|
2011-01-25 19:36:24 -06:00
|
|
|
|
|
|
|
def twitter_replyto(self, connection, event, nick, userhost, what, admin_unlocked):
|
|
|
|
"""Reply to a tweet, in the twitter in_reply_to_status_id sense. Needs authentication."""
|
|
|
|
|
|
|
|
match = self.replytore.search(what)
|
|
|
|
if match:
|
|
|
|
status_id = match.group(1)
|
|
|
|
tweet = match.group(2)
|
|
|
|
|
2012-07-14 08:41:40 -05:00
|
|
|
if self._verify_credentials() is None:
|
2012-07-14 08:17:21 -05:00
|
|
|
return "You must be authenticated to tweet."
|
2011-01-25 19:36:24 -06:00
|
|
|
if admin_unlocked is False:
|
2012-07-14 08:17:21 -05:00
|
|
|
return "Only admins can tweet."
|
2011-01-25 19:36:24 -06:00
|
|
|
|
|
|
|
replyee_tweet = self.twit.GetStatus(status_id)
|
|
|
|
target = replyee_tweet.user.screen_name.encode('utf-8', 'ignore')
|
|
|
|
|
|
|
|
try:
|
2012-07-26 18:18:16 -05:00
|
|
|
reptweet = "@{0:s}: {1:s}".format(target, tweet)
|
|
|
|
if self.twit.PostUpdate(reptweet, in_reply_to_status_id=status_id) is not None:
|
2012-07-14 08:17:21 -05:00
|
|
|
return "'{0:s}' tweeted.".format(tweet)
|
2011-01-25 19:36:24 -06:00
|
|
|
else:
|
2012-07-14 08:17:21 -05:00
|
|
|
return "Unknown error sending tweet."
|
2011-01-25 19:36:24 -06:00
|
|
|
except twitter.TwitterError as e:
|
2012-07-14 08:17:21 -05:00
|
|
|
return "Couldn't tweet: " + str(e)
|
2011-01-07 20:37:24 -06:00
|
|
|
|
2011-01-19 19:44:04 -06:00
|
|
|
def twitter_gettoken(self, connection, event, nick, userhost, what, admin_unlocked):
|
|
|
|
"""Get an oauth token, so that the user may authenticate the bot."""
|
|
|
|
|
|
|
|
match = self.gettokenre.search(what)
|
2011-01-07 20:37:24 -06:00
|
|
|
if match:
|
|
|
|
# get request token
|
|
|
|
resp, content = self.client.request(self.request_token_url, "GET")
|
|
|
|
if resp['status'] != '200':
|
|
|
|
raise Exception("Invalid response %s." % resp['status'])
|
|
|
|
|
|
|
|
self.request_token = dict(urlparse.parse_qsl(content))
|
|
|
|
|
|
|
|
# have the user auth
|
2012-07-14 08:17:21 -05:00
|
|
|
return "Go to the following link in your browser: %s?oauth_token=%s and then send me the pin." % (self.authorize_url, self.request_token['oauth_token'])
|
2011-01-07 20:37:24 -06:00
|
|
|
|
2011-01-19 19:44:04 -06:00
|
|
|
def twitter_auth(self, connection, event, nick, userhost, what, admin_unlocked):
|
|
|
|
"""Authenticate, given a PIN (following gettoken)."""
|
|
|
|
|
|
|
|
match = self.authre.search(what)
|
2011-01-07 20:37:24 -06:00
|
|
|
if match:
|
2012-07-14 08:41:40 -05:00
|
|
|
if self._verify_credentials() is not None:
|
|
|
|
return "The bot is already logged in!"
|
|
|
|
|
2011-01-07 20:37:24 -06:00
|
|
|
authtoken = match.group(1)
|
|
|
|
oauth_verifier = authtoken
|
|
|
|
|
|
|
|
# request access token
|
|
|
|
token = oauth.Token(self.request_token['oauth_token'], self.request_token['oauth_token_secret'])
|
|
|
|
token.set_verifier(oauth_verifier)
|
|
|
|
client = oauth.Client(self.consumer, token)
|
|
|
|
|
|
|
|
resp, content = client.request(self.access_token_url, "POST")
|
2012-07-14 08:41:40 -05:00
|
|
|
access_token = dict(urlparse.parse_qsl(content))
|
|
|
|
self._log_in_to_twitter(access_token['oauth_token'], access_token['oauth_token_secret'])
|
2011-01-07 20:37:24 -06:00
|
|
|
|
2012-07-14 08:41:40 -05:00
|
|
|
if self._verify_credentials() is not None:
|
|
|
|
# ugly, but the best place to track it. store the connection for later use
|
|
|
|
self.connection = connection
|
2011-01-19 22:56:49 -06:00
|
|
|
|
2012-07-14 08:41:40 -05:00
|
|
|
# print timeline stuff. this will set up the appropriate timer
|
|
|
|
self._check_self_timeline()
|
2011-01-19 22:56:49 -06:00
|
|
|
|
2012-07-14 08:41:40 -05:00
|
|
|
return "The bot is now logged in."
|
|
|
|
else:
|
|
|
|
return "Could not log in with supplied credentials."
|
|
|
|
|
|
|
|
def _verify_credentials(self):
|
|
|
|
"""Wrap the exceptions in the twitter client VerifyExceptions()."""
|
|
|
|
|
|
|
|
try:
|
|
|
|
return self.twit.VerifyCredentials()
|
2012-07-27 20:38:45 -05:00
|
|
|
except Exception:
|
2012-07-14 08:41:40 -05:00
|
|
|
return None
|
|
|
|
|
|
|
|
def _log_in_to_twitter(self, oauth_token, oauth_token_secret):
|
|
|
|
"""Do the actual authentication against Twitter."""
|
|
|
|
|
|
|
|
# create the twitter API object
|
|
|
|
self.twit = twitter.Api(self.consumer_key, self.consumer_secret, oauth_token, oauth_token_secret)
|
2011-01-19 22:56:49 -06:00
|
|
|
|
2012-07-14 09:29:12 -05:00
|
|
|
if self._verify_credentials() is not None:
|
|
|
|
# save the auth token for later reuse
|
|
|
|
self._persist_auth_tokens(oauth_token, oauth_token_secret)
|
2010-12-15 23:08:08 -06:00
|
|
|
|
2011-06-20 21:18:55 -05:00
|
|
|
def thread_do(self):
|
2011-04-23 12:58:35 -05:00
|
|
|
"""Check the timeline."""
|
2011-06-20 21:18:55 -05:00
|
|
|
|
|
|
|
while not self.is_shutdown:
|
|
|
|
self._check_self_timeline()
|
|
|
|
time.sleep(1)
|
2011-04-23 12:58:35 -05:00
|
|
|
|
2011-01-19 22:56:49 -06:00
|
|
|
def _check_self_timeline(self):
|
|
|
|
"""Check my timeline, and if there are entries, print them to the channel."""
|
|
|
|
|
2011-06-20 21:18:55 -05:00
|
|
|
if self.next_timeline_check < time.time():
|
|
|
|
self.next_timeline_check = time.time() + 300
|
|
|
|
|
2012-07-14 08:41:40 -05:00
|
|
|
if self._verify_credentials() is not None:
|
2011-06-20 21:18:55 -05:00
|
|
|
# get the id of the last check we made
|
|
|
|
since_id = self._get_last_since_id()
|
|
|
|
output_channel = self._get_output_channel()
|
2011-01-19 22:56:49 -06:00
|
|
|
|
2011-06-20 21:18:55 -05:00
|
|
|
if since_id is not None and output_channel != '':
|
|
|
|
tweets = self.twit.GetFriendsTimeline(since_id=since_id)
|
|
|
|
tweets.reverse()
|
|
|
|
for tweet in tweets:
|
|
|
|
tweet_text = self._return_tweet_or_retweet_text(tweet=tweet, print_source=True)
|
|
|
|
self.sendmsg(self.connection, output_channel.encode('utf-8', 'ignore'), tweet_text)
|
2011-01-19 22:56:49 -06:00
|
|
|
|
2011-06-20 21:18:55 -05:00
|
|
|
# friends timeline printed, find the latest id
|
|
|
|
new_since_id = self._get_latest_tweet_id(tweets, since_id)
|
2011-01-19 22:56:49 -06:00
|
|
|
|
2011-06-20 21:18:55 -05:00
|
|
|
tweets = self.twit.GetMentions(since_id=since_id)
|
|
|
|
tweets.reverse()
|
|
|
|
for tweet in tweets:
|
|
|
|
tweet_text = self._return_tweet_or_retweet_text(tweet=tweet, print_source=True)
|
|
|
|
self.sendmsg(self.connection, output_channel.encode('utf-8', 'ignore'), tweet_text)
|
2011-01-19 22:56:49 -06:00
|
|
|
|
2011-06-20 21:18:55 -05:00
|
|
|
# mentions printed, find the latest id
|
|
|
|
new_since_id = self._get_latest_tweet_id(tweets, new_since_id)
|
2011-01-19 22:56:49 -06:00
|
|
|
|
2011-06-20 21:18:55 -05:00
|
|
|
# set since_id
|
|
|
|
self._set_last_since_id(new_since_id)
|
2011-01-19 22:56:49 -06:00
|
|
|
|
2012-07-14 09:55:11 -05:00
|
|
|
def _return_tweet_or_retweet_text(self, tweet, print_source=False, print_id=True):
|
2010-12-16 13:04:58 -06:00
|
|
|
"""
|
|
|
|
Return a string of the author and text body of a status,
|
|
|
|
accounting for whether or not the fetched status is a
|
|
|
|
retweet.
|
|
|
|
"""
|
|
|
|
|
2012-07-14 09:55:11 -05:00
|
|
|
reply = ""
|
2010-12-16 13:04:58 -06:00
|
|
|
if tweet.retweeted_status:
|
|
|
|
retweet = tweet.retweeted_status
|
2010-12-16 15:22:34 -06:00
|
|
|
if print_source:
|
2012-07-14 09:55:11 -05:00
|
|
|
reply = "@%s (RT @%s): %s" % (tweet.user.screen_name.encode('utf-8', 'ignore'),
|
|
|
|
retweet.user.screen_name.encode('utf-8', 'ignore'),
|
|
|
|
super(Twitter, self)._unencode_xml(retweet.text.encode('utf-8', 'ignore')))
|
2010-12-16 15:22:34 -06:00
|
|
|
else:
|
2012-07-14 09:55:11 -05:00
|
|
|
reply = "(RT @%s): %s" % (retweet.user.screen_name.encode('utf-8', 'ignore'),
|
|
|
|
super(Twitter, self)._unencode_xml(retweet.text.encode('utf-8', 'ignore')))
|
2010-12-16 13:04:58 -06:00
|
|
|
else:
|
2010-12-16 15:22:34 -06:00
|
|
|
if print_source:
|
2012-07-14 09:55:11 -05:00
|
|
|
reply = "@%s: %s" % (tweet.user.screen_name.encode('utf-8', 'ignore'),
|
|
|
|
super(Twitter, self)._unencode_xml(tweet.text.encode('utf-8', 'ignore')))
|
2010-12-16 15:22:34 -06:00
|
|
|
else:
|
2012-07-14 09:55:11 -05:00
|
|
|
reply = "%s" % (super(Twitter, self)._unencode_xml(tweet.text.encode('utf-8', 'ignore')))
|
|
|
|
|
|
|
|
if print_id:
|
|
|
|
reply = reply + " [{0:d}]".format(tweet.id)
|
|
|
|
|
|
|
|
return reply
|
2010-12-16 13:04:58 -06:00
|
|
|
|
2011-01-19 22:56:49 -06:00
|
|
|
def _get_last_since_id(self):
|
|
|
|
"""Get the since_id out of the database."""
|
|
|
|
|
2012-07-27 02:18:01 -05:00
|
|
|
db = self.get_db()
|
2011-01-19 22:56:49 -06:00
|
|
|
try:
|
2012-07-27 02:18:01 -05:00
|
|
|
cur = db.cursor(mdb.cursors.DictCursor)
|
2012-07-14 08:17:21 -05:00
|
|
|
query = "SELECT since_id FROM twitter_settings"
|
2012-07-27 02:18:01 -05:00
|
|
|
cur.execute(query)
|
|
|
|
result = cur.fetchone()
|
2011-01-19 22:56:49 -06:00
|
|
|
if result:
|
|
|
|
return result['since_id']
|
2012-07-27 02:18:01 -05:00
|
|
|
except mdb.Error as e:
|
|
|
|
self.log.error("database error getting last since ID")
|
|
|
|
self.log.exception(e)
|
2011-01-19 22:56:49 -06:00
|
|
|
raise
|
2012-07-27 02:18:01 -05:00
|
|
|
finally: cur.close()
|
2011-01-19 22:56:49 -06:00
|
|
|
|
|
|
|
def _get_output_channel(self):
|
|
|
|
"""Get the output_channel out of the database."""
|
|
|
|
|
2012-07-27 02:18:01 -05:00
|
|
|
db = self.get_db()
|
2011-01-19 22:56:49 -06:00
|
|
|
try:
|
2012-07-27 02:18:01 -05:00
|
|
|
cur = db.cursor(mdb.cursors.DictCursor)
|
2012-07-14 08:17:21 -05:00
|
|
|
query = "SELECT output_channel FROM twitter_settings"
|
2012-07-27 02:18:01 -05:00
|
|
|
cur.execute(query)
|
|
|
|
result = cur.fetchone()
|
2011-01-19 22:56:49 -06:00
|
|
|
if result:
|
|
|
|
return result['output_channel']
|
2012-07-27 02:18:01 -05:00
|
|
|
except mdb.Error as e:
|
|
|
|
self.log.error("database error getting output channel")
|
|
|
|
self.log.exception(e)
|
2011-01-19 22:56:49 -06:00
|
|
|
raise
|
2012-07-27 02:18:01 -05:00
|
|
|
finally: cur.close()
|
2011-01-19 22:56:49 -06:00
|
|
|
|
|
|
|
def _set_last_since_id(self, since_id):
|
|
|
|
"""Set the since_id."""
|
|
|
|
|
2012-07-27 02:18:01 -05:00
|
|
|
db = self.get_db()
|
2011-01-19 22:56:49 -06:00
|
|
|
try:
|
2012-07-27 02:18:01 -05:00
|
|
|
cur = db.cursor(mdb.cursors.DictCursor)
|
|
|
|
statement = "UPDATE twitter_settings SET since_id = %s"
|
2011-01-19 22:56:49 -06:00
|
|
|
cur.execute(statement, (since_id,))
|
|
|
|
db.commit()
|
2012-07-27 02:18:01 -05:00
|
|
|
except mdb.Error as e:
|
|
|
|
self.log.error("database error saving last since ID")
|
|
|
|
self.log.exception(e)
|
2011-01-19 22:56:49 -06:00
|
|
|
raise
|
2012-07-27 02:18:01 -05:00
|
|
|
finally: cur.close()
|
2011-01-19 22:56:49 -06:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2012-07-14 09:29:12 -05:00
|
|
|
def _persist_auth_tokens(self, oauth_token, oauth_token_secret):
|
|
|
|
"""Save the auth tokens to the database, with the intent of reusing them."""
|
|
|
|
|
2012-07-27 02:18:01 -05:00
|
|
|
db = self.get_db()
|
2012-07-14 09:29:12 -05:00
|
|
|
try:
|
2012-07-27 02:18:01 -05:00
|
|
|
cur = db.cursor(mdb.cursors.DictCursor)
|
|
|
|
statement = "UPDATE twitter_settings SET oauth_token = %s, oauth_token_secret = %s"
|
2012-07-14 09:29:12 -05:00
|
|
|
cur.execute(statement, (oauth_token, oauth_token_secret))
|
|
|
|
db.commit()
|
2012-07-27 02:18:01 -05:00
|
|
|
except mdb.Error as e:
|
|
|
|
self.log.error("database error saving auth tokens")
|
|
|
|
self.log.exception(e)
|
2012-07-14 09:29:12 -05:00
|
|
|
raise
|
2012-07-27 02:18:01 -05:00
|
|
|
finally: cur.close()
|
2012-07-14 09:29:12 -05:00
|
|
|
|
|
|
|
def _retrieve_stored_auth_tokens(self):
|
|
|
|
"""Check the database for existing auth tokens, try reusing them."""
|
|
|
|
|
2012-07-27 02:18:01 -05:00
|
|
|
db = self.get_db()
|
2012-07-14 09:29:12 -05:00
|
|
|
try:
|
2012-07-27 02:18:01 -05:00
|
|
|
cur = db.cursor(mdb.cursors.DictCursor)
|
2012-07-14 09:29:12 -05:00
|
|
|
query = "SELECT oauth_token, oauth_token_secret FROM twitter_settings"
|
2012-07-27 02:18:01 -05:00
|
|
|
cur.execute(query)
|
|
|
|
result = cur.fetchone()
|
2012-07-14 09:29:12 -05:00
|
|
|
if result:
|
|
|
|
return (result['oauth_token'], result['oauth_token_secret'])
|
2012-07-27 02:18:01 -05:00
|
|
|
except mdb.Error as e:
|
|
|
|
self.log.error("database error retrieving auth tokens")
|
|
|
|
self.log.exception(e)
|
2012-07-14 09:29:12 -05:00
|
|
|
raise
|
2012-07-27 02:18:01 -05:00
|
|
|
finally: cur.close()
|
2012-07-14 09:29:12 -05:00
|
|
|
|
2010-12-15 23:08:08 -06:00
|
|
|
# vi:tabstop=4:expandtab:autoindent
|
|
|
|
# kate: indent-mode python;indent-width 4;replace-tabs on;
|