昵称中允许使用 + 号
[gaetalk.git] / gaetalk.py
blob6aaf40da685c525f0ab6338232e008abb559f922
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 r = []
132 now = datetime.datetime.now()
133 #一个查询中最多只能有一个不等比较
134 l = User.gql('where avail != :1', OFFLINE)
135 for u in l:
136 r.append(u)
137 return [unicode(x.jid) for x in r \
138 if x.snooze_before is None or x.snooze_before < now]
140 def send_to_all_except(jid, message):
141 if isinstance(jid, str):
142 jids = [x for x in get_member_list() if x != jid]
143 else:
144 jids = [x for x in get_member_list() if x not in jid]
145 logging.debug(jid)
146 logging.debug(jids)
147 try:
148 xmpp.send_message(jids, message)
149 except xmpp.InvalidJidError:
150 pass
152 def send_to_all(message):
153 jids = get_member_list()
154 xmpp.send_message(jids, message)
156 def send_status(jid):
157 u = get_blocked_user(jid)
158 if u is not None:
159 xmpp.send_presence(jid, status=u'您已经被本群封禁')
160 return
162 grp = get_group_info()
163 if grp is None or not grp.status:
164 xmpp.send_presence(jid)
165 else:
166 xmpp.send_presence(jid, status=grp.status)
168 def handle_message(msg):
169 jid = msg.sender.split('/')[0]
170 sender = get_blocked_user(jid)
171 if sender is not None:
172 msg.reply(u'您已经被本群封禁,原因为 %s。' % sender.reason)
173 return
174 sender = get_user_by_jid(jid)
175 if sender is None:
176 msg.reply('很抱歉,出错了,请尝试更改状态或者重新添加好友。')
177 return
178 if msg.body.startswith('?OTR:'):
179 msg.reply('不支持 OTR 加密!')
180 return
181 if msg.body in config.blocked_away_messages:
182 msg.reply('系统认为您的客户端正在自动发送离开消息。如果您认为这并不正确,请向管理员反馈。')
183 return
184 if len(msg.body) > 500:
185 msg.reply('由于技术限制,每条消息最长为 500 字。大段文本请贴 paste 网站。\m'
186 '如 http://paste.ubuntu.org.cn/ http://slexy.org/')
187 return
188 if sender.is_admin or sender.jid == config.root:
189 ch = AdminCommand(msg, sender)
190 else:
191 ch = BasicCommand(msg, sender)
192 if not ch.handled:
193 now = datetime.datetime.now()
194 if sender.black_before is not None \
195 and sender.black_before > now:
196 if (datetime.datetime.today()+timezone).date() == \
197 (sender.black_before+timezone).date():
198 format = '%H时%M分%S秒'
199 else:
200 format = '%m月%d日 %H时%M分%S秒'
201 msg.reply('你已被禁言至 ' \
202 + (sender.black_before+timezone).strftime(format))
203 return
205 if sender.last_speak_date is not None:
206 d = now - sender.last_speak_date
207 t = d.seconds
208 if d.days > 0 or t > 60:
209 sender.flooding_point = 0
210 else:
211 k = 1000 / (t * t + 1)
212 if k > 0:
213 sender.flooding_point += k
214 else:
215 sender.flooding_point = 0
217 k = sender.flooding_point / 1500
218 if k > 0:
219 msg.reply('刷屏啊?禁言 %d 分钟!' % k)
220 send_to_all_except(sender.jid,
221 (u'%s 已因刷屏而被禁言 %d 分钟。' % (sender.nick, k)) \
222 .encode('utf-8'))
223 log_onoff(sender, BLACK_AUTO % (60 * k))
224 sender.black_before = now + datetime.timedelta(seconds=60*k)
225 sender.put()
226 return
228 sender.last_speak_date = now
229 sender.snooze_before = None
230 try:
231 sender.msg_count += 1
232 sender.msg_chars += len(msg.body)
233 except TypeError:
234 sender.msg_count = 1
235 sender.msg_chars = len(msg.body)
236 sender.put()
237 body = utils.removelinks(msg.body)
238 for u in User.gql('where avail != :1', OFFLINE):
239 if u.snooze_before is not None and u.snooze_before >= now:
240 continue
241 if u.jid == sender.jid:
242 continue
243 try:
244 message = '%s %s' % (
245 u.nick_pattern % sender.nick,
246 body
248 xmpp.send_message(u.jid, message)
249 except xmpp.InvalidJidError:
250 pass
251 log_msg(sender, msg.body)
253 def try_add_user(jid, show=OFFLINE, resource=''):
254 '''使用 memcache 作为锁添加用户'''
255 u = get_blocked_user(jid)
256 if u is not None:
257 xmpp.send_message(jid, u'您已经被本群封禁,原因为 %s。' % u.reason)
258 return
260 L = utils.MemLock('add_user')
261 L.require()
262 try:
263 u = get_user_by_jid(jid)
264 if u is not None:
265 return
266 u = add_user(jid, show, resource)
267 finally:
268 L.release()
269 if show != OFFLINE:
270 log_onoff(u, show, resource)
271 logging.info(u'%s added', jid)
273 def add_user(jid, show=OFFLINE, resource=''):
274 '''resource 在 presence type 为 available 里使用'''
275 nick = jid.split('@')[0]
276 old = get_user_by_nick(nick)
277 while old:
278 nick += '_'
279 old = get_user_by_nick(nick)
280 u = User(jid=jid.lower(), avail=show, nick=nick)
281 if show != OFFLINE:
282 u.last_online_date = datetime.datetime.now()
283 if resource:
284 u.resources.append(resource)
285 u.put()
286 log_onoff(u, NEW)
287 logging.info(u'%s 已经加入' % jid)
288 send_to_all_except(jid, u'%s 已经加入' % u.nick)
289 send_status(jid)
290 xmpp.send_message(jid, u'欢迎 %s 加入!要获取使用帮助,请输入 %shelp,要获知群主题,请输入 %stopic。' % (
291 u.nick, u.prefix, u.prefix))
292 return u
294 class BasicCommand:
295 handled = True
296 def __init__(self, msg, sender):
297 self.sender = sender
298 self.msg = msg
300 if helpre.match(msg.body):
301 self.do_help()
302 elif msg.body.startswith(sender.prefix):
303 cmd = msg.body[len(sender.prefix):].split()
304 try:
305 handle = getattr(self, 'do_' + cmd[0])
306 except AttributeError:
307 msg.reply(u'错误:未知命令 %s' % cmd[0])
308 except IndexError:
309 msg.reply(u'错误:无命令')
310 except UnicodeEncodeError:
311 msg.reply(u'错误:命令名解码失败。此问题在 GAE 升级其 Python 到 3.x 后方能解决。')
312 else:
313 handle(cmd[1:])
314 logging.debug('%s did command %s' % (sender.jid, cmd[0]))
315 else:
316 self.handled = False
318 def do_online(self, args):
319 '''在线成员列表。可带一个参数,指定在名字中出现的一个子串。'''
320 r = []
321 pat = args[0] if args else None
322 now = datetime.datetime.now()
323 l = User.gql('where avail != :1', OFFLINE)
324 for u in l:
325 m = u.nick
326 if pat and m.find(pat) == -1:
327 continue
328 status = u.avail
329 if status != u'在线':
330 m += u' (%s)' % status
331 if u.snooze_before is not None and u.snooze_before > now:
332 m += u' (snoozing)'
333 if u.black_before is not None and u.black_before > now:
334 m += u' (已禁言)'
335 r.append(unicode('* ' + m))
336 r.sort()
337 n = len(r)
338 if pat:
339 r.insert(0, u'在线成员列表(包含子串 %s):' % pat)
340 r.append(u'共 %d 人。' % n)
341 else:
342 r.insert(0, u'在线成员列表:')
343 r.append(u'共 %d 人在线。' % n)
344 self.msg.reply(u'\n'.join(r).encode('utf-8'))
346 def do_lsadmin(self, args):
347 '''管理员列表'''
348 r = []
349 now = datetime.datetime.now()
350 l = User.gql('where is_admin = :1', True)
351 for u in l:
352 m = u.nick
353 status = u.avail
354 if status != u'在线':
355 m += u' (%s)' % status
356 if u.snooze_before is not None and u.snooze_before > now:
357 m += u' (snoozing)'
358 if u.black_before is not None and u.black_before > now:
359 m += u' (已禁言)'
360 r.append(unicode('* ' + m))
361 r.sort()
362 n = len(r)
363 r.insert(0, u'管理员列表:')
364 r.append(u'共 %d 位管理员。' % n)
365 self.msg.reply(u'\n'.join(r).encode('utf-8'))
367 def do_lsblocked(self, args):
368 '''列出被封禁用户名单'''
369 r = []
370 l = BlockedUser.all()
371 for u in l:
372 r.append(unicode('* %s (%s, %s)' % (u.jid,
373 utils.strftime(u.add_date, timezone),
374 u.reason)
377 r.sort()
378 n = len(r)
379 r.insert(0, u'封禁列表:')
380 r.append(u'共 %d 个 JID 被封禁。' % n)
381 self.msg.reply(u'\n'.join(r).encode('utf-8'))
383 def do_chatty(self, args):
384 '''消息数排行'''
385 r = []
386 for u in User.gql('ORDER BY msg_chars ASC'):
387 m = u.nick
388 m = u'* %s:\t%5d条,共 %s' % (
389 u.nick, u.msg_count,
390 utils.filesize(u.msg_chars))
391 r.append(m)
392 n = len(r)
393 r.insert(0, u'消息数量排行:')
394 r.append(u'共 %d 人。' % n)
395 self.msg.reply(u'\n'.join(r).encode('utf-8'))
397 def do_nick(self, args):
398 '''更改昵称,需要一个参数,不能使用大部分标点符号'''
399 if len(args) != 1:
400 self.msg.reply('错误:请给出你想到的昵称(不能包含空格)')
401 return
403 q = get_user_by_nick(args[0])
404 if q is not None:
405 self.msg.reply('错误:该昵称已被使用,请使用其它昵称')
406 elif not utils.checkNick(args[0]):
407 self.msg.reply('错误:非法的昵称')
408 else:
409 if not config.nick_can_change and self.sender.nick_changed:
410 self.msg.reply('乖哦,你已经没机会再改昵称了')
411 return
412 old_nick = self.sender.nick
413 log_onoff(self.sender, NICK % (old_nick, args[0]))
414 self.sender.nick = args[0]
415 self.sender.nick_changed = True
416 self.sender.put()
417 send_to_all_except(self.sender.jid,
418 (u'%s 的昵称改成了 %s' % (old_nick, args[0])).encode('utf-8'))
419 self.msg.reply('昵称更改成功!')
420 do_nick.__doc__ += ',最长 %d 字节' % config.nick_maxlen
422 def do_whois(self, args):
423 '''查看用户信息,参数为用户昵称'''
424 if len(args) != 1:
425 self.msg.reply('错误:你想知道关于谁的信息?')
426 return
428 u = get_user_by_nick(args[0])
429 if u is None:
430 self.msg.reply(u'Sorry,查无此人。')
431 return
433 now = datetime.datetime.now()
434 status = u.avail
435 addtime = (u.add_date + timezone).strftime('%Y年%m月%d日 %H时%M分').decode('utf-8')
436 allowpm = u'否' if u.reject_pm else u'是'
437 if u.snooze_before is not None and u.snooze_before > now:
438 status += u' (snoozing)'
439 if u.black_before is not None and u.black_before > now:
440 status += u' (已禁言)'
441 r = []
442 r.append(u'昵称:\t%s' % u.nick)
443 if self.sender.is_admin:
444 r.append(u'JID:\t%s' % u.jid)
445 r.append(u'状态:\t%s' % status)
446 r.append(u'消息数:\t%d' % u.msg_count)
447 r.append(u'消息总量:\t%s' % utils.filesize(u.msg_chars))
448 r.append(u'加入时间:\t%s' % addtime)
449 r.append(u'接收私信:\t%s' % allowpm)
450 r.append(u'自我介绍:\t%s' % u.intro)
451 self.msg.reply(u'\n'.join(r).encode('utf-8'))
453 def do_help(self, args=()):
454 '''显示本帮助。参数 long 显示详细帮助,也可指定命令名。'''
455 doc = []
456 prefix = self.sender.prefix
458 if len(args) > 1:
459 self.msg.reply('参数错误。')
460 return
461 arg = args[0] if args else None
463 if arg is None or arg == 'long':
464 for b in self.__class__.__bases__ + (self.__class__,):
465 for c, f in b.__dict__.items():
466 if c.startswith('do_'):
467 if arg is None:
468 doc.append(u'%s%s:\t%s' % (prefix, c[3:], f.__doc__.decode('utf-8').split(u',', 1)[0].split(u'。', 1)[0]))
469 else:
470 doc.append(u'%s%s:\t%s' % (prefix, c[3:], f.__doc__.decode('utf-8')))
471 doc.sort()
472 if arg is None:
473 doc.insert(0, u'** 命令指南 **\n(当前命令前缀 %s,可设置。使用 %shelp long 显示详细帮助)' % (prefix, prefix))
474 else:
475 doc.insert(0, u'** 命令指南 **\n(当前命令前缀 %s,可设置)' % prefix)
476 doc.append(u'要离开,直接删掉好友即可。')
477 doc.append(u'Gtalk 客户端用户要离开请使用 quit 命令。')
478 self.msg.reply(u'\n'.join(doc).encode('utf-8'))
479 else:
480 try:
481 handle = getattr(self, 'do_' + arg)
482 except AttributeError:
483 self.msg.reply(u'错误:未知命令 %s' % arg)
484 except UnicodeEncodeError:
485 self.msg.reply(u'错误:命令名解码失败。此问题在 GAE 升级其 Python 到 3.x 后方能解决。')
486 else:
487 self.msg.reply(u'%s%s:\t%s' % (prefix, arg, handle.__doc__.decode('utf-8')))
489 def do_topic(self, args=()):
490 '''查看群主题'''
491 grp = get_group_info()
492 if grp is None or not grp.topic:
493 self.msg.reply(u'没有设置群主题。')
494 else:
495 self.msg.reply(grp.topic)
497 def do_iam(self, args):
498 '''查看自己的信息'''
499 u = self.sender
500 addtime = (u.add_date + timezone).strftime('%Y年%m月%d日 %H时%M分').decode('utf-8')
501 allowpm = u'否' if u.reject_pm else u'是'
502 r = []
503 r.append(u'昵称:\t%s' % u.nick)
504 r.append(u'JID:\t%s' % u.jid)
505 r.append(u'资源:\t%s' % u' '.join(u.resources))
506 r.append(u'消息数:\t%d' % u.msg_count)
507 r.append(u'消息总量:\t%s' % utils.filesize(u.msg_chars))
508 r.append(u'加入时间:\t%s' % addtime)
509 r.append(u'命令前缀:\t%s' % u.prefix)
510 r.append(u'接收私信:\t%s' % allowpm)
511 r.append(u'自我介绍:\t%s' % u.intro)
512 self.msg.reply(u'\n'.join(r).encode('utf-8'))
514 def do_m(self, args):
515 '''发私信,需要昵称和内容两个参数。私信不会以任何方式被记录。用户可使用 set 命令设置是否接收私信。'''
516 if len(args) < 2:
517 self.msg.reply('请给出昵称和内容。')
518 return
520 target = get_user_by_nick(args[0])
521 if target is None:
522 self.msg.reply('Sorry,查无此人。')
523 return
525 if target.reject_pm:
526 self.msg.reply('很抱歉,对方不接收私信。')
527 return
529 msg = self.msg.body[len(self.sender.prefix):].split(None, 2)[-1]
530 msg = u'_私信_ %s %s' % (target.nick_pattern % self.sender.nick, msg)
531 if xmpp.send_message(target.jid, msg) == xmpp.NO_ERROR:
532 self.msg.reply(u'OK')
533 else:
534 self.msg.reply(u'消息发送失败')
536 def do_intro(self, arg):
537 '''设置自我介绍信息'''
538 if not arg:
539 self.msg.reply('请给出自我介绍的内容。')
540 return
542 msg = self.msg.body[len(self.sender.prefix):].split(None, 1)[-1]
543 u = self.sender
544 try:
545 u.intro = msg
546 except db.BadValueError:
547 # 过长文本已在 handle_message 中被拦截
548 self.msg.reply('错误:自我介绍内容只能为一行。')
549 return
551 u.put()
552 send_to_all_except(u.jid,
553 (u'%s 的新自我介绍:%s' % (u.nick, msg)).encode('utf-8'))
554 self.msg.reply(u'设置成功!')
556 def do_snooze(self, args):
557 '''暂停接收消息,参数为时间(默认单位为秒)。再次发送消息时自动清除'''
558 if len(args) != 1:
559 self.msg.reply('你想停止接收消息多久?')
560 return
561 else:
562 try:
563 n = utils.parseTime(args[0])
564 except ValueError:
565 self.msg.reply('Sorry,我无法理解你说的时间。')
566 return
568 try:
569 self.sender.snooze_before = datetime.datetime.now() + datetime.timedelta(seconds=n)
570 except OverflowError:
571 self.msg.reply('Sorry,你不能睡太久。')
572 return
573 self.sender.put()
574 if n == 0:
575 self.msg.reply('你已经醒来。')
576 else:
577 self.msg.reply('OK,停止接收消息 %s。' % utils.displayTime(n))
578 log_onoff(self.sender, SNOOZE % n)
580 def do_offline(self, args):
581 '''假装离线,让程序认为你的所有资源已离线。如在你离线时程序仍认为你在线,请使用此命令。'''
582 del self.sender.resources[:]
583 self.sender.avail = OFFLINE
584 self.sender.last_offline_date = datetime.datetime.now()
585 self.sender.put()
586 self.msg.reply('OK,在下次你说你在线之前我都认为你已离线。')
588 def do_fakeresource(self, args):
589 '''假装在线,人工加入一个新的资源,使程序认为你总是在线。使用 offline 命令可删除所有资源的记录。'''
590 self.sender.resources.append('fakeresouce')
591 self.sender.put()
592 self.msg.reply('OK,你将永远在线。')
594 def do_old(self, args):
595 '''聊天记录查询,可选一个数字参数。默认为最后20条。特殊参数 OFFLINE (不区分大小写)显示离线消息(最多 100 条)'''
596 s = self.sender
597 q = False
598 if not args:
599 q = Log.gql("WHERE type = 'chat' ORDER BY time DESC LIMIT 20")
600 elif len(args) == 1:
601 try:
602 n = int(args[0])
603 if n > 0:
604 q = Log.gql("WHERE type = 'chat' ORDER BY time DESC LIMIT %d" % n)
605 except ValueError:
606 if args[0].upper() == 'OFFLINE':
607 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)
608 else:
609 pass
610 if q is not False:
611 r = []
612 q = list(q)
613 q.reverse()
614 if q:
615 if datetime.datetime.today() - q[0].time > datetime.timedelta(hours=24):
616 show_date = True
617 else:
618 show_date = False
619 for l in q:
620 message = '%s %s %s' % (
621 utils.strftime(l.time, timezone, show_date),
622 s.nick_pattern % l.nick,
623 l.msg
625 r.append(message)
626 if r:
627 self.msg.reply(u'\n'.join(r).encode('utf-8'))
628 else:
629 self.msg.reply('没有符合的聊天记录。')
630 else:
631 self.msg.reply('Oops, 参数不正确哦。')
633 def do_set(self, args):
634 '''设置参数。参数格式 key=value;不带参数以查看说明。'''
635 #注意:选项名/值中不能包含空格
636 if len(args) != 1:
637 doc = []
638 for c, f in self.__class__.__dict__.items():
639 if c.startswith('set_'):
640 doc.append(u'* %s:\t%s' % (c[4:], f.__doc__.decode('utf-8')))
641 for b in self.__class__.__bases__:
642 for c, f in b.__dict__.items():
643 if c.startswith('set_'):
644 doc.append(u'* %s:\t%s' % (c[4:], f.__doc__.decode('utf-8')))
645 doc.sort()
646 doc.insert(0, u'设置选项:')
647 self.msg.reply(u'\n'.join(doc).encode('utf-8'))
648 else:
649 msg = self.msg
650 cmd = msg.body.split(None, 1)[1].split('=', 1)
651 if len(cmd) == 1:
652 msg.reply(u'错误:请给出选项值')
653 return
654 try:
655 handle = getattr(self, 'set_' + cmd[0])
656 except AttributeError:
657 msg.reply(u'错误:未知选项 %s' % cmd[0])
658 except IndexError:
659 msg.reply(u'错误:无选项')
660 except UnicodeEncodeError:
661 msg.reply(u'错误:选项名解码失败。此问题在 GAE 升级其 Python 到 3.x 后方能解决。')
662 else:
663 handle(cmd[1])
665 def do_quit(self, args):
666 '''删除用户数据。某些自称“不作恶”的公司的客户端会不按协议要求发送删除好友的消息,请 gtalk 官方客户端用户使用此命令退出。参见 http://xmpp.org/rfcs/rfc3921.html#rfc.section.6.3 。'''
667 u = self.sender
668 if u.jid == config.root:
669 xmpp.send_message(jid, u'root 用户:离开前请确定你已做好善后工作!')
670 log_onoff(u, LEAVE)
671 send_to_all(u'%s 已经离开 (通过使用命令)' % u.nick)
672 u.delete()
673 logging.info(u'%s (%s) 已经离开 (通过使用命令)' % (u.nick, u.jid))
675 def set_prefix(self, arg):
676 '''设置命令前缀'''
677 self.sender.prefix = arg
678 self.sender.put()
679 self.msg.reply(u'设置成功!')
681 def set_nickpattern(self, arg):
682 '''设置昵称显示格式,用 %s 表示昵称的位置'''
683 try:
684 arg % 'test'
685 except (TypeError, ValueError):
686 self.msg.reply(u'错误:不正确的格式')
687 return
689 self.sender.nick_pattern = arg
690 self.sender.put()
691 self.msg.reply(u'设置成功!')
693 def set_allowpm(self, arg):
694 '''设置是否接收私信,参数为 y(接收)或者 n(拒绝)'''
695 if arg not in 'yn':
696 self.msg.reply(u'错误的参数。')
697 return
699 if arg == 'y':
700 self.sender.reject_pm = False
701 else:
702 self.sender.reject_pm = True
703 self.sender.put()
704 self.msg.reply(u'设置成功!')
706 class AdminCommand(BasicCommand):
707 def do_kick(self, args):
708 '''删除某人。他仍可以重新加入。'''
709 if len(args) != 1:
710 self.msg.reply('请给出昵称。')
711 return
713 target = get_user_by_nick(args[0])
714 if target is None:
715 self.msg.reply('Sorry,查无此人。')
716 return
718 if target.jid == config.root:
719 self.msg.reply('不能删除 root 用户')
720 return
722 targetjid = target.jid
723 targetnick = target.nick
724 target.delete()
725 self.msg.reply((u'OK,删除 %s。' % target.nick).encode('utf-8'))
726 send_to_all_except(self.sender.jid, (u'%s 已被删除。' % target.nick) \
727 .encode('utf-8'))
728 xmpp.send_message(targetjid, u'你已被管理员从此群中删除,请删除该好友。')
729 log_admin(self.sender, KICK % (targetnick, targetjid))
731 def do_quiet(self, args):
732 '''禁言某人,参数为昵称和时间(默认单位秒)'''
733 if len(args) != 2:
734 self.msg.reply('请给出昵称和时间。')
735 return
736 else:
737 try:
738 n = utils.parseTime(args[1])
739 except ValueError:
740 self.msg.reply('Sorry,我无法理解你说的时间。')
741 return
743 target = get_user_by_nick(args[0])
744 if target is None:
745 self.msg.reply('Sorry,查无此人。')
746 return
748 target.black_before = datetime.datetime.now() + datetime.timedelta(seconds=n)
749 target.put()
750 self.msg.reply((u'OK,禁言 %s %s。' % (target.nick, utils.displayTime(n))).encode('utf-8'))
751 send_to_all_except((self.sender.jid, target.jid),
752 (u'%s 已被禁言 %s。' % (target.nick, utils.displayTime(n))) \
753 .encode('utf-8'))
754 xmpp.send_message(target.jid, u'你已被管理员禁言 %s。' % utils.displayTime(n))
755 log_admin(self.sender, BLACK % (target.nick, n))
757 def do_notice(self, arg):
758 '''发送群通告。只会发给在线的人,包括 snoozing 者。'''
759 if not arg:
760 self.msg.reply('请给出群通告的内容。')
761 return
763 msg = self.msg.body[len(self.sender.prefix):].split(None, 1)[-1]
765 l = User.gql('where avail != :1', OFFLINE)
766 log_admin(self.sender, NOTICE % msg)
767 for u in l:
768 try:
769 xmpp.send_message(u.jid, u'通告:' + msg)
770 except xmpp.InvalidJidError:
771 pass
773 def do_topic(self, args=()):
774 '''查看或设置群主题'''
775 grp = get_group_info()
776 if not args:
777 if grp is None or not grp.topic:
778 self.msg.reply(u'没有设置群主题。')
779 else:
780 self.msg.reply(grp.topic)
781 else:
782 grp = get_group_info()
783 if grp is None:
784 grp = Group()
785 grp.topic = self.msg.body[len(self.sender.prefix):].split(None, 1)[-1]
786 grp.put()
787 self.msg.reply(u'群主题已更新。')
789 def do_admin(self, args):
790 '''将某人添加为管理员'''
791 if len(args) != 1:
792 self.msg.reply(u'请给出昵称。')
793 return
795 target = get_user_by_nick(args[0])
796 if target is None:
797 self.msg.reply(u'Sorry,查无此人。')
798 return
800 if target.is_admin:
801 self.msg.reply(u'%s 已经是管理员了。' % target.nick)
802 return
804 target.is_admin = True
805 target.put()
806 send_to_all_except(target.jid,
807 (u'%s 已成为管理员。' % target.nick) \
808 .encode('utf-8'))
809 xmpp.send_message(target.jid, u'你已是本群管理员。')
810 log_admin(self.sender, ADMIN % (target.nick, self.sender.nick))
812 def do_unadmin(self, args):
813 '''取消某人管理员的权限'''
814 if len(args) != 1:
815 self.msg.reply(u'请给出昵称。')
816 return
818 target = get_user_by_nick(args[0])
819 if target is None:
820 self.msg.reply(u'Sorry,查无此人。')
821 return
823 if not target.is_admin:
824 self.msg.reply(u'%s 不是管理员。' % target.nick)
825 return
827 target.is_admin = False
828 target.put()
829 send_to_all_except(target.jid,
830 (u'%s 已不再是管理员。' % target.nick) \
831 .encode('utf-8'))
832 xmpp.send_message(target.jid, u'你已不再是本群管理员。')
833 log_admin(self.sender, UNADMIN % (target.nick, self.sender.nick))
835 def do_block(self, args):
836 '''封禁某个 ID,参数为用户昵称或者 ID(如果不是已经加入的 ID 的话),以及封禁原因'''
837 if len(args) < 2:
838 self.msg.reply(u'请给出要封禁的用户和原因。')
839 return
841 target = get_user_by_nick(args[0])
842 reason = self.msg.body[len(self.sender.prefix):].split(None, 2)[-1]
843 if target is None:
844 jid = args[0]
845 name = jid
846 fullname = name
847 else:
848 jid = target.jid
849 name = target.nick
850 fullname = '%s (%s)' % (name, jid)
851 target.delete()
852 u = BlockedUser.gql('where jid = :1', jid).get()
853 if u is not None:
854 self.msg.reply(u'此 JID 已经被封禁。')
855 return
857 u = BlockedUser(jid=jid, reason=reason)
858 u.put()
860 send_to_all_except(self.sender.jid,
861 (u'%s 已被本群封禁,理由为 %s。' % (name, reason)) \
862 .encode('utf-8'))
863 self.msg.reply(u'%s 已被本群封禁,理由为 %s。' % (fullname, reason))
864 xmpp.send_message(jid, u'你已被本群封禁,理由为 %s。' % reason)
865 xmpp.send_presence(jid, status=u'您已经被本群封禁')
866 log_admin(self.sender, BLOCK % (fullname, reason))
868 def do_unblock(self, args):
869 '''解封某个 ID'''
870 if len(args) != 1:
871 self.msg.reply(u'请给出要解封用户的 JID。')
872 return
874 target = get_blocked_user(args[0])
875 if target is None:
876 self.msg.reply(u'封禁列表中没有这个 JID。')
877 return
879 target.delete()
880 send_to_all((u'%s 已被解封禁。' % args[0]) \
881 .encode('utf-8'))
882 log_admin(self.sender, UNBLOCK % args[0])
884 def do_groupstatus(self, arg):
885 '''设置群状态'''
886 grp = get_group_info()
887 if grp is None:
888 grp = Group()
889 grp.status = self.msg.body[len(self.sender.prefix):].split(None, 1)[-1]
890 grp.put()
891 for u in User.all():
892 xmpp.send_presence(u.jid, status=grp.status)
893 self.msg.reply(u'设置成功!')