'''
Brotherhood Tools v1.3.2 by Tizen (tizenkotoko@gmail.com) - Public Release (March 6th 2010)

This is a bot for the online web game, Torpia.com. It uses several cloaking techniques to disguise itself as a standard web browser.
It is currently undetectable, though I recommend changing the __CLOAK_AGENT__ constant to your own useragent, for safety.

The following source code is Public Domain.
'''

from __future__ import print_function
import getpass,csv,hashlib,re,cPickle,os,random,string
from cookielib import CookieJar
from urllib2 import HTTPCookieProcessor,build_opener
from HTMLParser import HTMLParser,HTMLParseError
from urllib import urlencode
from sys import stdout
from time import time,strftime,localtime
from math import floor
from threading import Timer
pickle = cPickle

__USERNAME__ = '' # Set this or leave blank to prompt.
__PASSWORD__ = '' # Set this or leave blank to prompt.
__CLOAK_AGENT__ = 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)' # The useragent string to cloak your bot as.

__URL__ = 'http://w2.torpia.com' # Do *NOT* add a / to the end of this.
__OUTFILE__ = 'bhforum<bhnum>.csv'
__VERSION__ = '1.3.2 Public' # Version number *was* tied in to registration key creation... otherwise this is entirely cosmetic.
__LOGIN_ERROR__ = 'You are not logged in. Use the LOGIN command to log in.'
__MACROS__ = 'bh_macros.dat'
''' The commented lines below were used in the "Registration key" system, and are no longer needed. '''
#__REGCODES__ = 'bh_regcodes.dat'
#__SALT_BH__ = 'changethis'
#__SALT_USER__ = 'changethis too'

__RESTYPES__ = {
	# This is not a complete dictionary, only the things that were immediately noticed. Feel free to add the rest.
	# Currently this dict is only used by autoproduce, for formatting output results.
	'ironore':'iron ore',
	'goldore':'gold ore',
	'1':'boards',
	'8':'horses',
	'3':'gold bars',
	'2':'iron bars',
}

''' These functions were used in the registration key system, and are no longer needed.
def convDecToBase(num, base, dd=False):
	if not 2 <= base <= 62: raise ValueError, 'The base number must be between 2 and 62.'
	if not dd: dd = dict(zip(range(62), list(string.digits+string.ascii_lowercase+string.ascii_uppercase)))
	if num == 0: return ''
	num, rem = divmod(num, base)
	return convDecToBase(num, base, dd)+dd[rem]
	
def checkRegCode(code,username,bhnum):
	username = username if username else ''
	bhnum = bhnum if bhnum else ''
	if bhnum and code == convDecToBase(int(hashlib.sha256('%s%s%s' % (__URL__*int(bhnum),__VERSION__,hashlib.sha512(__SALT_BH__).hexdigest())).hexdigest(),16),62):
		# Brotherhood registration code
		return True
	elif code == convDecToBase(int(hashlib.sha256('%s%s%s%s' % (username.lower(),__URL__*len(username),__VERSION__,hashlib.sha512(__SALT_USER__).hexdigest())).hexdigest(),16),62):
		# Username registration code
		return True
	return False
'''

class ParseBHView(HTMLParser):
	'''
	Custom HTMLParser class for parsing data from the page containing a list of all the brothers in your brotherhood.
	This class' retval returns the name of the brotherhood, and a dictionary with details on all of the members.
	(name as dict key, profile href, and alignment)
	'''
	def __init__(self):
		self.retval = {'name':None,'members':{}}
		self.startnames = False
		self.nameflag = False
		self.activename = None
		self.activehref = None
		self.activeside = False
		HTMLParser.__init__(self)
	def handle_starttag(self,tag,attrs):
		raw = {}
		for attr in attrs: raw[attr[0]] = attr[1]
		if tag == 'a' and 'href' in raw and '/'.join(raw['href'].split('/')[:3]) == '/statistics/brotherhood':
			self.retval['name'] = raw['href'].split('/')[-1]
			self.startnames = True
		elif tag == 'a' and 'href' in raw and self.startnames and len(raw['href'].split('/')) == 4 and '/'.join(raw['href'].split('/')[0:3]) == '/userprofile/index':
			self.nameflag = True
			self.activehref = raw['href']
		elif tag == 'td' and self.activename:
			self.activeside = True
	def handle_data(self,data):
		if self.nameflag:
			name = data.strip()
			if name:
				self.retval['members'][name] = {}
				self.retval['members'][name]['href'] = self.activehref
				self.activehref = None
				self.activename = name
				self.nameflag = False
		elif self.activeside:
			side = data.strip()
			if side in ('Evil','Good'):
				self.retval['members'][self.activename]['side'] = side
				self.activename = None
				self.activeside = False

class ParseBuilding(HTMLParser):
	'''
	Custom HTMLParser class for parsing data from *any* building detail page.
	retval returns lots of information, in as generic a way as possible, so as to be useable for all building types... good and evil.
	however, it lacks in providing enough information about "multiple production" buildings... such as the weaponsmithy.
	This will need to be revised to make autoproduce better. -Tizen
	'''
	def __init__(self):
		self.retval = {'canupgrade':False,'batchsize':0,'name':None,'slot':None,'objectid':None,'batchtype':None,'resources':{},'capacity':999999999999,'making':0,'type':None}
		self.maybe = False
		self.queuestart = False
		self.namestart = False
		HTMLParser.__init__(self)
	def handle_starttag(self,tag,attrs):
		raw = {}
		for attr in attrs: raw[attr[0]] = attr[1]
		if tag == 'a' and 'href' in raw and '/building/upgrade/' in raw['href'] and not self.retval['canupgrade']:
			self.maybe = True
		elif tag == 'div' and 'class' in raw and raw['class'] == 'containblock queue':
			self.queuestart = True
		elif self.queuestart and tag == 'span' and 'id' in raw and raw['id'] == 'batchsize' and 'value' in raw and raw['value'].isdigit():
			self.retval['batchsize'] = int(raw['value'])
			self.queuestart = False
		elif not self.retval['name'] and tag == 'h2':
			self.namestart = True
		elif tag == 'input' and 'name' in raw and raw['name'] == 'slot':
			self.retval['slot'] = raw['value']
		elif tag == 'input' and 'name' in raw and raw['name'] == 'objectid':
			self.retval['objectid'] = raw['value']
		elif tag == 'input' and 'name' in raw and raw['name'] == 'batchtype':
			self.retval['batchtype'] = raw['value']
		elif tag == 'span' and 'has' in raw and 'need' in raw and 'restype' in raw:
			if raw['restype'] not in self.retval['resources']:
				self.retval['resources'][raw['restype']] = {}
				self.retval['resources'][raw['restype']]['has'] = int(raw['has']) if raw['has'].isdigit() else 0
				self.retval['resources'][raw['restype']]['need'] = int(raw['need']) if raw['need'].isdigit() else 0
		elif tag == 'span' and 'id' in raw and 'stored' in raw and 'capacity' in raw:
			if raw['stored'].isdigit() and raw['capacity'].isdigit(): self.retval['capacity'] = int(raw['capacity']) - int(raw['stored'])
		elif tag == 'span' and 'class' in raw and raw['class'] == 'batchindicator' and 'current' in raw and raw['current'].isdigit() and 'type' in raw:
			self.retval['making'] += int(raw['current'])
			self.retval['type'] = raw['type']
	def handle_data(self,data):
		if self.maybe and data.strip() == 'Upgrade':
			self.retval['canupgrade'] = True
			self.maybe = False
		elif self.namestart and data.strip():
			self.retval['name'] = data.strip()
			self.namestart = False

