#!/usr/bin/env python

# The contents of this file are subject to the BitTorrent Open Source License
# Version 1.1 (the License).  You may not copy or use this file, in either
# source code or executable form, except in compliance with the License.  You
# may obtain a copy of the License at http://www.bittorrent.com/license/.
#
# Software distributed under the License is distributed on an AS IS basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the License
# for the specific language governing rights and limitations under the
# License.

# Written by Petru Paler


class BTFailure(Exception):
	pass

def decode_int(x, f):
	f += 1
	newf = x.index('e', f)
	n = int(x[f:newf])
	if x[f] == '-':
		if x[f + 1] == '0':
			raise ValueError
	elif x[f] == '0' and newf != f+1:
		raise ValueError
	return (n, newf+1)

def decode_string(x, f):
	colon = x.index(':', f)
	n = int(x[f:colon])
	if x[f] == '0' and colon != f+1:
		raise ValueError
	colon += 1
	return (x[colon:colon+n], colon+n)

def decode_list(x, f):
	r, f = [], f+1
	while x[f] != 'e':
		v, f = decode_func[x[f]](x, f)
		r.append(v)
	return (r, f + 1)

def decode_dict(x, f):
	r, f = {}, f+1
	while x[f] != 'e':
		k, f = decode_string(x, f)
		r[k], f = decode_func[x[f]](x, f)
	return (r, f + 1)

decode_func = {}
decode_func['l'] = decode_list
decode_func['d'] = decode_dict
decode_func['i'] = decode_int
decode_func['0'] = decode_string
decode_func['1'] = decode_string
decode_func['2'] = decode_string
decode_func['3'] = decode_string
decode_func['4'] = decode_string
decode_func['5'] = decode_string
decode_func['6'] = decode_string
decode_func['7'] = decode_string
decode_func['8'] = decode_string
decode_func['9'] = decode_string

def bdecode(x):
	try:
		r, l = decode_func[x[0]](x, 0)
	except (IndexError, KeyError, ValueError):
		raise BTFailure("not a valid bencoded string")
	if l != len(x):
		raise BTFailure("invalid bencoded value (data after valid prefix)")
	return r

from types import StringType, IntType, LongType, DictType, ListType, TupleType

class Bencached(object):

	__slots__ = ['bencoded']

	def __init__(self, s):
		self.bencoded = s

def encode_bencached(x,r):
	r.append(x.bencoded)

def encode_int(x, r):
	r.extend(('i', str(x), 'e'))

def encode_bool(x, r):
	if x:
		encode_int(1, r)
	else:
		encode_int(0, r)

def encode_string(x, r):
	r.extend((str(len(x)), ':', x))

def encode_list(x, r):
	r.append('l')
	for i in x:
		encode_func[type(i)](i, r)
	r.append('e')

def encode_dict(x,r):
	r.append('d')
	ilist = x.items()
	ilist.sort()
	for k, v in ilist:
		r.extend((str(len(k)), ':', k))
		encode_func[type(v)](v, r)
	r.append('e')

encode_func = {}
encode_func[Bencached] = encode_bencached
encode_func[IntType] = encode_int
encode_func[LongType] = encode_int
encode_func[StringType] = encode_string
encode_func[ListType] = encode_list
encode_func[TupleType] = encode_list
encode_func[DictType] = encode_dict

try:
	from types import BooleanType
	encode_func[BooleanType] = encode_bool
except ImportError:
	pass

def bencode(x):
	r = []
	encode_func[type(x)](x, r)
	return ''.join(r)



if __name__ == '__main__':
	from socket import gethostbyname, gaierror
	from urlparse import urlparse
	import sys

	# CLI
	headers = {'-r': 'Referer', '-c': 'Cookie'}
	argz, key = dict(), 'url'
	for arg in sys.argv[1:]:
		if not arg: pass
		elif arg in headers: key = headers[arg]
		elif arg == '-d': key = 'dest'
		elif key is None:
			raise ValueError('Unrecognized value purpose: {0}'.format(arg))
		else: argz[key], key = arg, None

	# Get source
	if not argz: torrent = sys.stdin.read()
	else:
		import urllib2
		source = urllib2.Request(argz['url'])
		for arg in headers.itervalues():
			try: source.add_header(arg, argz[arg])
			except KeyError: pass
		torrent = urllib2.urlopen(source).read()

	# URL checker
	def trak_check(trak):
		if not trak: return False
		try: ip = gethostbyname(urlparse(trak).netloc.split(':', 1)[0])
		except gaierror: return True # prehaps it will resolve later on
		else: return ip not in ('127.0.0.1', '0.0.0.0')

	# Actual processing
	torrent = bdecode(torrent)
	for tier in list(torrent['announce-list']):
		for trak in list(tier):
			if not trak_check(trak):
				tier.remove(trak)
				# print >>sys.stderr, 'Dropped:', trak
		if not tier: torrent['announce-list'].remove(tier)
	# print >>sys.stderr, 'Result:', torrent['announce-list']
	if not trak_check(torrent['announce']):
		torrent['announce'] = torrent['announce-list'][0][0]
	torrent = bencode(torrent)

	# Output
	if 'dest' not in argz or 'url' not in argz: dest = sys.stdout
	else:
		from os.path import basename, join, exists
		from string import letters, digits
		from urllib import unquote_plus
		name = set(letters + digits + r'!"#$%&\'()*+,-.<=>@[]_~')
		name = join(argz['dest'], unquote_plus(''.join(
			(chr if chr in name else '_') for chr in basename(urlparse(argz['url']).path) )))
		if exists(name):
			from hashlib import md5
			if md5(open(name).read()).digest() == md5(torrent).digest(): sys.exit() # same thing
			name = join(argz['dest'], '{0}.torrent'.format(md5(argz['url']).hexdigest()))
		# print >>sys.stderr, 'Name:', name
		dest = open(name, 'w')
	dest.write(torrent)

