1 #!/usr/bin/python pseudoserver.py
3 # module for pypseudoserver
4 # written by ElChE <elche@rizon.net>, martin <martin@rizon.net>
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
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
28 class erepublik(AcidPlugin
):
31 def bind_function(self
, function
):
32 func
= types
.MethodType(function
, self
, erepublik
)
33 setattr(erepublik
, function
.__name
__, func
)
36 def bind_admin_commands(self
):
37 list = cmd_admin
.get_commands()
38 self
.commands_admin
= []
41 self
.commands_admin
.append((command
, {'permission': 'e', 'callback': self.bind_function(list[command][0]), 'usage': list[command][1]}
))
44 AcidPlugin
.__init
__(self
)
46 self
.name
= "erepublik"
47 self
.log
= logging
.getLogger(__name__
)
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
)
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
)
61 self
.bind_admin_commands()
63 def start_threads(self
):
69 self
.antiflood
.start()
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
)
81 self
.elog
.debug('Started core subsystems.')
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
)
93 self
.elog
.debug('Started subsystems.')
95 for channel
in self
.channels
.list_valid():
96 self
.join(channel
.name
)
98 self
.log
.debug('Joined channels.')
102 except Exception, err
:
103 self
.log
.exception('Error starting threads for erepublik module (%s)' % err
)
106 self
.initialized
= True
108 self
.elog
.debug('Started threads.')
112 for channel
in self
.channels
.list_valid():
113 self
.join(channel
.name
)
115 self
.log
.debug('Joined channels.')
118 if hasattr(self
, 'antiflood'):
119 self
.antiflood
.stop()
121 if hasattr(self
, 'auth'):
124 if hasattr(self
, 'news'):
127 if hasattr(self
, 'users'):
132 self
.users
.db_close()
134 if hasattr(self
, 'channels'):
136 self
.channels
.force()
139 self
.channels
.db_close()
141 if hasattr(self
, 'options'):
146 self
.options
.db_close()
148 def join(self
, channel
):
149 me
= self
.inter
.findUser(self
.nick
)
152 def part(self
, channel
):
153 me
= self
.inter
.findUser(self
.nick
)
156 def errormsg(self
, target
, message
):
157 self
.msg(target
, '@b@c4Error:@o %s' % message
)
159 def usagemsg(self
, target
, description
, examples
):
160 message
= '@errsep @bUsage@b %s @errsep' % description
163 message
+= ' @bExamples@b %s @errsep' % ', '.join(examples
)
165 self
.msg(target
, message
)
167 def msg(self
, target
, message
):
169 self
.inter
.privmsg(self
.nick
, target
, format_ascii_irc(message
))
171 def multimsg(self
, target
, count
, intro
, separator
, pieces
, outro
= ''):
174 while cur
< len(pieces
):
175 self
.msg(target
, intro
+ separator
.join(pieces
[cur
:cur
+ count
]) + outro
)
178 def multinotice(self
, target
, count
, intro
, separator
, pieces
, outro
= ''):
181 while cur
< len(pieces
):
182 self
.notice(target
, intro
+ separator
.join(pieces
[cur
:cur
+ count
]) + outro
)
185 def notice(self
, target
, message
):
187 self
.inter
.notice(self
.nick
, target
, format_ascii_irc(message
))
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
)
194 self
.msg(channel
, manager
.invalid
)
195 self
.elog
.debug('Parsed command @b%s@b: invalid command.' % full_command
)
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
202 if user
.ban_reason
!= None:
203 message
+= ' Reason: @b%s@b.' % user
.ban_reason
205 if user
.ban_expiry
!= None:
206 message
+= ' Expires: @b%s@b.' % datetime
.fromtimestamp(user
.ban_expiry
)
208 self
.notice(sender
, message
)
209 self
.elog
.debug('Parsed command @b%s@b: user is banned.' % full_command
)
212 self
.elog
.command('%s%s > %s' % (sender
, ':%s' % channel
if channel
!= sender
else '', full_command
))
214 parser
= erepparser
.ErepublikParser(add_help_option
= False, option_class
= erepparser
.ErepublikParserOption
)
218 parser
.add_option('-?', '--help', action
= 'store_true')
220 for cmd_arg
in cmd_args
:
221 parser
.add_option(cmd_arg
[1], '--' + cmd_arg
[0], **cmd_arg
[3])
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.
228 self
.elog
.debug('Parsed command @b%s@b: invalid options.' % full_command
)
231 if popts
.help == True:
232 manager
.commands
['help'][0](self
, manager
, {}, command
, channel
, sender
)
234 self
.elog
.debug('Parsed command @b%s@b: help intercepted.' % full_command
)
238 larg
= ' '.join(pargs
).strip()
241 for cmd_arg
in cmd_args
:
242 parg
= getattr(popts
, cmd_arg
[0])
245 if len(cmd_arg
) <= 4 or not (cmd_arg
[4] & cmd_manager
.ARG_OFFLINE
):
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])
251 self
.elog
.debug('Parsed command @b%s@b: option constraint was broken.' % full_command
)
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
):
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
)
261 self
.elog
.debug('Parsed command @b%s@b: offline.' % full_command
)
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'))
268 cmd
[0](self
, manager
, opt_dict
, larg
, channel
, sender
)
270 tb
= traceback
.extract_tb(sys
.exc_info()[2])
274 length
= len(entry
[2])
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!")
283 self
.elog
.traceback('@b%-*s@b : %d %s' % (longest
, entry
[2], entry
[1], entry
[3]))
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.')
288 self
.elog
.debug('Parsed command @b%s@b: execution terminated.' % full_command
)
290 def onChanModes(self
, prefix
, channel
, modes
):
291 if not self
.initialized
:
294 if not modes
== '-z':
297 if channel
in self
.channels
:
298 self
.channels
.remove(channel
)
299 self
.elog
.request('Channel @b%s@b was dropped. Deleting it.' % channel
)
301 def onNotice(self
, source
, target
, message
):
302 if not self
.initialized
:
305 me
= self
.inter
.findUser(self
.nick
)
306 user
= self
.inter
.findUser(target
)
307 userinfo
= self
.inter
.findUser(source
)
309 if me
!= user
or (userinfo
!= None and userinfo
['nick'] != 'ChanServ'):
313 msg
= message
.strip()
317 if msg
.startswith('[#'): #It's a channel welcome message. Let's ignore it.
320 self
.elog
.chanserv('%s' % msg
)
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
))
331 if "isn't registered" in msg
:
332 self
.auth
.reject_not_registered(strip_ascii_irc(sp
[1]))
338 if 'inviting' in sp
[2]: #It's an invite notice. Let's ignore it.
341 nick
= strip_ascii_irc(sp
[0])
342 channel
= sp
[5][0:len(sp
[5]) - 1]
344 if 'Founder' in sp
[2]:
345 self
.auth
.accept(nick
)
347 self
.auth
.reject_not_founder(nick
, channel
)
349 def onPrivmsg(self
, source
, target
, message
):
350 if not self
.initialized
:
353 userinfo
= self
.inter
.findUser(source
)
354 myself
= self
.inter
.findUser(self
.nick
)
356 sender
= userinfo
['nick']
358 msg
= message
.strip()
359 index
= msg
.find(' ')
365 command
= msg
[:index
]
366 argument
= msg
[index
+ 1:]
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
)
377 t
= threading
.Thread(target
=self
.execute
, args
=targs
)
381 def getCommands(self
):
382 return self
.commands_admin
384 def get_citizen(self
, opts
, arg
, channel
, sender
, allow_org
=True, no_api_request
=False):
395 except ValueError, e
:
396 self
.errormsg(channel
, '%s is not a valid id.' % arg
)
400 id = self
.users
.get(nick
, 'citizen')
402 if id == None and nick
!= None:
404 self
.msg(channel
, 'No citizen found linked to nick %s.' % arg
)
406 self
.msg(channel
, 'No citizen found linked to your nick. To link one type: @b.register_citizen <citizen name>@b')
411 return {'id': id, 'name': arg}
415 c
= citizen
.from_heapy(arg
) if id == None else citizen
.from_heapy(id, by_id
=True)
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
)
422 if not allow_org
and c
.is_organization
:
423 self
.errormsg(channel
, '@b%s@b is an organization, not a citizen.' % c
.name
)
428 def get_company(self
, opts
, arg
, channel
, sender
):
438 except ValueError, e
:
439 self
.errormsg(channel
, '%s is not a valid id.' % arg
)
443 id = self
.users
.get(nick
, 'company')
447 self
.msg(channel
, 'No company found linked to nick %s.' % arg
)
449 self
.msg(channel
, 'No company found linked to your nick. To link one type: @b.register_company <company id>@b')
454 return company
.from_id(id)
455 except feed
.FeedError
, e
:
456 self
.errormsg(channel
, e
.msg
)
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
)
467 arg
= cit
.country
['id']
471 c
= country
.from_id(arg
) if 'id' in opts
else country
.from_name(arg
)
473 self
.errormsg(channel
, '%s is not a valid id.' % arg
)
475 except feed
.FeedError
, e
:
476 self
.errormsg(channel
, e
.msg
)
480 self
.errormsg(channel
, 'country @b%s@b not found.' % arg
)
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
)
491 arg
= cit
.region
['id']
495 r
= region
.from_id(arg
) if 'id' in opts
else region
.from_name(arg
)
497 self
.errormsg(channel
, '%s is not a valid id.' % arg
)
499 except feed
.FeedError
, e
:
500 self
.errormsg(channel
, e
.msg
)
504 self
.errormsg(channel
, 'region @b%s@b not found.' % arg
)