]>
Commit | Line | Data |
---|---|---|
685e346e A |
1 | #!/usr/bin/python pseudoserver.py |
2 | # psm_esim.py | |
3 | # module for pypseudoserver | |
4 | # ground code taken from ElChE <elche@rizon.net> & martin <martin@rizon.net> | |
5 | # adapted for e-Sim by Digital_Lemon | |
6 | # | |
7 | # esim module | |
8 | ||
9 | import sys | |
10 | import threading | |
11 | import traceback | |
12 | import types | |
13 | from istring import istring | |
14 | from datetime import datetime | |
15 | from decimal import Decimal, InvalidOperation | |
16 | from pseudoclient import sys_options, sys_log, cmd_manager, sys_antiflood | |
17 | from utils import * | |
18 | ||
19 | from pyva import * | |
20 | import logging | |
21 | from core import * | |
22 | from plugin import * | |
23 | ||
24 | from api import citizen, region, feed | |
25 | import cmd_admin, cmd_private, cmd_user, esimparser, sys_auth, esim_users, esim_channels | |
26 | ||
27 | class esim(AcidPlugin): | |
28 | def bind_function(self, function): | |
29 | func = types.MethodType(function, self, esim) | |
30 | setattr(esim, function.__name__, func) | |
31 | return func | |
32 | ||
33 | def bind_admin_commands(self): | |
34 | list = cmd_admin.get_commands() | |
35 | self.commands_admin = [] | |
36 | ||
37 | for command in list: | |
38 | self.commands_admin.append((command, {'permission': 'e', 'callback': self.bind_function(list[command][0]), 'usage': list[command][1]})) | |
39 | ||
40 | def __init__(self): | |
41 | AcidPlugin.__init__(self) | |
42 | ||
43 | self.name = "esim" | |
44 | self.log = logging.getLogger(__name__) | |
45 | ||
46 | try: | |
47 | self.nick = istring(self.config.get('esim', 'nick')) | |
48 | except Exception, err: | |
49 | self.log.exception("Error reading 'esim:nick' configuration option: %s" % err) | |
50 | raise | |
51 | ||
52 | try: | |
53 | self.chan = istring(self.config.get('esim', 'channel')) | |
54 | except Exception, err: | |
55 | self.log.exception("Error reading 'esim:channel' configuration option: %s" % err) | |
56 | raise | |
57 | ||
58 | self.bind_admin_commands() | |
59 | ||
60 | def start_threads(self): | |
61 | self.options.start() | |
62 | self.channels.start() | |
63 | self.users.start() | |
64 | self.auth.start() | |
65 | self.antiflood.start() | |
66 | ||
67 | def start(self): | |
68 | self.initialized = False | |
69 | ||
70 | try: | |
71 | self.options = sys_options.OptionManager(self) | |
72 | self.elog = sys_log.LogManager(self) | |
73 | self.commands_private = cmd_private.PrivateCommandManager() | |
74 | self.commands_user = cmd_user.UserCommandManager() | |
75 | except Exception, err: | |
76 | self.log.exception('Error initializing core subsystems for esim module (%s)' % err) | |
77 | raise | |
78 | ||
79 | self.elog.debug('Started core subsystems.') | |
80 | ||
81 | try: | |
82 | self.channels = esim_channels.EsimChannelManager(self) | |
83 | self.users = esim_users.EsimUserManager(self) | |
84 | self.auth = sys_auth.EsimAuthManager(self) | |
85 | self.antiflood = sys_antiflood.AntiFloodManager(self) | |
86 | except Exception, err: | |
87 | self.log.exception('Error initializing subsystems for esim module (%s)' % err) | |
88 | raise | |
89 | ||
90 | self.elog.debug('Started subsystems.') | |
91 | ||
92 | for channel in self.channels.list_valid(): | |
93 | self.join(channel.name) | |
94 | ||
95 | self.log.debug('Joined channels.') | |
96 | ||
97 | try: | |
98 | self.start_threads() | |
99 | except Exception, err: | |
100 | self.log.exception('Error starting threads for esim module (%s)' % err) | |
101 | raise | |
102 | ||
103 | self.initialized = True | |
104 | self.online = True | |
105 | self.elog.debug('Started threads.') | |
106 | return True | |
107 | ||
108 | def onSync(self): | |
109 | for channel in self.channels.list_valid(): | |
110 | self.join(channel.name) | |
111 | ||
112 | self.elog.debug('Joined channels.') | |
113 | ||
114 | def stop(self): | |
115 | if hasattr(self, 'antiflood'): | |
116 | self.antiflood.stop() | |
117 | ||
118 | if hasattr(self, 'auth'): | |
119 | self.auth.stop() | |
120 | ||
121 | if hasattr(self, 'users'): | |
122 | if self.initialized: | |
123 | self.users.force() | |
124 | ||
125 | self.users.stop() | |
126 | self.users.db_close() | |
127 | ||
128 | if hasattr(self, 'channels'): | |
129 | if self.initialized: | |
130 | self.channels.force() | |
131 | ||
132 | self.channels.stop() | |
133 | self.channels.db_close() | |
134 | ||
135 | if hasattr(self, 'options'): | |
136 | if self.initialized: | |
137 | self.options.force() | |
138 | ||
139 | self.options.stop() | |
140 | self.options.db_close() | |
141 | ||
142 | def join(self, channel): | |
143 | me = self.inter.findUser(self.nick) | |
144 | me.joinChan(channel) | |
145 | ||
146 | def part(self, channel): | |
147 | me = self.inter.findUser(self.nick) | |
148 | me.partChan(channel) | |
149 | ||
150 | def errormsg(self, target, message): | |
151 | self.msg(target, '@b@c4Error:@o %s' % message) | |
152 | ||
153 | def usagemsg(self, target, description, examples): | |
154 | message = '@errsep @bUsage@b %s @errsep' % description | |
155 | ||
156 | if examples != None: | |
157 | message += ' @bExamples@b %s @errsep' % ', '.join(examples) | |
158 | ||
159 | self.msg(target, message) | |
160 | ||
161 | def msg(self, target, message): | |
162 | if message != '': | |
163 | self.inter.privmsg(self.nick, target, format_ascii_irc(message)) | |
164 | ||
165 | def multimsg(self, target, count, intro, separator, pieces, outro = ''): | |
166 | cur = 0 | |
167 | ||
168 | while cur < len(pieces): | |
169 | self.msg(target, intro + separator.join(pieces[cur:cur + count]) + outro) | |
170 | cur += count | |
171 | ||
172 | def multinotice(self, target, count, intro, separator, pieces, outro = ''): | |
173 | cur = 0 | |
174 | ||
175 | while cur < len(pieces): | |
176 | self.notice(target, intro + separator.join(pieces[cur:cur + count]) + outro) | |
177 | cur += count | |
178 | ||
179 | def notice(self, target, message): | |
180 | if message != '': | |
181 | self.inter.notice(self.nick, target, format_ascii_irc(message)) | |
182 | ||
183 | def execute(self, manager, command, argument, channel, sender): | |
184 | full_command = '%s%s' % (command, ' %s' % argument if len(argument) else '') | |
185 | cmd = manager.get_command(command) | |
186 | ||
187 | if cmd == None: | |
188 | self.msg(channel, manager.invalid) | |
189 | self.elog.debug('Parsed command @b%s@b: invalid command.' % full_command) | |
190 | return | |
191 | ||
192 | if self.users.is_banned(sender) or self.antiflood.check_user(sender, command, argument): | |
193 | user = self.users[sender] | |
194 | message = 'You were banned by @b%s@b.' % user.ban_source | |
195 | ||
196 | if user.ban_reason != None: | |
197 | message += ' Reason: @b%s@b.' % user.ban_reason | |
198 | ||
199 | if user.ban_expiry != None: | |
200 | message += ' Expires: @b%s@b.' % datetime.fromtimestamp(user.ban_expiry) | |
201 | ||
202 | self.notice(sender, message) | |
203 | self.elog.debug('Parsed command @b%s@b: user is banned.' % full_command) | |
204 | return | |
205 | ||
206 | self.elog.command('%s%s > %s' % (sender, ':%s' % channel if channel != sender else '', full_command)) | |
207 | ||
208 | parser = esimparser.ESimParser(add_help_option = False, option_class = esimparser.ESimParserOption) | |
209 | cmd_type = cmd[1] | |
210 | cmd_args = cmd[3] | |
211 | ||
212 | parser.add_option('-?', '--help', action = 'store_true') | |
213 | for cmd_arg in cmd_args: | |
214 | parser.add_option(cmd_arg[1], '--' + cmd_arg[0], **cmd_arg[3]) | |
215 | ||
216 | try: | |
217 | (popts, pargs) = parser.parse_args(args = argument.split(' ')) | |
218 | except esimparser.ESimParserError, err: | |
219 | self.msg(channel, str(err)) #TODO: Avoid str, use unicode. | |
220 | parser.destroy() | |
221 | self.elog.debug('Parsed command @b%s@b: invalid options.' % full_command) | |
222 | return | |
223 | ||
224 | if popts.help == True: | |
225 | manager.commands['help'][0](self, manager, {}, command, channel, sender) | |
226 | parser.destroy() | |
227 | self.elog.debug('Parsed command @b%s@b: help intercepted.' % full_command) | |
228 | return | |
229 | ||
230 | opt_dict = {} | |
231 | larg = ' '.join(pargs).strip() | |
232 | is_offline = True | |
233 | ||
234 | for cmd_arg in cmd_args: | |
235 | parg = getattr(popts, cmd_arg[0]) | |
236 | ||
237 | if parg != None: | |
238 | if len(cmd_arg) <= 4 or not (cmd_arg[4] & cmd_manager.ARG_OFFLINE): | |
239 | is_offline = False | |
240 | ||
241 | if len(cmd_arg) > 4 and (cmd_arg[4] & cmd_manager.ARG_YES) and larg == '': | |
242 | self.msg(channel, 'Error: %s option requires an argument.' % cmd_arg[1]) | |
243 | parser.destroy() | |
244 | self.elog.debug('Parsed command @b%s@b: option constraint was broken.' % full_command) | |
245 | return | |
246 | ||
247 | opt_dict[cmd_arg[0]] = parg | |
248 | elif len(cmd_arg) > 4 and (cmd_arg[4] & cmd_manager.ARG_OFFLINE and cmd_arg[4] & cmd_manager.ARG_OFFLINE_REQ): | |
249 | is_offline = False | |
250 | ||
251 | if not self.online and ((len(pargs) > 0 and not (cmd_type & cmd_manager.ARG_OFFLINE)) or not is_offline): | |
252 | self.notice(sender, 'The e-Sim API is offline. Please retry later.' if not self.offline_msg else self.offline_msg) | |
253 | parser.destroy() | |
254 | self.elog.debug('Parsed command @b%s@b: offline.' % full_command) | |
255 | return | |
256 | ||
257 | if (cmd_type & cmd_manager.ARG_YES) and (larg == None or larg == ''): | |
258 | self.msg(channel, '@bUsage@b: %s @b%s@b' % (command, cmd[4] if len(cmd) > 4 else 'argument')) | |
259 | else: | |
260 | try: | |
261 | cmd[0](self, manager, opt_dict, larg, channel, sender) | |
262 | except Exception, e: | |
263 | tb = traceback.extract_tb(sys.exc_info()[2]) | |
264 | longest = 0 | |
265 | ||
266 | for entry in tb: | |
267 | length = len(entry[2]) | |
268 | ||
269 | if length > longest: | |
270 | longest = length | |
271 | ||
272 | self.elog.exception('%s%s > @b%s@b: %s' % (sender, ':%s' % channel if channel != sender else '', full_command, e)) | |
273 | self.log.exception("e-Sim error!") | |
274 | ||
275 | for entry in tb: | |
276 | self.elog.traceback('@b%-*s@b : %d %s' % (longest, entry[2], entry[1], entry[3])) | |
277 | ||
278 | self.msg(channel, 'An exception occurred and has been reported to the developers. If this error persists please do not use the faulty command until it has been fixed.') | |
279 | ||
280 | parser.destroy() | |
281 | self.elog.debug('Parsed command @b%s@b: execution terminated.' % full_command) | |
282 | ||
283 | def onChanModes(self, prefix, channel, modes): | |
284 | if not self.initialized: | |
285 | return | |
286 | ||
287 | if not modes == '-z': | |
288 | return | |
289 | ||
290 | if channel in self.channels: | |
291 | self.channels.remove(channel) | |
292 | self.elog.request('Channel @b%s@b was dropped. Deleting it.' % channel) | |
293 | ||
294 | def onNotice(self, source, target, message): | |
295 | if not self.initialized: | |
296 | return | |
297 | ||
298 | me = self.inter.findUser(self.nick) | |
299 | user = self.inter.findUser(target) | |
300 | userinfo = self.inter.findUser(source) | |
301 | ||
302 | if me != user or (userinfo != None and userinfo['nick'] != 'ChanServ'): | |
303 | return | |
304 | ||
305 | try: | |
306 | msg = message.strip() | |
307 | except: | |
308 | return | |
309 | ||
310 | if msg.startswith('[#'): #It's a channel welcome message. Let's ignore it. | |
311 | return | |
312 | ||
313 | self.elog.chanserv('%s' % msg) | |
314 | sp = msg.split(' ') | |
315 | ||
316 | if userinfo == None: | |
317 | if 'tried to kick you from' in msg: | |
318 | nick = strip_ascii_irc(sp[1]) | |
319 | channel = strip_ascii_irc(sp[7]) | |
320 | self.notice(nick, 'To remove this bot (must be channel founder): @b/msg %s remove %s@b' % (self.nick, channel)) | |
321 | ||
322 | return | |
323 | ||
324 | if "isn't registered" in msg: | |
325 | self.auth.reject_not_registered(strip_ascii_irc(sp[1])) | |
326 | return | |
327 | ||
328 | if len(sp) < 6: | |
329 | return | |
330 | ||
331 | if 'inviting' in sp[2]: #It's an invite notice. Let's ignore it. | |
332 | return | |
333 | ||
334 | nick = strip_ascii_irc(sp[0]) | |
335 | channel = sp[5][0:len(sp[5]) - 1] | |
336 | ||
337 | if 'Founder' in sp[2]: | |
338 | self.auth.accept(nick) | |
339 | else: | |
340 | self.auth.reject_not_founder(nick, channel) | |
341 | ||
342 | def onPrivmsg(self, source, target, message): | |
343 | if not self.initialized: | |
344 | return | |
345 | ||
346 | userinfo = self.inter.findUser(source) | |
347 | myself = self.inter.findUser(self.nick) | |
348 | ||
349 | sender = userinfo['nick'] | |
350 | channel = target | |
351 | msg = message.strip() | |
352 | index = msg.find(' ') | |
353 | ||
354 | if index == -1: | |
355 | command = msg | |
356 | argument = '' | |
357 | else: | |
358 | command = msg[:index] | |
359 | argument = msg[index + 1:] | |
360 | ||
361 | if channel == myself['nick'] and command.startswith(self.commands_private.prefix): | |
362 | self.elog.debug('Deferred parsing of private message (sender: @b%s@b, command: @b%s@b, argument: @b%s@b)' % (sender, command, argument)) | |
363 | targs = (self.commands_private, command, argument, sender, sender) | |
364 | elif self.channels.is_valid(channel) and command.startswith(self.commands_user.prefix): | |
365 | self.elog.debug('Deferred parsing of channel message (sender: @b%s@b, channel: @b%s@b, command: @b%s@b, argument: @b%s@b)' % (sender, channel, command, argument)) | |
366 | targs = (self.commands_user, command, argument, channel, sender) | |
367 | else: | |
368 | return | |
369 | ||
370 | t = threading.Thread(target=self.execute, args=targs) | |
371 | t.daemon = True | |
372 | t.start() | |
373 | ||
374 | def getCommands(self): | |
375 | return self.commands_admin | |
376 | ||
377 | def get_citizen(self, opts, arg, channel, sender, allow_org=True): | |
378 | id = None | |
379 | nick = None | |
380 | ||
381 | if 'nick' in opts: | |
382 | nick = arg | |
383 | elif arg == '': | |
384 | nick = sender | |
385 | elif 'id' in opts: | |
386 | try: | |
387 | id = int(arg) | |
388 | except ValueError, e: | |
389 | self.errormsg(channel, '%s is not a valid id.' % arg) | |
390 | return None | |
391 | ||
392 | if nick != None: | |
393 | id = self.users.get(nick, 'citizen') | |
394 | ||
395 | if id == None and nick != None: | |
396 | if 'nick' in opts: | |
397 | self.msg(channel, 'No citizen found linked to nick %s.' % arg) | |
398 | else: | |
399 | self.msg(channel, 'No citizen found linked to your nick. To link one type: @b.register_citizen <citizen name>@b') | |
400 | ||
401 | return None | |
402 | ||
403 | try: | |
404 | # Less effort, will change soon. Too many QQers. (YES CX, I WILL!) | |
405 | secura = True if 'secura' in opts else False | |
406 | c = citizen.from_name(arg, secura) if id == None else citizen.from_id(id, secura) | |
407 | except feed.FeedError, e: | |
408 | self.errormsg(channel, e.msg) | |
409 | return None | |
410 | ||
411 | if not allow_org and c.is_organization: | |
412 | self.errormsg(channel, '@b%s@b is an organization, not a citizen.' % c.name) | |
413 | return None | |
414 | ||
415 | return c | |
416 | ||
417 | def get_region(self, opts, arg, channel, sender): | |
418 | try: | |
419 | #secura = True if 'secura' in opts else False | |
420 | r = region.from_id(arg) if 'id' in opts else region.from_name(arg) | |
421 | except ValueError: | |
422 | self.errormsg(channel, '%s is not a valid id.' % arg) | |
423 | return None | |
424 | except feed.FeedError, e: | |
425 | self.errormsg(channel, e.msg) | |
426 | return | |
427 | ||
428 | if r == None: | |
429 | self.errormsg(channel, 'region @b%s@b not found.' % arg) | |
430 | ||
431 | return r |