]> jfr.im git - z_archive/twitter.git/blobdiff - twitter/ircbot.py
Explicitly utf8 decode strings coming from Twitter.
[z_archive/twitter.git] / twitter / ircbot.py
index 53e4edef2bd64d9a0c35023586eb009c965ae8c3..bfac05086445e1257275d68f7a07a811c46bce2a 100644 (file)
@@ -4,7 +4,7 @@ twitterbot
   A twitter IRC bot. Twitterbot connected to an IRC server and idles in
   a channel, polling a twitter account and broadcasting all updates to
   friends.
   A twitter IRC bot. Twitterbot connected to an IRC server and idles in
   a channel, polling a twitter account and broadcasting all updates to
   friends.
-  
+
 USAGE
 
   twitterbot [config_file]
 USAGE
 
   twitterbot [config_file]
@@ -12,7 +12,7 @@ USAGE
 CONFIG_FILE
 
   The config file is an ini-style file that must contain the following:
 CONFIG_FILE
 
   The config file is an ini-style file that must contain the following:
-  
+
 [irc]
 server: <irc_server>
 port: <irc_port>
 [irc]
 server: <irc_server>
 port: <irc_port>
@@ -20,15 +20,21 @@ nick: <irc_nickname>
 channel: <irc_channels_to_join>
 
 [twitter]
 channel: <irc_channels_to_join>
 
 [twitter]
-email: <twitter_account_email>
-password: <twitter_account_password>
+oauth_token_file: <oauth_token_filename>
+
 
   If no config file is given "twitterbot.ini" will be used by default.
 
   The channel argument can accept multiple channels separated by commas.
 
   If no config file is given "twitterbot.ini" will be used by default.
 
   The channel argument can accept multiple channels separated by commas.
+
+  The default token file is ~/.twitterbot_oauth.
+
 """
 
 """
 
-BOT_VERSION = "TwitterBot 1.1 (http://mike.verdone.ca/twitter)"
+BOT_VERSION = "TwitterBot 1.4 (http://mike.verdone.ca/twitter)"
+
+CONSUMER_KEY = "XryIxN3J2ACaJs50EizfLQ"
+CONSUMER_SECRET = "j7IuDCNjftVY8DBauRdqXs4jDl5Fgk1IJRag8iE"
 
 IRC_BOLD = chr(0x02)
 IRC_ITALIC = chr(0x16)
 
 IRC_BOLD = chr(0x02)
 IRC_ITALIC = chr(0x16)
@@ -41,9 +47,12 @@ from dateutil.parser import parse
 from ConfigParser import SafeConfigParser
 from heapq import heappop, heappush
 import traceback
 from ConfigParser import SafeConfigParser
 from heapq import heappop, heappush
 import traceback
+import os
 import os.path
 
 from api import Twitter, TwitterError
 import os.path
 
 from api import Twitter, TwitterError
+from oauth import OAuth, read_token_file
+from oauth_dance import oauth_dance
 from util import htmlentitydecode
 
 try:
 from util import htmlentitydecode
 
 try:
@@ -53,6 +62,8 @@ except:
         "This module requires python irclib available from "
         + "http://python-irclib.sourceforge.net/")
 
         "This module requires python irclib available from "
         + "http://python-irclib.sourceforge.net/")
 
+OAUTH_FILE = os.environ.get('HOME', '') + os.sep + '.twitterbot_oauth'
+
 def debug(msg):
     # uncomment this for debug text stuff
     # print >> sys.stderr, msg
 def debug(msg):
     # uncomment this for debug text stuff
     # print >> sys.stderr, msg
@@ -67,10 +78,10 @@ class SchedTask(object):
     def __repr__(self):
         return "<SchedTask %s next:%i delta:%i>" %(
             self.task.__name__, self.next, self.delta)
     def __repr__(self):
         return "<SchedTask %s next:%i delta:%i>" %(
             self.task.__name__, self.next, self.delta)
-    
+
     def __cmp__(self, other):
         return cmp(self.next, other.next)
     def __cmp__(self, other):
         return cmp(self.next, other.next)
-    
+
     def __call__(self):
         return self.task()
 
     def __call__(self):
         return self.task()
 
@@ -79,7 +90,7 @@ class Scheduler(object):
         self.task_heap = []
         for task in tasks:
             heappush(self.task_heap, task)
         self.task_heap = []
         for task in tasks:
             heappush(self.task_heap, task)
