From c31a94405a53eb0419e3594555b88cc146b98183 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Tue, 14 Sep 2010 05:48:03 -0400 Subject: [PATCH 1/1] bricker ! --- bricker.py | 352 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ config.py | 68 ++++++++++++ 2 files changed, 420 insertions(+) create mode 100755 bricker.py create mode 100644 config.py diff --git a/bricker.py b/bricker.py new file mode 100755 index 0000000..33596aa --- /dev/null +++ b/bricker.py @@ -0,0 +1,352 @@ +#!/usr/bin/python + +# if you brick and have 0 -> kick from chan +# brick quantity must be queried, not automatically given +# if A bricks B, A--, B++ +# cant give them nicely +# new person gets 5 bricks + +import irclib + +import config + +class Bricker(object): + + LISTEN_EVENTS = [ + 'welcome', 'disconnect', 'nicknameinuse', 'kick', 'privmsg', 'privnotice', + 'bannedfromchan', 'erroneusnickname', 'join', 'namreply', 'nick', 'quit', + 'part', 'userhost' + ] + + OVERBRICKED_MESSAGE = 'Overbricked, haha!' + BRICKED_MESSAGE = '*** %s was bricked.' + PLEASE_REGISTER_MESSAGE = 'You need to register. Use register .' + BAD_PASSWORD_MESSAGE = 'Bad password.' + REGISTERED_MESSAGE = 'Registered successfully.' + SHARED_CHANNEL_MESSAGE = 'Both you and your target must be in the channel.' + COUNT_MESSAGE = 'You have %d bricks remaining.' + HELP_MESSAGE = ( + 'Available commands are:\n' + ' register - register yourself, or create a new password\n' + ' brick - brick someone in a channel\n' + ' (we must all be in the channel)\n' + ' count - tells you how many bricks you have left\n' + ' help - show this text\n') + + def __init__(self, confignetwork, irc): + """A brickbot instance, which exists for an irc network.. + + Args: + confignetwork: A ConfigNetwork object. + irc: An irclib.IRC instance. + """ + self.irc = irc + self.config = confignetwork + self._server_index = 0 + self._nick = None + self._channels = {} + self.state = State(self.config.statefile) + self.state.load() + + def run(self): + self._reconnect() + + def _on_event(self, connection, event): + print 'event %s' % event.eventtype() + + def _brick(self, connection, nick, target, channel): + if channel in self._channels: + fromuser = self.state.user(nick) + touser = self.state.user(target) + if not fromuser.registered: + connection.notice(nick, self.PLEASE_REGISTER_MESSAGE) + elif channel not in fromuser.visible or channel not in touser.visible: + connection.notice(nick, self.SHARED_CHANNEL_MESSAGE) + elif fromuser.bricks < 1: + # uh oh ! overbricked ! + connection.kick(channel, nick, self.OVERBRICKED_MESSAGE) + else: + connection.privmsg(channel, self.BRICKED_MESSAGE % target) + fromuser.bricks -= 1 + touser.bricks += 1 + self.state.save() + + def _register(self, connection, nick, password): + user = self.state.user(nick) + if not password or (user.password and password != user.password): + connection.notice(nick, self.BAD_PASSWORD_MESSAGE) + return + if not user.password: + user.password = password + self.state.save() + user.registered = True + user.lasthost = user.onhost + self.state.save() + connection.notice(nick, self.REGISTERED_MESSAGE) + + def _count(self, connection, nick): + user = self.state.user(nick) + if not user.registered: + connection.notice(nick, self.PLEASE_REGISTER_MESSAGE) + else: + connection.notice(nick, self.COUNT_MESSAGE % user.bricks) + + def _help(self, connection, nick): + for line in self.HELP_MESSAGE.split('\n'): + connection.notice(nick, line) + + def _reconnect(self): + try: + server = self.config.servers[self._server_index] + self._nick = self.config.nickname + print 'Connecting to %s:%s' % (server.host, server.port) + conn = self.irc.server().connect(server.host, server.port, self._nick) + for s in self.LISTEN_EVENTS: + conn.add_global_handler(s, getattr(self, '_on_%s' % s), -10) + conn.add_global_handler('all_events', self._on_event, -30) + except irclib.ServerConnectionError: + self._server_index = (self._server_index + 1) % len(self.config.servers) + net.connection.execute_delayed(net.reconnect_delay, + self._reconnect) + + def _rejoin(self, connection, channel): + password = '' + for c in self.config.channels: + if channel == c.name: + password = c.password + break + print 'rejoining %s with password %s' % (channel, password) + connection.join(channel, password) + + def _on_welcome(self, connection, event): + print 'welcome %s' % event + for s in self.config.autocommands: + connection.send_raw(s) + if self.config.nickservpw: + connection.privmsg('nickserv', 'identify %s %s' % ( + self.config.nickservpw, self.config.nickname)) + for c in self.config.channels: + connection.join(c.name, c.password) + + def _on_disconnect(self, connection, event): + print 'disconnect %s' % event + connection.execute_delayed(self.config.reconnect_delay, + self._reconnect) + + def _on_erroneusnickname(self, connection, event): + print 'unable to use nickname' + + def _on_nicknameinuse(self, connection, event): + print 'nicknameinuse %s' % event.target() + if not self.config.nickservpw: + self._nick += '_' + print 'using nickname %s' % self._nick + connection.nick(self._nick) + + def _on_kick(self, connection, event): + nick = event.arguments()[0] + channel = event.target() + print 'kick %s from %s' % (nick, channel) + if nick == self._nick: + self._channels[channel] = False + connection.execute_delayed(self.config.rejoin_delay, self._rejoin, + (connection, event.target())) + else: + self._rm_nick(connection, channel, nick) + + def _on_bannedfromchan(self, connection, event): + print 'bannedfromchan %s' % event.arguments()[0] + connection.execute_delayed(self.config.rejoin_delay, self._rejoin, + (connection, event.arguments()[0])) + + def _on_privmsg(self, connection, event): + print 'privmsg %s' % event.target() + print ' %s' % event.source() + print ' %s' % event.arguments() + data = event.arguments()[0].split(' ') + nick = irclib.nm_to_n(event.source()) + cmd = data[0] + args = data[1:] + if cmd == 'brick' and len(args) == 2: + target = args[0] + channel = args[1] + self._brick(connection, nick, target, channel) + if cmd == 'register' and len(args) == 1: + password = args[0] + self._register(connection, nick, password) + if cmd == 'count': + self._count(connection, nick) + if cmd == 'help': + self._help(connection, nick) + + def _on_privnotice(self, connection, event): + print 'privnotice %s' % event.target() + print ' %s' % event.source() + print ' %s' % event.arguments() + data = event.arguments()[0].split(' ') + cmd = data[0] + args = data[1:] + + def _add_nick(self, connection, channel, nick, host=None): + print 'adding nick %s (%s)' % (nick, host) + user = self.state.user(nick) + user.visible[channel] = True + user.onhost = host + if host: + if host == user.lasthost: + user.registered = True + print 'registered %s by host %s' % (nick, host) + user.ignore_userhost_reply = True + else: + connection.userhost([nick]) # get their userhost + user.ignore_userhost_reply = False + self.state.save() + + def _rm_nick(self, connection, channel, nick): + print 'removing nick %s' % nick + user = self.state.user(nick) + del user.visible[channel] + if not user.visible: + user.registered = False + user.onhost = None + + def _on_join(self, connection, event): + nick, host = event.source().split('!', 1) + channel = event.target() + print 'join %s (%s) on %s' % (nick, host, channel) + if nick == self._nick: + self._channels[channel] = True + else: + self._add_nick(connection, channel, nick, host=host) + + def _on_namreply(self, connection, event): + nicks = event.arguments()[2].split(' ') + channel = event.arguments()[1] + print 'namreply found nicks %s' % nicks + nicks = [n.strip('@+ ') for n in nicks] + for nick in nicks: + if nick != self._nick: + self._add_nick(connection, channel, nick) + + def _on_nick(self, connection, event): + tonick = event.target() + fromnick = irclib.nm_to_n(event.source()) + host = irclib.nm_to_uh(event.source()) + print 'nick %s -> %s' % fromnick, tonick + if tonick != self._nick: + for c in self._channels: + user = self.state.user(fromnick) + if c in user.visible: + self._rm_nick(connection, c, fromnick) + self._add_nick(connection, c, tonick, host=host) + + def _on_userhost(self, connection, event): + if not event.arguments()[0]: return + nick, host = event.arguments()[0].split('=', 1) + nick = nick.strip('* ') + host = host.strip('+- ') + print 'userhost for %s is %s' % (nick, host) + user = self.state.user(nick) + if not user.ignore_userhost_reply: + for c in user.visible: + self._add_nick(connection, c, nick, host=host) + + def _on_quit(self, connection, event): + nick = irclib.nm_to_n(event.source()) + print 'quit %s' % nick + if nick == self._nick: + self._channels[channel] = False + else: + user = self.state.user(nick) + for c in user.visible: + self._rm_nick(connection, c, nick) + + def _on_part(self, connection, event): + nick = irclib.nm_to_n(event.source()) + channel = event.target() + print 'part %s from %s' % (nick, channel) + if nick == self._nick: + self._channels[channel] = False + else: + self._rm_nick(connection, channel, nick) + + +class State(object): + + def __init__(self, filename): + # a dictionary of User objects keyed by their nicknames + self._users = {} + self._filename = filename + + def load(self): + if not self._filename: + print 'WARNING: not using a state file' + return + try: + f = open(self._filename, 'r+') + except IOError, e: + print 'IOError: %s' % e + print 'WARNING: not using a state file' + return + + curnick = None + for line in f: + data = line.strip().split(' ', 1) + if data[0] == 'nick' and len(data) > 1: + curnick = data[1] + if curnick and data[0] == 'lasthost' and len(data) > 1: + self.user(curnick).lasthost = data[1] + if curnick and data[0] == 'bricks' and len(data) > 1: + self.user(curnick).bricks = int(data[1]) + if curnick and data[0] == 'password' and len(data) > 1: + self.user(curnick).password = data[1] + f.close() + + def save(self): + if not self._filename: return + try: + f = open(self._filename, 'w') + except IOError, e: + print 'IOError: %s' % e + print 'WARNING: failed to write state file' + return + for u in self._users.values(): + f.write('nick %s\n' % u.nick) + if u.lasthost: f.write('lasthost %s\n' % u.lasthost) + if u.password: f.write('password %s\n' % u.password) + f.write('bricks %s\n' % u.bricks) + f.close() + + def user(self, nick): + nick = irclib.irc_lower(nick) + if nick not in self._users: + user = State.User(self, nick) + self._users[nick] = user + self.save() + else: + user = self._users[nick] + return user + + class User(object): + + def __init__(self, state, nick): + self.state = state + self.nick = nick + self.onhost = None + self.lasthost = None + self.registered = False + self.visible = {} + self.ignore_userhost_reply = True + self.bricks = 5 + self.password = None + + +def main(): + irc = irclib.IRC() + for n in config.Config.networks: + b = Bricker(n, irc) + b.run() + irc.process_forever() + +if __name__ == '__main__': + main() diff --git a/config.py b/config.py new file mode 100644 index 0000000..b23f58f --- /dev/null +++ b/config.py @@ -0,0 +1,68 @@ +class ConfigChannel(object): + + def __init__(self, name, password=''): + self.name = name + self.password = password + +class ConfigServer(object): + + def __init__(self, host, port=6667): + """Create a server for the config, which is part of a network. + + Args: + host: The host to connect to for the server + port: The port to connect to on the host + """ + self.host = host + self.port = port + +class ConfigNetwork(object): + + def __init__(self, statefile, servers, channels, nickname, realname, + nickservpw=None, autocommands=[], reconnect=None, rejoin=None): + """Create a network for the config. + + Args: + statefile: The name of the file to load/save state + servers: A list of ConfigServer objects + channels: A list of ConfigChannel objects + nickname: A string to use as a nickname + realname: A string to use as a real name + nickservpw: A string password for the nickserv service, if available + autocommands: A list of strings, which are commands to send to the server + on connect. + reconnect: An optional value to specify the number of seconds to wait + before reconnecting. If not set, it will not reconnect. + rejoin: An optional value to specify the number of seconds to wait + before rejoining a channel. If not set, it will not rejoin. + """ + self.statefile = statefile + self.servers = servers + self.channels = channels + self.nickname = nickname + self.realname = realname + self.nickservpw = nickservpw + self.autocommands = autocommands + self.reconnect_delay = reconnect + self.rejoin_delay = rejoin + +class Config: + """Configuration options for the brick bot.""" + + networks = [ + ConfigNetwork( + statefile='bricker-oftc.state', + servers=[ + ConfigServer('irc.oftc.net'), + ], + channels=[ + ConfigChannel('#bling'), + ], + nickname='bricker', + realname='brickerbot', + nickservpw='_br1ck3r_1z_m1n3_', + autocommands=[ + ], + reconnect=10, + rejoin=5), + ] -- 1.9.1