1 #!/usr/bin/python pseudoserver.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
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
24 from api
import citizen
, region
, feed
25 import cmd_admin
, cmd_private
, cmd_user
, esimparser
, sys_auth
, esim_users
, esim_channels
27 class esim(AcidPlugin
):
28 def bind_function(self
, function
):
29 func
= types
.MethodType(function
, self
, esim
)
30 setattr(esim
, function
.__name
__, func
)
33 def bind_admin_commands(self
):
34 list = cmd_admin
.get_commands()
35 self
.commands_admin
= []
38 self
.commands_admin
.append((command
, {'permission': 'e', 'callback': self.bind_function(list[command][0]), 'usage': list[command][1]}
))
41 AcidPlugin
.__init
__(self
)
44 self
.log
= logging
.getLogger(__name__
)
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
)
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
)
58 self
.bind_admin_commands()
60 def start_threads(self
):
65 self
.antiflood
.start()
68 self
.initialized
= False
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
)
79 self
.elog
.debug('Started core subsystems.')
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
)
90 self
.elog
.debug('Started subsystems.')
92 for channel
in self
.channels
.list_valid():
93 self
.join(channel
.name
)
95 self
.log
.debug('Joined channels.')
99 except Exception, err
:
100 self
.log
.exception('Error starting threads for esim module (%s)' % err
)
103 self
.initialized
= True
105 self
.elog
.debug('Started threads.')
109 for channel
in self
.channels
.list_valid():
110 self
.join(channel
.name
)
112 self
.elog
.debug('Joined channels.')
115 if hasattr(self
, 'antiflood'):
116 self
.antiflood
.stop()
118 if hasattr(self
, 'auth'):
121 if hasattr(self
, 'users'):
126 self
.users
.db_close()
128 if hasattr(self
, 'channels'):
130 self
.channels
.force()
133 self
.channels
.db_close()
135 if hasattr(self
, 'options'):
140 self
.options
.db_close()
142 def join(self
, channel
):
143 me
= self
.inter
.findUser(self
.nick
)
146 def part(self
, channel
):
147 me
= self
.inter
.findUser(self
.nick
)
150 def errormsg(self
, target
, message
):
151 self
.msg(target
, '@b@c4Error:@o %s' % message
)
153 def usagemsg(self
, target
, description
, examples
):
154 message
= '@errsep @bUsage@b %s @errsep' % description
157 message
+= ' @bExamples@b %s @errsep' % ', '.join(examples
)
159 self
.msg(target
, message
)
161 def msg(self
, target
, message
):
163 self
.inter
.privmsg(self
.nick
, target
, format_ascii_irc(message
))
165 def multimsg(self
, target
, count
, intro
, separator
, pieces
, outro
= ''):
168 while cur
< len(pieces
):
169 self
.msg(target
, intro
+ separator
.join(pieces
[cur
:cur
+ count
]) + outro
)
172 def multinotice(self
, target
, count
, intro
, separator
, pieces
, outro
= ''):
175 while cur
< len(pieces
):
176 self
.notice(target
, intro
+ separator
.join(pieces
[cur
:cur
+ count
]) + outro
)
179 def notice(self
, target
, message
):
181 self
.inter
.notice(self
.nick
, target
, format_ascii_irc(message
))
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
)
188 self
.msg(channel
, manager
.invalid
)
189 self
.elog
.debug('Parsed command @b%s@b: invalid command.' % full_command
)
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
196 if user
.ban_reason
!= None:
197 message
+= ' Reason: @b%s@b.' % user
.ban_reason
199 if user
.ban_expiry
!= None:
200 message
+= ' Expires: @b%s@b.' % datetime
.fromtimestamp(user
.ban_expiry
)
202 self
.notice(sender
, message
)
203 self
.elog
.debug('Parsed command @b%s@b: user is banned.' % full_command
)
206 self
.elog
.command('%s%s > %s' % (sender
, ':%s' % channel
if channel
!= sender
else '', full_command
))
208 parser
= esimparser
.ESimParser(add_help_option
= False, option_class
= esimparser
.ESimParserOption
)
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])
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.
221 self
.elog
.debug('Parsed command @b%s@b: invalid options.' % full_command
)
224 if popts
.help == True:
225 manager
.commands
['help'][0](self
, manager
, {}, command
, channel
, sender
)
227 self
.elog
.debug('Parsed command @b%s@b: help intercepted.' % full_command
)
231 larg
= ' '.join(pargs
).strip()
234 for cmd_arg
in cmd_args
:
235 parg
= getattr(popts
, cmd_arg
[0])
238 if len(cmd_arg
) <= 4 or not (cmd_arg
[4] & cmd_manager
.ARG_OFFLINE
):
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])
244 self
.elog
.debug('Parsed command @b%s@b: option constraint was broken.' % full_command
)
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
):
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
)
254 self
.elog
.debug('Parsed command @b%s@b: offline.' % full_command
)
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'))
261 cmd
[0](self
, manager
, opt_dict
, larg
, channel
, sender
)
263 tb
= traceback
.extract_tb(sys
.exc_info()[2])
267 length
= len(entry
[2])
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!")
276 self
.elog
.traceback('@b%-*s@b : %d %s' % (longest
, entry
[2], entry
[1], entry
[3]))
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.')
281 self
.elog
.debug('Parsed command @b%s@b: execution terminated.' % full_command
)
283 def onChanModes(self
, prefix
, channel
, modes
):
284 if not self
.initialized
:
287 if not modes
== '-z':
290 if channel
in self
.channels
:
291 self
.channels
.remove(channel
)
292 self
.elog
.request('Channel @b%s@b was dropped. Deleting it.' % channel
)
294 def onNotice(self
, source
, target
, message
):
295 if not self
.initialized
:
298 me
= self
.inter
.findUser(self
.nick
)
299 user
= self
.inter
.findUser(target
)
300 userinfo
= self
.inter
.findUser(source
)
302 if me
!= user
or (userinfo
!= None and userinfo
['nick'] != 'ChanServ'):
306 msg
= message
.strip()
310 if msg
.startswith('[#'): #It's a channel welcome message. Let's ignore it.
313 self
.elog
.chanserv('%s' % msg
)
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
))
324 if "isn't registered" in msg
:
325 self
.auth
.reject_not_registered(strip_ascii_irc(sp
[1]))
331 if 'inviting' in sp
[2]: #It's an invite notice. Let's ignore it.
334 nick
= strip_ascii_irc(sp
[0])
335 channel
= sp
[5][0:len(sp
[5]) - 1]
337 if 'Founder' in sp
[2]:
338 self
.auth
.accept(nick
)
340 self
.auth
.reject_not_founder(nick
, channel
)
342 def onPrivmsg(self
, source
, target
, message
):
343 if not self
.initialized
:
346 userinfo
= self
.inter
.findUser(source
)
347 myself
= self
.inter
.findUser(self
.nick
)
349 sender
= userinfo
['nick']
351 msg
= message
.strip()
352 index
= msg
.find(' ')
358 command
= msg
[:index
]
359 argument
= msg
[index
+ 1:]
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
)
370 t
= threading
.Thread(target
=self
.execute
, args
=targs
)
374 def getCommands(self
):
375 return self
.commands_admin
377 def get_citizen(self
, opts
, arg
, channel
, sender
, allow_org
=True):
388 except ValueError, e
:
389 self
.errormsg(channel
, '%s is not a valid id.' % arg
)
393 id = self
.users
.get(nick
, 'citizen')
395 if id == None and nick
!= None:
397 self
.msg(channel
, 'No citizen found linked to nick %s.' % arg
)
399 self
.msg(channel
, 'No citizen found linked to your nick. To link one type: @b.register_citizen <citizen name>@b')
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
)
411 if not allow_org
and c
.is_organization
:
412 self
.errormsg(channel
, '@b%s@b is an organization, not a citizen.' % c
.name
)
417 def get_region(self
, opts
, arg
, channel
, sender
):
419 #secura = True if 'secura' in opts else False
420 r
= region
.from_id(arg
) if 'id' in opts
else region
.from_name(arg
)
422 self
.errormsg(channel
, '%s is not a valid id.' % arg
)
424 except feed
.FeedError
, e
:
425 self
.errormsg(channel
, e
.msg
)
429 self
.errormsg(channel
, 'region @b%s@b not found.' % arg
)