class ParseVillage(HTMLParser):
	'''
	Handles the main village view. Quick way to detect the player's alignment... since this page is the first page viewed upon login.
	retval gives information about each building slot for the active village, buildings under construction, and a dict listing other available villages.
	Could probably add parsing here for the available resources in this village too... just wasn't needed at the time. -Tizen
	'''
	def __init__(self):
		self.retval = {'buildings':{},'construct':{},'villages':{},'align':''}
		self.mapstart = False
		self.startform = False
		self.startoption = True
		self.value = None
		self.solovil = False
		
		HTMLParser.__init__(self)
	def handle_starttag(self,tag,attrs):
		raw = {}
		for attr in attrs: raw[attr[0]] = attr[1]
		if tag == 'body' and 'class' in raw:
			self.retval['align'] = raw['class']		
		if tag == 'map' and 'name' in raw and raw['name'] == 'village_overview':
			self.mapstart = True
		if self.mapstart and tag == 'area' and 'title' in raw:
			self.retval['buildings'][raw['href']] = {
				'name':raw['title'],
				'time':int(raw['itimeleft']) if 'itimeleft' in raw else 0,
				'level':int(raw['ibuildinglevel']) if 'ibuildinglevel' in raw else 0,
			}
		if self.mapstart and tag == 'area' and 'title' in raw and 'itimeleft' in raw and raw['title'].split(': ')[0] == 'Under construction':
			self.retval['construct'][raw['href']] = {'name':raw['title'].split(': ')[1:][0],'time':raw['itimeleft']}
		if tag == 'form' and 'action' in raw and raw['action'] == '%s/village/changeVillage' % __URL__:
			self.startform = True
		if tag == 'option' and 'value' in raw and self.startform:
			self.value = raw['value']
			self.startoption = True
		if tag == 'span' and 'class' in raw and raw['class'] == 'cust_villagesel':
			self.solovil = True			
	def handle_data(self,data):
		if self.startform and self.startoption and data.strip():
			self.retval['villages'][self.value] = data.strip()
			self.startoption = False
		if self.solovil and data.strip():
			self.retval['villages'][data.strip()] = data.strip()
			self.solovil = False
	def handle_endtag(self,tag):
		if tag == 'form' and self.startform: self.startform = False

class ParseMap(HTMLParser):
	'''
	Custom HTMLParser class for parsing data from the map-view of a player's villages.
	The retval in this class returns a dict with (x,y) as the key dict and 'name','pop' as values of that dict.
	'''
	def __init__(self):
		self.retval = {'villages':{}}
		self.vlist = False
		self.vstart = False
		self.vxy = None
		HTMLParser.__init__(self)
	def handle_starttag(self,tag,attrs):
		raw = {}
		for attr in attrs: raw[attr[0]] = attr[1]
		if tag == 'div' and 'class' in raw and raw['class'] == 'villageselectionlist':
			self.vlist = True
		if tag == 'a' and self.vlist:
			self.vstart = True
	def handle_data(self,data):
		if self.vstart and data.strip():
			regex = re.search('(.*) \((.*)\,(.*)\)',data.strip())
			if not regex:
				self.retval['villages'][self.vxy]['pop'] = data.strip().replace('.','')
			else:
				villagedat = regex.groups()
				self.vxy = (villagedat[1],villagedat[2])
				self.retval['villages'][self.vxy] = {}
				self.retval['villages'][self.vxy]['name'] = villagedat[0]
	def handle_endtag(self,tag):
		if tag == 'div' and self.vlist:
			self.vlist = False
		elif tag == 'a' and self.vstart:
			self.vstart = False

class ParseLogin(HTMLParser):
	'''
	Custom HTMLParser class for parsing data from the login screen.
	retval returns False if errorlogin class found, True otherwise.
	'''
	def __init__(self):
		self.retval = True
		HTMLParser.__init__(self)
	def handle_starttag(self,tag,attrs):
		if not self.retval: return
		raw = {}
		for attr in attrs: raw[attr[0]] = attr[1]
		if tag == 'p' and 'class' in raw and raw['class'] == 'errorlogin':
			self.retval = False

class Post(object):
	'''
	Custom class used to hold scraped forum post data.
	used by the ParseThread class
	'''
	def __init__(self,date_time):
		self.date_time = date_time
		self.player_url = ''
		self.player = ''
		self.player_title = ''
		self.player_align = ''
		self.message = ''

class ParseThread(HTMLParser):
	'''
	Custom HTMLParser class for parsing a forum thread.
	retval returns a huge mess. enter at your own risk.
	used by forumscrape command.
	'''
	def __init__(self):
		self.start_post = False
		self.retval = {}
		self.retval['posts'] = []
		self.retval['nextpage'] = None
		self.post = None
		self.stage = 0
		self.td = 0
		self.pagination = False
		self.lasthref = None
		self.startalign = False
		HTMLParser.__init__(self)
	def handle_starttag(self,tag,attrs):
		raw = {}
		for attr in attrs: raw[attr[0]] = attr[1]
		if tag == 'p' and 'class' in raw and raw['class'] == 'pagination':
			self.pagination = True
		elif self.pagination and tag == 'a' and 'href' in raw:
			self.lasthref = raw['href']
		if not self.start_post: return
		if tag == 'td' and self.stage == 2 and self.td == 0:
			self.td = 1
		elif tag == 'a' and self.stage == 2 and self.td == 1 and 'href' in raw and 'class' in raw and raw['class'] == 'username':
			self.post.player_url = raw['href']
			self.post.player = raw['href'].split('/')[-1]
			self.stage = 3
		elif tag == 'a' and self.td == 2:
			self.post.message += ' <a href="%s">%s</a> ' % (raw['href'] if 'href' in raw else 'ERROR',raw['href'] if 'href' in raw else 'ERROR',)
		elif tag == 'span' and self.stage == 5 and 'style' in raw and raw['style'] == 'font-size:12px;':
			self.startalign = True
		elif tag == 'td' and self.td == 1:
			self.td = 2
	def handle_endtag(self,tag):
		if self.start_post and self.td == 2 and tag == 'td':
			self.td = 0
			self.stage = 1
			self.retval['posts'].append(self.post)
			self.post = None
		elif self.start_post and self.stage == 3 and tag == 'a':
			self.stage = 4
	def handle_data(self,data):
		if self.pagination and self.lasthref and data.strip():
			if data.strip() == 'next':
				self.retval['nextpage'] = self.lasthref
				self.pagination = False
			self.lasthref = None
		elif self.start_post and self.td == 2 and data.strip():
			self.post.message += data.strip()
		elif self.start_post and self.stage == 1 and data.strip():
			self.post = Post(data.strip())
			self.stage = 2
		elif self.start_post and self.stage == 4 and data.strip():
			self.post.player_title = data.strip()
			self.stage = 5
		elif self.start_post and self.startalign and data.strip():
			self.post.player_align = data.strip()
			self.startalign = False
	def handle_comment(self,data):
		if data.strip() and data.strip() == 'posts':
			self.start_post = True
			self.stage = 1
		elif data.strip() and data.strip() == '/posts':
			self.start_post = False

