Skip to content

Instantly share code, notes, and snippets.

@francoism90
Last active April 12, 2024 21:16
Show Gist options
  • Save francoism90/4db9efa5af546d831ca47208e58f3364 to your computer and use it in GitHub Desktop.
Save francoism90/4db9efa5af546d831ca47208e58f3364 to your computer and use it in GitHub Desktop.
libtorrent - get seeds/peers + metadata without downloading (DHT/magnet)
#!/usr/bin/env python3
import libtorrent as lt
import time
import json
import os
def write_json(path, contents):
with open(path, 'w', encoding='utf-8') as f:
json.dump(contents, f, default=str, indent=4, sort_keys=True)
def add_torrent(ses, filename, options):
atp = lt.add_torrent_params()
if filename.startswith('magnet:'):
atp = lt.parse_magnet_uri(filename)
atp.save_path = options.save_path
atp.storage_mode = lt.storage_mode_t.storage_mode_sparse
atp.flags |= lt.torrent_flags.duplicate_is_error \
| lt.torrent_flags.auto_managed \
| lt.torrent_flags.duplicate_is_error \
| lt.torrent_flags.upload_mode
ses.async_add_torrent(atp)
def get_torrent_info(info):
attributes = [
'name',
'comment',
'creator',
'total_size',
'piece_length',
'num_pieces',
'info_hash',
'num_files',
'priv',
'creation_date',
]
entry = {}
for attribute in attributes:
entry[attribute] = getattr(info, attribute, None)()
return entry
def get_file_info(file):
attributes = [
'path',
'symlink_path',
'offset',
'size',
'mtime',
'filehash',
'pad_file',
'hidden_attribute',
'executable_attribute',
'symlink_attribute',
]
entry = {}
for attribute in attributes:
entry[attribute] = getattr(file, attribute, None)
return entry
def main():
from optparse import OptionParser
parser = OptionParser()
parser.add_option('-p', '--port', type='int', help='set listening port')
parser.add_option(
'-i', '--listen-interface', type='string',
help='set interface for incoming connections', )
parser.add_option(
'-o', '--outgoing-interface', type='string',
help='set interface for outgoing connections')
parser.add_option(
'-d', '--max-download-rate', type='float',
help='the maximum download rate given in kB/s. 0 means infinite.')
parser.add_option(
'-u', '--max-upload-rate', type='float',
help='the maximum upload rate given in kB/s. 0 means infinite.')
parser.add_option(
'-c', '--connections_limit', type='int',
help='the global limit on the number of connections opened.')
parser.add_option(
'-s', '--save-path', type='string',
help='the path where the downloaded file/folder should be placed.')
parser.add_option(
'-r', '--proxy-host', type='string',
help='sets HTTP proxy host and port (separated by \':\')')
parser.add_option(
'-t', '--timeout', type='int',
help='the number of seconds to wait to receive any data from the torrents.')
parser.set_defaults(
port=6881,
listen_interface='0.0.0.0',
outgoing_interface='',
max_download_rate=0,
max_upload_rate=0,
connections_limit=400,
save_path='.',
proxy_host='',
timeout=90
)
(options, args) = parser.parse_args()
if options.port < 0 or options.port > 65525:
options.port = 6881
options.max_upload_rate *= 1000
options.max_download_rate *= 1000
if options.max_upload_rate <= 0:
options.max_upload_rate = -1
if options.max_download_rate <= 0:
options.max_download_rate = -1
settings = {
'user_agent': 'libtorrent/' + lt.__version__,
'listen_interfaces': '%s:%d' % (options.listen_interface, options.port),
'download_rate_limit': int(options.max_download_rate),
'upload_rate_limit': int(options.max_upload_rate),
'connections_limit': int(options.connections_limit),
'dht_bootstrap_nodes': 'router.bittorrent.com:6881,dht.transmissionbt.com:6881,router.utorrent.com:6881,',
'alert_mask': lt.alert.category_t.all_categories,
'outgoing_interfaces': options.outgoing_interface,
'announce_to_all_tiers': True,
'announce_to_all_trackers': True,
'auto_manage_interval': 5,
'auto_scrape_interval': 0,
'auto_scrape_min_interval': 0,
'max_failcount': 1,
'aio_threads': 8,
'checking_mem_usage': 2048,
}
if options.proxy_host != '':
settings['proxy_hostname'] = options.proxy_host.split(':')[0]
settings['proxy_type'] = lt.proxy_type_t.http
settings['proxy_port'] = options.proxy_host.split(':')[1]
ses = lt.session(settings)
# map torrent_handle to torrent_status
torrents = {}
alerts_log = []
process_time = time.time() + int(options.timeout)
for f in args:
print('Adding %s' % (f))
add_torrent(ses, f, options)
# process torrents data
while time.time() < process_time:
alerts = ses.pop_alerts()
for a in alerts:
alerts_log.append(a.message())
# add new torrents to our list of torrent_status
if isinstance(a, lt.add_torrent_alert):
h = a.handle
h.set_max_connections(60)
h.set_max_uploads(-1)
torrents[h] = h.status()
# update our torrent_status array for torrents that have
# changed some of their state
if isinstance(a, lt.state_update_alert):
for s in a.status:
torrents[s.handle] = s
if len(alerts_log) > 20:
alerts_log = alerts_log[-20:]
time.sleep(0.5)
ses.post_torrent_updates()
# parse torrents
ses.pause()
for h, t in torrents.items():
print('Parsing %s' % (t.info_hash))
if not h.is_valid() or not t.has_metadata:
print('Failed to parse %s' % (t.info_hash))
continue
# create path
path = [options.save_path, str(t.info_hash)]
os.makedirs(os.path.join(*path), exist_ok=True)
# write info
info = t.handle.get_torrent_info()
write_json(os.path.join(*path, 'info.json'), get_torrent_info(info))
# write files
files = []
for index, file in enumerate(info.files()):
files.append(get_file_info(file))
write_json(os.path.join(*path, 'files.json'), files)
# write trackers
write_json(os.path.join(*path, 'trackers.json'), t.handle.trackers())
# unsubscribe from torrent
print('Unsubscribing from %s' % (t.info_hash))
ses.remove_torrent(h)
main()
python retrieve.py -i eth0:0 "magnet:?xt=urn:btih:9FC20B9E98EA98B4A35E6223041A5EF94EA27809&dn=%5Bmonova.org%5D+ubuntu-20.04-desktop-amd64.iso&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=udp%3A%2F%2Fopen.demonii.com%3A1337&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Fexodus.desync.com%3A6969"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment