]> jfr.im git - erebus.git/blob - modules/basic_socket.py
03f7323e6c848c69183887f3d93a824f952b0185
[erebus.git] / modules / basic_socket.py
1 # Erebus IRC bot - Author: Erebus Team
2 # vim: fileencoding=utf-8
3 # This file is released into the public domain; see http://unlicense.org/
4
5 # module info
6 modinfo = {
7 'author': 'Erebus Team',
8 'license': 'public domain',
9 'compatible': [0],
10 'depends': [],
11 'softdeps': [],
12 }
13
14 # preamble
15 import modlib
16 lib = modlib.modlib(__name__)
17 modstart = lib.modstart
18 modstop = lib.modstop
19
20 # module code
21
22 # Note: bind_* does all of the following:
23 # - create a socket `sock = socket.socket()`
24 # - bind the socket `sock.bind()`
25 # - listen on the socket `sock.listen()`
26 # - accept `sock.accept()`
27 #
28 # Once a connection is accepted, your class is instantiated with the client socket.
29 # - When data comes in on the client socket, your `getdata` method will be called. It should return a list of strings.
30 # - For each element in the list returned by `getdata`, `parse` will be called.
31 # - When the socket is being closed by the bot (f.e. your module is unloaded), the optional method `closing` will be called.
32 # Then the bot will call `sock.shutdown()` and `sock.close()` for you.
33 # XXX error handling? what happens when the other side closes the socket?
34 #
35 # You can interact with the rest of the bot through `lib.parent`.
36
37 @lib.bind_tcp('0.0.0.0', 12543)
38 class BasicServer(object):
39 def __init__(self, sock):
40 self.chan = lib.parent.cfg.get('basic_socket', 'channel', '#')
41 self.buffer = b''
42 self.sock = sock
43
44 def getdata(self):
45 recvd = self.sock.recv(8192)
46 if recvd == b"": # EOF
47 if len(self.buffer) != 0:
48 # Process what's left in the buffer. We'll get called again after.
49 remaining_buf = self.buffer.decode('utf-8', 'backslashreplace')
50 self.buffer = b""
51 return [remaining_buf]
52 else:
53 # Nothing left in the buffer. Return None to signal the core to close this socket.
54 return None
55 self.buffer += recvd
56 lines = []
57
58 while b"\n" in self.buffer:
59 pieces = self.buffer.split(b"\n", 1)
60 s = pieces[0].decode('utf-8', 'backslashreplace').rstrip("\r")
61 lines.append(pieces[0].decode('utf-8', 'backslashreplace'))
62 self.buffer = pieces[1]
63
64 return lines
65
66 def parse(self, line):
67 peer = self.sock.getpeername()
68 bot = lib.parent.randbot()
69 maxlen = bot.maxmsglen() - len("PRIVMSG :") - len(self.chan)
70 lib.parent.log(str(self), 'I', line)
71 while len(line) > maxlen:
72 cutat = line.rfind(' ', 0, maxlen)
73 if cutat == -1:
74 cutat = maxlen
75 bot.msg(self.chan, line[0:cutat])
76 line = line[cutat:].strip()
77 bot.msg(self.chan, line)
78
79 def send(self, line):
80 self.socket.sendall(line.encode('utf-8', 'backslashreplace')+b"\r\n")
81
82 def _getsockerr(self):
83 try: # SO_ERROR might not exist on all platforms
84 return self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
85 except:
86 return None
87
88 def __str__(self):
89 return '%s#%d' % (__name__, self.sock.fileno())
90 def __repr__(self):
91 return '<%s #%d %s:%d>' % ((__name__, self.sock.fileno())+self.sock.getpeername())