class Thread(object):
	'''
	Custom class used to hold scraped forum thread data.
	used by ParseForum class
	'''
	def __init__(self,url,sticky=False):
		self.url = url
		self.topic = ''
		self.sticky = sticky
		self.author = ''
		self.author_url = ''
		self.replies = 0
		self.datenotation = ''
		self.timenotation = ''
		self.last_player = ''
		self.last_player_url = ''

class ParseForum(HTMLParser):
	'''
	Custom HTMLParser class for parsing a root brotherhood forum.
	retval returns a huge mess. enter at your own risk.
	used by forumscrape command.
	'''
	def __init__(self):
		self.retval = {}
		self.retval['threads'] = []
		self.retval['nextpage'] = None
		self.post_start = False
		self.pagination = False
		self.lasthref = None
		self._init_thread()
		HTMLParser.__init__(self)
	def _init_thread(self):
		self.thread = None
		self.post_url = False
		self.author_url = False
		self.tdcount = 0
		self.sticky = False
		self.datenotation = False
		self.timenotation = False
		self.last_player_url = False
	def handle_starttag(self,tag,attrs):
		raw = {}
		for attr in attrs: raw[attr[0]] = attr[1]
		if tag == 'p' and 'class' in raw and raw['class'] == 'pagination':
			self.pagination = True
		elif self.pagination and tag == 'a' and 'href' in raw:
			self.lasthref = raw['href']
		elif self.post_start and tag == 'tr':
			self._init_thread()
		elif self.post_start and tag == 'td':
			self.tdcount += 1
		elif self.post_start and tag == 'b' and self.tdcount == 1:
			self.sticky = True
		elif self.post_start and self.tdcount == 1 and tag == 'a' and 'href' in raw and '/brotherhoodforum/viewtopic/' in raw['href']:
			self.post_url = True
			self.thread = Thread(raw['href'],self.sticky)
		elif self.post_start and self.tdcount == 2 and tag == 'a' and 'href' in raw and '/userprofile/index/' in raw['href']:
			self.thread.author_url = raw['href']
			self.author_url = True
		elif self.post_start and self.tdcount == 4 and tag == 'span' and 'class' in raw and raw['class'] == 'datenotation':
			self.datenotation = True
		elif self.post_start and self.tdcount == 4 and tag == 'span' and 'class' in raw and raw['class'] == 'timenotation':
			self.timenotation = True
		elif self.post_start and self.tdcount == 4 and tag == 'a' and 'href' in raw and '/userprofile/index/' in raw['href']:
			self.last_player_url = True
			self.thread.last_player_url = raw['href']
	def handle_data(self,data):
		if self.pagination and self.lasthref and data.strip():
			if data.strip() == 'next':
				self.retval['nextpage'] = self.lasthref
			self.lasthref = None
		elif self.last_player_url and data.strip():
			self.last_player_url = False
			self.thread.last_player = data.strip()
		elif self.datenotation and data.strip():
			self.datenotation = False
			self.thread.datenotation = data.strip()
		elif self.timenotation and data.strip():
			self.timenotation = False
			self.thread.timenotation = data.strip()
		elif self.tdcount == 3 and data.strip() and data.strip().isdigit():
			self.thread.replies = int(data.strip())
		elif self.post_url and data.strip():
			self.post_url = False
			self.thread.topic = data.strip()
		elif self.author_url and data.strip():
			self.author_url = False
			self.thread.author = data.strip()
	def handle_endtag(self,tag):
		if self.post_start and tag == 'tr':
			if self.thread: self.retval['threads'].append(self.thread)
		elif self.post_url and tag == 'a':
			self.post_url = False
	def handle_comment(self,data):
		if data.strip() and data.strip() == 'posts':
			self.post_start = True
		elif self.post_start and data.strip() and data.strip() == '/posts':
			self.post_start = False

class ParseStats(HTMLParser):
	'''
	Custom HTMLParser class for parsing the player stats page.
	retval returns maximum number of pages and players dict containing name, alignment, bhname, num of amulets, num of buddies and their relative rank (+100,-123,etc...)
	used by the amuletscan command.
	'''
	def __init__(self):
		self.name = 'stats'
		self.retval = {'players':{},'maxpages':0,}
		self.checklink = False
		self.maxfound = False
		self.startlist = False
		self.startplayer = False
		self.tdstart = False
		self.player = None
		self.tdcount = 0
		HTMLParser.__init__(self)
	def handle_starttag(self,tag,attrs):
		raw = {}
		for attr in attrs: raw[attr[0]] = attr[1]
		if not self.maxfound and tag == 'a' and 'href' in raw and '/statistics/player/' in raw['href'] and len(raw['href'].split('/')) == 6 and raw['href'].split('/')[4] == 'all' and raw['href'].split('/')[5].isdigit():
			self.maybe = int(raw['href'].split('/')[5])
			self.checklink = True
		if tag == 'tbody' and not self.startlist:
			self.startlist = True
		if self.startlist and tag == 'tr':
			self.startplayer = True
			self.tdcount = 0
		if self.startplayer and tag == 'td':
			self.tdstart = True
			if self.tdcount < 1: self.tdcount = 1
		if self.tdcount == 2 and tag == 'a' and 'href' in raw and '/userprofile/index/' in raw['href']:
			self.retval['players'][raw['href']] = {'name':'','align':'','bhname':'','bhurl':'','amulets':0,'buddies':0,'rank':0}
			self.player = raw['href']
		if self.tdcount == 4 and tag == 'a' and 'href' in raw and '/brotherhoodprofile/view/' in raw['href']:
			self.retval['players'][self.player]['bhurl'] = raw['href']
	def handle_data(self,data):
		if self.checklink and data.strip() and data.strip().split()[0] == 'Last':
			self.retval['maxpages'] = self.maybe
			self.checklink = False
			self.maxfound = True
		if self.tdstart and self.player and data.strip():
			if self.tdcount == 2:
				self.retval['players'][self.player]['name'] = data.strip()
			elif self.tdcount == 3:
				self.retval['players'][self.player]['align'] = data.strip()
			elif self.tdcount == 4:
				self.retval['players'][self.player]['bhname'] = data.strip()
			elif self.tdcount == 5 and data.strip().isdigit():
				self.retval['players'][self.player]['amulets'] = int(data.strip())
			elif self.tdcount == 6 and data.strip().isdigit():
				self.retval['players'][self.player]['buddies'] = int(data.strip())
			elif self.tdcount == 7 and data.strip().replace('-','').replace('+','').isdigit():
				self.retval['players'][self.player]['rank'] = int(data.strip())
	def handle_endtag(self,tag):
		if self.startlist and tag == 'tbody': self.startlist = False
		if self.startplayer and tag == 'tr':
			self.startplayer = False
			self.tdcount = 0
		if self.tdstart and tag == 'td':
			self.tdstart = False
			self.tdcount += 1		

