1 #coding=utf-8
   2 #!/usr/bin/env python
   3 # confbot -- a conference bot for google talk.
   4 # Copyright (C) 2005 Perry Lorier (aka Isomer)
   5 # 
   6 # This program is free software; you can redistribute it and/or modify
   7 # it under the terms of the GNU General Public License as published by
   8 # the Free Software Foundation; either version 2 of the License, or
   9 # (at your option) any later version.
  10 # 
  11 # This program is distributed in the hope that it will be useful,
  12 #      but WITHOUT ANY WARRANTY; without even the implied warranty of
  13 #      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14 #      GNU General Public License for more details.
  15 # 
  16 #      You should have received a copy of the GNU General Public License
  17 #      along with this program; if not, write to the Free Software
  18 #      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  19 #
  20 # Modified by limodou 2005/09/05
  21 #   * 进行汉化处理
  22 #   * 当出现网络中断是不再退出,而是自动重联
  23 #   * 增加config.py,将配置分离
  24 #   * 增加是否记录日志标志recordflag
  25 #   * 增加日志保存路径设置logpath
  26 #   * 可自定义系统信息提示符 system_prompt
  27 #   * 将欢迎信息独立出来,可以方便修改welcome
  28 #   * 将用户名可以使用不同的包括字符处理,帮助显示name_quote_begin和name_quote_end
  29 #   * 完善表情串emotes
  30 #   * 增加管理员命令reload,用于重新装入配置信息,这样可以动态进行修改,对于logpath无效
  31 #
  32 # You will want to change these:
  33 
  34 import config
  35 
  36 # You shouldn't have to change anything below this line --------------------
  37 import socket
  38 import jabber
  39 import xmlstream
  40 import sys
  41 import time
  42 import random
  43 import traceback
  44 import urllib
  45 import os.path
  46 
  47 version='1.2'
  48 commandchrs = '/)'
  49 
  50 #打开日志文件 logf用于记录聊天日志 xmllogf用于记录xml消息
  51 #每运行一次bot,创建一个新的日志文件
  52 if config.recordflag:
  53         logf = open(os.path.join(config.logpath, time.strftime("%Y%m%d%H%M%S.log")),"w")
  54 xmllogf = open("xmllog","w")
  55 last_activity=time.time()
  56 #xmllogf = sys.stderr
  57 
  58 #打开管理员列表文件
  59 try:
  60         adminfile = open("adminlist.txt","r")
  61         admins=[i.strip() for i in adminfile.readlines()]
  62         adminfile.close()
  63 except:
  64         print "Could not open admin file, creating a new one"
  65 
  66 con=None
  67 
  68 def getdisplayname(x):
  69         "显示一个用户名,去掉/后面的东西和@gmail.com"
  70         x=unicode(x)
  71         if '/' in x:
  72                 x=x[:x.find("/")]
  73         if '@' in x and x[x.find('@'):]=="@gmail.com":
  74                 x=x[:x.find("@")]
  75         return x
  76 
  77 def getjid(x):
  78         "根据显示名得到一个完整的gmail帐号"
  79         if '@' not in x:
  80                 x=x+"@gmail.com"
  81         return x
  82 
  83 def saveadminlist():
  84         "保存管理员列表到文件中"
  85         a=open("adminlist.txt","w")
  86         for i in admins:
  87                 print >>a,i
  88         a.close()
  89 
  90 def sendtoone(who,msg):
  91         "向一个用户发送消息"
  92         m = jabber.Message(who,msg)
  93         m.setType('chat')
  94         con.send(m)
  95 
  96 def sendtoall(msg,butnot=[],including=[]):
  97         "向所有用户发送消息, 列在butnot中的用户不发送消息,including为必发消息名单列表"
  98         "如果用户的状态为'available', 'chat', None则可以发送消息,如果为busy则不会收到消息"
  99         r = con.getRoster()
 100         if config.recordflag:
 101                 print >>logf,time.strftime("%Y-%m-%d %H:%M:%S"),msg.encode("utf-8")
 102                 logf.flush()
 103         #print time.strftime("%Y-%m-%d %H:%M:%S"),msg.encode("utf-8")
 104         for i in r.getJIDs():
 105                 if getdisplayname(i) in butnot:
 106                         continue
 107                 state=r.getShow(unicode(i))
 108                 if state in ['available','chat',None] or getdisplayname(i) in including:
 109                         sendtoone(i,msg)
 110                         time.sleep(.1)
 111 
 112 statuses={}
 113 suppressing=1
 114 def sendstatus(who,txt,msg):
 115         "群发某人的状态"
 116         who = getdisplayname(who)
 117         if statuses.has_key(who) and statuses[who]==txt:
 118                 return
 119         if txt == 'busy':
 120                 txt = u'忙碌'
 121         statuses[who]=txt
 122         if not statuses.has_key(who):
 123                 # Suppress initial status
 124                 return
 125         if suppressing:
 126                 return
 127         if msg:
 128                 sendtoall(config.system_prompt + u'%s %s (%s)' % (who,txt,msg),including=[who])
 129         else:
 130                 sendtoall(config.system_prompt + u'%s %s' % (who,txt),including=[who])
 131 
 132 def boot(jid):
 133         "将某人从聊天室中去掉"
 134         con.send(jabber.Presence(to=jid, type='unsubscribe'))
 135         con.send(jabber.Presence(to=jid, type='unsubscribed'))
 136         if statuses.has_key(getdisplayname(jid)):
 137                 del statuses[getdisplayname(jid)]
 138 
 139 def cmd(who,msg):
 140         "聊天指令处理"
 141         if " " in msg:
 142                 cmd,msg=msg.split(" ",1)
 143         else:
 144                 cmd,msg=msg.strip(),""
 145         if cmd[:1] in commandchrs:
 146                 cmd=cmd[1:]
 147         if cmd in ["me"]:
 148                 if msg.strip()=="":
 149                         action=random.choice(config.emotes.keys())
 150                         sendtoone(who,  config.system_prompt + u'用法: /me <表情串>\n表现你的一种表情。\n'
 151                                                         u'例如 "/me %(action)s" 将表示为 "* %(nick)s %(emote)s" <消息>' % {
 152                                 "nick" : getdisplayname(who),
 153                                 "action" : action,
 154                                 "emote" : config.emotes[action]
 155                                 })
 156                 else:
 157                         if " " in msg:
 158                                 action, msg = msg.split(" ", 1)
 159                         else:
 160                                 action, msg = "", msg
 161                         sendtoall('%s%s%s _%s_ %s' % (config.name_quote_begin, getdisplayname(who), config.name_quote_end, config.emotes.get(action, ""), msg),butnot=[getdisplayname(who)])
 162         elif cmd in ["help"]:
 163                 sendtoone(who, config.system_prompt + u"""命令列表:
 164 /help        显示本帮助信息
 165 /me <emote> <msg> 设置表情串
 166 /names       显示聊天室人名
 167 /quit <msg>  退出聊天室,一旦退出需要重新加入
 168 /msg <nick> <msg>  私聊""")
 169                 if who.getStripped() in admins:
 170                         sendtoone(who, config.system_prompt + u'''管理员命令列表: 
 171 /die             关闭聊天室
 172 /addadmin <nick> 增加管理员
 173 /deladmin <nick> 删除管理员
 174 /kick <nick>     踢除某个人
 175 /reload          重新装入配置信息''')
 176                 sendtoone(who, config.system_prompt + u'请访问 http://coders.meta.net.nz/~perry/jabber/confbot.php 了解更多内容\n'
 177                         u'还可以访问 http://www.donews.net/limodou 的Blog了解本汉化修正版')
 178         elif cmd in ["names"]:
 179                 r = con.getRoster()
 180                 names=[]
 181                 for i in r.getJIDs():
 182                         state=r.getShow(unicode(i))
 183                         name=getdisplayname(i)
 184                         if i.getStripped() in admins:
 185                                 name="@%s" % name
 186                         if state in ['available','chat',None]:
 187                                 names.insert(0,name)
 188                         else:
 189                                 names.append('(%s)' % name)
 190                 sendtoone(who, config.system_prompt + u'名单:\n%s\n有@符的为管理员' % " ".join(names))
 191         elif cmd in ["quit","leave","exit"]:
 192                 if msg:
 193                         msg = u' (%s)' % msg
 194                 sendtoall(config.system_prompt + u'%s 已经退出%s' % (getdisplayname(who), msg))
 195                 boot(who.getStripped())
 196         elif cmd in ['msg']:
 197                 if not ' ' in msg:
 198                         sendtoone(who, config.system_prompt + u'用法: /msg <对方名称> <消息>')
 199                 else:
 200                         target,msg = msg.split(' ',1)
 201                         sendtoone(getjid(target),'%s%s%s 对你悄悄说: %s' % (config.name_quote_begin, getdisplayname(who),config.name_quote_end, msg))
 202                         sendtoone(who,'你对 %s%s%s 悄悄说: %s' % (config.name_quote_begin, getdisplayname(target), config.name_quote_end, msg))
 203         elif cmd in ['kick','boot'] and who.getStripped() in admins:
 204                 boot(getjid(msg))
 205                 sendtoall(config.system_prompt + u'%s 被管理员踢掉了' % msg.strip())
 206         elif cmd in ['addadmin'] and who.getStripped() in admins:
 207                 admins.append(getjid(msg.strip()))
 208                 sendtoone(who, config.system_prompt + u'把 %s 加为管理员' % getjid(msg.strip()))
 209                 sendtoone(getjid(msg.strip()), config.system_prompt + u'%s 已经把你加为管理员' % getdisplayname(who))
 210                 saveadminlist()
 211         elif cmd in ['deladmin'] and who.getStripped() in admins:
 212                 if getjid(msg.strip()) in admins:
 213                         admins.remove(getjid(msg.strip()))
 214                         sendtoone(who, config.system_prompt + u'把 %s 从管理员中删除' % getjid(msg.strip()))
 215                         sendtoone(getjid(msg.strip()), config.system_prompt + u'%s 把你从管理员中删除了' % getdisplayname(who))
 216                         saveadminlist()
 217                 else:
 218                         sendtoone(who, config.system_prompt + u'%s 不是一个管理员' % getjid(msg.strip()))
 219         elif cmd in ['die'] and who.getStripped() in admins:
 220                 sendtoall(config.system_prompt + u'聊天室被 %s 关闭' % who.getStripped())
 221                 sys.exit(1)
 222         elif cmd in ['reload'] and who.getStripped() in admins:
 223                 reload(config)
 224                 sendtoone(who, config.system_prompt + u'重载配置成功')
 225                 print 'reload config'
 226         else:
 227                 sendtoone(who, config.system_prompt + u'不可识别的命令 %s' % cmd)
 228 
 229 def messageCB(con,msg):
 230         if msg.getError()!=None:
 231                 if statuses.has_key(getdisplayname(msg.getFrom())):
 232                         sendstatus(unicode(msg.getFrom()), u'离开',"Blocked")
 233                 boot(msg.getFrom().getStripped())
 234         elif msg.getBody():
 235                 if len(msg.getBody())>1024:
 236                         sendtoall(config.system_prompt + u"%s 正在刷屏" % (getdisplayname(msg.getFrom())))
 237                 elif msg.getBody()[:1] in commandchrs:
 238                         cmd(msg.getFrom(),msg.getBody())
 239                 else:
 240                         global suppressing,last_activity
 241                         suppressing=0
 242                         last_activity=time.time()
 243                         sendtoall('%s%s%s %s' % (config.name_quote_begin, getdisplayname(msg.getFrom()), config.name_quote_end, msg.getBody()),
 244                                 butnot=[getdisplayname(msg.getFrom())],
 245                                 )
 246                         print 'status:',con.getRoster().getShow(msg.getFrom().getStripped()),msg.getFrom().getStripped()
 247                         if con.getRoster().getShow(msg.getFrom()) not in ['available','chat',None]:
 248                                 sendtoone(msg.getFrom(),
 249                                         u'config.system_prompt 警告: 你已经在客户端标记为"忙(busy)",\n'
 250                                         u'你将不会收到其他人的谈话,在客户端将你自已\n'
 251                                         u'设为"在线(available)"才可以看到别人的回复')
 252         xmllogf.flush() # just so flushes happen regularly
 253 
 254 
 255 def presenceCB(con,prs):
 256         who = unicode(prs.getFrom())
 257         type = prs.getType()
 258         # TODO: Try only acking their subscription when they ack ours.
 259         if type == 'subscribe':
 260                 con.send(jabber.Presence(to=who, type='subscribed'))
 261                 con.send(jabber.Presence(to=who, type='subscribe'))
 262                 print "Subscribe from",who
 263         elif type == 'unsubscribe':
 264                 boot(prs.getFrom().getStripped())
 265                 print "Unsubscribe from",who
 266         elif type == 'subscribed':
 267                 sendtoone(who, config.welcome)
 268                 sendstatus(who,u'在线', u'加入')
 269         elif type == 'unsubscribed':
 270                 sendtoall(config.system_prompt + u'%s 已经退出' % getdisplayname(who))
 271         elif type == 'available' or type == None:
 272                 show = prs.getShow()
 273                 if show in [None,'chat','available']:
 274                         sendstatus(who, u'在线', prs.getStatus())
 275                 elif show in ['xa']:
 276                         sendstatus(who, u'离开',prs.getStatus())
 277                 elif show in ['away']:
 278                         sendstatus(who,u'离开',prs.getStatus())
 279                 elif show in ['dnd']:
 280                         sendstatus(who,u'离开',prs.getStatus())
 281                 else:
 282                         sendstatus(who,u'离开',show+" [[%s]]" % prs.getStatus())
 283 
 284         elif type == 'unavailable':
 285                 status = prs.getShow()
 286                 sendstatus(who, u'离开', status)
 287         else:
 288                 print "Unknown presence:",who,type
 289 
 290 def iqCB(con,iq):
 291         # reply to all IQ's with an error
 292         reply=None
 293         try:
 294                 # Google are bad bad people
 295                 # they don't put their query inside a <query> in <iq>
 296                 reply=jabber.Iq(to=iq.getFrom(),type='error')
 297                 stuff=iq._node.getChildren()
 298                 for i in stuff:
 299                         reply._node.insertNode(i)
 300                 reply.setError('501','Feature not implemented')
 301                 con.send(reply)
 302         except:
 303                 traceback.print_exc()
 304 
 305 def disconnectedCB(con):
 306         sys.exit(1)
 307 
 308 reload(sys)
 309 sys.setdefaultencoding('utf-8')
 310 
 311 def connect():
 312         con = jabber.Client(host=config.server,debug=False ,log=xmllogf,
 313                                                 port=5223, connection=xmlstream.TCP_SSL)
 314         con.connect()
 315         con.setMessageHandler(messageCB)
 316         con.setPresenceHandler(presenceCB)
 317         con.setIqHandler(iqCB)
 318         con.setDisconnectHandler(disconnectedCB)
 319         con.auth(config.account,config.password,config.resource)
 320         con.requestRoster()
 321         con.sendInitPresence()
 322         _roster = con.getRoster()
 323         for jid in _roster.getJIDs():
 324                 print jid,_roster.getOnline(jid),_roster.getStatus(jid),_roster.getShow(jid)
 325         return con
 326 
 327 con = connect()
 328 
 329 JID="%s@%s/%s" % (config.account,config.server,config.resource)
 330 last_update=0
 331 saveadminlist()
 332 while 1:
 333         # We announce ourselves to a url, this url then keeps track of all
 334         # the conference bots that are running, and provides a directory
 335         # for people to browse.
 336         if time.time()-last_update>4*60*60: # every 4 hours
 337                 args={
 338                         'action':'register',
 339                         'account':"%s@%s" % (config.account,config.server),
 340                         'users':len(con.getRoster().getJIDs()),
 341                         'last_activity':time.time()-last_activity,
 342                         'version':version,
 343                         'topic':config.topic,
 344                         }
 345                 try:
 346                         urllib.urlretrieve('http://coders.meta.net.nz/~perry/jabber/confbot.php?'+urllib.urlencode(args))
 347                         print "Updated directory site"
 348                 except:
 349                         print "Can't reach the directory site"
 350                         traceback.print_exc()
 351                 last_update = time.time()
 352         try:
 353                 con.process(1)
 354         except KeyboardInterrupt:
 355                 break
 356         except SystemExit:
 357                 break
 358         except:
 359                 traceback.print_exc()
 360                 try:
 361                         time.sleep(1)
 362                         con = connect()
 363                 except:
 364                         traceback.print_exc()