#!/usr/bin/env python """ FortuneBot - an irc bot that prints output of unix command "fortune". And can keep a database of quotes. type fortune help on irc in the channel where the bot is to get a list of commands. Copyright (C) 2001 Andrei Kulakov GPL """ __version__ = "0.7b" import socket, commands, thread, time, re, random, sys, os.path, cPickle, string interval = 4 # print fortune every N hours, 0 to disable prog_dir = os.path.expanduser("~/.fortune_bot") conf_file = os.path.join(prog_dir, "conf") # servers, nick, pwd, chans # handey and curmudgeon quote files. handey = os.path.join(prog_dir, "handey") curm = os.path.join(prog_dir, "curm") quotes_fname = os.path.join(prog_dir, "quotes") # quotes database. quit_message = "I quit." log_name = os.path.join(prog_dir, "log") log = 1 illegal = "$`;/\.:&|" class Bot: def __init__(self, addr, chans): self.last_out_message = 0 self.fortune_dict = {} self.channels = [] self.quotes = quotes self.addr = addr self.connect(self.addr) self.chans = chans self.display("Created bot on server " + addr[0]) self.topics = {} def help(self, chan): h = c = "" if handey_list: h = "[handey]" if curm_list: c = "[curm]" msg = """ fortune_bot.py v%s by Rainy-Day: http://silmarill.org/fortune_bot/ Usage: fortune [pat1] [pat2] [#] %s %s; quote [def]; quote find ; quote.rm ; quote.rnd; topic.restore""" % (__version__, h, c) self.priv_msg(chan, msg) def display(self, text): log_write(text) print text def connect(self, addr): self.display("Connecting to: %s:%d" % (addr[0], addr[1])) self.irc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.irc.connect(addr) self.server = self.irc.makefile() def join(self, channel): """Join #channel or &channel (server chan).""" if channel[0] not in "&#": channel = "#" + channel self.display("Joining %s" % channel) self.send("JOIN %s" % channel) def send(self, text): """Send raw irc command.""" self.display("Sending: %s" % text) text = text.replace("\t", " "*4) self.irc.send(text + '\n') def priv_msg(self, channel, text): """Send private message to current channel.""" self.display("Privmsg: %s\n\n" % text) lines = text.split('\n') for line in lines: if line: self.send("PRIVMSG %s :%s" % (channel, line)) def set_nick(self, nick): """Set irc nick.""" self.display("Setting nick to: %s" % nick) self.nick = nick self.send("NICK %s" % nick) def initialize(self): self.display("\tInitializing") self.send("USER sill +i sill :Andy") self.send("USERHOST RD") def handey(self, chan): if handey_list: self.display("Printing handey quote") adage = random.choice(handey_list) self.priv_msg(chan, adage) def curm(self, chan): if curm: self.display("Printing Curm quote") adage = random.choice(curm_list) self.priv_msg(chan, adage) def rand_fortune(self, chan): """Send random fortune to current irc channel.""" self.display("Printing a fortune") fortune = commands.getoutput("fortune -s") self.priv_msg(chan, fortune) def exit(self, text): if not text: return if text[0] == pwd: self.display("Exiting.") self.send("QUIT :%s" % quit_message) quotes_file = open(quotes_fname, 'w') bin = 1 cPickle.dump(self.quotes, quotes_file, bin) sys.exit() def fortune(self, chan, list): """Send specific fortune to current irc channel. List can be a single word or multiple words and there may be a numerical index at the end. If there's more than one match and no index, random one is used.""" self.display("Printing a fortune") fortune = None fortune_list = [] num = None try: num = int(list[-1]); list = list[:-1] except: pass if not list: return def grab_list_from_fortune(pat): fortune_list = [] fortunes = commands.getoutput("fortune -i -s -m %s" % pat) fortunes = fortunes.split('%') # ( means its a section name, not a fortune.. for f in fortunes: if '(' not in f[:2] and f.strip(): fortune_list.append(f) return fortune_list pat = list[0] if list[0] in self.fortune_dict.keys(): self.display("Using cached fortune list.") fortune_list = self.fortune_dict[pat] else: fortune_list = grab_list_from_fortune(list[0]) if len(fortune_list) > 30: self.fortune_dict[pat] = fortune_list del list[0] if list: new_list = [] for pat in list: for hit in fortune_list: if hit.find(pat) != -1: new_list.append(hit) fortune_list = new_list if not fortune_list: self.priv_msg(chan, "No matches for: %s" % pat) return if num: try: fortune = fortune_list[num-1] except IndexError: self.priv_msg(chan, "Indexing error.") return else: n = random.randrange(len(fortune_list)) fortune = fortune_list[n] if len(fortune_list) > 1: self.priv_msg(chan, "#%d of %d matches:" % (n+1, len( fortune_list))) self.priv_msg(chan, fortune) def quote(self, chan, text): if not text: self.priv_msg(chan, "%d quotes in database." % len(self.quotes)) return key = text[0] value = " ".join(text[1:]).lower() if key == "find": if value: matches = [] for k, v in self.quotes.items(): if re.search(value, k.lower()) or re.search(value, v.lower()): matches.append(k) if matches: self.priv_msg(chan, "Matches: %s" % " ".join(matches)) else: self.priv_msg(chan, "No matches found for %s." % value) else: if value: self.quotes[key] = value else: if self.quotes.has_key(key): self.priv_msg(chan, "%s = %s" % (key, self.quotes[key])) else: self.priv_msg(chan, "No matches found for %s." % key) def parse_privmsg(self, chan, text): cmd = text[0] text = text[1:] if cmd[-1] == ":": cmd = cmd[:-1] if cmd: if cmd[0] == ":": cmd = cmd[1:] if cmd in cmds.keys(): eval(cmds[cmd]) elif cmd == self.nick: self.send_fortune(chan, text) def parse_topic(self, chan, source, text): nick = source.split("!") nick = nick[1:] if nick != "ChanServ": self.topics[chan] = " ".join(text) else: if "t" in self.chans[chan]: if self.topics.has_key(chan): self.send("TOPIC %s %s" % (chan, self.topics[chan])) def quote_rm(self, chan, text): """Remove quote.""" key = text[0] if self.quotes.has_key(key): del self.quotes[key] self.priv_msg(chan, "%s removed" % key) else: self.priv_msg(chan, "%s: no such quote!" % key) def quote_rnd(self, chan): """Random quote.""" key = random.choice(self.quotes.keys()) self.priv_msg(chan, "%s = %s" % (key, self.quotes[key])) def send_fortune(self, chan, text): if not text: self.rand_fortune(chan) else: for char in illegal: if "".join(text).find(char) != -1: self.priv_msg(chan, "illegal pattern") return if text[0] == "help": self.help(chan) elif text[0] == "handey": self.handey(chan) elif text[0] == "curm": self.curm(chan) else: self.fortune(chan, text) def topic_restore(self, chan): if self.topics.has_key(chan): self.send("TOPIC %s %s" % (chan, self.topics[chan])) def schedule_fortune(bot, interval): """Print fortune. 1 argument: interval in hours.""" time.sleep(10) if interval: while 1: try: time.sleep(60*60*interval) for chan in bot.channels.keys(): bot.rand_fortune(chan) except KeyboardInterrupt: bot.exit([pwd]) def log_write(text): if log: log_file.write(time.ctime(time.time()) + " "*4) log_file.write(text+'\n') log_file.flush() print text def main(): for bot in bots: while 1: try: line = bot.server.readline() except KeyboardInterrupt: bot.exit([pwd]) sys.stdout.flush() log_write("<<< %s" % line) if not line: # Most likely we got disconnected.. (kludge) try: time.sleep(10) except KeyboardInterrupt: bot.exit([pwd]) bot.connect(bot.addr) bot.initialize() bot.set_nick(bot.nick) bot.channels = [] continue command = line.split() args = line.split(':') if command[0] == "PING": bot.send("PONG %s" % line[5:]) if not bot.channels: for chan in bot.chans.keys(): bot.join(chan) bot.channels = bot.chans elif len(command) >= 4: source, cmd, chan = command[:3] text = command[3:] if cmd in irc_cmds.keys(): exec(irc_cmds[cmd]) def init(): handey_list = [] curm_list = [] quotes = {} if not os.path.exists(prog_dir): os.mkdir(prog_dir) if log: log_file = open(log_name, 'w') if len(sys.argv) == 2: if sys.argv[1] == "-h": print __doc__ sys.exit() if os.path.exists(handey): h = open(handey) handey_list = h.read().split('%') if os.path.exists(quotes_fname): quotes_file = open(quotes_fname) quotes = cPickle.load(quotes_file) if os.path.exists(curm): curm_file = open(curm) curm_list = curm_file.read().split("%") return log_file, quotes, handey_list, curm_list cmds = { "fortune" : "bot.send_fortune(chan, text)", "quote.rm" : "bot.quote_rm(chan, text)", "quote.rnd" : "bot.quote_rnd(chan)", "quote" : "bot.quote(chan, text)", "quit" : "bot.exit(text)", "topic.restore" : "bot.topic_restore(chan)", } irc_cmds = { "PRIVMSG" : "bot.parse_privmsg(chan, text)", "TOPIC" : "bot.parse_topic(chan, source, text)", } def parse_conf(): try: f = open(conf_file) except: f = open(conf_file, "w") f.write("# server:port nick chan1 chan2 ...\n") f.write("pwd = your_password\n") print "Edit conf file: %s and start the bot again." % conf_file sys.exit() lines = f.readlines() pwd = "" servers = {} chans = {} for line in lines: if line.strip().startswith("#"): continue elif line.strip().startswith("pwd"): words = line.split("=") pwd = words[1].strip() else: try: words = line.split() tup = words[0].split(":") server, port = tup[0], int(tup[1]) nick = words[1] tmplst = words[2:] for chan in tmplst: chan = chan.split(",") chans[chan[0]] = chan[1:] servers[server] = (port, nick, chans) except: print "Something wrong with conf file format." print "Edit", conf_file sys.exit() if not pwd or not servers or pwd == "your_password": print "Add server(s) and change password from default in conf file." print pwd, servers sys.exit() return servers, pwd if __name__ == "__main__": log_file, quotes, handey_list, curm_list = init() servers, pwd = parse_conf() bots = [] for server, tup in servers.items(): addr = server, tup[0] nick = tup[1] chans = tup[2] bot = Bot(addr, chans) bot.initialize() bot.set_nick(nick) bots.append(bot) thread.start_new_thread(schedule_fortune, (bot, interval)) main() # TODO