class ParseProfile(HTMLParser):
	'''
	Custom HTMLParser class for parsing a player's profile page.
	retval returns brotherhood ID and brotherhood name.
	mostly used internally as a method to determine which (if any) bh the person logging in, in in.
	this was used mostly in the registration system, which is now unused.
	this class could be expanded to scrape more available information from a player's profile.
	'''
	def __init__(self):
		self.retval = {}
		self.retval['bhnum'] = None
		self.retval['bhname'] = None
		self.watchth = False
		self.bhflag = False
		self.gotbh = False
		self.thdata = None
		HTMLParser.__init__(self)
	def handle_starttag(self,tag,attrs):
		raw = {}
		for attr in attrs: raw[attr[0]] = attr[1]
		if tag == 'th': self.watchth = True
		if tag == 'a' and self.bhflag and 'href' in raw and '/brotherhoodprofile/view/' in raw['href'] and len(raw['href'].split('/')) == 4 and raw['href'].split('/')[3].isdigit():
			self.bhflag = False
			self.gotbh = True
			self.retval['bhnum'] = raw['href'].split('/')[3]
	def handle_data(self,data):
		if self.watchth and data.strip():
			self.thdata = data.strip()
		elif self.gotbh and data.strip():
			self.retval['bhname'] = data.strip()
	def handle_endtag(self,tag):
		if self.watchth and tag == 'th':
			self.watchth = False
			if self.thdata == 'Brotherhood':                                                                                                               
				self.bhflag = True
			self.thdata = None
		elif self.gotbh and tag == 'a':
			self.gotbh = False
			
class Torpia(object):
	'''
	Main bhtool class. This is where all the magic happens.
	methods are invoked here, which in turn decide which page to request and how to parse them.
	
	to create a new torpia "bot" in Python, just use it like this:

	bot = Torpia(username,password)
	if bot.do_login():
		print('Logged in!')
		# rest of your bot code here
	else:
		print('Login failed!')
	'''
	def __init__(self,username,password):
		'''
		Invoking method. Must provide a username and password.
		Notice that a new cookie jar is used each time... this is the same as opening a different browser to log in to a different (or the same) account.
		This allows more than 1 active account at a time. Just create new instances of this class and keep them stored in a list.
		'''
		self.username = username
		self.password = password
		self.bhname = ''
		self.bhnum = ''
		self.login = False
		self.cj = CookieJar()
	def do_login(self):
		'''
		login method. this must be performed at least once, to get the bot running.
		currently there is no way to detect if the bot is logged out, so that may be something to add in the future.
		'''
		self.login = True if self.username and self.password and self.parse(self._open('/login',urlencode({'sUserName':self.username,'sPassword':self.password,})),'login') else False
		return self.login
	def get_vdata(self):
		'''
		returns all available data on the currently active village.
		'''
		return self.parse(self._open('/village'),'village')
	def fetch_building(self,bnum):
		'''
		returns data on the specified building slot, in the currently active village.
		'''
		b_url = '/building/building/%s' % bnum
		return self.parse(self._open(b_url),'building')
	def fetch_buildings(self,village,onlytown):
		'''
		returns data on all building slots in a specified village.
		'''
		return self.get_vdata()['buildings'] if onlytown else self.parse(self._open('/village/changeVillage',urlencode({'focusvillage':village,})),'village')['buildings']
	def produce(self,b_url,amnt,slot,objectid,batchtype):
		'''
		invokes a building's production
		'''
		produce_data = urlencode({
			'amount':amnt,
			'slot':slot,
			'objectid':objectid,
			'batchtype':batchtype,
			'fill':'Fill up'
		})
		self._open('%s/produce' % b_url,produce_data)
	def upgrade(self,b_url):
		'''
		invokes a building's upgrade function
		returns true if able to upgrade, false otherwise.
		reason for not being able to upgrade is not provided.
		'''
		if not self.parse(self._open(b_url),'building')['canupgrade']: return False
		try:
			url = b_url.split('/')
			url[2] = 'upgrade'
			url = '/'.join(url)
			self._open(url)
		except:
			return False
		return True
	def construction(self,villages=None):
		'''
		returns data on all villages; wether or not they are available to build and if not, next closest time to build.
		'''
		vdata = self.get_vdata()
		villages = villages if villages else vdata['villages']
		maxbuild = 2 if vdata['align'] == 'dark' else 3
		construct = {}
		for v in villages:
			if len(villages) > 1:
				c = self.parse(self._open('/village/changeVillage',urlencode({'focusvillage':v,})),'village')['construct']
			else:
				c = vdata['construct']
			if len(c) < maxbuild:
				construct[villages[v]] = {'name':'Construction Available','time':0}
			else:
				times = []
				for b in c: times.append(int(c[b]['time']))
				times.sort()
				for b in c:
					if int(c[b]['time']) == times[0]:
						construct[villages[v]] = {'name':c[b]['name'],'time':times[0]}
						break
		return construct
	def get_statmaxpages(self):
		'''
		method used by amuletscan to determine maximum amount of stat pages
		'''
		return self.parse(self._open('/statistics'),'stats')['maxpages']
	def get_statpage(self,n):
		'''
		method used by amuletscan to fetch scraped data from a specific stat page
		'''
		return self.parse(self._open('/statistics/player/%s/all/%d' % (self.username,n)),'stats')['players']
	def get_villages(self,player):
		'''
		method used by bhscrape and amuletscan to pull village information about a specific player
		'''
		return self.parse(self._open('/map/jump/accountname/%s' % player),'map')['villages']
	def do_forumscrape(self,outfile):
		'''
		main forumscrape method. dumps scraped information to a CSV file.
		'''
		threads = self.get_forumthreads()
		print('(got %d threads)...' % len(threads),end='')
		stdout.flush()
		if len(threads) < 1: return False
		outfile = '%s%s\\%s' % (os.getenv('HOMEDRIVE'),os.getenv('HOMEPATH'),outfile.replace('<bhnum>',self.bhnum))
		db = csv.writer(open(outfile,'w'),delimiter=',',quotechar='"',quoting=csv.QUOTE_MINIMAL,lineterminator='\n')
		out = []
		for thread in threads:
			posts = self.fetch_thread(thread.url)
			print('.',end='')
			stdout.flush()
			for post in posts:
				row = [thread.url,thread.topic,thread.sticky,thread.author,thread.author_url,thread.replies,thread.datenotation,thread.timenotation,thread.last_player,thread.last_player_url]
				row += [post.date_time,post.player_url,post.player,post.player_title,post.player_align,post.message]
				out.append(row)
		db.writerows(out)
		return True
	def view_forum(self):
		'''
		debugging method for testing forumscrape command
		'''
		threads = self.get_forumthreads()
		posts = self.fetch_thread(threads[0].url)
		for post in posts:
			print('date_time: %s' % post.date_time)
			print('player_url: %s' % post.player_url)
			print('player: %s' % post.player)
			print('player_title: %s' % post.player_title)
			print('player_align: %s' % post.player_align)
			print('message: %s' % post.message)
	def fetch_thread(self,thread_url):
		'''
		method used by do_forumscrape to pull data from individual threads
		'''
		allposts = []
		while thread_url:
			if len(thread_url.split('/')) != 4:
				thread_url = '/%s' % '/'.join(thread_url.split('/')[4:])
			pdata = self.parse(self._open(thread_url),'forumthread')
			thread_url = pdata['nextpage']
			posts = pdata['posts']
			if posts:
				allposts += posts
			else:
				break
		return allposts
	def get_forumthreads(self):
		'''
		method used by do_forumscrape to pull base info on all available threads
		'''
		allthreads = []
		get_thread = '/brotherhoodforum/overview'
		while get_thread:
			if len(get_thread.split('/')) == 7:
				get_thread = '/%s' % '/'.join(get_thread.split('/')[4:])
			tdata = self.parse(self._open(get_thread),'forumthreads')
			get_thread = tdata['nextpage']
			threads = tdata['threads']
			if threads:
				allthreads += threads
			else:
				break
		return allthreads
	def do_bhscrape(self,bhnum):
		'''
		main bhscrape method. dumps scraped information to a CSV file.
		'''
		print('Gathering brotherhood data...',end='')
		stdout.flush()
		bhdat = self.parse(self._open('/brotherhoodprofile/view/%s/members' % bhnum),'bhview')
		print('OK!')
		print('Brotherhood: %s' % bhdat['name'])
		print('Scraping brotherhood members:')
		maxvillage = 0
		for n,member in enumerate(bhdat['members']):
			name = bhdat['members'][member]['href'].split('/')[3]
			villages = self.get_villages(name)
			if len(villages) > 0:
				if len(villages) > maxvillage: maxvillage = len(villages)
				bhdat['members'][member]['villages'] = villages
				print('    [%s%d/%d] %s (%d village%s)' % (' '*(len(str(len(bhdat['members']))) - len(str(n + 1))),n + 1,len(bhdat['members']),member,len(villages),'' if len(villages) == 1 else 's'))
			else:
				print('Error! No village found for %s?' % member)
		print('Complete.')
		print('Saving...',end='')
		f = open('%s%s\\%s' % (os.getenv('HOMEDRIVE'),os.getenv('HOMEPATH'),'brotherhood<bhnum>.csv'.replace('<bhnum>',bhnum)),'w')
		cfile = csv.writer(f,lineterminator='\n')
		header = ['member','alignment']
		for n in range(maxvillage):
			vname = 'village%dname' % (n + 1,)
			vpop = 'village%dpop' % (n + 1,)
			vx = 'village%dx' % (n + 1,)
			vy = 'village%dy' % (n + 1,)
			header.append(vname)
			header.append(vpop)
			header.append(vx)
			header.append(vy)
		cfile.writerow(header)
		for member in bhdat['members']:
			if 'villages' not in bhdat['members'][member]: continue
			out = []
			out.append(member)
			out.append(bhdat['members'][member]['side'])
			for n,village in enumerate(bhdat['members'][member]['villages']):
				out.append(bhdat['members'][member]['villages'][village]['name'])
				out.append(bhdat['members'][member]['villages'][village]['pop'])
				out.append(village[0])
				out.append(village[1])
			if maxvillage - (n + 1) > 0:
				for extra in range((maxvillage - (n + 1)) * 4): out.append('')
			cfile.writerow(out)
		f.close()
		print('OK!')	
	def get_playerbh(self,player):
		'''
		method returns bh ID and bhname of the specified player.
		these values are None if the player does not exist or isn't in a brotherhood.
		'''
		profile = self.parse(self._open('/userprofile/index/%s' % player),'userprofile')
		return (profile['bhnum'],profile['bhname'])
	def _open(self,page,data=None):
		'''
		method used to request html pages from the torpia website
		'''
		opener = build_opener(HTTPCookieProcessor(self.cj))
		opener.addheaders = [('User-agent', __CLOAK_AGENT__)]
		try:
			retval = opener.open('%s%s' % (__URL__,page),data)
		except:
			retval = opener.open('%s%s' % (__URL__,page),data)
		return retval
	def parse(self,data,stage):
		'''
		method used to parse opened html pages from the torpia website
		new parsers can easily be added by updating the parsedict dictionary
		'''
		html = data.readlines()
		parsedict = {
			'login':ParseLogin,
			'forumthreads':ParseForum,
			'forumthread':ParseThread,
			'userprofile':ParseProfile,
			'stats':ParseStats,
			'map':ParseMap,
			'building':ParseBuilding,
			'village':ParseVillage,
			'bhview':ParseBHView,
		}
		parser = parsedict[stage]()
		for line in html:
			string = line
			try:
				string = string.replace('<th>No next villager</td>','<th>No next villager</th>') # Yet another hack for an html typo
				parser.feed(string)
			except HTMLParseError:
				'''
				This is a hack to bypass the html typo on the brotherhood lodge page. The offending HTML looks like this:
				<th>&nbsp;&nbsp;&nbsp;&nbsp;Functions</th
				Notice the distinct lack of a greater than bracket.
				'''
				pass
		return parser.retval

