]>
jfr.im git - erebus.git/blob - modlib.py
06276184ef27876a3c338adb94a0d3e2f473db8f
1 # Erebus IRC bot - Author: John Runyon
2 # vim: fileencoding=utf-8
3 # module helper functions, see modules/modtest.py for usage
4 # This file is released into the public domain; see http://unlicense.org/
8 from functools
import wraps
10 if sys
.version_info
.major
< 3:
11 stringbase
= basestring
15 """Used to return an error to the bot core."""
17 def __init__(self
, desc
):
19 def __nonzero__(self
):
20 return False #object will test to False
21 __bool__
= __nonzero__
#py3 compat
23 return '<modlib.error %r>' % self
.errormsg
25 return str(self
.errormsg
)
28 # default (global) access levels
34 AUTHED
= 0 # Users which have are known to be authed
35 ANYONE
= -1 # non-authed users have glevel set to -1
36 IGNORED
= -2 # The default reqglevel is ANYONE, so any commands will be ignored from IGNORED users unless the command reglevel=-2
48 # (channel) access levels
54 PUBLIC
= 0 # Anyone (use glevel to control whether auth is needed)
55 BANNED
= -1 # The default reqclevel is PUBLIC, so any commands which needchan will be ignored from BANNED users unless the command reqclevel=-1
57 clevs
= [None, 'Friend', 'Voice', 'Op', 'Master', 'Owner', 'Banned']
60 WRONGARGS
= "Wrong number of arguments."
62 def __init__(self
, name
):
63 self
.hooks
= {} # {command:handler}
64 self
.chanhooks
= {} # {channel:handler}
65 self
.exceptionhooks
= [] # [(exception,handler)]
66 self
.numhooks
= {} # {numeric:handler}
67 self
.sockhooks
= [] # [(af,ty,address,handler_class)]
68 self
.sockets
= [] # [(sock,obj)]
72 self
.name
= (name
.split("."))[-1]
74 def modstart(self
, parent
):
75 #modstart can return a few things...
76 # None: unspecified success
77 # False: unspecified error
78 # modlib.error (or anything else False-y): specified error
79 # True: unspecified success
80 # non-empty string (or anything else True-y): specified success
81 #"specified" values will be printed. unspecified values will result in "OK" or "failed"
83 for cmd
, func
in self
.hooks
.items():
84 parent
.hook(cmd
, func
)
85 parent
.hook("%s.%s" % (self
.name
, cmd
), func
)
86 for chan
, func
in self
.chanhooks
.items():
87 parent
.hookchan(chan
, func
)
88 for exc
, func
in self
.exceptionhooks
:
89 parent
.hookexception(exc
, func
)
90 for num
, func
in self
.numhooks
.items():
91 parent
.hooknum(num
, func
)
92 for hookdata
in self
.sockhooks
:
93 self
._create
_socket
(*hookdata
)
95 for func
, args
, kwargs
in self
.helps
:
97 self
.mod('help').reghelp(func
, *args
, **kwargs
)
101 def modstop(self
, parent
):
102 for cmd
, func
in self
.hooks
.items():
103 parent
.unhook(cmd
, func
)
104 parent
.unhook("%s.%s" % (self
.name
, cmd
), func
)
105 for chan
, func
in self
.chanhooks
.items():
106 parent
.unhookchan(chan
, func
)
107 for exc
, func
in self
.exceptionhooks
:
108 parent
.unhookexception(exc
, func
)
109 for num
, func
in self
.numhooks
.items():
110 parent
.unhooknum(num
, func
)
111 for sock
, obj
in self
.sockets
:
112 self
._destroy
_socket
(sock
, obj
)
114 for func
, args
, kwargs
in self
.helps
:
116 self
.mod('help').dereghelp(func
, *args
, **kwargs
)
121 def hook(self
, cmd
=None, needchan
=True, glevel
=ANYONE
, clevel
=PUBLIC
, wantchan
=None):
122 if wantchan
is None: wantchan
= needchan
123 _cmd
= cmd
#save this since it gets wiped out...
125 cmd
= _cmd
#...and restore it
127 cmd
= func
.__name
__ # default to function name
128 if isinstance(cmd
, stringbase
):
131 if clevel
> self
.PUBLIC
and not needchan
:
132 raise Exception('clevel must be left at default if needchan is False')
134 func
.needchan
= needchan
135 func
.wantchan
= wantchan
136 func
.reqglevel
= glevel
137 func
.reqclevel
= clevel
139 func
.module
= func
.__module
__.split('.')[1]
143 if self
.parent
is not None:
144 self
.parent
.hook(c
, func
)
145 self
.parent
.hook("%s.%s" % (self
.name
, c
), func
)
149 def hookchan(self
, chan
, glevel
=ANYONE
, clevel
=PUBLIC
):
151 self
.chanhooks
[chan
] = func
152 if self
.parent
is not None:
153 self
.parent
.hookchan(chan
, func
)
157 def hookexception(self
, exc
):
159 self
.exceptionhooks
.append((exc
, func
))
160 if self
.parent
is not None:
161 self
.parent
.hookexception(exc
, func
)
165 def hooknum(self
, num
):
167 self
.numhooks
[str(num
)] = func
168 if self
.parent
is not None:
169 self
.parent
.hooknum(str(num
), func
)
173 def bind(self
, bindto
):
174 """Used as a decorator on a class which implements getdata and parse methods.
175 See modules/sockets.py for an example.
181 raise Exception('bindto must have a value')
183 return self
._hooksocket
(socket
.AF_UNIX
, socket
.SOCK_STREAM
, bindto
)
184 if len(bindto
) > 5 and bindto
[0:5] == 'unix:':
185 return self
._hooksocket
(socket
.AF_UNIX
, socket
.SOCK_STREAM
, bindto
[5:])
187 ty
= socket
.SOCK_STREAM
189 if len(bindto
) > 4 and bindto
[0:4] == 'udp:':
190 ty
= socket
.SOCK_DGRAM
192 if len(bindto
) > 4 and bindto
[0:4] == 'tcp:':
194 print(repr(bindto
), ':' in bindto
)
197 pieces
= bindto
.rsplit(':', 1)
200 print(pieces
,host
,bindto
)
202 return self
._hooksocket
(af
, ty
, (host
, port
))
204 def bind_tcp(self
, host
, port
):
205 return self
._hooksocket
(socket
.AF_INET
, socket
.SOCK_STREAM
, (host
, port
))
206 def bind_udp(self
, host
, port
):
207 return self
._hooksocket
(socket
.AF_INET
, socket
.SOCK_DGRAM
, (host
, port
))
208 def bind_unix(self
, path
):
209 return self
._hooksocket
(socket
.AF_UNIX
, socket
.SOCK_STREAM
, path
)
210 def _hooksocket(self
, af
, ty
, address
):
212 if not (hasattr(cls
, 'getdata') and callable(cls
.getdata
)):
213 # Check early that the object implements getdata.
214 # If getdata ever returns a non-empty list, then a parse method must also exist, but we don't check that.
215 raise Exception('Attempted to hook a socket without a class to process data')
216 self
.sockhooks
.append((af
, ty
, address
, cls
))
217 if self
.parent
is not None:
218 self
._create
_socket
(af
, ty
, address
, cls
)
221 def _create_socket(self
, af
, ty
, address
, cls
):
222 ty
= ty | socket
.SOCK_NONBLOCK
223 sock
= socket
.socket(af
, ty
)
224 sock
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEADDR
, 1)
226 obj
= _ListenSocket(self
, sock
, cls
)
227 self
.sockets
.append((sock
,obj
))
229 self
.parent
.newfd(obj
, sock
.fileno())
230 self
.parent
.log(repr(obj
), '?', 'Socket ready to accept new connections (%r, %r, %r, %r)' % (af
, ty
, address
, cls
))
231 def _destroy_socket(self
, sock
, obj
):
234 def mod(self
, modname
):
235 if self
.parent
is not None:
236 return self
.parent
.module(modname
)
238 return error('unknown parent')
240 def argsEQ(self
, num
):
243 def checkargs(bot
, user
, chan
, realtarget
, *args
):
245 return func(bot
, user
, chan
, realtarget
, *args
)
247 bot
.msg(user
, self
.WRONGARGS
)
251 def argsGE(self
, num
):
254 def checkargs(bot
, user
, chan
, realtarget
, *args
):
256 return func(bot
, user
, chan
, realtarget
, *args
)
258 bot
.msg(user
, self
.WRONGARGS
)
262 def help(self
, *args
, **kwargs
):
263 """help(syntax, shorthelp, longhelp?, more lines longhelp?, cmd=...?)
265 help("<user> <pass>", "login")
266 ^ Help will only be one line. Command name determined based on function name.
267 help("<user> <level>", "add a user", cmd=("adduser", "useradd"))
268 ^ Help will be listed under ADDUSER; USERADD will say "alias for adduser"
269 help(None, "do stuff", "This command is really complicated.")
270 ^ Command takes no args. Short description (in overall HELP listing) is "do stuff".
271 Long description (HELP <command>) will say "<command> - do stuff", newline, "This command is really complicated."
274 if self
.parent
is not None:
276 self
.mod('help').reghelp(func
, *args
, **kwargs
)
279 self
.helps
.append((func
, args
, kwargs
))
283 class _ListenSocket(object):
284 def __init__(self
, lib
, sock
, cls
):
290 def _make_closer(self
, obj
, client
):
292 self
.lib
.parent
.log(repr(self
), '?', 'Closing child socket #%d' % (client
.fileno()))
295 except AttributeError:
297 self
.lib
.parent
.delfd(client
.fileno())
298 client
.shutdown(socket
.SHUT_RDWR
)
300 self
.clients
.remove((client
,obj
))
304 client
, addr
= self
.sock
.accept()
305 obj
= self
.cls(client
)
306 obj
.close
= self
._make
_closer
(obj
, client
)
307 self
.lib
.parent
.log(repr(self
), '?', 'New connection #%d from %s' % (client
.fileno(), addr
))
308 self
.clients
.append((client
,obj
))
309 self
.lib
.parent
.newfd(obj
, client
.fileno())
313 self
.lib
.parent
.log(repr(self
), '?', 'Socket closing')
314 if self
.sock
.fileno() != -1:
315 self
.lib
.parent
.delfd(self
.sock
.fileno())
316 self
.sock
.shutdown(socket
.SHUT_RDWR
)
318 for client
, obj
in self
.clients
:
321 def __repr__(self
): return '<_ListenSocket #%d>' % (self
.sock
.fileno())