]> jfr.im git - erebus.git/blame - modules/sockets.py
update comments
[erebus.git] / modules / sockets.py
CommitLineData
aaa8eb8d
JR
1# Erebus IRC bot - Author: Erebus Team
2# vim: fileencoding=utf-8
bac69af4 3# Configurable sockets module. DO NOT USE without understanding the risks
aaa8eb8d
JR
4# This file is released into the public domain; see http://unlicense.org/
5
6# Note: this module doesn't do any kind of authentication, so anyone who can connect to the bound port can spam you.
7
bac69af4
JR
8"""
9To use - add in bot.config something like:
10
11[sockets]
12127.0.0.1:1337 = #example
13
14The left side is the address to listen on and the right side is the channel to send to.
15The exmaple will send incoming lines/packets on localhost, port 1337 to channel #example
16
17The full syntax for the address is:
18[unix:]</path/to/socket>
19[udp|tcp:][<ip>:]<port>
20
21
22Address examples:
23
24Unix domain socket: /path
25Unix domain socket: unix:/path
26TCP socket (all interfaces): 1337
27TCP socket (one interface): 127.0.0.1:1337
28UDP socket (all interfaces): udp:1337
29UDP socket (one interface): udp:127.0.0.1:1337
30"""
31
aaa8eb8d
JR
32# module info
33modinfo = {
34 'author': 'Erebus Team',
35 'license': 'public domain',
36 'compatible': [0],
37 'depends': [],
38 'softdeps': [],
39}
40
41# preamble
42import modlib
43lib = modlib.modlib(__name__)
44def modstart(parent, *args, **kwargs):
45 gotParent(parent)
46 return lib.modstart(parent, *args, **kwargs)
47modstop = lib.modstop
48
49# module code
50
51def gotParent(parent):
52 for bindto, channel in parent.cfg.items('sockets'):
53 @lib.bind(bindto, data=channel)
54 class BasicServer(object):
55 def __init__(self, sock, data):
b5e5c447
JR
56 # NB neither directly referencing `channel`, nor trying to pass it through a default-arg-to-a-lambda like the python docs suggest, works here.
57 # Yay python. At least passing it via bind works.
aaa8eb8d 58 self.chan = data
aaa8eb8d
JR
59 self.buffer = b''
60 self.sock = sock
61
62 def getdata(self):
63 recvd = self.sock.recv(8192)
64 if recvd == b"": # EOF
65 if len(self.buffer) != 0:
66 # Process what's left in the buffer. We'll get called again after.
67 remaining_buf = self.buffer.decode('utf-8', 'backslashreplace')
68 self.buffer = b""
69 return [remaining_buf]
70 else:
71 # Nothing left in the buffer. Return None to signal the core to close this socket.
72 return None
73 self.buffer += recvd
74 lines = []
75
76 while b"\n" in self.buffer:
77 pieces = self.buffer.split(b"\n", 1)
78 s = pieces[0].decode('utf-8', 'backslashreplace').rstrip("\r")
79 lines.append(pieces[0].decode('utf-8', 'backslashreplace'))
80 self.buffer = pieces[1]
81
82 return lines
83
84 def parse(self, line):
33af27c6
JR
85 try:
86 bot = lib.parent.channel(self.chan).bot
87 except AttributeError: # <class 'AttributeError'> 'NoneType' object has no attribute 'bot'
88 bot = lib.parent.randbot()
aaa8eb8d
JR
89 maxlen = bot.maxmsglen() - len("PRIVMSG :") - len(self.chan)
90 while len(line) > maxlen:
91 cutat = line.rfind(' ', 0, maxlen)
92 if cutat == -1:
93 cutat = maxlen
94 bot.msg(self.chan, line[0:cutat])
95 line = line[cutat:].strip()
96 bot.msg(self.chan, line)
97
98 def send(self, line):
99 if lib.parent.parent.cfg.getboolean('debug', 'io'):
100 lib.parent.log(str(self), 'O', line)
101 self.sock.sendall(line.encode('utf-8', 'backslashreplace')+b"\r\n")
102
103 def _getsockerr(self):
104 try: # SO_ERROR might not exist on all platforms
105 return self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
106 except:
107 return None
108
109 def __str__(self):
110 return '%s#%d' % (__name__, self.sock.fileno())
111 def __repr__(self):
112 return '<%s #%d %s:%d>' % ((__name__, self.sock.fileno())+self.sock.getpeername())