durconst = { # duration constant, to make time lengths more readable
    'year':60 * 60 * 24 * 365,
    'week':60 * 60 * 24 * 7,
    'day':60 * 60 * 24,
    'hour':60 * 60,
    'minute':60,
}
def secdur(t):
	# Converts seconds in to duration format string.
	t = int(t)
	out = ''
	if floor(t / durconst['year']) > 0:
		out = '%s %dyr%s' % (out,floor(t / durconst['year']),'s' if floor(t / durconst['year']) > 1 else '')
		t = t % durconst['year']
	if floor(t / durconst['week']) > 0:
		out = '%s %dwk%s' % (out,floor(t / durconst['week']),'s' if floor(t / durconst['week']) > 1 else '')
		t = t % durconst['week']
	if floor(t / durconst['day']) > 0:
		out = '%s %dday%s' % (out,floor(t / durconst['day']),'s' if floor(t / durconst['day']) > 1 else '')
		t = t % durconst['day']
	if floor(t / durconst['hour']) > 0:
		out = '%s %dhr%s' % (out,floor(t / durconst['hour']),'s' if floor(t / durconst['hour']) > 1 else '')
		t = t % durconst['hour']
	if floor(t / durconst['minute']) > 0:
		out = '%s %dmin%s' % (out,floor(t / durconst['minute']),'s' if floor(t / durconst['minute']) > 1 else '')
		t = t % durconst['minute']
	out = '%s %dsec' % (out,t)
	return out.strip()		

def get_torpia(u=None,p=None):
	'''
	This method somewhat simplifies asking the bot operator for a username/password, depending on if one was or wasn't provided in the source.
	'''
	u = __USERNAME__ if not u else u
	p = __PASSWORD__ if not p else p
	username = raw_input('Username: ') if not u else u
	password = getpass.getpass() if not p else p
	return Torpia(username,password)

def client_quit():
	'''
	method attempts to clean up all the active threads and shut them down properly, before quitting.
	'''
	global killauto,timers
	print('')
	print('Client shutting down.')
	killauto = True
	didcancel = False
	for t in timers:
		try:
			t.cancel()
			didcancel = True
		except:
			pass
	if didcancel: print('Automation cancelled.')	

