#!/usr/bin/env python
"""
agg06_aggbot.py
Interactive IM bot which manages feed subscriptions and polls feeds
on demand.
"""
import time
from imconn import AIMConnection, JabberConnection
from agglib import *
AIM_OWNER = "your_name"
AIM_USER = "your_name"
AIM_PASSWD = "your_password"
JAB_OWNER = "your_name@jabber.org"
JAB_USER = "your_name@jabber.org"
JAB_PASSWD = "your_password"
FEEDS_FN = "feeds.txt"
FEED_DB_FN = "feeds_db"
ENTRY_DB_FN = "entry_seen_db"
def main():
"""
Create a new bot, add some IM network connections, and fire it up.
"""
bot = AggBot(FEEDS_FN, FEED_DB_FN, ENTRY_DB_FN)
bot.addConnection(AIMConnection, AIM_OWNER, AIM_USER, AIM_PASSWD)
bot.addConnection(JabberConnection, JAB_OWNER, JAB_USER, JAB_PASSWD)
bot.go()
class AggBot:
"""
This is a feed aggregator bot that accepts commands via instant message.
"""
IM_CHUNK = 7
FEED_HDR_TMPL = """\n%(feed.title)s\n\n"""
ENTRY_TMPL = """ * %(entry.title)s\n"""
MSG_TMPL = "%s"
#FEED_HDR_TMPL = """\n%(feed.title)s - %(feed.link)s\n\n"""
#ENTRY_TMPL = """ * %(entry.title)s - %(entry.link)s\n\n"""
#MSG_TMPL = "%s"
def __init__(self, feeds_fn, feed_db_fn, entry_db_fn):
"""
Initialize the bot object, open aggregator databases, load up
subscriptions.
"""
self.connections, self.owners, self.running = [], [], False
self.feeds_fn = feeds_fn
self.feed_db_fn = feed_db_fn
self.entry_db_fn = entry_db_fn
def __del__(self):
"""
Object destructor - make sure the aggregator databases get closed.
"""
try: closeDBs(self.feed_db, self.entry_db)
except: pass
def addConnection(self, conn_cls, owner, user, passwd):
"""
Given a connection class, owner screen name, and a user/password,
create the IM connection and store it away along with the owner
user.
"""
self.connections.append(conn_cls(user, passwd, self.receiveIM))
self.owners.append(owner)
def getOwner(self, conn):
"""
For a given connection, return the owner's screen name.
"""
return self.owners[self.connections.index(conn)]
def connect(self):
"""
Cause all the IM connections objects connect to their networks.
"""
for c in self.connections: c.connect()
def runOnce(self):
"""
Run through one event loop step.
"""
for c in self.connections: c.runOnce()
def go(self):
"""
Connect and run event loop, until running flag set to false.
"""
try:
self.feed_db, self.entry_db = openDBs(self.feed_db_fn, self.entry_db_fn)
self.feeds = loadSubs(self.feeds_fn)
self.running = True
self.connect()
while self.running:
self.runOnce()
time.sleep(0.1)
finally:
try: closeDBs(self.feed_db, self.entry_db)
except: pass
def stop(self):
"""
Stop event loop by setting running flag to false.
"""
self.running = False
def receiveIM(self, conn, from_name, msg):
"""
Process incoming messages as commands. Message is space-delimited,
command is first word found, everything else becomes parameters.
Commands are handled by methods with 'cmd_' prepended to the name
of the command.
"""
try:
owner = self.getOwner(conn)
if from_name != owner:
# Don't listen to commands from anyone who's not the bot owner.
conn.sendIM(from_name, "I don't talk to strangers.")
else:
if msg == "":
# Check for empty messages.
conn.sendIM(from_name, "Did you say something?")
else:
# Try to parse the message. Space-delimited, first
# part of message is the command, everything else is
# optional parameters.
try:
fs = msg.index(" ")
cmd, args = msg[:fs], msg[fs+1:].split(" ")
except:
cmd, args = msg, []
# Look for a method in this class corresponding to
# command prepended with 'cmd_'. If found, execute it.
cmd_func_name = 'cmd_%s' % cmd
if hasattr(self, cmd_func_name):
getattr(self, cmd_func_name)(conn, from_name, args)
else:
conn.sendIM(from_name, "I don't understand you.")
except Exception, e:
# Something unexpected happened, so make some attempt to
# say what.
conn.sendIM(from_name, "That last message really confused me! (%s)" % e)
def getSubscriptionURI(self, sub_arg):
"""
Utility function which allows reference to a subscription either by
integer index in list of subscriptions, or by direct URI reference.
"""
try:
sub_num = int(sub_arg)
return self.feeds[sub_num]
except ValueError:
return sub_arg
def pollFeed(self, conn, from_name, sub_uri):
"""
Perform a feed poll and send the new entries as messages.
"""
entries = getNewFeedEntries([sub_uri], self.feed_db,
self.entry_db)
if len(entries) > 0:
sendEntriesViaIM(conn, from_name, entries, self.IM_CHUNK,
self.FEED_HDR_TMPL, self.ENTRY_TMPL, self.MSG_TMPL)
else:
conn.sendIM(from_name, "No new entries available.")
def cmd_signoff(self, conn, from_name, args):
"""
signoff: Command the bot to sign off and exit the program.
"""
conn.sendIM(from_name, "Okay, signing off now.")
self.stop()
def cmd_list(self, conn, from_name, args):
"""
list: List all subscriptions by index and URI.
"""
out = []
out.append("You have the following subscriptions:")
for i in range(len(self.feeds)):
out.append(" %s: %s" % (i, self.feeds[i]))
conn.sendIM(from_name, "\n".join(out))
def cmd_unsubscribe(self, conn, from_name, args):
"""
unsubscribe : Unsubscribe from a feed by index or URI.
"""
try:
sub_uri = self.getSubscriptionURI(args[0])
unsubscribeFeed(self.feeds, sub_uri)
saveSubs(self.feeds_fn, self.feeds)
conn.sendIM(from_name, "Unsubscribed from %s" % sub_uri)
except SubsNotSubscribed:
conn.sendIM(from_name, "Not subscribed to that feed.")
except IndexError:
conn.sendIM(from_name, "Need a valid number or a URI.")
def cmd_subscribe(self, conn, from_name, args):
"""
subscribe : Use the feedfinder module to find a feed URI
and add a subscription, if possible. Reports exceptions such as
no feeds found, multiple feeds found, or already subscribed.
"""
try:
feed_uri = subscribeFeed(self.feeds, args[0])
saveSubs(self.feeds_fn, self.feeds)
conn.sendIM(from_name, "Subscribed to %s" % feed_uri)
except SubsNoFeedsFound:
conn.sendIM(from_name, "Sorry, no feeds found at %s" % args[0])
except SubsAlreadySubscribed:
conn.sendIM(from_name, "You're already subscribed.")
except SubsMultipleFeedsFound, e:
feeds_found = e.getFeeds()
out = ['Multiple feeds found, please pick one:']
for f in feeds_found:
out.append(" %s" % f)
conn.sendIM(from_name, "\n".join(out))
def cmd_poll(self, conn, from_name, args):
"""
poll : Perform an on-demand poll of a feed.
"""
try:
sub_uri = self.getSubscriptionURI(args[0])
self.pollFeed(conn, from_name, sub_uri)
except IndexError:
conn.sendIM(from_name, "Need a valid number or a URI.")
def cmd_pollsubs(self, conn, from_name, args):
"""
pollsubs: Perform an on-demand poll of all subscriptions.
"""
conn.sendIM(from_name, "Polling all subscriptions...")
for feed in self.feeds:
conn.sendIM(from_name, "Polling %s" % feed)
self.pollFeed(conn, from_name, feed)
if __name__ == "__main__": main()