# Erebus IRC bot - Author: Erebus Team
# vim: fileencoding=utf-8
+# Configurable sockets module. DO NOT USE without understanding the risks
# This file is released into the public domain; see http://unlicense.org/
# Note: this module doesn't do any kind of authentication, so anyone who can connect to the bound port can spam you.
+"""
+To use - add in bot.config something like:
+
+[sockets]
+127.0.0.1:1337 = #example
+
+The left side is the address to listen on and the right side is the channel to send to.
+The exmaple will send incoming lines/packets on localhost, port 1337 to channel #example
+
+The full syntax for the address is:
+[unix:]</path/to/socket>
+[udp|tcp:][<ip>:]<port>
+
+
+Address examples:
+
+Unix domain socket: /path
+Unix domain socket: unix:/path
+TCP socket (all interfaces): 1337
+TCP socket (one interface): 127.0.0.1:1337
+UDP socket (all interfaces): udp:1337
+UDP socket (one interface): udp:127.0.0.1:1337
+"""
+
# module info
modinfo = {
'author': 'Erebus Team',
modstop = lib.modstop
# module code
+class BasicServer(lib.Socketlike):
+ def __init__(self, sock, data):
+ super(BasicServer, self).__init__(sock, data)
+ # NB neither directly referencing `channel`, nor trying to pass it through a default-arg-to-a-lambda like the python docs suggest, works here.
+ # Yay python. At least passing it via bind works.
+ self.chan = data
-def gotParent(parent):
- for bindto, channel in parent.cfg.items('sockets'):
- @lib.bind(bindto, data=channel)
- class BasicServer(object):
- def __init__(self, sock, data):
- # NB neither directly referencing `channel`, nor trying to pass it through a default-arg-to-a-lambda like the python docs suggest, works here.
- # Yay python. At least passing it via bind works.
- self.chan = data
- self.buffer = b''
- self.sock = sock
-
- def getdata(self):
- recvd = self.sock.recv(8192)
- if recvd == b"": # EOF
- if len(self.buffer) != 0:
- # Process what's left in the buffer. We'll get called again after.
- remaining_buf = self.buffer.decode('utf-8', 'backslashreplace')
- self.buffer = b""
- return [remaining_buf]
- else:
- # Nothing left in the buffer. Return None to signal the core to close this socket.
- return None
- self.buffer += recvd
- lines = []
-
- while b"\n" in self.buffer:
- pieces = self.buffer.split(b"\n", 1)
- s = pieces[0].decode('utf-8', 'backslashreplace').rstrip("\r")
- lines.append(pieces[0].decode('utf-8', 'backslashreplace'))
- self.buffer = pieces[1]
-
- return lines
+ # default getdata() and send() methods are defined by lib.Socketlike
+ # suitable for line-based protocols like IRC
- def parse(self, line):
- try:
- bot = lib.parent.channel(self.chan).bot
- except AttributeError: # <class 'AttributeError'> 'NoneType' object has no attribute 'bot'
- bot = lib.parent.randbot()
- maxlen = bot.maxmsglen() - len("PRIVMSG :") - len(self.chan)
- while len(line) > maxlen:
- cutat = line.rfind(' ', 0, maxlen)
- if cutat == -1:
- cutat = maxlen
- bot.msg(self.chan, line[0:cutat])
- line = line[cutat:].strip()
- bot.msg(self.chan, line)
+ def parse(self, line):
+ try:
+ bot = lib.parent.channel(self.chan).bot
+ except AttributeError: # <class 'AttributeError'> 'NoneType' object has no attribute 'bot'
+ bot = lib.parent.randbot()
+ maxlen = bot.maxmsglen() - len("PRIVMSG :") - len(self.chan)
+ while len(line) > maxlen:
+ cutat = line.rfind(' ', 0, maxlen)
+ if cutat == -1:
+ cutat = maxlen
+ bot.msg(self.chan, line[0:cutat])
+ line = line[cutat:].strip()
+ bot.msg(self.chan, line)
- def send(self, line):
- if lib.parent.parent.cfg.getboolean('debug', 'io'):
- lib.parent.log(str(self), 'O', line)
- self.sock.sendall(line.encode('utf-8', 'backslashreplace')+b"\r\n")
+ # default __str__() and __repr__() methods are defined by lib.Socketlike
- def _getsockerr(self):
- try: # SO_ERROR might not exist on all platforms
- return self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
- except:
- return None
-
- def __str__(self):
- return '%s#%d' % (__name__, self.sock.fileno())
- def __repr__(self):
- return '<%s #%d %s:%d>' % ((__name__, self.sock.fileno())+self.sock.getpeername())
+def gotParent(parent):
+ for bindto, channel in parent.cfg.items('sockets'):
+ lib.bind(bindto, data=channel)(BasicServer)