Code: Select all
#!/usr/bin/env python3
try:
import bencodepy
except ImportError:
print("Please install 'bencodepy'")
exit(1)
from typing import OrderedDict
import argparse
from pathlib import Path
from hashlib import sha1
I2P_TRACKERS = [
b"http://tracker2.postman.i2p/announce.php",
b"http://opentracker.r4sas.i2p/a",
b"http://ahsplxkbhemefwvvml7qovzl5a2b5xo5i7lyai7ntdunvcyfdtna.b32.i2p/announce.php",
b"http://opentracker.skank.i2p/a",
b"http://opentracker.dg2.i2p/a",
b"http://omitracker.i2p/announce.php",
b"http://w7tpbzncbcocrqtwwm3nezhnnsw4ozadvi2hmvzdhrqzfxfum7wa.b32.i2p/a",
b"http://tu5skej67ftbxjghnx3r2txp6fqz6ulkolkejc77be2er5v5zrfq.b32.i2p/announce.php",
b"http://lnQ6yoBTxQuQU8EQ1FlF395ITIQF-HGJxUeFvzETLFnoczNjQvKDbtSB7aHhn853zjVXrJBgwlB9sO57KakBDaJ50lUZgVPhjlI19TgJ-CxyHhHSCeKx5JzURdEW-ucdONMynr-b2zwhsx8VQCJwCEkARvt21YkOyQDaB9IdV8aTAmP~PUJQxRwceaTMn96FcVenwdXqleE16fI8CVFOV18jbJKrhTOYpTtcZKV4l1wNYBDwKgwPx5c0kcrRzFyw5~bjuAKO~GJ5dR7BQsL7AwBoQUS4k1lwoYrG1kOIBeDD3XF8BWb6K3GOOoyjc1umYKpur3G~FxBuqtHAsDRICkEbKUqJ9mPYQlTSujhNxiRIW-oLwMtvayCFci99oX8MvazPS7~97x0Gsm-onEK1Td9nBdmq30OqDxpRtXBimbzkLbR1IKObbg9HvrKs3L-kSyGwTUmHG9rSQSoZEvFMA-S0EXO~o4g21q1oikmxPMhkeVwQ22VHB0-LZJfmLr4SAAAA.i2p/announce.php",
]
I2P_TRACKER_DOMAINS = {
b"http://tracker2.postman.i2p/announce.php": b"http://6a4kxkg5wp33p25qqhgwl6sj4yh4xuf5b3p3qldwgclebchm3eea.b32.i2p/announce.php",
b"http://opentracker.r4sas.i2p/a": b"http://punzipidirfqspstvzpj6gb4tkuykqp6quurj6e23bgxcxhdoe7q.b32.i2p/a",
b"http://opentracker.skank.i2p/a": b"http://by7luzwhx733fhc5ug2o75dcaunblq2ztlshzd7qvptaoa73nqua.b32.i2p/a",
b"http://opentracker.dg2.i2p/a": b"http://w7tpbzncbcocrqtwwm3nezhnnsw4ozadvi2hmvzdhrqzfxfum7wa.b32.i2p/a",
b"http://omitracker.i2p/announce.php": b"http://a5ruhsktpdhfk5w46i6yf6oqovgdlyzty7ku6t5yrrpf4qedznjq.b32.i2p/announce.php",
}
POSTMAN_TRACKER = b"http://tracker2.postman.i2p/announce.php"
MAIN_TRACKER = POSTMAN_TRACKER
# MAIN_TRACKER = b'http://punzipidirfqspstvzpj6gb4tkuykqp6quurj6e23bgxcxhdoe7q.b32.i2p/a'
def get_infohash(torrent: OrderedDict) -> str:
"""Calcuate and return the infohash based on given .torrent data"""
info_encoded = bencodepy.encode(torrent[b"info"])
_hash = sha1()
_hash.update(info_encoded)
infohash = _hash.hexdigest()
return infohash
def remove_private_flag(torrent: OrderedDict) -> OrderedDict:
"""
Removes the 'private: 1' flag found in private torrents
NOTE: This will change the infohash!
"""
if torrent and torrent.get(b"info") and torrent.get(b"info").get(b"private"):
del torrent[b"info"][b"private"]
return torrent
def remove_source_info(torrent: OrderedDict) -> OrderedDict:
"""
Removes the 'source' field
NOTE: This will change the infohash!
"""
if torrent and torrent.get(b"info") and torrent.get(b"info").get(b"source"):
del torrent[b"info"][b"source"]
return torrent
def remove_comment(torrent: OrderedDict) -> OrderedDict:
"""Removes the 'comment' field"""
if torrent and torrent.get(b"comment"):
del torrent[b"comment"]
return torrent
def remove_created_by(torrent: OrderedDict) -> OrderedDict:
"""Removes the 'created by' field"""
if torrent and torrent.get(b"created by"):
del torrent[b"created by"]
return torrent
def update_trackers(torrent: OrderedDict) -> OrderedDict:
"""Replace all trackers with I2P-only trackers
https://wiki.theory.org/BitTorrentSpecification#Metainfo_File_Structure
"""
torrent[b"announce"] = MAIN_TRACKER
torrent[b"announce-list"] = [[tracker] for tracker in I2P_TRACKERS]
return torrent
def write_output(data: OrderedDict, directory: str | Path = "./", filename: str = "out.torrent") -> None:
"""Write torrent file to specified location"""
output = Path(directory) / Path(filename)
with open(output, "wb") as file:
file.write(bencodepy.encode(data))
def should_replace_outfile(filename: Path | str) -> None:
"""Test if output file already exists, if so, ask to replace"""
if Path(filename).exists() and Path(filename).is_file():
answer = input(f"{filename} already exists, do you want to overwrite? [y/n]: ")
return "y" in answer.lower()
return True
def remove_all_extras(torrent: OrderedDict) -> OrderedDict:
"""Remove all extra metadata from .torrent"""
remove_comment(torrent)
remove_created_by(torrent)
remove_private_flag(torrent)
remove_source_info(torrent)
return torrent
def parse_args():
"""Parse arguments given to this program"""
parser = argparse.ArgumentParser(
prog="torrent-to-i2p",
description="Convert clearnet torrent to i2p-only torrent",
# epilog='Text at the bottom of help'
)
parser.add_argument("filename")
parser.add_argument(
"-p",
"--parse",
default=False,
action="store_true",
help="Parse a file and print it for debugging",
)
parser.add_argument("-o", "--out", help="Output file to write to")
parser.add_argument(
"-d", "--dir", default="./", type=str, help="Output directory to write to"
)
args = parser.parse_args()
return args
def main():
args = parse_args()
torrent = bencodepy.decode(open(args.filename, "rb").read())
if args.parse:
print("INFOHASH:", get_infohash(torrent))
print()
del torrent[b"info"][b"pieces"]
print(torrent)
print()
exit(0)
out_dir = Path(args.dir)
if not out_dir.exists():
out_dir.mkdir(parents=True, exist_ok=True)
out_name = args.out or Path(args.filename).name
if not should_replace_outfile(out_dir / Path(out_name)):
exit(0)
pre_infohash = get_infohash(torrent)
remove_all_extras(torrent)
update_trackers(torrent)
post_infohash = get_infohash(torrent)
if pre_infohash != post_infohash:
print("[!]", args.filename, "info hash changed, beware!")
write_output(torrent, directory=args.dir, filename=out_name)
if __name__ == "__main__":
main()
Code: Select all
$ ./torrent-to-i2p.py -p ubuntu-23.10.1-desktop-amd64.iso.torrent
INFOHASH: 9ecd4676fd0f0474151a4b74a5958f42639cebdf
OrderedDict([(b'announce', b'https://torrent.ubuntu.com/announce'), (b'announce-list', [[b'https://torrent.ubuntu.com/announce'], [b'https://ipv6.torrent.ubuntu.com/announce']]), (b'comment', b'Ubuntu CD releases.ubuntu.com'), (b'created by', b'mktorrent 1.1'), (b'creation date', 1697466120), (b'info', OrderedDict([(b'length', 5173995520), (b'name', b'ubuntu-23.10.1-desktop-amd64.iso'), (b'piece length', 262144)]))])
$ ./torrent-to-i2p.py ./ubuntu-23.10.1-desktop-amd64.iso.torrent
ubuntu-23.10.1-desktop-amd64.iso.torrent already exists, do you want to overwrite? [y/n]: y
$ ./torrent-to-i2p.py -p ubuntu-23.10.1-desktop-amd64.iso.torrent
INFOHASH: 9ecd4676fd0f0474151a4b74a5958f42639cebdf
OrderedDict([(b'announce', b'http://tracker2.postman.i2p/announce.php'), (b'announce-list', [[b'http://tracker2.postman.i2p/announce.php'], [b'http://opentracker.r4sas.i2p/a'], [b'http://ahsplxkbhemefwvvml7qovzl5a2b5xo5i7lyai7ntdunvcyfdtna.b32.i2p/announce.php'], [b'http://opentracker.skank.i2p/a'], [b'http://opentracker.dg2.i2p/a'], [b'http://omitracker.i2p/announce.php'], [b'http://w7tpbzncbcocrqtwwm3nezhnnsw4ozadvi2hmvzdhrqzfxfum7wa.b32.i2p/a'], [b'http://tu5skej67ftbxjghnx3r2txp6fqz6ulkolkejc77be2er5v5zrfq.b32.i2p/announce.php'], [b'http://lnQ6yoBTxQuQU8EQ1FlF395ITIQF-HGJxUeFvzETLFnoczNjQvKDbtSB7aHhn853zjVXrJBgwlB9sO57KakBDaJ50lUZgVPhjlI19TgJ-CxyHhHSCeKx5JzURdEW-ucdONMynr-b2zwhsx8VQCJwCEkARvt21YkOyQDaB9IdV8aTAmP~PUJQxRwceaTMn96FcVenwdXqleE16fI8CVFOV18jbJKrhTOYpTtcZKV4l1wNYBDwKgwPx5c0kcrRzFyw5~bjuAKO~GJ5dR7BQsL7AwBoQUS4k1lwoYrG1kOIBeDD3XF8BWb6K3GOOoyjc1umYKpur3G~FxBuqtHAsDRICkEbKUqJ9mPYQlTSujhNxiRIW-oLwMtvayCFci99oX8MvazPS7~97x0Gsm-onEK1Td9nBdmq30OqDxpRtXBimbzkLbR1IKObbg9HvrKs3L-kSyGwTUmHG9rSQSoZEvFMA-S0EXO~o4g21q1oikmxPMhkeVwQ22VHB0-LZJfmLr4SAAAA.i2p/announce.php']]), (b'creation date', 1697466120), (b'info', OrderedDict([(b'length', 5173995520), (b'name', b'ubuntu-23.10.1-desktop-amd64.iso'), (b'piece length', 262144)]))])