diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..16d3c4d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.cache diff --git a/README.md b/README.md index 183ea8f..0b91ed4 100644 --- a/README.md +++ b/README.md @@ -28,3 +28,8 @@ Usage: * Adjust other parameters if needed (see comments) * Make the script run by cron e.g. every minute * You're done + +Cron Setup: +* Open Crontab using crontab -e +* For Updating Trackers to Every New Torrent Every 15 Minutes Add: */15 * * * * /usr/bin/sh [path]]/transmission-trackers-auto-cron.sh +* Modify the transmission-trackers-auto.sh file as per your needs. We have provided two options, transmission-daemon & docker \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5689f64 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +transmission-rpc>=3.2; python_version >= '3' +transmissionrpc>=0.11; python_version < '3' +toml diff --git a/tracker_ipv6.txt b/tracker_ipv6.txt new file mode 100644 index 0000000..9c90f39 --- /dev/null +++ b/tracker_ipv6.txt @@ -0,0 +1 @@ +http://tracker.ipv6tracker.ru/announce diff --git a/transmission-trackers-auto-cron.sh b/transmission-trackers-auto-cron.sh new file mode 100755 index 0000000..b4cc0e7 --- /dev/null +++ b/transmission-trackers-auto-cron.sh @@ -0,0 +1,14 @@ +#!/bin/bash +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null && pwd)" + +# If you are using transmission-daemon directly on system the below code will ensure that trackers script run only when it is running +if [ "$(systemctl is-active transmission-daemon)" = "active" ] +then +/usr/bin/python3 $DIR/transmission-trackers.py +fi + +# If you are using docker for transmission use the following. Replace 'docker-container-name' with the appropriate name and uncomment the following +#if [ "$( docker container inspect -f '{{.State.Running}}' 'docker-container-name' )" = "true" ] +#then +#/usr/bin/python3 $DIR/transmission-trackers.py +#fi diff --git a/transmission-trackers.py b/transmission-trackers.py old mode 100644 new mode 100755 index 8ba948c..203ec20 --- a/transmission-trackers.py +++ b/transmission-trackers.py @@ -1,64 +1,120 @@ #!/usr/bin/env python3 +from __future__ import print_function +import socket +import time +import os +import sys +from os import getcwd # Host, port, username and password to connect to Transmission # Set user and pw to None if auth is not required -host, port, user, pw = 'localhost', 9091, 'admin', 'pwd' - -# Work with torrents having only these statuses. -# Can be any combination of: 'check pending', 'checking', 'downloading', 'seeding', 'stopped' -# If empty - will affect all torrents -status_filter = () - -# How frequently to update trackers cache -update_freq = 86400 - -# A list of URLs where to get the tracker lists from. -# The lists are combined into one with duplicates removed. -# The trackers from these lists are checked by looking up the URL's hostname in DNS. -urls = [ - 'https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all.txt', - # 'http://some.other.tracker.list/trackers.txt' - # ... -] - -# Whether to print an error if connection failed (no Transmission running?) -err_on_connect = False - -# Where to cache downloaded lists -cache_file = '/tmp/trackers_cache.txt' - -# Additional local lists of trackers to load. -# Better to use absolute paths. -# These are not checked against DNS -local_lists = [ - # '/var/cache/trackers1.txt' - # '/var/cache/trackers2.txt' - # ... -] - -# Don't print anything (unless an error occures) -silent = False -# Debug output -debug = False +client = { + 'host': 'localhost', + 'port': 9091, + 'user': 'admin', + 'password': 'passwd' +} +config = { + + # Work with torrents having only these statuses. + # Can be any combination of: 'check pending', 'checking', 'downloading', 'seeding', 'stopped' + # If empty - will affect all torrents + 'status_filter': (), + + # Ignore torrents that contain any of these trackers. + # This is useful, e.g., for excluding torrents from private trackers. + # trackers are matched by substring, so you can cover https://my.site/announce with just my.site + 'tracker_filter': [], + + # A list of URLs where to get the tracker lists from. + # The lists are combined into one with duplicates removed. + # The trackers from these lists are checked by looking up the URL's hostname in DNS. + 'remote_lists': [ + 'https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all.txt', + 'https://raw.githubusercontent.com/zcq100/transmission-trackers/master/tracker_ipv6.txt', + # ... + ], + + # How frequently to update trackers cache + 'update_freq': 86400, + + # Additional local lists of trackers to load. + # Better to use absolute paths. + # These are not checked against DNS + 'local_lists': [ + # '/var/cache/trackers1.txt' + # '/var/cache/trackers2.txt' + # ... + ], + + # Whether to print an error if connection failed (no Transmission running?) + 'err_on_connect': False, + + # Don't print anything (unless an error occures) + 'silent': False, + + # Debug output + 'debug': True +} + +cache_file = None # Universal scope +if getcwd() != '/docker/transmission/transmission-trackers': + from os import environ as env, path, mkdir + try: + cache_file = path.join(env.get('TEMP',env.get('TMP','')) ,'.cache/trackers.txt') + if not path.isdir(path.dirname(cache_file)): + mkdir(path.dirname(cache_file)) + import toml + configfile = path.join( \ + env.get('XDG_CONFIG_HOME', path.join(env.get('HOME',env.get('USERPROFILE',env.get('HOMEPATH',None))),'.config')), + 'transmission/trackers.toml' + ) + if path.exists(configfile): + with open(configfile, 'r') as f: + client, config = toml.load(f).values() + else: + if not path.isdir(path.dirname(configfile)): + mkdir(path.dirname(configfile)) + with open(configfile, 'w') as f: + toml.dump( {'client': client, 'config': config }, f ) + except KeyError: + # Where to cache downloaded lists + cache_file = path.join(env['TEMP'] ,'.cache/trackers.txt') +else: + cache_file = '/tmp/trackers_cache.txt' + ### Configuration ends here ### hdrs = {'User-Agent': 'Mozilla/5.0'} hosts, ips = set(()), set(()) -import transmissionrpc, sys, os, time, socket +try: + from transmissionrpc import Client + if 'host' in client: + client['address'] = client['host'] + del client['host'] +except ImportError: + try: + from transmission_rpc import Client + if 'user' in client: + client['username'] = client['user'] + del client['user'] + except ImportError: + print("neither transmissionrpc nor transmission-rpc is installed") + exit() if sys.version_info[0] == 2: - from urllib import Request, urlopen + from urllib2 import Request, urlopen from urlparse import urlparse else: from urllib.request import Request, urlopen from urllib.parse import urlparse def lg(msg): - if not silent: print(msg) + if not config['silent']: print(msg) def dbg(msg): - if debug: lg(msg) + if config['debug']: lg(msg) def parse(txt): l = [] @@ -70,7 +126,7 @@ def parse(txt): def validateTrackerURL(url, dns=True): try: - h = urlparse(url).netloc.split(':', 1)[0] + h = ':'.join(urlparse(url).netloc.split(':')[0:-1]) except: lg("Tracker URL '{}' is malformed".format(url)) return False @@ -117,7 +173,7 @@ def downloadLists(): try: mt = os.stat(cache_file).st_mtime - if time.time() - mt > update_freq: + if time.time() - mt > config['update_freq']: update = True except: update = True @@ -126,7 +182,7 @@ def downloadLists(): return None trk = [] - for url in urls: + for url in config['remote_lists']: l = loadURL(url) trk += l dbg("Remote URL '{}' loaded: {} trackers".format(url, len(l))) @@ -143,7 +199,7 @@ def downloadLists(): def readLocalLists(): trk = [] - for f in local_lists: + for f in config['local_lists']: l = loadFile(f) trk += l dbg("Local list '{}' loaded: {} trackers".format(f, len(l))) @@ -159,7 +215,7 @@ def readLocalLists(): lg('Remote URLs downloaded: {} trackers'.format(len(trk_remote))) elif trk_remote is None: trk_remote = [] - local_lists.append(cache_file) + config['local_lists'].append(cache_file) trk_local = readLocalLists() if trk_local: @@ -173,9 +229,9 @@ def readLocalLists(): exit(1) try: - tc = transmissionrpc.Client(host, port=port, user=user, password=pw) + tc = Client(**client) except: - if not err_on_connect: + if not config['err_on_connect']: exit() print("Unable to connect to Transmission: ", sys.exc_info()[0]) @@ -186,18 +242,28 @@ def readLocalLists(): dbg('{} torrents total'.format(len(torrents))) for t in torrents: - if status_filter and not t.status in status_filter: + if config['status_filter'] and not t.status in config['status_filter']: dbg('{}: skipping due to status filter'.format(t.name)) continue + if getattr(t, 'isPrivate', getattr(t, 'is_private', False)): + dbg('{}: skipping private torrent'.format(t.name)) + continue ttrk = set(()) for trk in t.trackers: - ttrk.add(trk['announce']) + ttrk.add(getattr(trk, 'fields', trk).get('announce')) + + # Use .get() to avoid error if key doesn't exist + if any(tracker in url for url in ttrk for tracker in config.get('tracker_filter', [])): + dbg('{}: skipping due to tracker filter'.format(t.name)) + continue diff = trackers - ttrk if diff: lg('{}: Adding {} trackers (before: {})'.format(t.name, len(diff), len(ttrk))) tc.change_torrent(t.id, trackerAdd=list(diff)) + time.sleep(1) + tc.reannounce_torrent(t.id) else: dbg('{}: update not needed'.format(t.name)) diff --git a/transmission-trackers.service b/transmission-trackers.service new file mode 100644 index 0000000..3daae6e --- /dev/null +++ b/transmission-trackers.service @@ -0,0 +1,15 @@ +[Unit] +Description=add tracker to torent +After=network.target network-online.target +Wants=network-online.target + +[Service] +User=zcq100 +Type=oneshot +#Your WorkingDirectory +WorkingDirectory=/docker/transmission/transmission-trackers +ExecStart=python3 transmission-trackers.py + + +[Install] +WantedBy=multi-user.target diff --git a/transmission-trackers.timer b/transmission-trackers.timer new file mode 100644 index 0000000..7c0a4b7 --- /dev/null +++ b/transmission-trackers.timer @@ -0,0 +1,10 @@ +[Unit] +Description=add trackers to torrent every 10 minutes + +[Timer] +OnBootSec=0s +OnUnitActiveSec=10min +Unit=transmission-trackers.service + +[Install] +WantedBy=multi-user.target