Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
executable file 352 lines (293 sloc) 11 KB
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of tofbot, a friendly IRC bot.
# You may redistribute it under the Simplified BSD License.
# If we meet some day, and you think this stuff is worth it,
# you can buy us a beer in return.
# Copyright (c) 2011 Etienne Millon <>
# Martin Kirchgessner <>
# Nicolas Dumazet <>
# Quentin Sabah <>
./ [options] [legacy-arguments]
Don't prepend a # to chan names
Tofbot will connect to
from datetime import datetime
from irc import Bot
import time
import random
import sys
import os
import plugins
import types
from toflib import *
from toflib import _simple_dispatch, _simple_conf_dispatch
import re
from optparse import OptionParser
import plugins.euler
import plugins.lolrate
import plugins.donnezmoi
import plugins.jokes
import plugins.twitter
import plugins.dassin
import plugins.eightball
class Tofbot(Bot):
# Those attributes are published and can be changed by irc users
# value is a str to object converter. It could do sanitization:
# if value is incorrect, raise ValueError
_mutable_attributes = {
def __init__(self, nick=None, name=None, channels=None, password=None, debug=True):
Bot.__init__(self, nick, name, channels, password)
self.joined = False
self.autoTofadeThreshold = 98
self.riddleMaxDist = 2
self.debug = debug
self.TGtime = 5
self.pings = {}
self.memoryDepth = 20
self.lolRateDepth = 8
self.msgMemory = []
self.cron = Cron()
self.plugins = self.load_plugins()
def run(self, host=None):
if host == None and not hasattr(self,'host'):
raise Exception("run: no host set or given")
if self.nick == None:
raise Exception("run: no nick set")
if == None:
raise Exception("run: no name set") = host or,
def load_plugins(self):
d = os.path.dirname(__file__)
plugindir = os.path.join(d, 'plugins')
plugin_instances = []
for m in dir(plugins):
if type(getattr(plugins,m)) != types.ModuleType:
plugin = getattr(plugins, m)
for n in dir(plugin):
c = getattr(plugin, n)
if type(c) not in [types.ClassType, types.TypeType]:
if c.__name__.startswith('Plugin'):
instance = c(self)
return plugin_instances
# line-feed-safe
def msg(self, chan, msg):
for m in msg.split("\n"):
Bot.msg(self, chan, m)
def log(self, msg):
if self.debug:
def try_join(self, args):
if (args[0] in ['End of /MOTD command.',
"This server was created ... I don't know"]
for chan in self.channels:
self.write(('JOIN', chan))
self.joined = True
def dispatch(self, origin, args):
self.log("o=%s n=%s a=%s" % (origin.sender, origin.nick, args))
is_config = False
senderNick = origin.nick
commandType = args[1]
# if command type is 'BOTCONFIG', bypass the try_join
# because we are configuring the bot before any
# connection.
if commandType != 'BOTCONFIG':
if not self.joined:
is_config = 1
commandType = args[1]
if commandType == 'JOIN':
for p in self.plugins:
if hasattr(p, 'handle_join'):
p.handle_join(args[0], senderNick)
elif commandType == 'KICK' and args[3] == self.nick:
reason = args[0]
chan = args[2]
if reason == self.nick:
respawn_msg = 'respawn, LOL'
respawn_msg = 'comment ça, %s ?' % reason
self.write(('JOIN', chan))
self.msg(chan, respawn_msg)
elif commandType == 'PRIVMSG':
msg_text = args[0]
msg = msg_text.split(" ")
cmd = msg[0]
chan = args[2]
self.pings[senderNick] =
if is_config == False:
if len(cmd) == 0:
for p in self.plugins:
if hasattr(p, 'handle_msg'):
p.handle_msg(msg_text, chan, senderNick)
if chan == self.channels[0] and cmd[0] != '!':
self.msgMemory.append("<" + senderNick + "> " + msg_text)
if len(self.msgMemory) > self.memoryDepth:
del self.msgMemory[0]
if len(cmd) == 0 or cmd[0] != '!':
cmd = cmd[1:]
chan = None
if len(self.channels) == 0:
chan = 'config'
chan = self.channels[0]
if cmd in _simple_dispatch:
act = self.find_cmd_action("cmd_" + cmd)
act(chan, msg[1:])
elif is_config and (cmd in _simple_conf_dispatch):
act = self.find_cmd_action("confcmd_" + cmd)
act(chan, msg[1:])
elif cmd == 'context':
def find_cmd_action(self, cmd_name):
targets = self.plugins
targets.insert(0, self)
for t in targets:
if (hasattr(t, cmd_name)):
action = getattr(t, cmd_name)
return action
def nop(self, chan, args):
return nop
def safe_getattr(self, key):
if key not in self._mutable_attributes:
return None
if not hasattr(self, key):
return "(None)"
return str(getattr(self, key))
def safe_setattr(self, key, value):
converter = self._mutable_attributes.get(key)
if converter is None:
return False
value = converter(value)
setattr(self, key, value)
return True
except ValueError:
def confcmd_chan(self, chan, args):
new_chan = args[0]
if self.channels.count(new_chan) == 0:
def confcmd_server(self, chan, args):
host = args[0].strip() = host
def confcmd_port(self, chan, args):
port = int(args[0].strip())
self.port = port
def confcmd_nick(self, chan, args):
nick = args[0].strip()
self.nick = nick
self.user = nick
def confcmd_name(self, chan, args):
name = args[0].strip() = name
def cmd_ping(self, chan, args):
"Find when X was last online"
who = args[0]
if who in self.pings:
"Last message from %s was on %s (btw my local time is %s)" %
(who, self.pings[who].__str__(), ))
self.msg(chan, "I havn't seen any message from " + who)
def cmd_get(self, chan, args):
"Retrieve a configuration variable's value"
key = args[0]
value = self.safe_getattr(key)
if value is None:
self.msg(chan, "Ne touche pas à mes parties privées !")
self.msg(chan, "%s = %s" % (key, value))
def cmd_set(self, chan, args):
"Set a configuration variable's value"
key = args[0]
value = args[1]
ok = self.safe_setattr(key, value)
if not ok:
self.msg(chan, "N'écris pas sur mes parties privées !")
def send_context(self, to):
intro = "Last " + str(len(self.msgMemory)) + " messages sent on " + self.channels[0] + " :"
self.msg(to, intro)
for msg in self.msgMemory:
self.msg(to, msg)
def cmd_help(self, chan, args):
"Show this help message"
maxlen = 1 + max(map(len, _simple_dispatch))
self.msg(chan, "Commands should be entered in the channel or by private message")
for cmd in _simple_dispatch:
f = self.find_cmd_action("cmd_" + cmd)
self.msg(chan, '%*s - %s' % (maxlen, "!"+cmd, f.__doc__))
self.msg(chan, "you can also !get or !set " + ", ".join(self._mutable_attributes.keys()))
self.msg(chan, "If random-tofades are boring you, enter 'TG " + self.nick + "' (but can be cancelled by GG " + self.nick + ")")
if __name__ == "__main__":
class FakeOrigin:
def bot_config(b, cmd):
o = FakeOrigin
o.sender = 'bot_config'
o.nick = 'bot_config'
b.dispatch(o, [cmd.strip(), 'BOTCONFIG','PRIVMSG','#bot_config'])
# option parser
parser = OptionParser(__doc__)
parser.add_option("-x","--execute", dest="cmds",action="append",help="File to execute prior connection. Can be used several times.")
parser.add_option("-s","--host", dest="host",help="IRC server hostname")
parser.add_option("-p","--port", dest="port",help="IRC server port")
parser.add_option("-k","--nick", dest="nick",help="Bot nickname",default='Tofbot')
parser.add_option("-n","--name", dest="name",help="Bot name",default='Tofbot')
parser.add_option("-c","--channel",dest="channel",action="append",help="Channel to join (without # prefix). Can be used several times.")
parser.add_option("--password", dest="password")
parser.add_option("-d","--debug", action="store_true", dest="debug", default=False)
(options,args) = parser.parse_args();
# legacy arguments handled first
# (new-style arguments prevail)
if len(args) > 0:
options.nick = options.nick or args[0] = or []
for chan in args[1:]:
if == 0:
# initialize Tofbot
# using command-line arguments
b = Tofbot(options.nick,,, options.password, options.debug)
# execute command files
# these commands may override command-line arguments
options.cmds = options.cmds or []
for filename in options.cmds:
cmdsfile = open(filename,'r')
for line in cmdsfile:
bot_config(b, line)
# default host when legacy-mode
if == None and len(options.cmds) == 0 and len(args) > 0: = ''