-    
+
     def next_task(self):
         now = time.time()
         task = heappop(self.task_heap)
     def next_task(self):
         now = time.time()
         task = heappop(self.task_heap)
@@ -90,23 +101,33 @@ class Scheduler(object):
             time.sleep(wait)
         task()
         debug("tasks: " + str(self.task_heap))
             time.sleep(wait)
         task()
         debug("tasks: " + str(self.task_heap))
-        
+
     def run_forever(self):
         while True:
             self.next_task()
 
     def run_forever(self):
         while True:
             self.next_task()
 
-            
+
 class TwitterBot(object):
     def __init__(self, configFilename):
         self.configFilename = configFilename
         self.config = load_config(self.configFilename)
 class TwitterBot(object):
     def __init__(self, configFilename):
         self.configFilename = configFilename
         self.config = load_config(self.configFilename)
+
+        oauth_file = self.config.get('twitter', 'oauth_token_file')
+        if not os.path.exists(oauth_file):
+            oauth_dance("IRC Bot", CONSUMER_KEY, CONSUMER_SECRET, oauth_file)
+        oauth_token, oauth_secret = read_token_file(oauth_file)
+
+        self.twitter = Twitter(
+            auth=OAuth(
+                oauth_token, oauth_secret, CONSUMER_KEY, CONSUMER_SECRET),
+            api_version='1',
+            domain='api.twitter.com')
+
         self.irc = irclib.IRC()
         self.irc.add_global_handler('privmsg', self.handle_privmsg)
         self.irc.add_global_handler('ctcp', self.handle_ctcp)
         self.ircServer = self.irc.server()
         self.irc = irclib.IRC()
         self.irc.add_global_handler('privmsg', self.handle_privmsg)
         self.irc.add_global_handler('ctcp', self.handle_ctcp)
         self.ircServer = self.irc.server()
-        self.twitter = Twitter(
-            self.config.get('twitter', 'email'),
-            self.config.get('twitter', 'password'))
+
         self.sched = Scheduler(
             (SchedTask(self.process_events, 1),
              SchedTask(self.check_statuses, 120)))
         self.sched = Scheduler(
             (SchedTask(self.process_events, 1),
              SchedTask(self.check_statuses, 120)))
@@ -120,7 +141,7 @@ class TwitterBot(object):
             print >> sys.stderr, "Exception while querying twitter:"
             traceback.print_exc(file=sys.stderr)
             return
             print >> sys.stderr, "Exception while querying twitter:"
             traceback.print_exc(file=sys.stderr)
             return
-        
+
         nextLastUpdate = self.lastUpdate
         for update in updates:
             crt = parse(update['created_at']).utctimetuple()
         nextLastUpdate = self.lastUpdate
         for update in updates:
             crt = parse(update['created_at']).utctimetuple()
@@ -137,16 +158,16 @@ class TwitterBot(object):
                         u"=^_^=  %s%s%s %s" %(
                             IRC_BOLD, update['user']['screen_name'],
                             IRC_BOLD, text.decode('utf-8')))
                         u"=^_^=  %s%s%s %s" %(
                             IRC_BOLD, update['user']['screen_name'],
                             IRC_BOLD, text.decode('utf-8')))
-                
+
                 nextLastUpdate = crt
             else:
                 break
         self.lastUpdate = nextLastUpdate
                 nextLastUpdate = crt
             else:
                 break
         self.lastUpdate = nextLastUpdate
-        
+
     def process_events(self):
         debug("In process_events")
         self.irc.process_once()
     def process_events(self):
         debug("In process_events")
         self.irc.process_once()
-    
+
     def handle_privmsg(self, conn, evt):
         debug('got privmsg')
         args = evt.arguments()[0].split(' ')
     def handle_privmsg(self, conn, evt):
         debug('got privmsg')
         args = evt.arguments()[0].split(' ')
@@ -159,13 +180,13 @@ class TwitterBot(object):
                 self.unfollow(conn, evt, args[1])
             else:
                 conn.privmsg(
                 self.unfollow(conn, evt, args[1])
             else:
                 conn.privmsg(
-                    evt.source().split('!')[0], 
+                    evt.source().split('!')[0],
                     "=^_^= Hi! I'm Twitterbot! you can (follow "
                     + "<twitter_name>) to make me follow a user or "
                     + "(unfollow <twitter_name>) to make me stop.")
         except Exception:
             traceback.print_exc(file=sys.stderr)
                     "=^_^= Hi! I'm Twitterbot! you can (follow "
                     + "<twitter_name>) to make me follow a user or "
                     + "(unfollow <twitter_name>) to make me stop.")
         except Exception:
             traceback.print_exc(file=sys.stderr)
