#!/usr/bin/python2.3 ######################################################################### # # btfriend.py - Copyright (C) 1992,2004 Bisqwit (http://iki.fi/bisqwit/) # # This script periodically (by default, once in 10 minutes) checks # for files that need to be better shared on the given Bittorrent tracker. # # You don't need to download the .torrent files yourself. # # It assumes you have a btlaunchmany* client running in the current # working directory. # # Version 1.5.0 # ######################################################################### from BitTorrent.bencode import bencode, bdecode from BitTorrent.zurllib import urlopen, quote from binascii import b2a_hex from time import sleep, ctime, time from sha import sha import os # for listdir, remove. import re # for sub, search. # Whose files to share TRACKER1 = 'http://tracker.tasvideos.org:6969'; TRACKER2 = 'http://tracker.tasvideos.org:6967'; # How often to juggle PERIOD = 40 # Threshold for sharing THRESHOLD = 0 # Max number of files to share at given time MAX_SHARE = 5 # Ignore these files: IGNORE_FILES = ['youkai-timeattack-morimoto.avi'] # OR don't ignore anything #IGNORE_FILES = [] # Do you want to automatically delete unsupported files? DELETE_UNSUPPORTED = False DELETE_FILE_EXT = 'avi' which_tracker = {} for name in IGNORE_FILES: print 'Deleting ', name open(name, 'wb') os.remove(name) def LoadBittorrentTrackerData(): filelist = {} # TODO: Merge datas, return merged data # TODO: Create which_tracker data1 = bdecode(urlopen(TRACKER1 + '/scrape').read()) filelist.update(data1['files']) for key,filedata in data1['files'].items(): which_tracker[key] = TRACKER1 data2 = bdecode(urlopen(TRACKER2 + '/scrape').read()) filelist.update(data2['files']) for key,filedata in data2['files'].items(): which_tracker[key] = TRACKER2 return {'files': filelist} torrentfile_filter = re.compile('\\.torrent$') def TorrentToFile(file): return torrentfile_filter.sub('', file) def FileToTorrent(file): return file + '.torrent' # Strip malicious characters off the filename. # Important if you don't trust the server. safety_filter = re.compile('[*/]') def SafetyFilename(filedata): # Ignore anonymous files if not filedata.has_key('name'): return '' return safety_filter.sub('', filedata['name']) def LoadOurSharingList(): return dict([(TorrentToFile(file), file) for file in os.listdir('.') if torrentfile_filter.search(file)]) def LoadOurFileList(ext): filter = re.compile('\\.' + ext + '$') return list([file for file in os.listdir('.') if filter.search(file)]) def CalcPrio(filedata, our_files): # Number of downloaders (are also somewhat uploaders) dl = filedata['incomplete'] # Number of seeds (who are the valuable uploaders) ul = filedata['complete'] name='' if filedata.has_key('name'): name = filedata['name'] #print "dl(%d)ul(%d)name(%s)\n" % (dl,ul,name) # Ignore anonymous files if not filedata.has_key('name'): return -100 # Ignore certain files if filedata['name'] in IGNORE_FILES: return -100 if our_files.has_key(filedata['name']): # We must calculate the situations as if # we weren't there, in order to see what # we should be doing. # FIXME: Assuming we're a seed, # which isn't necessarily the case. if ul>0: ul=ul-1 else: dl=dl-1 if ul<=0: if dl==0: return 0 return 10000 + dl # The magic formula - should give big values # for torrents that are in survival trouble # and small values for files that are well seeded. return (0.6 * (dl / ul**1.2)) ** 2 def NeedTorrentDownload(key, file): #if(file == 'NTSCDEMO-castlevania-tas.avi'): # return False torrentfn = FileToTorrent(file) try: response = open(torrentfn, 'rb').read() except IOError, e: # Torrent needs downloading return True # Check if the file matches what the server told us response = bdecode(response) info = response['info'] infohash = sha(bencode(info)).digest() if key == infohash: # Torrent is ok return False # Torrent has been updated # - Must redownload torrent # - Must erase existing file print 'Deleting ', torrentfn os.remove(torrentfn) print 'Deleting ', file os.remove(file) return True def DownloadTorrent(key, file): torrentfn = FileToTorrent(file) tracker = which_tracker[key] url = tracker + '/file?info_hash=' + quote(key) print url try: response = urlopen(url).read() # print '- Downloaded ', torrentfn open(torrentfn, 'wb').write(response) except: a=1 def GetImportantList(response, our_files): # Select files which have enough priority. #for key,filedata in response['files'].items(): # print "%s: %d\n" % (quote(key), CalcPrio(filedata,our_files)) files = filter(lambda (prio,key,file): prio >= THRESHOLD, # Collect the priority and fields from the response. [(CalcPrio(filedata,our_files), key, SafetyFilename(filedata)) for key,filedata in response['files'].items()]) # Sort them highest-first and prepare them so # that it can be converted into a dictionary. files.sort() files.reverse() files = [(file,(key,prio)) for prio,key,file in files] # Create a dictionary of the top MAX_SHARE of them. return (dict(files[:MAX_SHARE]), # wanted dict(files[MAX_SHARE:])) # unwanted def DeleteUnsupportedFile(name): print 'Deleting unsupported file %s' % name os.remove(name) def DeleteUnsupportedFiles(ext, response): # What we have files=LoadOurFileList(ext) found=False # What we should have supported=list() for key,filedata in response['files'].items(): if filedata.has_key('name'): supported.append(filedata['name']) # Check length in case the tracker was down or other problem if len(supported) > 0: # Now delete files that are not supported for name in files: if name not in supported: DeleteUnsupportedFile(name) def Unshare(file, prio): print 'Unsharing %s (prio=%s)' % (file,prio) torrentfn = FileToTorrent(file) os.remove(torrentfn) # The actual file will be kept on the disk # to avoid redownloading. Maybe ancient files # could be deleted automatically... Well, it's # currently left to be done by the user. def Notshare(file, prio): # Just informal #print 'Not sharing %s (prio=%s)' % (file,prio) dummy = 0 def Share(file, key, prio): print 'Sharing %s (prio=%.2f)' % (file,prio) if NeedTorrentDownload(key, file): DownloadTorrent(key, file) def CheckSharing(): # Find out what we're sharing now # Must be done before priority calculations. our_files = LoadOurSharingList() # See what's going on at the server response = LoadBittorrentTrackerData() if DELETE_UNSUPPORTED: DeleteUnsupportedFiles(DELETE_FILE_EXT, response) # Get a list of important files wanted = {} unwanted = {} wanted,unwanted = GetImportantList(response, our_files) #wanted['NTSCDEMO-castlevania-tas.avi'] = ('',10000) #del unwanted['NTSCDEMO-castlevania-tas.avi'] # Remove files we don't want to share now for file in our_files: if not wanted.has_key(file): prio = '?' if unwanted.has_key(file): prio = '%.2f' % unwanted[file][1] Unshare(file, prio) for file,(key,prio) in unwanted.items(): if not file in our_files: Notshare(file, prio) # Ensure we're sharing what we need to for file,(key,prio) in wanted.items(): Share(file, key, prio) while True: print '-------------------------------------------------' print 'Rechecking sharing ', ctime(time()) print '-------------------------------------------------' #try CheckSharing() print '---- Sleeping %d seconds' % PERIOD sleep(PERIOD)