#!/usr/bin/python # Copyright 2007-2008 Robin H. Johnson # Distributed under the terms of the GNU General Public License v2 only. # # This is a small hack-script that allows detailed editing of the contents of a # .torrent file, using the official BitTorrent python. # # It will NOT actually change the file unless you pass --save! # # - If you want to remove a key, you need to pass a value of 'DELETE-ME' # - If you want to set an integer key to the present time, pass a value of # 'NOW' # - If you want to pass a list, it must be parsable by the Python 'eval'! # List example: # changetorrent-console --url_list '["a","b"]' # - If you need to do something really crazy, like pass in a bdict, # then you should use extra_data_key and extra_data_raw, and pass in a string # that iswell-formed Bencoding. # - Beware that some changes MAY affect the infohash! The infohash is the SHA1 # checksum of the contents of the top-level 'info' key! # - If you know of other reasonably standarized keys, I invite you to email me # their details, so that I can document them in here, and avoid the need for # extra_data all the time. app_name = "BitTorrent" from BitTorrent.translation import _ from os.path import basename import sys import locale import copy import time from sha import * from os.path import * from BitTorrent import version from BTL.bencode import * from BitTorrent.defaultargs import get_defaults from BitTorrent import configfile from BitTorrent.parseargs import parseargs, printHelp from BitTorrent import BTFailure defaults = [] defaults.extend([ ('creation_date', 'OPTIONAL', _("unix timestamp of .torrent creation date")), ('modification_date', 'OPTIONAL', _("unix timestamp of .torrent modification date")), ('locale', 'OPTIONAL', _("locale for encoding")), ('title', 'OPTIONAL', _("optional human-readable title for entire .torrent")), ('comment', 'OPTIONAL', _("optional human-readable comment to put in .torrent")), ('tracker_list', 'OPTIONAL', _("list of tracker urls")), ('extra_data_key', 'OPTIONAL', _("key for extra data to include in the .torrent data")), ('extra_data_string', 'OPTIONAL', _("string of extra data to include in the .torrent data")), ('extra_data_integer', 'OPTIONAL', _("integer-form extra data to include in the .torrent data")), ('extra_data_raw', 'OPTIONAL', _("bencoded extra data to include in the .torrent data")), ('created_by', 'OPTIONAL', _("specify the creator of the .torrent")), ('publisher', 'OPTIONAL', _("specify the publisher of the .torrent")), ('publisher_url', 'OPTIONAL', _("specify a URL for the publisher of the .torrent")), ('secure_publisher', 'OPTIONAL', _("specify the publisher of the .torrent (inside info)")), ('secure_publisher_url', 'OPTIONAL', _("specify a URL for the publisher of the .torrent (inside info)")), ('url_list', 'OPTIONAL', _("specify set of URLs for HTTP seeding")), ('encoding', 'OPTIONAL', _("specify an explicit encoding")), ('save',False, _("Save the .torrent after changing")), ('verbose',False, _("Print changes as they are made")), ('display',False, _("Display the metainfo (after any changes)")), ('display_raw_pieces',False, _("Include the raw pieces output in the metainfo display")), ]) processing = { 'modification_date' : [int,['modification date']], 'creation_date' : [int,['creation date']], 'comment' : [str,['comment']], 'locale' : [str,['locale']], 'created_by' : [str,['created by']], 'encoding' : [str,['encoding']], 'publisher' : [str,['publisher']], 'publisher_url' : [str,['publisher-url']], 'secure_publisher' : [str,['info','publisher']], 'secure_publisher_url' : [str,['info','publisher-url']], 'title' : [str,['title']], 'tracker_list' : [list,['announce-list']], 'url_list' : [list,['url-list']], } defconfig = dict([(name, value) for (name, value, doc) in defaults]) del name, value, doc def getpos(metainfo,loc): l = loc[0] if len(loc) > 1: return getpos(metainfo[l],loc[1:]) else: if l in metainfo: return metainfo[l] else: return None def setpos(metainfo,loc,value): l = loc[0] if len(loc) > 1: setpos(metainfo[l],loc[1:],value) else: if l in metainfo and (value == 'DELETE-ME' or value is None): del metainfo[l] else: metainfo[l] = value return metainfo if __name__ == '__main__': config, args = configfile.parse_configuration_and_args(defaults, 'changetorrent-console', sys.argv[1:], minargs=1) le = locale.getpreferredencoding() for name in config.keys(): if config[name] == "OPTIONAL": config[name] = None if name in processing and processing[name][0] == int and config[name] == "NOW": config[name] = int(time.time()) #print config c = 0 if config['extra_data_string']: c += 1 if config['extra_data_integer']: c += 1 if config['extra_data_raw']: c += 1 if c > 1: print _("You must only specify a single type of extra_data!") sys.exit(2) if len(args) < 1: print _("Usage: %s [OPTIONS] [TORRENTFILE [TORRENTFILE ... ] ]") % basename(sys.argv[0]) print sys.exit(2) # common exit code for syntax error for f in args[0:]: h = open(f, 'rb') metainfo = bdecode(h.read()) h.close() metainfo_initial = copy.deepcopy(metainfo) for k in processing.keys(): if config[k]: (type,loc) = processing[k] l = False l_is_set = False if config[k] == "DELETE-ME": l = None l_is_set = True elif type == str: l = config[k] l_is_set = True elif type == int: l = config[k] l_is_set = True elif type == list: try: l = eval(config[k]) l_is_set = True except SyntaxError, e: print "Unable to parse input for list: %s" % e raise e if not isinstance(l,list): print "%s input is not a list!" % (k) sys.exit(2) if not l_is_set: print "unknown error handling input for %s" % (k) sys.exit(2) if getpos(metainfo,loc) != l: if config['verbose']: print 'Changing "%s" from "%s" to "%s"' % (loc,getpos(metainfo,loc),l) metainfo = setpos(metainfo,loc,l) if metainfo != metainfo_initial: be = bencode(metainfo) if config['save']: if config['verbose']: print 'Saving '+f h = open(f, 'wb') h.write(be) h.close() if config['display']: if not config['display_raw_pieces']: metainfo['info']['pieces'] = 'HIDDEN' print metainfo #if metainfo['announce'] != argv[1]: # print _("old announce for %s: %s") % (f, metainfo['announce']) # metainfo['announce'] = argv[1] # h = open(f, 'wb') # h.write(bencode(metainfo)) # h.close() # vim: expandtab: