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