<?php /** * TorrentPier – Bull-powered BitTorrent tracker engine * * @copyright Copyright (c) 2005-2025 TorrentPier (https://torrentpier.com) * @link https://github.com/torrentpier/torrentpier for the canonical source repository * @license https://github.com/torrentpier/torrentpier/blob/master/LICENSE MIT License */ define('IN_TRACKER', true); define('BB_ROOT', './../'); require dirname(__DIR__) . '/common.php'; global $bb_cfg; if (!$bb_cfg['tracker']['scrape']) { msg_die('Please disable SCRAPE!'); } // Recover info_hash if (isset($_GET['?info_hash']) && !isset($_GET['info_hash'])) { $_GET['info_hash'] = $_GET['?info_hash']; } $info_hash = isset($_GET['info_hash']) ? (string)$_GET['info_hash'] : null; // Verify info_hash if (!isset($info_hash)) { msg_die('info_hash was not provided'); } // Store info hash in hex format $info_hash_hex = bin2hex($info_hash); // Check info_hash length if (strlen($info_hash) !== 20) { msg_die('Invalid info_hash: ' . (mb_check_encoding($info_hash, 'UTF8') ? $info_hash : $info_hash_hex)); } // Handle multiple hashes preg_match_all('/info_hash=([^&]*)/i', $_SERVER['QUERY_STRING'], $info_hash_array); $torrents = []; $info_hashes = []; foreach ($info_hash_array[1] as $hash) { $decoded_hash = urldecode($hash); if (strlen($decoded_hash) !== 20) { continue; } if ($scrape_cache = CACHE('tr_cache')->get(SCRAPE_LIST_PREFIX . bin2hex($decoded_hash))) { $torrents['files'][$info_key = array_key_first($scrape_cache)] = $scrape_cache[$info_key]; } else { $info_hashes[] = DB()->escape(($decoded_hash)); } } $info_hash_count = count($info_hashes); if (!empty($info_hash_count)) { if ($info_hash_count > $bb_cfg['max_scrapes']) { $info_hashes = array_slice($info_hashes, 0, $bb_cfg['max_scrapes']); } $info_hashes_sql = implode('\', \'', $info_hashes); /** * Currently torrent clients send truncated v2 hashes (the design raises questions). * @see https://github.com/bittorrent/bittorrent.org/issues/145#issuecomment-1720040343 */ $info_hash_where = "tor.info_hash IN ('$info_hashes_sql') OR SUBSTRING(tor.info_hash_v2, 1, 20) IN ('$info_hashes_sql')"; $sql = " SELECT tor.info_hash, tor.info_hash_v2, tor.complete_count, snap.seeders, snap.leechers FROM " . BB_BT_TORRENTS . " tor LEFT JOIN " . BB_BT_TRACKER_SNAP . " snap ON (snap.topic_id = tor.topic_id) WHERE $info_hash_where "; $scrapes = DB()->fetch_rowset($sql); if (!empty($scrapes)) { foreach ($scrapes as $scrape) { $hash_v1 = !empty($scrape['info_hash']) ? $scrape['info_hash'] : ''; $hash_v2 = !empty($scrape['info_hash_v2']) ? substr($scrape['info_hash_v2'], 0, 20) : ''; $info_hash_scrape = (in_array(urlencode($hash_v1), $info_hash_array[1])) ? $hash_v1 : $hash_v2; // Replace logic to prioritize $hash_v2, in case of future prioritization of v2 $torrents['files'][$info_hash_scrape] = [ 'complete' => (int)$scrape['seeders'], 'downloaded' => (int)$scrape['complete_count'], 'incomplete' => (int)$scrape['leechers'] ]; CACHE('tr_cache')->set(SCRAPE_LIST_PREFIX . bin2hex($info_hash_scrape), array_slice($torrents['files'], -1, null, true), SCRAPE_LIST_EXPIRE); } } } // Verify if torrent registered on tracker if (empty($torrents)) { msg_die('Torrent not registered, info_hash = ' . (mb_check_encoding($info_hash, 'UTF8') ? $info_hash : $info_hash_hex)); } die(\Arokettu\Bencode\Bencode::encode($torrents));