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