-    
+
     def handle_ctcp(self, conn, evt):
         args = evt.arguments()
         source = evt.source().split('!')[0]
     def handle_ctcp(self, conn, evt):
         args = evt.arguments()
         source = evt.source().split('!')[0]
@@ -180,12 +201,12 @@ class TwitterBot(object):
     def privmsg_channel(self, msg):
         return self.ircServer.privmsg(
             self.config.get('irc', 'channel'), msg.encode('utf-8'))
     def privmsg_channel(self, msg):
         return self.ircServer.privmsg(
             self.config.get('irc', 'channel'), msg.encode('utf-8'))
-            
+
     def privmsg_channels(self, msg):
         return_response=True
         channels=self.config.get('irc','channel').split(',')
         return self.ircServer.privmsg_many(channels, msg.encode('utf-8'))
     def privmsg_channels(self, msg):
         return_response=True
         channels=self.config.get('irc','channel').split(',')
         return self.ircServer.privmsg_many(channels, msg.encode('utf-8'))
-            
+
     def follow(self, conn, evt, name):
         userNick = evt.source().split('!')[0]
         friends = [x['name'] for x in self.twitter.statuses.friends()]
     def follow(self, conn, evt, name):
         userNick = evt.source().split('!')[0]
         friends = [x['name'] for x in self.twitter.statuses.friends()]
@@ -208,7 +229,7 @@ class TwitterBot(object):
             self.privmsg_channels(
                 "=o_o= %s has asked me to start following %s" %(
                     userNick, name))
             self.privmsg_channels(
                 "=o_o= %s has asked me to start following %s" %(
                     userNick, name))
-    
+
     def unfollow(self, conn, evt, name):
         userNick = evt.source().split('!')[0]
         friends = [x['name'] for x in self.twitter.statuses.friends()]
     def unfollow(self, conn, evt, name):
         userNick = evt.source().split('!')[0]
         friends = [x['name'] for x in self.twitter.statuses.friends()]
@@ -225,10 +246,10 @@ class TwitterBot(object):
             self.privmsg_channels(
                 "=o_o= %s has asked me to stop following %s" %(
                     userNick, name))
             self.privmsg_channels(
                 "=o_o= %s has asked me to stop following %s" %(
                     userNick, name))
-    
+
     def run(self):
         self.ircServer.connect(
     def run(self):
         self.ircServer.connect(
-            self.config.get('irc', 'server'), 
+            self.config.get('irc', 'server'),
             self.config.getint('irc', 'port'),
             self.config.get('irc', 'nick'))
         channels=self.config.get('irc', 'channel').split(',')
             self.config.getint('irc', 'port'),
             self.config.get('irc', 'nick'))
         channels=self.config.get('irc', 'channel').split(',')
@@ -245,13 +266,18 @@ class TwitterBot(object):
                 pass
 
 def load_config(filename):
                 pass
 
 def load_config(filename):
-    defaults = dict(server=dict(port=6667, nick="twitterbot"))
-    cp = SafeConfigParser(defaults)
+    # Note: Python ConfigParser module has the worst interface in the
+    # world. Mega gross.
+    cp = SafeConfigParser()
+    cp.add_section('irc')
+    cp.set('irc', 'port', '6667')
+    cp.set('irc', 'nick', 'twitterbot')
+    cp.add_section('twitter')
+    cp.set('twitter', 'oauth_token_file', OAUTH_FILE)
     cp.read((filename,))
     cp.read((filename,))
-    
+
     # attempt to read these properties-- they are required
     # attempt to read these properties-- they are required
-    cp.get('twitter', 'email'),
-    cp.get('twitter', 'password')
+    cp.get('twitter', 'oauth_token_file'),
     cp.get('irc', 'server')
     cp.getint('irc', 'port')
     cp.get('irc', 'nick')
     cp.get('irc', 'server')
     cp.getint('irc', 'port')
     cp.get('irc', 'nick')
@@ -273,7 +299,7 @@ def main():
     configFilename = "twitterbot.ini"
     if (sys.argv[1:]):
         configFilename = sys.argv[1]
     configFilename = "twitterbot.ini"
     if (sys.argv[1:]):
         configFilename = sys.argv[1]
-        
+
     try:
         if not os.path.exists(configFilename):
             raise Exception()
     try:
         if not os.path.exists(configFilename):
             raise Exception()