]> jfr.im git - z_archive/twitter.git/blobdiff - twitter/ircbot.py
More stuff in gitignore.
[z_archive/twitter.git] / twitter / ircbot.py
index 53e4edef2bd64d9a0c35023586eb009c965ae8c3..c1bae0814a851c7f2b78dd9bbdc5d229ecb4999e 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,32 @@ 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)"
+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,15 +46,35 @@ IRC_REGULAR = chr(0x0f)
 
 import sys
 import time
 
 import sys
 import time
-from dateutil.parser import parse
+from datetime import datetime, timedelta
+from email.utils import parsedate
 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
 
 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
 except:
 try:
     import irclib
 except:
@@ -53,6 +82,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 +98,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 +110,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 +120,56 @@ 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()
+            updates = reversed(self.twitter.statuses.friends_timeline())
         except Exception, e:
             print >> sys.stderr, "Exception while querying twitter:"
             traceback.print_exc(file=sys.stderr)
             return
         except Exception, e:
             print >> sys.stderr, "Exception while querying twitter:"
             traceback.print_exc(file=sys.stderr)
             return
-        
+
         nextLastUpdate = self.lastUpdate
         nextLastUpdate = self.lastUpdate
+        debug("self.lastUpdate is %s" % self.lastUpdate)
         for update in updates:
         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'))
@@ -134,19 +179,19 @@ class TwitterBot(object):
                 #   to people who are not on our following list.
                 if not text.startswith("@"):
                     self.privmsg_channels(
                 #   to people who are not on our following list.
                 if not text.startswith("@"):
                     self.privmsg_channels(
-                        u"=^_^=  %s%s%s %s" %(
+                        u"%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
+
+        debug("setting self.lastUpdate to %s" % nextLastUpdate)
         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 +204,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]
@@ -180,12 +226,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()]
@@ -193,22 +239,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 +264,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 +290,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 = SafeConfigParser()
+    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,7 +330,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()