class Irc(Greenlet): """ irc connection abstraction. inherits: start(), join(), kill(exception=GreenletExit, block=False, timeout=None), ready(), successful(), etc __init__ only receives: tag: "myfreenode", fd: 2 or None the other options are accessed through the following properties, which delegate calls to conf.get: servers: [Server("server.address +1234"), ...], encoding: "utf-8" network: "freenode", nick: "sqrl", username: "******", password: "******", nickservpassword: "******", realname: "real name", chans: ["#channel": {"blacklist": ["ex"]}, ...], scripts: ["users", "seen", "com", "version", "wik", "title", "choice", "dic", "ex", ...], masters: ["*!*@unaffiliated/squirrel", ...] """ def __init__(self, tag, fd=None, me=None): super(Irc, self).__init__() self.tag = tag self.net = Network(fd) self.group = Group() self.logger = logging.getLogger(self.tag) self.logger.setLevel(1) self.formatter = CuteFormatter(maxbytes=400, encoding=self.encoding) self.connected = fd is not None if self.connected: self.me = me else: self.me = (self.nick, None, None) def __repr__(self): return u"Irc(tag=%s)" % self.tag ############################################################################################### core def _run(self): """ greenlet starts here connect to irc, serve in a loop run self.disconnect() and self.onunload() on GreenletExit and die """ self.onload() # let it fail try: while True: if not self.connected: self.connect() # let it fail (should not happen, relly) for chan in self.chans: self.joinchan(chan) while True: try: line = self.net.getline() line = line.decode(self.encoding) try: msg = Message(irc=self, line=line) except MessageMalformed as e: self.onexception(e, unexpected=True) continue self.onmessage(msg) if type(msg) == Message and msg.command == "ping": self.send("PONG %s" % msg.params[0]) elif type(msg) == Numeric and msg.num == 1: self.me = (msg.target, None, None) self.onconnected(msg) self.privmsg(msg.target, "id") elif msg.frommyself: self.me = msg.sender self.formatter.maxbytes = 512 - 7 - len("".join(self.me).encode(self.encoding)) # :nick!user@host <PRIVMSG :text>+\r\n" self.logger.log(OTHER, "i am {0[0]}!{0[1]}@{0[2]} and i can send {1} bytes".format(self.me, self.formatter.maxbytes)) except ConnectionFailed as e: self.onexception(e, unexpected=True) self.disconnect() break except GreenletRehash: # don't disconnect raise except GreenletExit: # same as above, but disconnect self.disconnect() # let it fail (should not happen, relly) raise except Exception as e: self.onexception(e, unexpected=True) finally: self.onunload() # let it fail def shutdown(self, exception=GreenletExit): """ kills sender greenlet, all greenlets in group, oneself this will cause _run to exit and the thing should get deleted from memory ! if exception is GreenletExit, disconnects """ self.group.kill(exception, block=False) self.kill(exception, block=False) ############################################################################################### my cute methods def send(self, data, log=True): """ encodes and sends one line """ self.net.send(data.encode(self.encoding) + "\r\n") self.onsent(data, log) def connect(self): """ connects to servers[currectserver] """ delay = 0 while True: for server in self.servers: try: self.onconnect(server) self.net.connect(server.address, server.ssl) self.send(u"NICK {0.nick}".format(self)) self.send(u"USER {0.username} lol wut :{0.realname}".format(self)) self.connected = True return except ConnectionFailed as e: self.onexception(e) sleep(10 + delay) delay += 1 def disconnect(self): """ good-bye! """ try: self.ondisconnect() finally: self.net.disconnect() self.connected = False def notice(self, target, line, *args, **kwargs): line = self._format(u"NOTICE {target} :{line}", target, line, *args, **kwargs) self.send(line, False) self.onsentnotice(target, line[9 + len(target):]) def privmsg(self, target, line, nick=None, *args, **kwargs): command = u"PRIVMSG {target} :{line}" if nick is None else u"PRIVMSG {target} :%s: {line}" % nick line = self._format(command, target, line, *args, **kwargs) self.send(line, False) self.onsentprivmsg(target, line[10 + len(target):]) def action(self, target, line, *args, **kwargs): line = self._format(u"PRIVMSG {target} :\x01ACTION {line}\x01", target, line, *args, **kwargs) self.send(line, False) self.onsentaction(target, line[18 + len(target):]) def joinchan(self, chan): self.send(u"JOIN " + chan) def _format(self, command, target, line, *args, **kwargs): kwargs["lang"] = conf.getdefault("lang", self.tag, target, "en") line = self.formatter.translate_format_string(line, **kwargs) return self.formatter.format(command.format(target=target, line=line), *args, **kwargs) ############################################################################################### replace me def onconnect(self, server): """ called when we *start* connecting """ self.logger.log(OTHER, "connecting to %s..." % server) def onconnected(self, msg): """ called when we have successfully connected to a server """ self.logger.log(OTHER, "connected to %s!" % msg.sender[0]) def ondisconnect(self): """ called when we disconnect """ self.logger.log(OTHER, "disconnecting") def onload(self): """ called when the thing starts """ self.logger.log(OTHER, "loading") def onunload(self): """ called when the thing dies """ self.logger.log(OTHER, "unloading") def onexception(self, e, unexpected=False): """ called when any bot's internal exception occurs (the exception gets handled) """ if unexpected: self.logger.exception(unicode(e)) else: self.logger.error(unicode(e)) ### def onmessage(self, msg): """ called when the thing receives any irc message """ if type(msg) == Privmsg: self.onprivmsg(msg) elif type(msg) == Action: self.onaction(msg) else: self.logger.log(IN, msg.line) def onprivmsg(self, msg): dt = msg.sender[0] if msg.target == self.me[0] else msg.target self.logger.log(IN_PRIVMSG, "%s | <%s> %s", dt, msg.sender[0], msg.message) def onaction(self, msg): self.logger.log(IN_ACTION, "%s | * %s %s", msg.target, msg.sender[0], msg.message) ### def onsent(self, text, unprocessed): """ called on every send. if unprocessed is True, onsenetprivmsg and the sech have not been called """ if unprocessed: self.logger.log(OUT, text) def onsentprivmsg(self, target, text): self.logger.log(OUT_PRIVMSG, "%s | <%s> %s", target, self.me[0], text) def onsentnotice(self, target, text): self.logger.log(OUT_NOTICE, "%s | <%s> %s", target, self.me[0], text) def onsentaction(self, target, text): self.logger.log(OUT_ACTION, "%s | * %s %s", target, self.me[0], text)