]> jfr.im git - z_archive/twitter.git/blobdiff - twitter/ircbot.py
Fix oauth_dance function.
[z_archive/twitter.git] / twitter / ircbot.py
index 53e4edef2bd64d9a0c35023586eb009c965ae8c3..873850d8715e3805e711e8877d926897de660eb9 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,23 +12,34 @@ 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>
 nick: <irc_nickname>
 channel: <irc_channels_to_join>
 [irc]
 server: <irc_server>
 port: <irc_port>
 nick: <irc_nickname>
 channel: <irc_channels_to_join>
+prefixes: <prefix_type>
 
 [twitter]
 
 [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.
+
+  The default prefix type is 'cats'. You can also use 'none'.
+
 """
 
 """
 
-BOT_VERSION = "TwitterBot 1.1 (http://mike.verdone.ca/twitter)"
+from __future__ import print_function
+
+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)
@@ -37,25 +48,50 @@ IRC_REGULAR = chr(0x0f)
 
 import sys
 import time
 
 import sys
 import time
-from dateutil.parser import parse
-from ConfigParser import SafeConfigParser
+from datetime import datetime, timedelta
+from email.utils import parsedate
+try:
+    from configparser import ConfigParser
+except ImportError:
+    from ConfigParser import ConfigParser
 from heapq import heappop, heappush
 import traceback
 from heapq import heappop, heappush
 import traceback
+import os
 import os.path
 
 import os.path
 
-from api import Twitter, TwitterError
-from util import htmlentitydecode
+from .api import Twitter, TwitterError
+from .oauth import OAuth, read_token_file
+from .oauth_dance import oauth_dance
+from .util import htmlentitydecode
+
+PREFIXES = dict(
+    cats=dict(
+        new_tweet="=^_^= ",
+        error="=O_o= ",
+        inform="=o_o= "
+        ),
+    none=dict(
+        new_tweet=""
+        ),
+    )
+ACTIVE_PREFIXES=dict()
+
+def get_prefix(prefix_typ=None):
+    return ACTIVE_PREFIXES.get(prefix_typ, ACTIVE_PREFIXES.get('new_tweet', ''))
+
 
 try:
     import irclib
 
 try:
     import irclib
-except:
+except ImportError:
     raise ImportError(
         "This module requires python irclib available from "
     raise ImportError(
         "This module requires python irclib available from "
-        + "http://python-irclib.sourceforge.net/")
+        + "https://github.com/sixohsix/python-irclib/zipball/python-irclib3-0.4.8")
+
+OAUTH_FILE = os.environ.get('HOME', '') + os.sep + '.twitterbot_oauth'
 
 def debug(msg):
     # uncomment this for debug text stuff
 
 def debug(msg):
     # uncomment this for debug text stuff
-    # print >> sys.stderr, msg
+    # print(msg, file=sys.stdout)
     pass
 
 class SchedTask(object):
     pass
 
 class SchedTask(object):
@@ -66,11 +102,11 @@ class SchedTask(object):
 
     def __repr__(self):
         return "<SchedTask %s next:%i delta:%i>" %(
 
     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)
-    
+            self.task.__name__, self.__next__, self.delta)
+
+    def __lt__(self, other):
+        return self.next < other.next
+
     def __call__(self):
         return self.task()
 
     def __call__(self):
         return self.task()
 
@@ -79,7 +115,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)
@@ -89,42 +125,55 @@ class Scheduler(object):
         if (wait > 0):
             time.sleep(wait)
         task()
         if (wait > 0):
             time.sleep(wait)
         task()
-        debug("tasks: " + str(self.task_heap))
-        
+        #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)
+
+        global ACTIVE_PREFIXES
+        ACTIVE_PREFIXES = PREFIXES[self.config.get('irc', 'prefixes')]
+
+        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)))
-        self.lastUpdate = time.gmtime()
+        self.lastUpdate = (datetime.utcnow() - timedelta(minutes=10)).utctimetuple()
 
     def check_statuses(self):
         debug("In check_statuses")
         try:
 
     def check_statuses(self):
         debug("In check_statuses")
         try:
-            updates = self.twitter.statuses.friends_timeline()
-        except Exception, e:
-            print >> sys.stderr, "Exception while querying twitter:"
+            updates = reversed(self.twitter.statuses.friends_timeline())
+        except Exception as e:
+            print("Exception while querying twitter:", file=sys.stderr)
             traceback.print_exc(file=sys.stderr)
             return
             traceback.print_exc(file=sys.stderr)
             return
-        
+
         nextLastUpdate = self.lastUpdate
         for update in updates:
         nextLastUpdate = self.lastUpdate
         for update in updates:
-            crt = parse(update['created_at']).utctimetuple()
-            if (crt > self.lastUpdate):
+            crt = parsedate(update['created_at'])
+            if (crt > nextLastUpdate):
                 text = (htmlentitydecode(
                     update['text'].replace('\n', ' '))
                     .encode('utf-8', 'replace'))
                 text = (htmlentitydecode(
                     update['text'].replace('\n', ' '))
                     .encode('utf-8', 'replace'))
@@ -132,21 +181,20 @@ class TwitterBot(object):
                 # Skip updates beginning with @
                 # TODO This would be better if we only ignored messages
                 #   to people who are not on our following list.
                 # Skip updates beginning with @
                 # TODO This would be better if we only ignored messages
                 #   to people who are not on our following list.
