## page was renamed from zhArticleTemplate
##language:zh
#pragma section-numbers on
{{{#!python
#coding=utf-8
#!/usr/bin/env python
# confbot -- a conference bot for google talk.
# Copyright (C) 2005 Perry Lorier (aka Isomer)
# 
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
#      but WITHOUT ANY WARRANTY; without even the implied warranty of
#      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#      GNU General Public License for more details.
# 
#      You should have received a copy of the GNU General Public License
#      along with this program; if not, write to the Free Software
#      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Modified by limodou 2005/09/05
#   * 进行汉化处理
#   * 当出现网络中断是不再退出,而是自动重联
#   * 增加config.py,将配置分离
#   * 增加是否记录日志标志recordflag
#   * 增加日志保存路径设置logpath
#   * 可自定义系统信息提示符 system_prompt
#   * 将欢迎信息独立出来,可以方便修改welcome
#   * 将用户名可以使用不同的包括字符处理,帮助显示name_quote_begin和name_quote_end
#   * 完善表情串emotes
#   * 增加管理员命令reload,用于重新装入配置信息,这样可以动态进行修改,对于logpath无效
#
# You will want to change these:

import config

# You shouldn't have to change anything below this line --------------------
import socket
import jabber
import xmlstream
import sys
import time
import random
import traceback
import urllib
import os.path

version='1.2'
commandchrs = '/)'

#打开日志文件 logf用于记录聊天日志 xmllogf用于记录xml消息
#每运行一次bot,创建一个新的日志文件
if config.recordflag:
	logf = open(os.path.join(config.logpath, time.strftime("%Y%m%d%H%M%S.log")),"w")
xmllogf = open("xmllog","w")
last_activity=time.time()
#xmllogf = sys.stderr

#打开管理员列表文件
try:
	adminfile = open("adminlist.txt","r")
	admins=[i.strip() for i in adminfile.readlines()]
	adminfile.close()
except:
	print "Could not open admin file, creating a new one"

con=None

def getdisplayname(x):
	"显示一个用户名,去掉/后面的东西和@gmail.com"
	x=unicode(x)
	if '/' in x:
		x=x[:x.find("/")]
	if '@' in x and x[x.find('@'):]=="@gmail.com":
		x=x[:x.find("@")]
	return x

def getjid(x):
	"根据显示名得到一个完整的gmail帐号"
	if '@' not in x:
		x=x+"@gmail.com"
	return x

def saveadminlist():
	"保存管理员列表到文件中"
	a=open("adminlist.txt","w")
	for i in admins:
		print >>a,i
	a.close()

def sendtoone(who,msg):
	"向一个用户发送消息"
	m = jabber.Message(who,msg)
	m.setType('chat')
	con.send(m)

def sendtoall(msg,butnot=[],including=[]):
	"向所有用户发送消息, 列在butnot中的用户不发送消息,including为必发消息名单列表"
	"如果用户的状态为'available', 'chat', None则可以发送消息,如果为busy则不会收到消息"
	r = con.getRoster()
	if config.recordflag:
		print >>logf,time.strftime("%Y-%m-%d %H:%M:%S"),msg.encode("utf-8")
		logf.flush()
	#print time.strftime("%Y-%m-%d %H:%M:%S"),msg.encode("utf-8")
	for i in r.getJIDs():
		if getdisplayname(i) in butnot:
			continue
		state=r.getShow(unicode(i))
		if state in ['available','chat',None] or getdisplayname(i) in including:
			sendtoone(i,msg)
			time.sleep(.1)

statuses={}
suppressing=1
def sendstatus(who,txt,msg):
	"群发某人的状态"
	who = getdisplayname(who)
	if statuses.has_key(who) and statuses[who]==txt:
		return
	if txt == 'busy':
		txt = u'忙碌'
	statuses[who]=txt
	if not statuses.has_key(who):
		# Suppress initial status
		return
	if suppressing:
		return
	if msg:
		sendtoall(config.system_prompt + u'%s %s (%s)' % (who,txt,msg),including=[who])
	else:
		sendtoall(config.system_prompt + u'%s %s' % (who,txt),including=[who])

def boot(jid):
	"将某人从聊天室中去掉"
	con.send(jabber.Presence(to=jid, type='unsubscribe'))
	con.send(jabber.Presence(to=jid, type='unsubscribed'))
	if statuses.has_key(getdisplayname(jid)):
		del statuses[getdisplayname(jid)]

def cmd(who,msg):
	"聊天指令处理"
	if " " in msg:
		cmd,msg=msg.split(" ",1)
	else:
		cmd,msg=msg.strip(),""
	if cmd[:1] in commandchrs:
		cmd=cmd[1:]
	if cmd in ["me"]:
		if msg.strip()=="":
			action=random.choice(config.emotes.keys())
			sendtoone(who,  config.system_prompt + u'用法: /me <表情串>\n表现你的一种表情。\n'
							u'例如 "/me %(action)s" 将表示为 "* %(nick)s %(emote)s" <消息>' % {
				"nick" : getdisplayname(who),
				"action" : action,
				"emote" : config.emotes[action]
				})
		else:
			if " " in msg:
				action, msg = msg.split(" ", 1)
			else:
				action, msg = "", msg
			sendtoall('%s%s%s _%s_ %s' % (config.name_quote_begin, getdisplayname(who), config.name_quote_end, config.emotes.get(action, ""), msg),butnot=[getdisplayname(who)])
	elif cmd in ["help"]:
		sendtoone(who, config.system_prompt + u"""命令列表:
/help        显示本帮助信息
/me <emote> <msg> 设置表情串
/names       显示聊天室人名
/quit <msg>  退出聊天室,一旦退出需要重新加入
/msg <nick> <msg>  私聊""")
		if who.getStripped() in admins:
			sendtoone(who, config.system_prompt + u'''管理员命令列表: 
/die             关闭聊天室
/addadmin <nick> 增加管理员
/deladmin <nick> 删除管理员
/kick <nick>     踢除某个人
/reload          重新装入配置信息''')
		sendtoone(who, config.system_prompt + u'请访问 http://coders.meta.net.nz/~perry/jabber/confbot.php 了解更多内容\n'
			u'还可以访问 http://www.donews.net/limodou 的Blog了解本汉化修正版')
	elif cmd in ["names"]:
		r = con.getRoster()
		names=[]
		for i in r.getJIDs():
			state=r.getShow(unicode(i))
			name=getdisplayname(i)
			if i.getStripped() in admins:
				name="@%s" % name
			if state in ['available','chat',None]:
				names.insert(0,name)
			else:
				names.append('(%s)' % name)
		sendtoone(who, config.system_prompt + u'名单:\n%s\n有@符的为管理员' % " ".join(names))
	elif cmd in ["quit","leave","exit"]:
		if msg:
			msg = u' (%s)' % msg
		sendtoall(config.system_prompt + u'%s 已经退出%s' % (getdisplayname(who), msg))
		boot(who.getStripped())
	elif cmd in ['msg']:
		if not ' ' in msg:
			sendtoone(who, config.system_prompt + u'用法: /msg <对方名称> <消息>')
		else:
			target,msg = msg.split(' ',1)
			sendtoone(getjid(target),'%s%s%s 对你悄悄说: %s' % (config.name_quote_begin, getdisplayname(who),config.name_quote_end, msg))
			sendtoone(who,'你对 %s%s%s 悄悄说: %s' % (config.name_quote_begin, getdisplayname(target), config.name_quote_end, msg))
	elif cmd in ['kick','boot'] and who.getStripped() in admins:
		boot(getjid(msg))
		sendtoall(config.system_prompt + u'%s 被管理员踢掉了' % msg.strip())
	elif cmd in ['addadmin'] and who.getStripped() in admins:
		admins.append(getjid(msg.strip()))
		sendtoone(who, config.system_prompt + u'把 %s 加为管理员' % getjid(msg.strip()))
		sendtoone(getjid(msg.strip()), config.system_prompt + u'%s 已经把你加为管理员' % getdisplayname(who))
		saveadminlist()
	elif cmd in ['deladmin'] and who.getStripped() in admins:
		if getjid(msg.strip()) in admins:
			admins.remove(getjid(msg.strip()))
			sendtoone(who, config.system_prompt + u'把 %s 从管理员中删除' % getjid(msg.strip()))
			sendtoone(getjid(msg.strip()), config.system_prompt + u'%s 把你从管理员中删除了' % getdisplayname(who))
			saveadminlist()
		else:
			sendtoone(who, config.system_prompt + u'%s 不是一个管理员' % getjid(msg.strip()))
	elif cmd in ['die'] and who.getStripped() in admins:
		sendtoall(config.system_prompt + u'聊天室被 %s 关闭' % who.getStripped())
		sys.exit(1)
	elif cmd in ['reload'] and who.getStripped() in admins:
		reload(config)
		sendtoone(who, config.system_prompt + u'重载配置成功')
		print 'reload config'
	else:
		sendtoone(who, config.system_prompt + u'不可识别的命令 %s' % cmd)

def messageCB(con,msg):
	if msg.getError()!=None:
		if statuses.has_key(getdisplayname(msg.getFrom())):
			sendstatus(unicode(msg.getFrom()), u'离开',"Blocked")
		boot(msg.getFrom().getStripped())
	elif msg.getBody():
		if len(msg.getBody())>1024:
			sendtoall(config.system_prompt + u"%s 正在刷屏" % (getdisplayname(msg.getFrom())))
		elif msg.getBody()[:1] in commandchrs:
			cmd(msg.getFrom(),msg.getBody())
		else:
			global suppressing,last_activity
			suppressing=0
			last_activity=time.time()
			sendtoall('%s%s%s %s' % (config.name_quote_begin, getdisplayname(msg.getFrom()), config.name_quote_end, msg.getBody()),
				butnot=[getdisplayname(msg.getFrom())],
				)
			print 'status:',con.getRoster().getShow(msg.getFrom().getStripped()),msg.getFrom().getStripped()
			if con.getRoster().getShow(msg.getFrom()) not in ['available','chat',None]:
				sendtoone(msg.getFrom(), 
					u'config.system_prompt 警告: 你已经在客户端标记为"忙(busy)",\n'
					u'你将不会收到其他人的谈话,在客户端将你自已\n'
					u'设为"在线(available)"才可以看到别人的回复')
	xmllogf.flush() # just so flushes happen regularly


def presenceCB(con,prs):
	who = unicode(prs.getFrom())
	type = prs.getType()
	# TODO: Try only acking their subscription when they ack ours.
	if type == 'subscribe':
		con.send(jabber.Presence(to=who, type='subscribed'))
		con.send(jabber.Presence(to=who, type='subscribe'))
		print "Subscribe from",who
	elif type == 'unsubscribe':
		boot(prs.getFrom().getStripped())
		print "Unsubscribe from",who
	elif type == 'subscribed':
		sendtoone(who, config.welcome)
		sendstatus(who,u'在线', u'加入')
	elif type == 'unsubscribed':
		sendtoall(config.system_prompt + u'%s 已经退出' % getdisplayname(who))
	elif type == 'available' or type == None:
		show = prs.getShow()
		if show in [None,'chat','available']:
			sendstatus(who, u'在线', prs.getStatus())
		elif show in ['xa']:
			sendstatus(who, u'离开',prs.getStatus())
		elif show in ['away']:
			sendstatus(who,u'离开',prs.getStatus())
		elif show in ['dnd']:
			sendstatus(who,u'离开',prs.getStatus())
		else:
			sendstatus(who,u'离开',show+" [[%s]]" % prs.getStatus())

	elif type == 'unavailable':
		status = prs.getShow()
		sendstatus(who, u'离开', status)
	else:
		print "Unknown presence:",who,type

def iqCB(con,iq):
	# reply to all IQ's with an error
	reply=None
	try:
		# Google are bad bad people
		# they don't put their query inside a <query> in <iq>
		reply=jabber.Iq(to=iq.getFrom(),type='error')
		stuff=iq._node.getChildren()
		for i in stuff:
			reply._node.insertNode(i)
		reply.setError('501','Feature not implemented')
		con.send(reply)
	except:
		traceback.print_exc()

def disconnectedCB(con):
	sys.exit(1)

reload(sys)
sys.setdefaultencoding('utf-8')

def connect():
	con = jabber.Client(host=config.server,debug=False ,log=xmllogf,
						port=5223, connection=xmlstream.TCP_SSL)
	con.connect()
	con.setMessageHandler(messageCB)
	con.setPresenceHandler(presenceCB)
	con.setIqHandler(iqCB)
	con.setDisconnectHandler(disconnectedCB)
	con.auth(config.account,config.password,config.resource)
	con.requestRoster()
	con.sendInitPresence()
	_roster = con.getRoster()
	for jid in _roster.getJIDs():
		print jid,_roster.getOnline(jid),_roster.getStatus(jid),_roster.getShow(jid)
	return con

con = connect()

JID="%s@%s/%s" % (config.account,config.server,config.resource)
last_update=0
saveadminlist()
while 1:
	# We announce ourselves to a url, this url then keeps track of all
	# the conference bots that are running, and provides a directory
	# for people to browse.
	if time.time()-last_update>4*60*60: # every 4 hours
		args={
			'action':'register',
			'account':"%s@%s" % (config.account,config.server),
			'users':len(con.getRoster().getJIDs()),
			'last_activity':time.time()-last_activity,
			'version':version,
			'topic':config.topic,
			}
		try:
			urllib.urlretrieve('http://coders.meta.net.nz/~perry/jabber/confbot.php?'+urllib.urlencode(args))
			print "Updated directory site"
		except:
			print "Can't reach the directory site"
			traceback.print_exc()
		last_update = time.time()
	try:
		con.process(1)
	except KeyboardInterrupt:
		break
	except SystemExit:
		break
	except:
		traceback.print_exc()
		try:
			time.sleep(1)
			con = connect()
		except:
			traceback.print_exc()
}}}