def autoupgrade(torpbot,villages,bnames,threaded=False):
	'''
	I don't know how this works.
	I put a six-pack of redbull under my pillow and when I woke up the next day, the coding fairy had taken them and left me this.
	
	Actually, it's just messy. It deserves a re-write. No human should have to figure this crap out, to try and extend it.
	'''
	if killauto: return
	if threaded: print('')
	newlist = []
	for v in villages.split(','): newlist.append(v.strip())
	villages = newlist
	vdata = torpbot.get_vdata()
	vlist = vdata['villages']
	maxbuild = 2 if vdata['align'] == 'dark' else 3
	maxlevel = 20 if vdata['align'] == 'dark' else 30
	minwait = 60 * 60 * 24 * 365 * 5 # 5 Years
	foundcities = 0
	for v in vlist:
		if vlist[v] not in villages: continue
		foundcities += 1
		buildings = torpbot.fetch_buildings(v,False if len(vlist) > 1 else True)
		under,townwait = 0,60 * 60 * 24 * 365 * 5
		for b in buildings:
			if buildings[b]['time'] > 0:
				under += 1
				if buildings[b]['time'] < townwait: townwait = buildings[b]['time']
		if under >= maxbuild:
			if townwait < minwait: minwait = townwait
			continue
		for bname in bnames.split(','):
			keepgoing = False
			bhalt = True if len(bname.split(':')) > 1 and '!' in bname.split(':')[1] else False
			blevel = int(bname.split(':')[1].replace('!','')) if len(bname.split(':')) > 1 else maxlevel
			for b in buildings:
				if 'Under construction' in buildings[b]['name']: continue
				lowest = True
				for b2 in buildings:
					if buildings[b2]['name'].split('(')[0] == buildings[b]['name'].split('(')[0] and buildings[b2]['level'] < buildings[b]['level']:
						lowest = False
						break
				if not lowest: continue
				if bname.split(':')[0].lower() in buildings[b]['name'].lower() and buildings[b]['level'] < blevel:
					keepgoing = True
					break
			if not keepgoing: continue
			if len(b.split('/')) < 4: continue
			print('%s - Upgrading %s...' % (vlist[v],buildings[b]['name']),end='')
			stdout.flush()
			townwait = 1 if torpbot.upgrade(b) else 0
			print('OK!' if townwait > 0 else 'FAIL!')
			if townwait > 0 or bhalt: break
		if townwait < minwait: minwait = townwait
	if minwait == 60 * 60 * 24 * 365 * 5 or minwait == 0:
		minwait = (60 * 5) + random.randint(-60,60)
		print('Not enough resources. Will recheck in %s (%s)' % (secdur(minwait),strftime('%I:%M%p',localtime(time() + minwait))))
	else:
		if minwait > 1:
			print('Next upgrade: %s (%s)' % (secdur(minwait),strftime('%I:%M%p',localtime(time() + minwait))))
		else:
			print('Checking next wait time...')
	if threaded: print('> ',end='')
	stdout.flush()
	if not killauto and minwait > 0 and minwait < 60 * 60 * 24 * 365 * 5:
		t = Timer(minwait + 5,autoupgrade,[torpbot,','.join(villages),bnames,True])
		t.start()
		timers.append(t)

def autoproduce(torpia,goodvnames,bnames,threaded=False):
	'''
	This is just as bad as the autoupgrade function up there... because it was largely a copy/paste job!
	You've been warned.
	'''
	if killauto: return
	if threaded: print('')
	if not goodvnames or len(goodvnames) < 1:
		print('Invalid village entry. Autoproduce cancelled.')
		return
	if len(bnames.split(',')) < 1:
		print('Invalid building orders. Autoproduce cancelled.')
		return
	vdata = torpia.get_vdata()
	villages = vdata['villages']
	for v in villages:
		if villages[v] not in goodvnames: continue
		buildings = torpia.fetch_buildings(v,False if len(villages) > 1 else True)
		print(villages[v])
		for bname in bnames.split(','):
			for burl in buildings:
				if buildings[burl]['level'] == 0 or len(burl.split('/')) != 4: continue
				if not bname.lower() in buildings[burl]['name'].lower(): continue

				bnum = burl.split('/')[3]
				bstats = torpia.fetch_building(bnum)
				maxbatch = bstats['batchsize']*5
				badhas = False
				amnts = []
				for res in bstats['resources']:
					has,need = bstats['resources'][res]['has'],bstats['resources'][res]['need']
					if has < 1:
						badhas = res
						break
					canmake = int(floor(has / need))
					spacefor = bstats['capacity'] - bstats['making']
					canmake = spacefor if canmake > spacefor else canmake
					capped = maxbatch - bstats['making']
					amnt = capped if capped < canmake else canmake
					amnts.append(amnt)
				amnts.sort()
				if badhas:
					print('    Not enough %s to produce %ss at %s.' % (__RESTYPES__[res] if res in __RESTYPES__ else res,bstats['type'] if bstats['type'] else 'item',bstats['name']))
					continue
				if len(amnts) < 1 or amnts[0] < 1:
					print('    %s is full!' % (bstats['name'],))
					continue
				print('    Producing %d %ss for %s...' % (amnts[0],bstats['type'] if bstats['type'] else 'item',bstats['name'],))
				torpia.produce(burl,amnts[0],bstats['slot'],bstats['objectid'],bstats['batchtype'])
	if not killauto:		
		minwait = (60 * 60) + random.randint(-(60*30),60*30)
		print('Next production run: %s (%s)' % (secdur(minwait),strftime('%I:%M%p',localtime(time() + minwait))))
		t = Timer(minwait,autoproduce,[torpia,goodvnames,bnames,True])
		t.start()
		timers.append(t)
	if threaded: print('> ',end='')
	stdout.flush()		

def loadMacros():
	'''
	function looks for default macro file and attempts to load it. otherwise, returns blank dict.
	'''
	if os.path.isfile('%s%s\\%s' % (os.getenv('HOMEDRIVE'),os.getenv('HOMEPATH'),__MACROS__)):
		try:
			m = pickle.load(open('%s%s\\%s' % (os.getenv('HOMEDRIVE'),os.getenv('HOMEPATH'),__MACROS__)))
		except:
			m = {}
	else:
		m = {}
	return m
	
def saveMacros(m):
	'''
	function attempts to write provided dict to macro file
	'''
	try:
		pickle.dump(m,open('%s%s\\%s' % (os.getenv('HOMEDRIVE'),os.getenv('HOMEPATH'),__MACROS__),'w'))
	except:
		print('Fatal error! Could not access %s. Check permissions.' % (__MACROS__,))

'''
# These functions are no longer needed. used to load/save registration data.
def loadRegcodes():
	default = {'brotherhood':None,'usernames':{}}
	if os.path.isfile('%s%s\\%s' % (os.getenv('HOMEDRIVE'),os.getenv('HOMEPATH'),__REGCODES__)):
		try:
			r = pickle.load(open('%s%s\\%s' % (os.getenv('HOMEDRIVE'),os.getenv('HOMEPATH'),__REGCODES__)))
		except:
			r = default
	else:
		r = default
	return r
	
def saveRegcodes(r):
	try:
		pickle.dump(r,open('%s%s\\%s' % (os.getenv('HOMEDRIVE'),os.getenv('HOMEPATH'),__REGCODES__),'w'))
	except:
		print('Fatal error! Could not access %s. Check permissions.' % (__REGCODES__,))
'''
		
