]> jfr.im git - z_archive/twitter.git/blobdiff - twitter/ircbot.py
IRC Bot now functions with OAuth.
[z_archive/twitter.git] / twitter / ircbot.py
index e882ba7433206eaef42e5151075fcb47cf0582ba..1ea68237946caa973157813ad6926f491188f0bc 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.
-  
+
 USAGE
 
   twitterbot [config_file]
@@ -12,21 +12,29 @@ USAGE
 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_channel_to_join>
+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.
+
+  The default token file is ~/.twitterbot_oauth.
+
 """
 
-BOT_VERSION = "TwitterBot 1.0 (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)
@@ -39,9 +47,12 @@ from dateutil.parser import parse
 from ConfigParser import SafeConfigParser
 from heapq import heappop, heappush
 import traceback
+import os
 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:
@@ -51,6 +62,8 @@ except:
         "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
@@ -65,10 +78,10 @@ class SchedTask(object):
     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 __call__(self):
         return self.task()
 
@@ -77,7 +90,7 @@ class Scheduler(object):
         self.task_heap = []
         for task in tasks:
             heappush(self.task_heap, task)
-    
+
     def next_task(self):
         now = time.time()
         task = heappop(self.task_heap)
@@ -88,23 +101,33 @@ class Scheduler(object):
             time.sleep(wait)
         task()
         debug("tasks: " + str(self.task_heap))
-        
+
     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)
+
+        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.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)))
@@ -118,7 +141,7 @@ class TwitterBot(object):
             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()
@@ -131,20 +154,20 @@ class TwitterBot(object):
                 # TODO This would be better if we only ignored messages
                 #   to people who are not on our following list.
                 if not text.startswith("@"):
-                    self.privmsg_channel(
+                    self.privmsg_channels(
                         u"=^_^=  %s%s%s %s" %(
                             IRC_BOLD, update['user']['screen_name'],
                             IRC_BOLD, text.decode('utf-8')))
-                
+
                 nextLastUpdate = crt
             else:
                 break
         self.lastUpdate = nextLastUpdate
-        
+
     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(' ')
@@ -157,13 +180,13 @@ class TwitterBot(object):
                 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)
-    
+
     def handle_ctcp(self, conn, evt):
         args = evt.arguments()
         source = evt.source().split('!')[0]
@@ -178,7 +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_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()]
@@ -198,10 +226,10 @@ class TwitterBot(object):
             conn.privmsg(
                 userNick,
                 "=^_^= Okay! I'm now following %s." %(name))
-            self.privmsg_channel(
+            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()]
@@ -215,16 +243,18 @@ class TwitterBot(object):
             conn.privmsg(
                 userNick,
                 "=^_^= Okay! I've stopped following %s." %(name))
-            self.privmsg_channel(
+            self.privmsg_channels(
                 "=o_o= %s has asked me to stop following %s" %(
                     userNick, name))
-    
+
     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'))
-        self.ircServer.join(self.config.get('irc', 'channel'))
+        channels=self.config.get('irc', 'channel').split(',')
+        for channel in channels:
+            self.ircServer.join(channel)
 
         while True:
             try:
@@ -236,13 +266,14 @@ class TwitterBot(object):
                 pass
 
 def load_config(filename):
-    defaults = dict(server=dict(port=6667, nick="twitterbot"))
+    defaults = dict(
+        server=dict(port=6667, nick="twitterbot"),
+        twitter=dict(oauth_token_file=OAUTH_FILE))
     cp = SafeConfigParser(defaults)
     cp.read((filename,))
-    
+
     # 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')
@@ -250,22 +281,21 @@ def load_config(filename):
 
     return cp
 
-
-# Howdy, hacker!! You've found the secret Twitter business model!!
-#
-# 1. provide awesome status-update service
-# 2. buy a lot of new hardware to keep it running
-# 3. ???
-# 4. profit!
+# So there was a joke here about the twitter business model
+# but I got rid of it. Not because I want this codebase to
+# be "professional" in any way, but because someone forked
+# this and deleted the comment because they couldn't take
+# a joke. Hi guy!
 #
-# I'm just kidding... :3
-
+# Fact: The number one use of Google Code is to look for that
+# comment in the Linux kernel that goes "FUCK me gently with
+# a chainsaw." Pretty sure Linus himself wrote it.
 
 def main():
     configFilename = "twitterbot.ini"
     if (sys.argv[1:]):
         configFilename = sys.argv[1]
-        
+
     try:
         if not os.path.exists(configFilename):
             raise Exception()