password: <twitter_account_password>
If no config file is given "twitterbot.ini" will be used by default.
-
"""
-# TODO add delimiter if first word isn't "is" or "was"
-# TODO handle newlines
-# TODO handle quotes
-
-BOT_VERSION = "TwitterBot 0.2.1 (mike.verdone.ca/twitter)"
+BOT_VERSION = "TwitterBot 1.0 (http://mike.verdone.ca/twitter)"
IRC_BOLD = chr(0x02)
IRC_ITALIC = chr(0x16)
import sys
import time
from dateutil.parser import parse
-from ConfigParser import ConfigParser
+from ConfigParser import SafeConfigParser
from heapq import heappop, heappush
import traceback
+import os.path
from api import Twitter, TwitterError
+from util import htmlentitydecode
try:
import irclib
now = time.time()
task = heappop(self.task_heap)
wait = task.next - now
+ task.next = now + task.delta
+ heappush(self.task_heap, task)
if (wait > 0):
time.sleep(wait)
task()
- task.next = now + task.delta
- heappush(self.task_heap, task)
debug("tasks: " + str(self.task_heap))
def run_forever(self):
- try:
- while True:
- self.next_task()
- except KeyboardInterrupt:
- pass
+ while True:
+ self.next_task()
+
class TwitterBot(object):
def __init__(self, configFilename):
traceback.print_exc(file=sys.stderr)
return
+ nextLastUpdate = self.lastUpdate
for update in updates:
crt = parse(update['created_at']).utctimetuple()
if (crt > self.lastUpdate):
- text = (
- update['text']
- .replace('\n', ' ')
- .replace(""", "\"")
- .replace('&', '&'))
- self.privmsg_channel(
- "=^_^= %s%s%s %s" %(
- IRC_BOLD, update['user']['screen_name'],
- IRC_BOLD, text))
- self.lastUpdate = crt
+ text = (htmlentitydecode(
+ update['text'].replace('\n', ' '))
+ .encode('utf-8', '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_channel(
+ 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 privmsg_channel(self, msg):
return self.ircServer.privmsg(
- self.config.get('irc', 'channel'), msg)
+ self.config.get('irc', 'channel'), msg.encode('utf-8'))
def follow(self, conn, evt, name):
userNick = evt.source().split('!')[0]
self.config.getint('irc', 'port'),
self.config.get('irc', 'nick'))
self.ircServer.join(self.config.get('irc', 'channel'))
- try:
- self.sched.run_forever()
- except KeyboardInterrupt:
- pass
+
+ while True:
+ try:
+ self.sched.run_forever()
+ except KeyboardInterrupt:
+ break
+ except TwitterError:
+ # twitter.com is probably down because it sucks. ignore the fault and keep going
+ pass
def load_config(filename):
defaults = dict(server=dict(port=6667, nick="twitterbot"))
- cp = ConfigParser(defaults)
+ cp = SafeConfigParser(defaults)
cp.read((filename,))
+
+ # attempt to read these properties-- they are required
+ cp.get('twitter', 'email'),
+ cp.get('twitter', 'password')
+ cp.get('irc', 'server')
+ cp.getint('irc', 'port')
+ cp.get('irc', 'nick')
+ cp.get('irc', 'channel')
+
return cp
+# 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!
+#
+# 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()
load_config(configFilename)
- except:
- print >> sys.stderr, "Error loading ini file %s" %(
+ except Exception, e:
+ print >> sys.stderr, "Error while loading ini file %s" %(
configFilename)
- print __doc__
+ print >> sys.stderr, e
+ print >> sys.stderr, __doc__
sys.exit(1)
+
bot = TwitterBot(configFilename)
return bot.run()