]> jfr.im git - irc/rizon/acid.git/blame - pyva/pyva/src/main/python/esim/esim.py
Split pyva plugin into pyva.core and pyva.pyva
[irc/rizon/acid.git] / pyva / pyva / src / main / python / esim / esim.py
CommitLineData
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
9import sys
10import threading
11import traceback
12import types
13from istring import istring
14from datetime import datetime
15from decimal import Decimal, InvalidOperation
16from pseudoclient import sys_options, sys_log, cmd_manager, sys_antiflood
17from utils import *
18
19from pyva import *
20import logging
21from core import *
22from plugin import *
23
24from api import citizen, region, feed
25import cmd_admin, cmd_private, cmd_user, esimparser, sys_auth, esim_users, esim_channels
26
27class 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