-                if not text.startswith("@"):
+                if not text.startswith(b"@"):
                     self.privmsg_channels(
                     self.privmsg_channels(
-                        u"=^_^=  %s%s%s %s" %(
+                        "%s %s%s%s %s" %(
+                            get_prefix(),
                             IRC_BOLD, update['user']['screen_name'],
                             IRC_BOLD, text.decode('utf-8')))
                             IRC_BOLD, update['user']['screen_name'],
                             IRC_BOLD, text.decode('utf-8')))
-                
+
                 nextLastUpdate = crt
                 nextLastUpdate = crt
-            else:
-                break
+
         self.lastUpdate = nextLastUpdate
         self.lastUpdate = nextLastUpdate
-        
+
     def process_events(self):
     def process_events(self):
-        debug("In process_events")
         self.irc.process_once()
         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 +207,14 @@ 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], 
-                    "=^_^= Hi! I'm Twitterbot! you can (follow "
-                    + "<twitter_name>) to make me follow a user or "
-                    + "(unfollow <twitter_name>) to make me stop.")
+                    evt.source().split('!')[0],
+                    "%sHi! I'm Twitterbot! you can (follow "
+                    "<twitter_name>) to make me follow a user or "
+                    "(unfollow <twitter_name>) to make me stop." %
+                    get_prefix())
         except Exception:
             traceback.print_exc(file=sys.stderr)
         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]
@@ -179,13 +228,13 @@ class TwitterBot(object):
 
     def privmsg_channel(self, msg):
         return self.ircServer.privmsg(
 
     def privmsg_channel(self, msg):
         return self.ircServer.privmsg(
-            self.config.get('irc', 'channel'), msg.encode('utf-8'))
-            
+            self.config.get('irc', 'channel'), msg)
+
     def privmsg_channels(self, msg):
         return_response=True
         channels=self.config.get('irc','channel').split(',')
     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'))
-            
+        return self.ircServer.privmsg_many(channels, msg)
+
     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()]
@@ -193,22 +242,24 @@ class TwitterBot(object):
         if (name in friends):
             conn.privmsg(
                 userNick,
         if (name in friends):
             conn.privmsg(
                 userNick,
-                "=O_o= I'm already following %s." %(name))
+                "%sI'm already following %s." %(get_prefix('error'), name))
         else:
             try:
                 self.twitter.friendships.create(id=name)
             except TwitterError:
                 conn.privmsg(
                     userNick,
         else:
             try:
                 self.twitter.friendships.create(id=name)
             except TwitterError:
                 conn.privmsg(
                     userNick,
-                    "=O_o= I can't follow that user. Are you sure the name is correct?")
+                    "%sI can't follow that user. Are you sure the name is correct?" %(
+                        get_prefix('error')
+                        ))
                 return
             conn.privmsg(
                 userNick,
                 return
             conn.privmsg(
                 userNick,
-                "=^_^= Okay! I'm now following %s." %(name))
+                "%sOkay! I'm now following %s." %(get_prefix('followed'), name))
             self.privmsg_channels(
             self.privmsg_channels(
-                "=o_o= %s has asked me to start following %s" %(
-                    userNick, name))
-    
+                "%s%s has asked me to start following %s" %(
+                    get_prefix('inform'), 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()]
@@ -216,19 +267,20 @@ class TwitterBot(object):
         if (name not in friends):
             conn.privmsg(
                 userNick,
         if (name not in friends):
             conn.privmsg(
                 userNick,
-                "=O_o= I'm not following %s." %(name))
+                "%sI'm not following %s." %(get_prefix('error'), name))
         else:
             self.twitter.friendships.destroy(id=name)
             conn.privmsg(
                 userNick,
         else:
             self.twitter.friendships.destroy(id=name)
             conn.privmsg(
                 userNick,
-                "=^_^= Okay! I've stopped following %s." %(name))
+                "%sOkay! I've stopped following %s." %(
+                    get_prefix('stop_follow'), name))
             self.privmsg_channels(
             self.privmsg_channels(
-                "=o_o= %s has asked me to stop following %s" %(
-                    userNick, name))
-    
+                "%s%s has asked me to stop following %s" %(
+                    get_prefix('inform'), 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(',')
@@ -241,17 +293,25 @@ class TwitterBot(object):
             except KeyboardInterrupt:
                 break
             except TwitterError:
             except KeyboardInterrupt:
                 break
             except TwitterError:
-                # twitter.com is probably down because it sucks. ignore the fault and keep going
+                # twitter.com is probably down because it
+                # sucks. ignore the fault and keep going
                 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 = ConfigParser()
+    cp.add_section('irc')
+    cp.set('irc', 'port', '6667')
+    cp.set('irc', 'nick', 'twitterbot')
+    cp.set('irc', 'prefixes', 'cats')
+    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,16 +333,16 @@ 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()
         load_config(configFilename)
     try:
         if not os.path.exists(configFilename):
             raise Exception()
         load_config(configFilename)
-    except Exception, e:
-        print >> sys.stderr, "Error while loading ini file %s" %(
-            configFilename)
-        print >> sys.stderr, e
-        print >> sys.stderr, __doc__
+    except Exception as e:
+        print("Error while loading ini file %s" %(
+            configFilename), file=sys.stderr)
+        print(e, file=sys.stderr)
+        print(__doc__, file=sys.stderr)
         sys.exit(1)
 
     bot = TwitterBot(configFilename)
         sys.exit(1)
 
     bot = TwitterBot(configFilename)