代码预览时跳过空行
[gaetalk.git] / gaetalk.py
blob7d2a339b69a0f950d65596545b6b99d6011d1761
1 #!/usr/bin/env python2
2 # vim:fileencoding=utf-8
4 import re
5 import logging
6 import datetime
8 from google.appengine.ext import db
9 from google.appengine.api import xmpp
11 import utils
12 import config
14 helpre = re.compile(r'^\W{0,2}help$')
16 #用户所有资源离线时,会加上“完全”二字
17 OFFLINE = u'离线'
18 AWAY = u'离开'
19 XAWAY = u'离开'
20 BUSY = u'忙碌'
21 ONLINE = u'在线'
22 CHAT = u'和我说话吧'
24 NEW = u'加入'
25 LEAVE = u'退出'
26 NICK = u'昵称更改 (%s -> %s)'
27 SNOOZE = u'snooze %ds'
28 BLACK = u'禁言 %s %ds'
29 BLACK_AUTO = u'被禁言 %ds'
30 KICK = u'删除 %s (%s)'
31 ADMIN = u'%s 成为管理员 (by %s)'
32 UNADMIN = u'%s 不再是管理员 (by %s)'
33 NOTICE = u'通告:%s'
34 BLOCK = u'封禁 %s,原因:%s'
35 UNBLOCK = u'解封 %s'
37 STATUS_CODE = {
38 '': ONLINE,
39 'away': AWAY,
40 'dnd': BUSY,
41 'xa': XAWAY,
42 'chat': CHAT,
45 #状态的排序顺序
46 STATUS_LIST = [CHAT, ONLINE, AWAY, XAWAY, BUSY, OFFLINE]
48 timezone = datetime.timedelta(hours=config.timezoneoffset)
50 class User(db.Model):
51 jid = db.StringProperty(required=True, indexed=True)
52 nick = db.StringProperty(required=True, indexed=True)
54 add_date = db.DateTimeProperty(auto_now_add=True)
55 last_online_date = db.DateTimeProperty()
56 last_offline_date = db.DateTimeProperty()
57 last_speak_date = db.DateTimeProperty()
59 msg_count = db.IntegerProperty(required=True, default=0)
60 msg_chars = db.IntegerProperty(required=True, default=0)
61 credit = db.IntegerProperty(required=True, default=0)
63 black_before = db.DateTimeProperty(auto_now_add=True)
64 snooze_before = db.DateTimeProperty()
65 flooding_point = db.IntegerProperty(default=0)
67 avail = db.StringProperty(required=True)
68 is_admin = db.BooleanProperty(required=True, default=False)
69 resources = db.StringListProperty(required=True)
70 #允许他人私信?
71 reject_pm = db.BooleanProperty(default=False)
73 prefix = db.StringProperty(required=True, default=config.default_prefix)
74 nick_pattern = db.StringProperty(required=True, default='[%s]')
75 nick_changed = db.BooleanProperty(required=True, default=False)
76 intro = db.StringProperty()
78 class Log(db.Model):
79 time = db.DateTimeProperty(auto_now_add=True, indexed=True)
80 msg = db.StringProperty(required=True, multiline=True)
81 jid = db.StringProperty()
82 nick = db.StringProperty()
83 type = db.StringProperty(required=True, indexed=True,
84 choices=set(['chat', 'member', 'admin', 'misc']))
86 class Group(db.Model):
87 time = db.DateTimeProperty(auto_now_add=True)
88 topic = db.StringProperty(multiline=True)
89 status = db.StringProperty()
91 class BlockedUser(db.Model):
92 jid = db.StringProperty(required=True, indexed=True)
93 add_date = db.DateTimeProperty(auto_now_add=True)
94 reason = db.StringProperty()
96 def log_msg(sender, msg):
97 l = Log(jid=sender.jid, nick=sender.nick,
98 type='chat', msg=msg)
99 l.put()
101 def log_onoff(sender, action, resource=''):
102 if resource:
103 if action == OFFLINE and not sender.resources:
104 msg = u'完全%s (%s)' % (action, resource)
105 else:
106 msg = '%s (%s)' % (action, resource)
107 else:
108 msg = action
109 l = Log(jid=sender.jid, nick=sender.nick,
110 type='member', msg=msg)
111 l.put()
113 def log_admin(sender, action):
114 l = Log(jid=sender.jid, nick=sender.nick,
115 type='admin', msg=action)
116 l.put()
118 def get_user_by_jid(jid):
119 return User.gql('where jid = :1', jid.lower()).get()
121 def get_user_by_nick(nick):
122 return User.gql('where nick = :1', nick).get()
124 def get_group_info():
125 return Group.all().get()
127 def get_blocked_user(jid):
128 return BlockedUser.gql('where jid = :1', jid.lower()).get()
130 def get_member_list():
131 now = datetime.datetime.now()
132 #一个查询中最多只能有一个不等比较
133 l = User.gql('where avail != :1', OFFLINE)
134 return [unicode(x.jid) for x in l \
135 if x.snooze_before is None or x.snooze_before < now]
137 def send_to_all_except(jid, message):
138 if isinstance(jid, str):
139 jids = [x for x in get_member_list() if x != jid]
140 else:
141 jids = [x for x in get_member_list() if x not in jid]
142 logging.debug(jid)
143 logging.debug(jids)
144 try:
145 xmpp.send_message(jids, message)
146 except xmpp.InvalidJidError:
147 pass
149 def send_to_all(message):
150 jids = get_member_list()
151 xmpp.send_message(jids, message)
153 def send_status(jid):
154 if get_blocked_user(jid):
155 xmpp.send_presence(jid, status=u'您已经被本群封禁')
156 return
158 grp = get_group_info()
159 if grp is None or not grp.status:
160 xmpp.send_presence(jid)
161 else:
162 xmpp.send_presence(jid, status=grp.status)
164 def handle_message(msg):
165 jid = msg.sender.split('/')[0]
167 sender = get_blocked_user(jid)
168 if sender is not None:
169 msg.reply(u'您已经被本群封禁,原因为 %s。' % sender.reason)
170 return
172 sender = get_user_by_jid(jid)
173 if sender is None:
174 msg.reply('很抱歉,出错了,请尝试更改状态或者重新添加好友。')
175 return
177 if msg.body.startswith('?OTR:'):
178 msg.reply('不支持 OTR 加密!')
179 return
181 if msg.body in config.blocked_away_messages:
182 msg.reply('系统认为您的客户端正在自动发送离开消息。如果您认为这并不正确,请向管理员反馈。')
183 return
185 if len(msg.body) > 500 or msg.body.count('\n') > 5:
186 msgbody = config.post_code(msg.body)
187 if msgbody:
188 msgbody += '/text' # 默认当作纯文本高亮
189 msg.reply(u'内容过长,已贴至 %s 。' % msgbody)
190 firstline = ''
191 lineiter = iter(msg.body.split('\n'))
192 try:
193 while not firstline:
194 firstline = lineiter.next()
195 except StopIteration:
196 pass
197 if len(firstline) > 40:
198 firstline = firstline[:40]
199 msgbody += '\n' + firstline + '...'
200 else:
201 logging.warn(u'贴代码失败,代码长度 %d' % len(msg.body))
202 msg.reply('由于技术限制,每条消息最长为 500 字。大段文本请贴 paste 网站。\n'
203 '如 http://paste.ubuntu.org.cn/ http://slexy.org/\n'
205 return
206 ch = None
207 else:
208 msgbody = msg.body
209 if sender.is_admin or sender.jid == config.root:
210 ch = AdminCommand(msg, sender)
211 else:
212 ch = BasicCommand(msg, sender)
214 if not ch or not ch.handled:
215 now = datetime.datetime.now()
216 if sender.black_before is not None \
217 and sender.black_before > now:
218 if (datetime.datetime.today()+timezone).date() == \
219 (sender.black_before+timezone).date():
220 format = '%H时%M分%S秒'
221 else:
222 format = '%m月%d日 %H时%M分%S秒'
223 msg.reply('你已被禁言至 ' \
224 + (sender.black_before+timezone).strftime(format))
225 return
227 sender.last_speak_date = now
228 sender.snooze_before = None
229 try:
230 sender.msg_count += 1
231 sender.msg_chars += len(msgbody)
232 except TypeError:
233 sender.msg_count = 1
234 sender.msg_chars = len(msgbody)
235 sender.put()
236 body = utils.removelinks(msgbody)
237 for u in User.gql('where avail != :1', OFFLINE):
238 if u.snooze_before is not None and u.snooze_before >= now:
239 continue
240 if u.jid == sender.jid:
241 continue
242 try:
243 message = '%s %s' % (
244 u.nick_pattern % sender.nick,
245 body
247 xmpp.send_message(u.jid, message)
248 except xmpp.InvalidJidError:
249 pass
250 log_msg(sender, msgbody)
252 def try_add_user(jid, show=OFFLINE, resource=''):
253 '''使用 memcache 作为锁添加用户'''
254 u = get_blocked_user(jid)
255 if u is not None:
256 xmpp.send_message(jid, u'您已经被本群封禁,原因为 %s。' % u.reason)
257 return
259 L = utils.MemLock('add_user')
260 L.require()
261 try:
262 u = get_user_by_jid(jid)
263 if u is not None:
264 return
265 u = add_user(jid, show, resource)
266 finally:
267 L.release()
268 if show != OFFLINE:
269 log_onoff(u, show, resource)
270 logging.info(u'%s added', jid)
272 def add_user(jid, show=OFFLINE, resource=''):
273 '''resource 在 presence type 为 available 里使用'''
274 nick = jid.split('@')[0]
275 # same user name with different domains are possible
276 while get_user_by_nick(nick):
277 nick += '_'
278 u = User(jid=jid.lower(), avail=show, nick=nick)
279 if show != OFFLINE:
280 u.last_online_date = datetime.datetime.now()
281 if resource:
282 u.resources.append(resource)
283 u.put()
284 log_onoff(u, NEW)
285 logging.info(u'%s 已经加入' % jid)
286 send_status(jid)
287 xmpp.send_message(jid, u'欢迎 %s 加入!要获取使用帮助,请输入 %shelp,要获知群主题,请输入 %stopic。' % (
288 u.nick, u.prefix, u.prefix))
289 return u
291 def del_user(jid, by_cmd=False):
292 l = User.gql('where jid = :1', jid.lower())
293 for u in l:
294 if u.jid == config.root:
295 xmpp.send_message(jid, u'root 用户:离开前请确定你已做好善后工作!')
296 log_onoff(u, LEAVE)
297 if by_cmd:
298 logging.info(u'%s (%s) 已经离开 (通过使用命令)' % (u.nick, u.jid))
299 else:
300 logging.info(u'%s (%s) 已经离开' % (u.nick, u.jid))
301 u.delete()
303 class BasicCommand:
304 handled = True
305 def __init__(self, msg, sender):
306 self.sender = sender
307 self.msg = msg
309 if helpre.match(msg.body):
310 self.do_help()
311 elif msg.body.startswith(sender.prefix):
312 cmd = msg.body[len(sender.prefix):].split()
313 try:
314 handle = getattr(self, 'do_' + cmd[0])
315 except AttributeError:
316 msg.reply(u'错误:未知命令 %s' % cmd[0])
317 except IndexError:
318 msg.reply(u'错误:无命令')
319 except UnicodeEncodeError:
320 msg.reply(u'错误:命令名解码失败。此问题在 GAE 升级其 Python 到 3.x 后方能解决。')
321 else:
322 handle(cmd[1:])
323 logging.debug('%s did command %s' % (sender.jid, cmd[0]))
324 else:
325 self.handled = False
327 def get_msg_part(self, index):
328 '''返回消息中第 index 个单词及其后的所有字符'''
329 return self.msg.body[len(self.sender.prefix):].split(None, index)[-1]
331 def do_online(self, args):
332 '''在线成员列表。可带一个参数,指定在名字中出现的一个子串。'''
333 r = []
334 pat = args[0] if args else None
335 now = datetime.datetime.now()
336 l = User.gql('where avail != :1', OFFLINE)
337 for u in l:
338 m = u.nick
339 if pat and m.find(pat) == -1:
340 continue
341 status = u.avail
342 if status != ONLINE:
343 m += u' (%s)' % status
344 if u.snooze_before is not None and u.snooze_before > now:
345 m += u' (snoozing)'
346 if u.black_before is not None and u.black_before > now:
347 m += u' (已禁言)'
348 r.append(unicode('* ' + m))
349 r.sort()
350 n = len(r)
351 if pat:
352 r.insert(0, u'在线成员列表(包含子串 %s):' % pat)
353 r.append(u'共 %d 人。' % n)
354 else:
355 r.insert(0, u'在线成员列表:')
356 r.append(u'共 %d 人在线。' % n)
357 self.msg.reply(u'\n'.join(r).encode('utf-8'))
359 def do_lsadmin(self, args):
360 '''管理员列表'''
361 r = []
362 now = datetime.datetime.now()
363 l = User.gql('where is_admin = :1', True)
364 for u in l:
365 m = u.nick
366 status = u.avail
367 if status != ONLINE:
368 m += u' (%s)' % status
369 if u.snooze_before is not None and u.snooze_before > now:
370 m += u' (snoozing)'
371 if u.black_before is not None and u.black_before > now:
372 m += u' (已禁言)'
373 r.append(unicode('* ' + m))
374 r.sort()
375 n = len(r)
376 r.insert(0, u'管理员列表:')
377 r.append(u'共 %d 位管理员。' % n)
378 self.msg.reply(u'\n'.join(r).encode('utf-8'))
380 def do_lsblocked(self, args):
381 '''列出被封禁用户名单'''
382 r = []
383 l = BlockedUser.all()
384 for u in l:
385 r.append(unicode('* %s (%s, %s)' % (u.jid,
386 utils.strftime(u.add_date, timezone),
387 u.reason)
390 r.sort()
391 n = len(r)
392 r.insert(0, u'封禁列表:')
393 r.append(u'共 %d 个 JID 被封禁。' % n)
394 self.msg.reply(u'\n'.join(r).encode('utf-8'))
396 def do_chatty(self, args):
397 '''消息数排行'''
398 r = []
399 for u in User.gql('ORDER BY msg_count ASC'):
400 m = u.nick
401 m = u'* %s:\t%5d条,共 %s' % (
402 u.nick, u.msg_count,
403 utils.filesize(u.msg_chars))
404 r.append(m)
405 n = len(r)
406 r.insert(0, u'消息数量排行:')
407 r.append(u'共 %d 人。' % n)
408 self.msg.reply(u'\n'.join(r).encode('utf-8'))
410 def do_nick(self, args):
411 '''更改昵称,需要一个参数,不能使用大部分标点符号'''
412 if len(args) != 1:
413 self.msg.reply('错误:请给出你想到的昵称(不能包含空格)')
414 return
416 q = get_user_by_nick(args[0])
417 if q is not None:
418 self.msg.reply('错误:该昵称已被使用,请使用其它昵称')
419 elif not utils.checkNick(args[0]):
420 self.msg.reply('错误:非法的昵称')
421 else:
422 if not config.nick_can_change and self.sender.nick_changed:
423 self.msg.reply('乖哦,你已经没机会再改昵称了')
424 return
425 old_nick = self.sender.nick
426 log_onoff(self.sender, NICK % (old_nick, args[0]))
427 self.sender.nick = args[0]
428 self.sender.nick_changed = True
429 self.sender.put()
430 send_to_all_except(self.sender.jid,
431 (u'%s 的昵称改成了 %s' % (old_nick, args[0])).encode('utf-8'))
432 self.msg.reply('昵称更改成功!')
433 do_nick.__doc__ += ',最长 %d 字节' % config.nick_maxlen
435 def do_whois(self, args):
436 '''查看用户信息,参数为用户昵称'''
437 if len(args) != 1:
438 self.msg.reply('错误:你想知道关于谁的信息?')
439 return
441 u = get_user_by_nick(args[0])
442 if u is None:
443 self.msg.reply(u'Sorry,查无此人。')
444 return
446 now = datetime.datetime.now()
447 status = u.avail
448 addtime = (u.add_date + timezone).strftime('%Y年%m月%d日 %H时%M分').decode('utf-8')
449 allowpm = u'否' if u.reject_pm else u'是'
450 if u.snooze_before is not None and u.snooze_before > now:
451 status += u' (snoozing)'
452 if u.black_before is not None and u.black_before > now:
453 status += u' (已禁言)'
454 r = []
455 r.append(u'昵称:\t%s' % u.nick)
456 if self.sender.is_admin:
457 r.append(u'JID:\t%s' % u.jid)
458 r.append(u'状态:\t%s' % status)
459 r.append(u'消息数:\t%d' % u.msg_count)
460 r.append(u'消息总量:\t%s' % utils.filesize(u.msg_chars))
461 r.append(u'加入时间:\t%s' % addtime)
462 r.append(u'接收私信:\t%s' % allowpm)
463 r.append(u'自我介绍:\t%s' % u.intro)
464 self.msg.reply(u'\n'.join(r).encode('utf-8'))
466 def do_help(self, args=()):
467 '''显示本帮助。参数 long 显示详细帮助,也可指定命令名。'''
468 doc = []
469 prefix = self.sender.prefix
471 if len(args) > 1:
472 self.msg.reply('参数错误。')
473 return
474 arg = args[0] if args else None
476 if arg is None or arg == 'long':
477 for b in self.__class__.__bases__ + (self.__class__,):
478 for c, f in b.__dict__.items():
479 if c.startswith('do_'):
480 if arg is None:
481 doc.append(u'%s%s:\t%s' % (prefix, c[3:], f.__doc__.decode('utf-8').\
482 split(u',', 1)[0].split(u'。', 1)[0]))
483 else:
484 doc.append(u'%s%s:\t%s' % (prefix, c[3:], f.__doc__.decode('utf-8')))
485 doc.sort()
486 if arg is None:
487 doc.insert(0, u'** 命令指南 **\n(当前命令前缀 %s,可设置。使用 %shelp long 显示详细帮助)' % (prefix, prefix))
488 else:
489 doc.insert(0, u'** 命令指南 **\n(当前命令前缀 %s,可设置)' % prefix)
490 doc.append(u'要离开,直接删掉好友即可。')
491 doc.append(u'Gtalk 客户端用户要离开请使用 quit 命令。')
492 self.msg.reply(u'\n'.join(doc).encode('utf-8'))
493 else:
494 try:
495 handle = getattr(self, 'do_' + arg)
496 except AttributeError:
497 self.msg.reply(u'错误:未知命令 %s' % arg)
498 except UnicodeEncodeError:
499 self.msg.reply(u'错误:命令名解码失败。此问题在 GAE 升级其 Python 到 3.x 后方能解决。')
500 else:
501 self.msg.reply(u'%s%s:\t%s' % (prefix, arg, handle.__doc__.decode('utf-8')))
503 def do_topic(self, args=()):
504 '''查看群主题'''
505 grp = get_group_info()
506 if grp is None or not grp.topic:
507 self.msg.reply(u'没有设置群主题。')
508 else:
509 self.msg.reply(grp.topic)
511 def do_iam(self, args):
512 '''查看自己的信息'''
513 u = self.sender
514 addtime = (u.add_date + timezone).strftime('%Y年%m月%d日 %H时%M分').decode('utf-8')
515 allowpm = u'否' if u.reject_pm else u'是'
516 r = []
517 r.append(u'昵称:\t%s' % u.nick)
518 r.append(u'JID:\t%s' % u.jid)
519 r.append(u'资源:\t%s' % u' '.join(u.resources))
520 r.append(u'消息数:\t%d' % u.msg_count)
521 r.append(u'消息总量:\t%s' % utils.filesize(u.msg_chars))
522 r.append(u'加入时间:\t%s' % addtime)
523 r.append(u'命令前缀:\t%s' % u.prefix)
524 r.append(u'接收私信:\t%s' % allowpm)
525 r.append(u'自我介绍:\t%s' % u.intro)
526 self.msg.reply(u'\n'.join(r).encode('utf-8'))
528 def do_m(self, args):
529 '''发私信,需要昵称和内容两个参数。私信不会以任何方式被记录。用户可使用 set 命令设置是否接收私信。'''
530 if len(args) < 2:
531 self.msg.reply('请给出昵称和内容。')
532 return
534 target = get_user_by_nick(args[0])
535 if target is None:
536 self.msg.reply('Sorry,查无此人。')
537 return
539 if target.reject_pm:
540 self.msg.reply('很抱歉,对方不接收私信。')
541 return
543 msg = self.get_msg_part(2)
544 msg = u'_私信_ %s %s' % (target.nick_pattern % self.sender.nick, msg)
545 xmpp.send_message(target.jid, msg)
547 def do_intro(self, arg):
548 '''设置自我介绍信息'''
549 if not arg:
550 self.msg.reply('请给出自我介绍的内容。')
551 return
553 msg = self.get_msg_part(1)
554 u = self.sender
555 try:
556 u.intro = msg
557 except db.BadValueError:
558 # 过长文本已在 handle_message 中被拦截
559 self.msg.reply('错误:自我介绍内容只能为一行。')
560 return
562 u.put()
563 self.msg.reply(u'设置成功!')
565 def do_snooze(self, args):
566 '''暂停接收消息,参数为时间(默认单位为秒)。再次发送消息时自动清除'''
567 if len(args) != 1:
568 self.msg.reply('你想停止接收消息多久?')
569 return
570 else:
571 try:
572 n = utils.parseTime(args[0])
573 except ValueError:
574 self.msg.reply('Sorry,我无法理解你说的时间。')
575 return
577 try:
578 self.sender.snooze_before = datetime.datetime.now() + datetime.timedelta(seconds=n)
579 except OverflowError:
580 self.msg.reply('Sorry,你不能睡太久。')
581 return
582 self.sender.put()
583 if n == 0:
584 self.msg.reply('你已经醒来。')
585 else:
586 self.msg.reply(u'OK,停止接收消息 %s。' % utils.displayTime(n))
587 log_onoff(self.sender, SNOOZE % n)
589 def do_offline(self, args):
590 '''假装离线,让程序认为你的所有资源已离线。如在你离线时程序仍认为你在线,请使用此命令。'''
591 del self.sender.resources[:]
592 self.sender.avail = OFFLINE
593 self.sender.last_offline_date = datetime.datetime.now()
594 self.sender.put()
595 self.msg.reply('OK,在下次你说你在线之前我都认为你已离线。')
597 def do_fakeresource(self, args):
598 '''假装在线,人工加入一个新的资源,使程序认为你总是在线。使用 off 参数来取消。'''
599 if args and args[0] == 'off':
600 try:
601 self.sender.resources.remove('fakeresouce')
602 self.msg.reply('OK,fakeresouce 已取消。')
603 self.sender.put()
604 except ValueError:
605 self.msg.reply('没有设置 fakeresouce。')
606 else:
607 try:
608 self.sender.resources.index('fakeresouce')
609 self.msg.reply('你已经设置了永远在线。')
610 except ValueError:
611 self.sender.resources.append('fakeresouce')
612 self.msg.reply('OK,你将永远在线。')
613 self.sender.put()
615 def do_old(self, args):
616 '''聊天记录查询,可选一个数字参数。默认为最后20条。特殊参数 OFFLINE (不区分大小写)显示离线消息(最多 100 条)'''
617 s = self.sender
618 q = False
619 if not args:
620 q = Log.gql("WHERE type = 'chat' ORDER BY time DESC LIMIT 20")
621 elif len(args) == 1:
622 try:
623 n = int(args[0])
624 if n > 0:
625 q = Log.gql("WHERE type = 'chat' ORDER BY time DESC LIMIT %d" % n)
626 except ValueError:
627 if args[0].upper() == 'OFFLINE':
628 q = Log.gql("WHERE time < :1 AND time > :2 AND type = 'chat' ORDER BY time DESC LIMIT 100", s.last_online_date, s.last_offline_date)
629 else:
630 pass
631 if q is not False:
632 r = []
633 q = list(q)
634 q.reverse()
635 if q:
636 if datetime.datetime.today() - q[0].time > datetime.timedelta(hours=24):
637 show_date = True
638 else:
639 show_date = False
640 for l in q:
641 message = '%s %s %s' % (
642 utils.strftime(l.time, timezone, show_date),
643 s.nick_pattern % l.nick,
644 l.msg
646 r.append(message)
647 if r:
648 self.msg.reply(u'\n'.join(r).encode('utf-8'))
649 else:
650 self.msg.reply('没有符合的聊天记录。')
651 else:
652 self.msg.reply('Oops, 参数不正确哦。')
654 def do_set(self, args):
655 '''设置参数。参数格式 key=value;不带参数以查看说明。'''
656 #注意:选项名/值中不能包含空格
657 if len(args) != 1:
658 doc = []
659 for c, f in self.__class__.__dict__.items():
660 if c.startswith('set_'):
661 doc.append(u'* %s:\t%s' % (c[4:], f.__doc__.decode('utf-8')))
662 for b in self.__class__.__bases__:
663 for c, f in b.__dict__.items():
664 if c.startswith('set_'):
665 doc.append(u'* %s:\t%s' % (c[4:], f.__doc__.decode('utf-8')))
666 doc.sort()
667 doc.insert(0, u'设置选项:')
668 self.msg.reply(u'\n'.join(doc).encode('utf-8'))
669 else:
670 msg = self.msg
671 cmd = args[0].split('=', 1)
672 if len(cmd) == 1 or cmd[1] == '':
673 msg.reply(u'错误:请给出选项值')
674 return
675 try:
676 handle = getattr(self, 'set_' + cmd[0])
677 except AttributeError:
678 msg.reply(u'错误:未知选项 %s' % cmd[0])
679 except IndexError:
680 msg.reply(u'错误:无选项')
681 except UnicodeEncodeError:
682 msg.reply(u'错误:选项名解码失败。此问题在 GAE 升级其 Python 到 3.x 后方能解决。')
683 else:
684 handle(cmd[1])
686 def do_quit(self, args):
687 '''删除用户数据。某些自称“不作恶”的公司的客户端会不按协议要求发送删除好友的消息,请 gtalk 官方客户端用户使用此命令退出。参见 http://xmpp.org/rfcs/rfc3921.html#rfc.section.6.3 。'''
688 del_user(self.sender.jid)
689 self.msg.reply(u'OK.')
691 def set_prefix(self, arg):
692 '''设置命令前缀'''
693 self.sender.prefix = arg
694 self.sender.put()
695 self.msg.reply(u'设置成功!')
697 def set_nickpattern(self, arg):
698 '''设置昵称显示格式,用 %s 表示昵称的位置'''
699 try:
700 arg % 'test'
701 except (TypeError, ValueError):
702 self.msg.reply(u'错误:不正确的格式')
703 return
705 self.sender.nick_pattern = arg
706 self.sender.put()
707 self.msg.reply(u'设置成功!')
709 def set_allowpm(self, arg):
710 '''设置是否接收私信,参数为 y(接收)或者 n(拒绝)'''
711 if arg not in 'yn':
712 self.msg.reply(u'错误的参数。')
713 return
715 if arg == 'y':
716 self.sender.reject_pm = False
717 else:
718 self.sender.reject_pm = True
719 self.sender.put()
720 self.msg.reply(u'设置成功!')
722 class AdminCommand(BasicCommand):
723 def do_kick(self, args):
724 '''删除某人。他仍可以重新加入。'''
725 if len(args) != 1:
726 self.msg.reply('请给出昵称。')
727 return
729 target = get_user_by_nick(args[0])
730 if target is None:
731 self.msg.reply('Sorry,查无此人。')
732 return
734 if target.jid == config.root:
735 self.msg.reply('不能删除 root 用户')
736 return
738 targetjid = target.jid
739 targetnick = target.nick
740 target.delete()
741 self.msg.reply((u'OK,删除 %s。' % target.nick).encode('utf-8'))
742 send_to_all_except(self.sender.jid, (u'%s 已被删除。' % target.nick) \
743 .encode('utf-8'))
744 xmpp.send_message(targetjid, u'你已被管理员从此群中删除,请删除该好友。')
745 log_admin(self.sender, KICK % (targetnick, targetjid))
747 def do_quiet(self, args):
748 '''禁言某人,参数为昵称和时间(默认单位秒)'''
749 if len(args) != 2:
750 self.msg.reply('请给出昵称和时间。')
751 return
752 else:
753 try:
754 n = utils.parseTime(args[1])
755 except ValueError:
756 self.msg.reply('Sorry,我无法理解你说的时间。')
757 return
759 target = get_user_by_nick(args[0])
760 if target is None:
761 self.msg.reply('Sorry,查无此人。')
762 return
764 target.black_before = datetime.datetime.now() + datetime.timedelta(seconds=n)
765 target.put()
766 self.msg.reply(u'OK,禁言 %s %s。' % (target.nick, utils.displayTime(n)))
767 send_to_all_except((self.sender.jid, target.jid),
768 (u'%s 已被禁言 %s。' % (target.nick, utils.displayTime(n))) \
769 .encode('utf-8'))
770 xmpp.send_message(target.jid, u'你已被管理员禁言 %s。' % utils.displayTime(n))
771 log_admin(self.sender, BLACK % (target.nick, n))
773 def do_notice(self, arg):
774 '''发送群通告。只会发给在线的人,包括 snoozing 者。'''
775 if not arg:
776 self.msg.reply('请给出群通告的内容。')
777 return
779 msg = self.msg.body[len(self.sender.prefix):].split(None, 1)[-1]
781 l = User.gql('where avail != :1', OFFLINE)
782 log_admin(self.sender, NOTICE % msg)
783 for u in l:
784 try:
785 xmpp.send_message(u.jid, u'通告:' + msg)
786 except xmpp.InvalidJidError:
787 pass
789 def do_topic(self, args=()):
790 '''查看或设置群主题'''
791 grp = get_group_info()
792 if not args:
793 if grp is None or not grp.topic:
794 self.msg.reply(u'没有设置群主题。')
795 else:
796 self.msg.reply(grp.topic)
797 else:
798 grp = get_group_info()
799 if grp is None:
800 grp = Group()
801 grp.topic = self.msg.body[len(self.sender.prefix):].split(None, 1)[-1]
802 grp.put()
803 self.msg.reply(u'群主题已更新。')
805 def do_admin(self, args):
806 '''将某人添加为管理员'''
807 if len(args) != 1:
808 self.msg.reply(u'请给出昵称。')
809 return
811 target = get_user_by_nick(args[0])
812 if target is None:
813 self.msg.reply(u'Sorry,查无此人。')
814 return
816 if target.is_admin:
817 self.msg.reply(u'%s 已经是管理员了。' % target.nick)
818 return
820 target.is_admin = True
821 target.put()
822 send_to_all_except(target.jid,
823 (u'%s 已成为管理员。' % target.nick) \
824 .encode('utf-8'))
825 xmpp.send_message(target.jid, u'你已是本群管理员。')
826 log_admin(self.sender, ADMIN % (target.nick, self.sender.nick))
828 def do_unadmin(self, args):
829 '''取消某人管理员的权限'''
830 if len(args) != 1:
831 self.msg.reply(u'请给出昵称。')
832 return
834 target = get_user_by_nick(args[0])
835 if target is None:
836 self.msg.reply(u'Sorry,查无此人。')
837 return
839 if not target.is_admin:
840 self.msg.reply(u'%s 不是管理员。' % target.nick)
841 return
843 target.is_admin = False
844 target.put()
845 send_to_all_except(target.jid,
846 (u'%s 已不再是管理员。' % target.nick) \
847 .encode('utf-8'))
848 xmpp.send_message(target.jid, u'你已不再是本群管理员。')
849 log_admin(self.sender, UNADMIN % (target.nick, self.sender.nick))
851 def do_block(self, args):
852 '''封禁某个 ID,参数为用户昵称或者 ID(如果不是已经加入的 ID 的话),以及封禁原因'''
853 if len(args) < 2:
854 self.msg.reply(u'请给出要封禁的用户和原因。')
855 return
857 target = get_user_by_nick(args[0])
858 reason = self.msg.body[len(self.sender.prefix):].split(None, 2)[-1]
859 if target is None:
860 jid = args[0]
861 name = jid
862 fullname = name
863 else:
864 jid = target.jid
865 name = target.nick
866 fullname = '%s (%s)' % (name, jid)
867 u = BlockedUser.gql('where jid = :1', jid).get()
868 if u is not None:
869 self.msg.reply(u'此 JID 已经被封禁。')
870 return
872 if jid == config.root:
873 self.msg.reply('不能封禁 root 用户')
874 return
876 if target:
877 target.delete()
878 u = BlockedUser(jid=jid, reason=reason)
879 u.put()
881 send_to_all_except(self.sender.jid,
882 (u'%s 已被本群封禁,理由为 %s。' % (name, reason)) \
883 .encode('utf-8'))
884 self.msg.reply(u'%s 已被本群封禁,理由为 %s。' % (fullname, reason))
885 xmpp.send_message(jid, u'你已被本群封禁,理由为 %s。' % reason)
886 xmpp.send_presence(jid, status=u'您已经被本群封禁')
887 log_admin(self.sender, BLOCK % (fullname, reason))
889 def do_unblock(self, args):
890 '''解封某个 ID'''
891 if len(args) != 1:
892 self.msg.reply(u'请给出要解封用户的 JID。')
893 return
895 target = get_blocked_user(args[0])
896 if target is None:
897 self.msg.reply(u'封禁列表中没有这个 JID。')
898 return
900 target.delete()
901 send_to_all((u'%s 已被解除封禁。' % args[0]) \
902 .encode('utf-8'))
903 log_admin(self.sender, UNBLOCK % args[0])
905 def do_groupstatus(self, arg):
906 '''设置群状态'''
907 grp = get_group_info()
908 if grp is None:
909 grp = Group()
910 grp.status = self.msg.body[len(self.sender.prefix):].split(None, 1)[-1]
911 grp.put()
912 for u in User.all():
913 xmpp.send_presence(u.jid, status=grp.status)
914 self.msg.reply(u'设置成功!')