X-Git-Url: https://jfr.im/git/z_archive/twitter.git/blobdiff_plain/85681608c592c7e3eb6cc75367dc0cdd2ea953dd..4f0b5ca6894574589ef3598aa0b366d702c69583:/twitter/ircbot.py diff --git a/twitter/ircbot.py b/twitter/ircbot.py index 6819a88..463136f 100644 --- a/twitter/ircbot.py +++ b/twitter/ircbot.py @@ -34,7 +34,9 @@ oauth_token_file: """ -BOT_VERSION = "TwitterBot 1.4 (http://mike.verdone.ca/twitter)" +from __future__ import print_function + +BOT_VERSION = "TwitterBot 1.6.1 (http://mike.verdone.ca/twitter)" CONSUMER_KEY = "XryIxN3J2ACaJs50EizfLQ" CONSUMER_SECRET = "j7IuDCNjftVY8DBauRdqXs4jDl5Fgk1IJRag8iE" @@ -46,17 +48,21 @@ IRC_REGULAR = chr(0x0f) 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 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 +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( @@ -76,16 +82,16 @@ def get_prefix(prefix_typ=None): try: import irclib -except: +except ImportError: 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 - # print >> sys.stderr, msg + # print(msg, file=sys.stdout) pass class SchedTask(object): @@ -96,10 +102,10 @@ class SchedTask(object): def __repr__(self): return "" %( - self.task.__name__, self.next, self.delta) + self.task.__name__, self.__next__, self.delta) - def __cmp__(self, other): - return cmp(self.next, other.next) + def __lt__(self, other): + return self.next < other.next def __call__(self): return self.task() @@ -148,48 +154,43 @@ class TwitterBot(object): self.irc = irclib.IRC() self.irc.add_global_handler('privmsg', self.handle_privmsg) self.irc.add_global_handler('ctcp', self.handle_ctcp) + self.irc.add_global_handler('umode', self.handle_umode) self.ircServer = self.irc.server() 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: - 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 nextLastUpdate = self.lastUpdate - debug("self.lastUpdate is %s" % self.lastUpdate) for update in updates: - crt = parse(update['created_at']).utctimetuple() + crt = parsedate(update['created_at']) if (crt > nextLastUpdate): text = (htmlentitydecode( update['text'].replace('\n', ' ')) - .encode('utf-8', 'replace')) + .encode('utf8', 'replace')) # 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("@"): - self.privmsg_channels( - u"%s %s%s%s %s" %( - get_prefix(), - IRC_BOLD, update['user']['screen_name'], - IRC_BOLD, text.decode('utf-8'))) - - debug("tweet has crt %s, updating nextLastUpdate (was %s)" %( - crt, nextLastUpdate, - )) + if not text.startswith(b"@"): + msg = "%s %s%s%s %s" %( + get_prefix(), + IRC_BOLD, update['user']['screen_name'], + IRC_BOLD, text.decode('utf8')) + self.privmsg_channels(msg) + nextLastUpdate = crt - else: - break - debug("setting self.lastUpdate to %s" % nextLastUpdate) + self.lastUpdate = nextLastUpdate def process_events(self): @@ -209,8 +210,8 @@ class TwitterBot(object): conn.privmsg( evt.source().split('!')[0], "%sHi! I'm Twitterbot! you can (follow " - + ") to make me follow a user or " - + "(unfollow ) to make me stop." % + ") to make me follow a user or " + "(unfollow ) to make me stop." % get_prefix()) except Exception: traceback.print_exc(file=sys.stderr) @@ -226,14 +227,23 @@ class TwitterBot(object): elif args[0] == 'CLIENTINFO': conn.ctcp_reply(source, "CLIENTINFO PING VERSION CLIENTINFO") - def privmsg_channel(self, msg): - return self.ircServer.privmsg( - self.config.get('irc', 'channel'), msg.encode('utf-8')) + def handle_umode(self, conn, evt): + """ + QuakeNet ignores all your commands until after the MOTD. This + handler defers joining until after it sees a magic line. It + also tries to join right after connect, but this will just + make it join again which should be safe. + """ + args = evt.arguments() + if (args and args[0] == '+i'): + channels = self.config.get('irc', 'channel').split(',') + for channel in channels: + self.ircServer.join(channel) 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.encode('utf8')) def follow(self, conn, evt, name): userNick = evt.source().split('!')[0] @@ -278,7 +288,7 @@ class TwitterBot(object): "%s%s has asked me to stop following %s" %( get_prefix('inform'), userNick, name)) - def run(self): + def _irc_connect(self): self.ircServer.connect( self.config.get('irc', 'server'), self.config.getint('irc', 'port'), @@ -287,6 +297,9 @@ class TwitterBot(object): for channel in channels: self.ircServer.join(channel) + def run(self): + self._irc_connect() + while True: try: self.sched.run_forever() @@ -296,11 +309,15 @@ class TwitterBot(object): # twitter.com is probably down because it # sucks. ignore the fault and keep going pass + except irclib.ServerNotConnectedError: + # Try and reconnect to IRC. + self._irc_connect() + def load_config(filename): # Note: Python ConfigParser module has the worst interface in the # world. Mega gross. - cp = SafeConfigParser() + cp = ConfigParser() cp.add_section('irc') cp.set('irc', 'port', '6667') cp.set('irc', 'nick', 'twitterbot') @@ -338,11 +355,11 @@ def main(): 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)