]>
Commit | Line | Data |
---|---|---|
9d44d267 JR |
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': [], | |
867df393 | 11 | 'softdeps': [], |
9d44d267 JR |
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 | # | |
b5e5c447 | 28 | # Once a connection is accepted, your class is instantiated with the client socket. (And the optional third argument you passed to bind_*, or None) |
9d44d267 JR |
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`. | |
a9a00d34 | 36 | |
9d44d267 JR |
37 | @lib.bind_tcp('0.0.0.0', 12543) |
38 | class BasicServer(object): | |
b5e5c447 | 39 | def __init__(self, sock, data): |
a9a00d34 | 40 | self.chan = lib.parent.cfg.get('basic_socket', 'channel', '#') |
9d44d267 JR |
41 | self.buffer = b'' |
42 | self.sock = sock | |
43 | ||
44 | def getdata(self): | |
45 | recvd = self.sock.recv(8192) | |
867df393 | 46 | if recvd == b"": # EOF |
9d44d267 JR |
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): | |
a9a00d34 JR |
67 | bot = lib.parent.randbot() |
68 | maxlen = bot.maxmsglen() - len("PRIVMSG :") - len(self.chan) | |
a9a00d34 | 69 | while len(line) > maxlen: |
867df393 JR |
70 | cutat = line.rfind(' ', 0, maxlen) |
71 | if cutat == -1: | |
72 | cutat = maxlen | |
73 | bot.msg(self.chan, line[0:cutat]) | |
74 | line = line[cutat:].strip() | |
a9a00d34 | 75 | bot.msg(self.chan, line) |
9d44d267 JR |
76 | |
77 | def send(self, line): | |
4aa86bbb JR |
78 | if lib.parent.parent.cfg.getboolean('debug', 'io'): |
79 | lib.parent.log(str(self), 'O', line) | |
fbe898f7 | 80 | self.sock.sendall(line.encode('utf-8', 'backslashreplace')+b"\r\n") |
9d44d267 JR |
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 | |
867df393 JR |
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()) |