def askVillages(vdata,verb):
	'''
	function lets the bhTools user view and select from a list of villages.
	
	TODO: add allowance for "select all" option
	'''
	villages = vdata['villages']
	vnames = []
	for v in villages: vnames.append(villages[v])
	vnames.sort()
	vindex = {}
	for n,vname in enumerate(vnames):
		print('%d - %s' % (n+1,vname))
		vindex[str(n+1)] = vname
	vchoice = raw_input('Village numbers to %s (comma separated): ' % verb)
	vchoices = vchoice.split(',')
	goodv = []
	for v in vchoices:
		if v.isdigit() and int(v) <= len(vnames) and v not in goodv: goodv.append(v)
	if len(goodv) < 1: return False
	goodv.sort()
	goodvnames = []
	for v in goodv: goodvnames.append(vindex[v])
	return goodvnames
		
def main():
	'''
	This function is the bot client.
	All commands are entered and parsed here, before being sent to the Torpia class for execution.
	'''
	torpia,oldtorpia = None,None
	print('Brotherhood Tools %s' % __VERSION__)
	print('')
	print('Type HELP for a list of commands.')
	'''
	# No longer needed
	regcodes = loadRegcodes()
	saveRegcodes(regcodes)
	'''
	macros = loadMacros()
	saveMacros(macros)	
	
	try:
		while True:
			cmd = raw_input('> ')
			if cmd.split()[0].upper() in ('Q','QUIT'):
				client_quit()
				break
			elif cmd.split()[0].upper() in ('AP','AUTOPRODUCE'):
				if not torpia:
					print(__LOGIN_ERROR__)
					continue
				vdata = torpia.get_vdata()
				goodvnames = askVillages(vdata,'produce')
				if not goodvnames:
					print('Cancelled.')
					continue				
				bnames = raw_input('Building names to produce (comma separated, macros allowed): ')
				for m in macros: bnames = bnames.replace('<%s>' % (m,),macros[m])
				if len(bnames.split(',')) > 0: print('Produce: %s' % (bnames,));
				autoproduce(torpia,goodvnames,bnames)				
			elif cmd.split()[0].upper() in ('AUTOUPGRADE','AU'):
				if not torpia:
					print(__LOGIN_ERROR__)
					continue
				vdata = torpia.get_vdata()
				villages = askVillages(vdata,'upgrade')
				if not villages:
					print('Cancelled.')
					continue
				villages = ','.join(villages)
				bname = raw_input('Names of buildings to auto-upgrade (comma separated): ')
				if bname and villages:
					for m in macros: bname = bname.replace('<%s>' % (m,),macros[m])
					print('Autoupgrading %s: %s' % (villages,bname))
					killauto = False
					autoupgrade(torpia,villages,bname)
				else:
					print('Cancelled.')
					continue
			elif cmd.split()[0].upper() in ('NOAUTO','KILLAUTO'):
				killauto = True
				for t in timers:
					try:
						t.cancel()
					except:
						pass
				print('Automation cancelled.')
			elif cmd.split()[0].upper() in ('CONSTRUCTION','C'):
				if not torpia:
					print(__LOGIN_ERROR__)
					continue
				print('Fetching village data...',end='')
				stdout.flush()
				construct = torpia.construction()
				print('OK!')
				if len(construct) < 1:
					print('No buildings are under construction.')
					continue
				for v in construct:
					print('%s: %s' % (v,'%s (%s)' % (construct[v]['name'],secdur(construct[v]['time'])) if construct[v]['time'] > 0 else construct[v]['name']),end='')
					if construct[v]['time'] > 0:
						print(' (%s)' % (strftime('%I:%M%p',localtime(time() + construct[v]['time'])),))
					else:
						print('')				
			elif cmd.split()[0].upper() == 'AMULETSCAN':
				if not torpia:
					print(__LOGIN_ERROR__)
					continue
				only_brotherless = True if raw_input('Only scrape brotherless players?\n[y,N]> ').upper() == 'Y' else False
				noamulets = 0
				bhamulets = {}
				playerdb = {}
				bhurls = {}
				print('Scanning player list...',end='')
				stdout.flush()
				for n in range(torpia.get_statmaxpages()):
					pageamulets = 0
					page = torpia.get_statpage(n + 1)
					for player in page:
						if page[player]['amulets'] < 1: continue
						pageamulets += page[player]['amulets']
						if only_brotherless and page[player]['bhname'] != '-': continue
						if page[player]['bhname'] == '-':
							page[player]['bhname'] = '(No Brotherhood)'
							bhname = '(No Brotherhood)'
						else:
							bhname = page[player]['bhname']
						bhurls[bhname] = page[player]['bhurl']
						playerdb[player] = page[player]
						if bhname not in bhamulets: bhamulets[bhname] = 0
						bhamulets[bhname] += page[player]['amulets']
					if not pageamulets:
						noamulets += 1
						print('!',end='')
					else:
						noamulets = 0
						print('.',end='')
					stdout.flush()
					if noamulets == 10: break
				print('OK!')
				print('Writing detail report... (this may take a few minutes!) ',end='')
				stdout.flush()
				sortamulets = sorted(bhamulets.items(),key=lambda x:x[1],reverse=True)
				f = open('%s%s\\amulets.html' % (os.getenv('HOMEDRIVE'),os.getenv('HOMEPATH')),'w')
				f.write('<ul class="players">\n')
				n = 0
				for bh,amulets in sortamulets:
					if not bhurls[bh]:
						f.write('\t<li>%s: %d\n' % (bh,amulets))
					else:
						f.write('\t<li><a href="%s%s">%s</a>: %d\n' % (__URL__,bhurls[bh],bh,amulets))
					bhplayers = {}
					f.write('\t\t<ul class="players">\n')
					for player in playerdb:
						if playerdb[player]['bhname'] == bh:
							bhplayers[playerdb[player]['name']] = playerdb[player]['amulets']
					sortplayers = sorted(bhplayers.items(),key=lambda x:x[1],reverse=True)
					for p,a in sortplayers:
						f.write('\t\t<li><a href="%s/userprofile/index/%s">%s</a>: %d<ul>\n' % (__URL__,p,p,a))
						try:
							villages = torpia.get_villages(p)
							for v in villages:
								x,y = v
								f.write('\t\t\t<li><a href="%s/tile/tile/%s/%s">%s (%s,%s)</a></li>\n' % (__URL__,x,y,villages[v]['name'],x,y))
						except:
							f.write('\t\t\t<li>SCRAPE ERROR!</li>\n')
						f.write('\t\t</ul></li>\n')
					f.write('\t\t</ul>\n')
					f.write('\t</li>\n')
					f.flush()
				f.write('</ul>\n')
				f.flush()
				f.close()
				print('OK!')
				'''
			# No longer needed.
			elif cmd.split()[0].upper() in ('REGISTER','REG'):
				print('Registration:')
				print('    1) Brotherhood')
				print('    2) Username')
				which = raw_input('[1,2]> ')
				if which == '1':
					if regcodes['brotherhood'] and raw_input('You have already entered a brotherhood code. Overwrite?\n[y,N]> ').upper() != 'Y':
						print('Registration cancelled.')
						continue
					regcodes['brotherhood'] = raw_input('Code: ')
				elif which == '2':
					reguser = raw_input('Username: ').upper()
					if reguser in regcodes['usernames'] and raw_input('You have already entered a code for this username. Overwrite?\n[y,N]> ').upper() != 'Y':
						print('Registration cancelled.')
						continue
					regcodes['usernames'][reguser] = raw_input('Code: ')
				if which in ('1','2'):
					try:
						saveRegcodes(regcodes)
						print('Registration saved.')
					except:
						print('Failed to save registration data.')
				'''
			elif cmd.split()[0].upper() in ('BHSCRAPE','SCRAPEBH'):
				if not torpia:
					print(__LOGIN_ERROR__)
					continue
				bhnum = raw_input('Brotherhood index #: ')
				if not bhnum or not bhnum.isdigit():
					print('Cancelled.')
					continue
				torpia.do_bhscrape(bhnum)
			elif cmd.split()[0].upper() == 'BH':
				if not torpia:
					print(__LOGIN_ERROR__)
					continue
				bhnum,bhname = torpia.get_playerbh(torpia.username)
				if bhname:
					print('%s (%s)' % (bhname,bhnum))
				else:
					print('You are not in a brotherhood.')
			elif cmd.split()[0].upper() in ('FORUMSCRAPE','FS'):
				tmplogin = None
				if not torpia:
					tmp = raw_input('You are not logged in. Would you like to use a temporary login?\n[Y,n]> ')
					if tmp.upper() == 'N':
						print('Forumscrape cancelled.')
						continue
					u = cmd.split()[1] if len(cmd.split()) > 1 else None
					p = ' '.join(cmd.split()[2:]) if len(cmd.split()) > 2 else None
					tmplogin = get_torpia(u,p)
					print('Logging in...',end='')
					stdout.flush()
					if tmplogin.do_login():
						print('OK!')
						print('Scanning profile...',end='')
						stdout.flush()
						tmplogin.bhnum,tmplogin.bhname = tmplogin.get_playerbh(tmplogin.username)
						print('OK!')						
					else:
						print('Failed!')
						continue
				else:
					tmplogin = torpia
				if not tmplogin.bhname:
					print('You are not in a brotherhood.\nForumscrape cancelled.')
					tmplogin = None
					continue
				outfile = ' '.join(cmd.split()[1:]) if len(cmd.split()) > 1 else __OUTFILE__
				print('Scraping forum...',end='')
				stdout.flush()
				print('OK!' if tmplogin.do_forumscrape(outfile) else 'Failed!')
				tmplogin = None
			elif cmd.split()[0].upper() == 'HELP':
				if len(cmd.split()) < 2:
					print('Commands: LOGIN, QUIT, HELP, FORUMSCRAPE, AMULETSCAN, BH, BHSCRAPE, REGISTER, CONSTRUCTION, AUTOUPGRADE, MACRO, AUTOPRODUCE, KILLAUTO')
			elif cmd.split()[0].upper() == 'MACRO':
				parm = cmd.split()[1:]
				if len(parm) < 1:
					print('Macro commands: LIST, ADD, DEL, EDIT')
					continue
				if parm[0].upper() == 'LIST':
					if len(macros) < 1:
						print('No macros exist. MACRO ADD to create one.')
						continue
					print('%d macro%s:' % (len(macros),'' if len(macros) == 1 else 's'))
					list = macros.keys()
					list.sort()
					for m in list:
						print('    %s: %s' % (m,macros[m]))
				elif parm[0].upper() == 'ADD':
					macro_name = raw_input('Macro name: ') if len(parm) < 2 else parm[1]
					macro_name = macro_name.split()[0] if macro_name else ''
					macro_data = raw_input('Building orders: ') if len(parm) < 3 else ' '.join(parm[2:])
					if not macro_name:
						print('Cannot add macro: macro name missing.')
						continue
					elif macro_name in macros:
						print('Cannot add macro: macro name already exists. Use MACRO EDIT instead.')
						continue
					elif not macro_data:
						print('Cannot add macro: building orders missing.')
						continue
					else:
						macros[macro_name] = macro_data
						print('Macro added.')
						saveMacros(macros)
				elif parm[0].upper() in ('DEL','REM','REMOVE','DELETE',):
					macro_name = raw_input('Macro name: ') if len(parm) < 2 else parm[1]
					macro_name = macro_name.split()[0] if macro_name else ''
					if not macro_name:
						print('Cannot delete macro: macro name missing.')
						continue
					elif macro_name not in macros:
						print('Cannot delete macro: macro does not exist.')
					else:
						del macros[macro_name]
						print('Macro deleted.')
						saveMacros(macros)
				elif parm[0].upper() == 'EDIT':
					macro_name = raw_input('Macro name: ') if len(parm) < 2 else parm[1]
					macro_name = macro_name.split()[0] if macro_name else ''
					macro_data = raw_input('Building orders: ') if len(parm) < 3 else ' '.join(parm[2:])
					if not macro_name:
						print('Cannot edit macro: macro name missing.')
						continue
					elif macro_name not in macros:
						print('Cannot edit macro: macro does not exist.')
					elif not macro_data:
						print('Cannot edit macro: building orders missing.')
						continue
					else:
						macros[macro_name] = macro_data
						print('Macro edited.')
						saveMacros(macros)					
			elif cmd.split()[0].upper() == 'LOGIN':
				if torpia: oldtorpia = torpia
				u = cmd.split()[1] if len(cmd.split()) > 1 else None
				p = ' '.join(cmd.split()[2:]) if len(cmd.split()) > 2 else None
				torpia = get_torpia(u,p)
				print('Logging in...',end='')
				stdout.flush()
				if torpia.do_login():
					print('OK!')
					print('Scanning profile...',end='')
					stdout.flush()
					torpia.bhnum,torpia.bhname = torpia.get_playerbh(torpia.username)
					print('OK! (%s)' % (torpia.bhname,))
					'''
					# Registration codes no longer needed.
					goodreg = False
					print('Checking registration:')
					if torpia.bhname and regcodes['brotherhood']:
						if checkRegCode(regcodes['brotherhood'],torpia.username,torpia.bhnum):
							print('    Brotherhood verified!')
							goodreg = True
						else:
							print('    Invalid brotherhood code!')
					elif not torpia.bhname:
						print('    Skipping brotherhood check.')
					elif not regcodes['brotherhood']:
						print('    No brotherhood registration found.')
					if not goodreg and torpia.username.upper() in regcodes['usernames']:
						if checkRegCode(regcodes['usernames'][torpia.username.upper()],torpia.username,torpia.bhnum):
							print('    Username verified!')
							goodreg = True
						else:
							print('    Invalid username code!')
					elif not goodreg:
						print('    No username registration found.')
					if goodreg:
						print('Registration OK!')
					else:
						print('Registration failed!')
						torpia = None
						if oldtorpia: torpia = oldtorpia
					'''
				else:
					print('Failed!')
					torpia = None
					if oldtorpia: torpia = oldtorpia
				oldtorpia = None
			else:
				print('Unknown command. Type HELP for a list of commands.')
	except KeyboardInterrupt:
		client_quit()

if __name__ == '__main__':
	timers = []
	killauto = False
	main()